diff --git a/org.eclipse.jdt.debug.tests/testprograms/StackFrameColoring.java b/org.eclipse.jdt.debug.tests/testprograms/StackFrameColoring.java new file mode 100644 index 0000000000..0bfdefeb8b --- /dev/null +++ b/org.eclipse.jdt.debug.tests/testprograms/StackFrameColoring.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2025 Zsombor Gegesy 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: + * Zsombor Gegesy - initial API and implementation + *******************************************************************************/ + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class StackFrameColoring { + + public static void main(String[] args) { + new StackFrameColoring().run(); + } + + void run() { + List result = Arrays.asList("hello", "world").stream().map(value -> { + breakpointMethod(); + return value; + }).collect(Collectors.toList()); + System.out.println("StackFrameColoring.run called: "+ result); + } + + public void breakpointMethod() { + System.out.println("set a breakpoint here"); + } + +} diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AbstractDebugTest.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AbstractDebugTest.java index 26b68454c1..5ec5922312 100644 --- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AbstractDebugTest.java +++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AbstractDebugTest.java @@ -188,7 +188,7 @@ public abstract class AbstractDebugTest extends TestCase implements IEvaluation public static final String CLONE_SUFFIX = "Clone"; final String[] LAUNCH_CONFIG_NAMES_1_4 = { "LargeSourceFile", "LotsOfFields", - "Breakpoints", + "Breakpoints", "StackFrameColoring", "InstanceVariablesTests", "LocalVariablesTests", "LocalVariableTests2", "StaticVariablesTests", "DropTests", "ThrowsNPE", "ThrowsException", "org.eclipse.debug.tests.targets.Watchpoint", @@ -3068,7 +3068,7 @@ protected void assertNoErrorMarkersExist(IProject[] projects) throws Exception { protected void assertNoErrorMarkersExist(IProject project) throws Exception { if (project.isAccessible()) { IMarker[] projectMarkers = project.findMarkers(null, false, IResource.DEPTH_INFINITE); - List errorMarkers = Arrays.stream(projectMarkers).filter(marker -> isErrorMarker(marker)).collect(Collectors.toList()); + List errorMarkers = Arrays.stream(projectMarkers).filter(AbstractDebugTest::isErrorMarker).toList(); String projectErrors = toString(errorMarkers); assertEquals("found errors on project " + project + ":" + System.lineSeparator() + projectErrors, Collections.EMPTY_LIST, errorMarkers); } diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java index e2cbcfb1b5..748b240f2e 100644 --- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java +++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java @@ -149,8 +149,10 @@ import org.eclipse.jdt.debug.tests.ui.DebugHoverTests; import org.eclipse.jdt.debug.tests.ui.DebugViewTests; import org.eclipse.jdt.debug.tests.ui.DetailPaneManagerTests; +import org.eclipse.jdt.debug.tests.ui.GroupedStackFrameTest; import org.eclipse.jdt.debug.tests.ui.JavaSnippetEditorTest; import org.eclipse.jdt.debug.tests.ui.OpenFromClipboardTests; +import org.eclipse.jdt.debug.tests.ui.StackFrameGroupingTest; import org.eclipse.jdt.debug.tests.ui.ViewManagementTests; import org.eclipse.jdt.debug.tests.ui.VirtualThreadsDebugViewTests; import org.eclipse.jdt.debug.tests.ui.presentation.ModelPresentationTests; @@ -246,6 +248,8 @@ public AutomatedSuite() { addTest(new TestSuite(StepFilterTests.class)); addTest(new TestSuite(StepIntoSelectionTests.class)); addTest(new TestSuite(InstanceFilterTests.class)); + addTest(new TestSuite(StackFrameGroupingTest.class)); + addTest(new TestSuite(GroupedStackFrameTest.class)); if (JavaProjectHelper.isJava6Compatible()) { addTest(new TestSuite(ForceReturnTests.class)); } diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/AbstractDebugViewTests.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/AbstractDebugViewTests.java index 564cde63b5..a4ee5b84d3 100644 --- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/AbstractDebugViewTests.java +++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/AbstractDebugViewTests.java @@ -147,14 +147,14 @@ protected IJavaThread launchToBreakpoint(String typeName, String breakpointMetho return thread; } - protected void assertStackFrameIsSelected(String breakpointMethodName) throws Exception { + protected TreeItem assertStackFrameIsSelected(String breakpointMethodName) throws Exception { // Get and check the selection form the tree, we expect only one method selected TreeItem[] selected = getSelectedItemsFromDebugView(true); Object[] selectedText = selectedText(selected); if (selected.length != 1) { if (Platform.OS.isMac()) { // skip this test on Mac - see bug 516024 - return; + return null; } throw new TestAgainException("Unexpected selection: " + Arrays.toString(selectedText)); } @@ -162,7 +162,7 @@ protected void assertStackFrameIsSelected(String breakpointMethodName) throws Ex IJavaStackFrame selectedFrame = selectedFrame(selected); assertEquals("\"breakpointMethod\" should be selected after reaching breakpoint", selectedFrame.getMethodName(), breakpointMethodName); - + return selected[0]; } @Override @@ -208,7 +208,7 @@ protected void waitForNonConsoleJobs() throws Exception { } protected Object[] selectedText(TreeItem[] selected) throws Exception { - Object[] selectedText = sync(() -> Arrays.stream(selected).map(x -> x.getText()).toArray()); + Object[] selectedText = sync(() -> Arrays.stream(selected).map(TreeItem::getText).toArray()); return selectedText; } @@ -235,7 +235,7 @@ protected String dumpFrames(Object[] childrenData) { protected TreeItem[] getSelectedItemsFromDebugView(boolean wait) throws Exception { return sync(() -> { - Tree tree = (Tree) debugView.getViewer().getControl(); + Tree tree = getDebugViewTree(); TreeItem[] selected = tree.getSelection(); if (!wait) { return selected; @@ -254,6 +254,24 @@ protected TreeItem[] getSelectedItemsFromDebugView(boolean wait) throws Exceptio }); } + private Tree getDebugViewTree() { + return (Tree) debugView.getViewer().getControl(); + } + + protected IJavaThread runCodeUntilBreakpoint(String typeName, String breakpointMethodName) throws Exception { + sync(() -> getActivePage().hideView(getActivePage().findView(IDebugUIConstants.ID_DEBUG_VIEW))); + + waitForNonConsoleJobs(); + assertNoErrorMarkersExist(); + setPreferenceToShowSystemThreads(); + sync(() -> openEditor(typeName + ".java")); + + var thread = launchToBreakpoint(typeName, breakpointMethodName, 1); + assertDebugViewIsOpen(); + + return thread; + } + protected ISelection getDebugViewSelection() throws Exception { return debugView.getViewer().getSelection(); } diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/DebugViewTests.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/DebugViewTests.java index 71afcf3920..f9a1344a32 100644 --- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/DebugViewTests.java +++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/DebugViewTests.java @@ -19,9 +19,12 @@ import org.eclipse.debug.core.model.IStackFrame; import org.eclipse.debug.ui.IDebugUIConstants; import org.eclipse.jdt.debug.core.IJavaStackFrame; +import org.eclipse.jdt.debug.core.IJavaStackFrame.Category; import org.eclipse.jdt.debug.core.IJavaThread; import org.eclipse.jdt.debug.ui.IJavaDebugUIConstants; +import org.eclipse.jdt.internal.debug.ui.IJDIPreferencesConstants; import org.eclipse.jdt.internal.debug.ui.JDIDebugUIPlugin; +import org.eclipse.jdt.internal.debug.ui.StackFrameCategorizer; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.swt.widgets.TreeItem; import org.eclipse.test.OrderedTestSuite; @@ -226,6 +229,42 @@ public void testWrongSelectionBug540243() throws Exception { doTestWrongSelection(iterations, typeName, breakpointMethodName, expectedBreakpointHitsCount); } + public void testStackFrameGrouppingAndColors() throws Exception { + IPreferenceStore jdiUIPreferences = JDIDebugUIPlugin.getDefault().getPreferenceStore(); + jdiUIPreferences.setValue(IJDIPreferencesConstants.PREF_COLLAPSE_STACK_FRAMES, true); + IJavaThread thread = null; + try { + thread = runCodeUntilBreakpoint("StackFrameColoring", "breakpointMethod"); + assertNotNull("thread", thread); + var selectedStackFrame = assertStackFrameIsSelected("breakpointMethod"); + if (selectedStackFrame == null) { + // skip this test on Mac - see bug 516024 + return; + } + sync(() -> { + var allFrames = selectedStackFrame.getParentItem().getItems(); + assertNotNull("all frames", allFrames); + assertEquals("frame[0]", "StackFrameColoring.breakpointMethod() line: 34", allFrames[0].getText()); + assertEquals("frame[0] - production", StackFrameCategorizer.CATEGORY_PRODUCTION, getStackFrameCategory(allFrames, 0)); + assertEquals("frame[1]", "StackFrameColoring.lambda$0(String) line: 27", allFrames[1].getText()); + assertEquals("frame[1] - production", StackFrameCategorizer.CATEGORY_PRODUCTION, getStackFrameCategory(allFrames, 1)); + assertTrue("frame[2]", allFrames[2].getText().contains("apply(Object) line: not available")); + assertEquals("frame[2] - production", StackFrameCategorizer.CATEGORY_PRODUCTION, getStackFrameCategory(allFrames, 2)); + assertEquals("frame[3]", "7 collapsed frames", allFrames[3].getText()); + assertEquals("frame[4]", "StackFrameColoring.run() line: 29", allFrames[4].getText()); + assertEquals("frame[4] - production", StackFrameCategorizer.CATEGORY_PRODUCTION, getStackFrameCategory(allFrames, 4)); + assertEquals("frame[5]", "StackFrameColoring.main(String[]) line: 22", allFrames[5].getText()); + assertEquals("frame[5] - production", StackFrameCategorizer.CATEGORY_PRODUCTION, getStackFrameCategory(allFrames, 5)); + }); + } finally { + terminateAndCleanUp(thread); + } + } + + private Category getStackFrameCategory(TreeItem[] allFrames, int idx) { + return ((IJavaStackFrame) allFrames[idx].getData()).getCategory(); + } + /** * Test for Bug 534319 - Debug View shows wrong information due to threads with short lifetime * diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/GroupedStackFrameTest.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/GroupedStackFrameTest.java new file mode 100644 index 0000000000..cb6ec7249e --- /dev/null +++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/GroupedStackFrameTest.java @@ -0,0 +1,95 @@ +/******************************************************************************* + * Copyright (c) 2025 Zsombor Gegesy 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: + * Zsombor Gegesy - initial API and implementation + *******************************************************************************/ +package org.eclipse.jdt.debug.tests.ui; + + +import org.eclipse.jdt.debug.core.IJavaStackFrame; +import org.eclipse.jdt.internal.debug.core.model.GroupedStackFrame; + +import junit.framework.TestCase; + +public class GroupedStackFrameTest extends TestCase { + + private GroupedStackFrame groupedStackFrame; + + private IJavaStackFrame mockFrame1; + private IJavaStackFrame mockFrame2; + + @Override + public void setUp() throws Exception { + groupedStackFrame = new GroupedStackFrame(null); + mockFrame1 = JavaStackFrameMock.createFrame(JavaReferenceTypeMock.createReference("java.util.ArrayList"), false); + mockFrame2 = JavaStackFrameMock.createFrame(JavaReferenceTypeMock.createReference("java.util.LinkedList"), false); + } + + public void testAddFrame() { + groupedStackFrame.add(mockFrame1); + assertEquals(1, groupedStackFrame.getFrameCount()); + + groupedStackFrame.add(mockFrame2); + assertEquals(2, groupedStackFrame.getFrameCount()); + } + + public void testGetFrameCount() { + assertEquals(0, groupedStackFrame.getFrameCount()); + + groupedStackFrame.add(mockFrame1); + assertEquals(1, groupedStackFrame.getFrameCount()); + + groupedStackFrame.add(mockFrame2); + assertEquals(2, groupedStackFrame.getFrameCount()); + } + + public void testGetFramesAsArray() { + groupedStackFrame.add(mockFrame1); + groupedStackFrame.add(mockFrame2); + + Object[] frames = groupedStackFrame.getFramesAsArray(0, 2); + assertNotNull(frames); + assertEquals(2, frames.length); + assertSame(mockFrame1, frames[0]); + assertSame(mockFrame2, frames[1]); + + frames = groupedStackFrame.getFramesAsArray(1, 1); + assertNotNull(frames); + assertEquals(1, frames.length); + assertSame(mockFrame2, frames[0]); + + frames = groupedStackFrame.getFramesAsArray(2, 1); + assertNull(frames); + } + + public void testGetTopMostFrame() { + assertNull(groupedStackFrame.getTopMostFrame()); + + groupedStackFrame.add(mockFrame1); + assertSame(mockFrame1, groupedStackFrame.getTopMostFrame()); + + groupedStackFrame.add(mockFrame2); + assertSame(mockFrame1, groupedStackFrame.getTopMostFrame()); + } + + public void testGetAdapter() { + var adapterType = String.class; + + groupedStackFrame.add(mockFrame1); + Object adapter = groupedStackFrame.getAdapter(adapterType); + assertEquals("getAdapter called with class java.lang.String", adapter); + + groupedStackFrame = new GroupedStackFrame(null); + adapter = groupedStackFrame.getAdapter(adapterType); + assertNull(adapter); + } + +} diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/JavaReferenceTypeMock.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/JavaReferenceTypeMock.java new file mode 100644 index 0000000000..772ccf002f --- /dev/null +++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/JavaReferenceTypeMock.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2025 Zsombor Gegesy 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: + * Zsombor Gegesy - initial API and implementation + *******************************************************************************/ +package org.eclipse.jdt.debug.tests.ui; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +import org.eclipse.jdt.debug.core.IJavaReferenceType; + +/** + * Class to mock {@link IJavaReferenceType}. + */ +class JavaReferenceTypeMock implements InvocationHandler { + + final String name; + + JavaReferenceTypeMock(String name) { + this.name = name; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if ("getName".equals(method.getName())) { + return name; + } + return null; + } + + /** + * Create a new mocked {@link IJavaReferenceType}. + * + * @param name + * @return + */ + public static IJavaReferenceType createReference(String name) { + return (IJavaReferenceType) Proxy.newProxyInstance(JavaReferenceTypeMock.class.getClassLoader(), new Class[] { + IJavaReferenceType.class }, new JavaReferenceTypeMock(name)); + } + +} \ No newline at end of file diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/JavaStackFrameMock.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/JavaStackFrameMock.java new file mode 100644 index 0000000000..732c47a288 --- /dev/null +++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/JavaStackFrameMock.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright (c) 2025 Zsombor Gegesy 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: + * Zsombor Gegesy - initial API and implementation + *******************************************************************************/ + +package org.eclipse.jdt.debug.tests.ui; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +import org.eclipse.jdt.debug.core.IJavaReferenceType; +import org.eclipse.jdt.debug.core.IJavaStackFrame; + +class JavaStackFrameMock implements InvocationHandler { + + final IJavaReferenceType referenceType; + final boolean synthetic; + + JavaStackFrameMock(IJavaReferenceType referenceType, boolean synthetic) { + this.referenceType = referenceType; + this.synthetic = synthetic; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + var methodName = method.getName(); + if ("getReferenceType".equals(methodName)) { + return referenceType; + } + if ("isSynthetic".equals(methodName)) { + return synthetic; + } + if ("getLaunch".equals(methodName)) { + return LaunchMock.createLaunch(); + } + if ("getAdapter".equals(methodName)) { + return "getAdapter called with " + args[0]; + } + return null; + } + + /** + * Create a mocked {@link IJavaStackFrame} + * + * @param refType + * the type in which this stack frame's method is declared + * @param syntetic + * if the frame is synthetic or not. + * @return a mocked stack frame. + */ + public static IJavaStackFrame createFrame(IJavaReferenceType refType, boolean syntetic) { + return (IJavaStackFrame) Proxy.newProxyInstance(JavaStackFrameMock.class.getClassLoader(), new Class[] { + IJavaStackFrame.class }, new JavaStackFrameMock(refType, syntetic)); + } + +} \ No newline at end of file diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/LaunchMock.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/LaunchMock.java new file mode 100644 index 0000000000..12a9c9691b --- /dev/null +++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/LaunchMock.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright (c) 2025 Zsombor Gegesy 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: + * Zsombor Gegesy - initial API and implementation + *******************************************************************************/ +package org.eclipse.jdt.debug.tests.ui; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +import org.eclipse.debug.core.ILaunch; + +/** + * Class to mock {@link ILaunch}. + */ +class LaunchMock implements InvocationHandler { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + return null; + } + + public static ILaunch createLaunch() { + return (ILaunch) Proxy.newProxyInstance(LaunchMock.class.getClassLoader(), new Class[] { ILaunch.class }, new LaunchMock()); + } +} \ No newline at end of file diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/PreferenceServiceMock.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/PreferenceServiceMock.java new file mode 100644 index 0000000000..65e60c873d --- /dev/null +++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/PreferenceServiceMock.java @@ -0,0 +1,51 @@ +package org.eclipse.jdt.debug.tests.ui; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Map; + +import org.eclipse.core.runtime.preferences.IEclipsePreferences; +import org.eclipse.core.runtime.preferences.IPreferencesService; + +public class PreferenceServiceMock implements InvocationHandler { + + private Map values; + + public PreferenceServiceMock(Map values) { + this.values = values; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + var methodName = method.getName(); + if ("getBoolean".equals(methodName)) { + String key = (String) args[1]; + boolean defaultValue = (boolean) args[2]; + return values.getOrDefault(key, defaultValue); + } + if ("getString".equals(methodName)) { + String key = (String) args[1]; + String defaultValue = (String) args[2]; + return values.getOrDefault(key, defaultValue); + } + if ("put".equals(methodName) || "putBoolean".equals(methodName)) { + String key = (String) args[0]; + Object value = args[1]; + values.put(key, value); + return null; + } + throw new IllegalArgumentException("Unexpected method called: " + methodName); + } + + public static IPreferencesService createPreferencesService(Map values) { + return (IPreferencesService) Proxy.newProxyInstance(IPreferencesService.class.getClassLoader(), new Class[] { + IPreferencesService.class }, new PreferenceServiceMock(values)); + } + + public static IEclipsePreferences createEclipsePreferences(Map values) { + return (IEclipsePreferences) Proxy.newProxyInstance(IEclipsePreferences.class.getClassLoader(), new Class[] { + IEclipsePreferences.class }, new PreferenceServiceMock(values)); + } + +} diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/StackFrameGroupingTest.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/StackFrameGroupingTest.java new file mode 100644 index 0000000000..2965e983e3 --- /dev/null +++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/ui/StackFrameGroupingTest.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * Copyright (c) 2021, 2025 Zsombor Gegesy 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: + * Zsombor Gegesy - initial API and implementation + *******************************************************************************/ +package org.eclipse.jdt.debug.tests.ui; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.eclipse.core.runtime.preferences.IEclipsePreferences; +import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent; +import org.eclipse.jdt.debug.core.IJavaReferenceType; +import org.eclipse.jdt.debug.core.IJavaStackFrame; +import org.eclipse.jdt.debug.tests.AbstractDebugTest; +import org.eclipse.jdt.internal.debug.ui.IJDIPreferencesConstants; +import org.eclipse.jdt.internal.debug.ui.StackFrameCategorizer; + +public class StackFrameGroupingTest extends AbstractDebugTest { + + public StackFrameGroupingTest(String name) { + super(name); + } + + private StackFrameCategorizer stackFrameCategorizer; + private IEclipsePreferences preferences; + private Map values; + + @Override + protected void setUp() throws Exception { + super.setUp(); + values = new HashMap<>(); + values.put(IJDIPreferencesConstants.PREF_ACTIVE_PLATFORM_FRAME_FILTER_LIST, "java.*,javax.*"); + preferences = PreferenceServiceMock.createEclipsePreferences(values); + stackFrameCategorizer = new StackFrameCategorizer(PreferenceServiceMock.createPreferencesService(values), preferences); + } + + private IJavaStackFrame.Category categorize(String refTypeName, boolean syntetic) { + return categorize(JavaReferenceTypeMock.createReference(refTypeName), syntetic); + } + + private IJavaStackFrame.Category categorize(IJavaReferenceType refType, boolean syntetic) { + return stackFrameCategorizer.categorize(JavaStackFrameMock.createFrame(refType, syntetic)); + } + + public void testFiltering() { + assertEquals(StackFrameCategorizer.CATEGORY_SYNTHETIC, categorize("org.eclipse.Something", true)); + assertEquals(StackFrameCategorizer.CATEGORY_PLATFORM, categorize("java.lang.String", false)); + assertEquals(StackFrameCategorizer.CATEGORY_UNKNOWN, categorize("org.eclipse.Other", false)); + } + + public void testUpdateWorks() { + var something = JavaReferenceTypeMock.createReference("org.eclipse.Something"); + var other = JavaReferenceTypeMock.createReference("org.eclipse.Other"); + assertEquals(StackFrameCategorizer.CATEGORY_UNKNOWN, categorize(something, false)); + assertEquals(StackFrameCategorizer.CATEGORY_UNKNOWN, categorize(other, false)); + assertEquals(StackFrameCategorizer.CATEGORY_PLATFORM, categorize("java.lang.String", false)); + stackFrameCategorizer.addTypesToActiveCustomFilters(Set.of("org.eclipse.Something")); + stackFrameCategorizer.preferenceChange(new PreferenceChangeEvent(preferences, IJDIPreferencesConstants.PREF_ACTIVE_CUSTOM_FRAME_FILTER_LIST, null, null)); + + assertEquals(StackFrameCategorizer.CATEGORY_CUSTOM_FILTERED, categorize(something, false)); + assertEquals(StackFrameCategorizer.CATEGORY_UNKNOWN, categorize(other, false)); + assertEquals(StackFrameCategorizer.CATEGORY_PLATFORM, categorize("java.lang.String", false)); + } + + public void testSwitchOffPlatform() { + assertEquals(StackFrameCategorizer.CATEGORY_PLATFORM, categorize("java.lang.String", false)); + stackFrameCategorizer.setEnabled(StackFrameCategorizer.CATEGORY_PLATFORM, false); + assertEquals(StackFrameCategorizer.CATEGORY_UNKNOWN, categorize("java.lang.String", false)); + } + + +} diff --git a/org.eclipse.jdt.debug.ui/css/e4-dark_jdi_debug_prefstyle.css b/org.eclipse.jdt.debug.ui/css/e4-dark_jdi_debug_prefstyle.css new file mode 100644 index 0000000000..73dd2ceaa2 --- /dev/null +++ b/org.eclipse.jdt.debug.ui/css/e4-dark_jdi_debug_prefstyle.css @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2024 Zsombor Gegesy 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: + * Zsombor Gegesy - initial API and implementation + *******************************************************************************/ + +/* ############################## Debug preferences ############################## */ + +IEclipsePreferences#org-eclipse-ui-workbench:org-eclipse-jdt-debug-ui { + preferences: + 'org.eclipse.jdt.debug.ui.CustomFilteredStackFrameFgColor=252,226,186' + 'org.eclipse.jdt.debug.ui.CustomFilteredStackFrameBgColor=53,53,53' + 'org.eclipse.jdt.debug.ui.ProductionStackFrameFgColor=238,238,255' + 'org.eclipse.jdt.debug.ui.ProductionStackFrameBgColor=53,53,53' + 'org.eclipse.jdt.debug.ui.TestStackFrameFgColor=238,255,238' + 'org.eclipse.jdt.debug.ui.TestStackFrameBgColor=53,53,53' + 'org.eclipse.jdt.debug.ui.SyntheticStackFrameFgColor=101,101,65' + 'org.eclipse.jdt.debug.ui.SyntheticStackFrameBgColor=53,53,53' + 'org.eclipse.jdt.debug.ui.LibraryStackFrameFgColor=182,164,206' + 'org.eclipse.jdt.debug.ui.LibraryStackFrameBgColor=53,53,53' + 'org.eclipse.jdt.debug.ui.PlatformStackFrameFgColor=108,132,108' + 'org.eclipse.jdt.debug.ui.PlatformStackFrameBgColor=53,53,53' +} diff --git a/org.eclipse.jdt.debug.ui/plugin.properties b/org.eclipse.jdt.debug.ui/plugin.properties index 4e7fca4bc4..28513502a0 100644 --- a/org.eclipse.jdt.debug.ui/plugin.properties +++ b/org.eclipse.jdt.debug.ui/plugin.properties @@ -98,6 +98,8 @@ JavaSnippetEditor.label= Scrapbook javaStepFilterPrefName=Step Filtering javaDetailFormattersPrefName=Detail Formatters +javaStackFramePrefName=Stack Frames + javaVariableHoverLabel=Variable Values javaVariableHoverDescription=Shows the value of the selected variable when debugging. @@ -184,6 +186,12 @@ showNullEntriesAction.tooltip=Show Null Array Entries collapseStackFrames.label=Collapse Stack Frames collapseStackFrames.tooltip=Hide less relevant stack frames +colorizeStackFrames.label=Mark Stack Frames with &Colors +colorizeStackFrames.tooltip=Based on the function mark the stack frames with different colors + +highlightStackFrame.label=Highlight Stack Frame +highlightStackFrame.tooltip=Always mark this stack frame distinctly + stepIntoSelectionHyperlinkDetector.label=Step Into Selection stepIntoSelectionHyperlinkDetector.description=Performs the step into selection command on demand via a hyperlink @@ -295,6 +303,36 @@ InDeadlockColorDefinition.description=The color used to render deadlocked thread LabeledObjectColorDefinition.label=Labeled objects LabeledObjectColorDefinition.description=The color used to render labeled objects in debug views. +CustomFilteredStackFrameFgColorDefinition.label=Highlighted methods +CustomFilteredStackFrameBgColorDefinition.label=Highlighted methods background +CustomFilteredStackFrameFgColorDefinition.description=The color used to render the Highlighted stack frames in the Debug view. +CustomFilteredStackFrameBgColorDefinition.description=The background color used to render the Highlighted stack frames in the Debug view. + +SyntheticStackFrameFgColorDefinition.label=Synthetic methods +SyntheticStackFrameBgColorDefinition.label=Synthetic methods background +SyntheticStackFrameFgColorDefinition.description=The color used to render stack frames for synthetic methods in the Debug view. +SyntheticStackFrameBgColorDefinition.description=The background color used to render stack frames for synthetic methods in the Debug view. + +PlatformStackFrameFgColorDefinition.label=Platform methods +PlatformStackFrameBgColorDefinition.label=Platform methods background +PlatformStackFrameFgColorDefinition.description=The color used to render stack frames for Platform methods in the Debug view. +PlatformStackFrameBgColorDefinition.description=The background color used to render stack frame for Platform methods in the Debug view. + +TestStackFrameFgColorDefinition.label=Your Test code +TestStackFrameBgColorDefinition.label=Your Test code background +TestStackFrameFgColorDefinition.description=The color used to render stack frames for Your Test code in the Debug view. +TestStackFrameBgColorDefinition.description=The background color used to render stack frames for Your Test code in the Debug view. + +LibraryStackFrameFgColorDefinition.label=Library code +LibraryStackFrameBgColorDefinition.label=Library code background +LibraryStackFrameFgColorDefinition.description=The color used to render stack frames for Library code in the Debug view. +LibraryStackFrameBgColorDefinition.description=The background color used to render stack frames for Library code in the Debug view. + +ProductionStackFrameFgColorDefinition.label=Your Source code +ProductionStackFrameBgColorDefinition.label=Your Source code background +ProductionStackFrameFgColorDefinition.description=The color used used to render stack frames for Your Source code in the Debug view. +ProductionStackFrameBgColorDefinition.description=The background color used to render stack frames for Your Source code in the Debug view. + javaStackTraceConsole.label= Java Stack Trace Console FormatStackTraceActionDelegate.name= Format FormatStackTraceActionDelegate.tooltip= Format diff --git a/org.eclipse.jdt.debug.ui/plugin.xml b/org.eclipse.jdt.debug.ui/plugin.xml index 4643154b7b..921c425f2b 100644 --- a/org.eclipse.jdt.debug.ui/plugin.xml +++ b/org.eclipse.jdt.debug.ui/plugin.xml @@ -1782,6 +1782,14 @@ enablesFor="+" id="org.eclipse.jdt.debug.ui.actions.EditStepFiltersAction"> + + + + + + %LabeledObjectColorDefinition.description + + + %CustomFilteredStackFrameFgColorDefinition.description + + + %CustomFilteredStackFrameBgColorDefinition.description + + + + %SyntheticStackFrameFgColorDefinition.description + + + %SyntheticStackFrameBgColorDefinition.description + + + + %PlatformStackFrameFgColorDefinition.description + + + %PlatformStackFrameBgColorDefinition.description + + + + %TestStackFrameFgColorDefinition.description + + + %TestStackFrameBgColorDefinition.description + + + + %ProductionStackFrameFgColorDefinition.description + + + %ProductionStackFrameBgColorDefinition.description + + + + %LibraryStackFrameFgColorDefinition.description + + + %LibraryStackFrameBgColorDefinition.description + @@ -3983,4 +4108,9 @@ M4 = Platform-specific fourth key + + + + + diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/DebugUIMessages.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/DebugUIMessages.java index ffb45e5603..c9e4479d84 100644 --- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/DebugUIMessages.java +++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/DebugUIMessages.java @@ -100,6 +100,38 @@ public class DebugUIMessages extends NLS { public static String JavaHotCodeReplaceListener_0; + public static String JavaStackFramesPreferencePage_title; + public static String JavaStackFramesPreferencePage_description; + public static String JavaStackFramesPreferencePage__Color_stack_frames; + public static String JavaStackFramesPreferencePage__Collapse_stack_frames; + public static String JavaStackFramesPreferencePage_Defined_stack_frame_filters_for_platform; + public static String JavaStackFramesPreferencePage_Defined_custom_stack_frame_filters; + public static String JavaStackFramesPreferencePage_Appearance_of_stack_frames; + public static String JavaStackFramesPreferencePage_fg_color; + public static String JavaStackFramesPreferencePage_bg_color; + public static String JavaStackFramesPreferencePage_category_platform; + public static String JavaStackFramesPreferencePage_category_synthetic; + public static String JavaStackFramesPreferencePage_category_library; + public static String JavaStackFramesPreferencePage_category_test; + public static String JavaStackFramesPreferencePage_category_production; + public static String JavaStackFramesPreferencePage_category_custom_filter; + public static String JavaStackFramesPreferencePage_Filter_platform; + public static String JavaStackFramesPreferencePage_Filter_synthetic; + public static String JavaStackFramesPreferencePage_Filter_library; + public static String JavaStackFramesPreferencePage_Filter_test; + public static String JavaStackFramesPreferencePage_Filter_production; + public static String JavaStackFramesPreferencePage_Filter_custom; + public static String JavaStackFramesPreferencePage_Add__Filter; + public static String JavaStackFramesPreferencePage_Add__Package; + public static String JavaStackFramesPreferencePage_Add__Type; + public static String JavaStackFramesPreferencePage_Remove; + public static String JavaStackFramesPreferencePage_Enable_All; + public static String JavaStackFramesPreferencePage_Disable_All; + public static String JavaStackFramesPreferencePage_Add_package_for_stack_filters; + public static String JavaStackFramesPreferencePage_Select_a_package_for_stack_filter; + public static String JavaStackFramesPreferencePage_Add_type_for_stack_filters; + public static String JavaStackFramesPreferencePage_Select_a_type_for_stack_filter; + public static String JavaStepFilterPreferencePage_0; public static String JavaStepFilterPreferencePage_Filter_co_nstructors_19; diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/DebugUIMessages.properties b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/DebugUIMessages.properties index fc346fa11b..e7e8506138 100644 --- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/DebugUIMessages.properties +++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/DebugUIMessages.properties @@ -47,6 +47,40 @@ JavaHotCodeReplaceListener_0= JavaPrimitivesPreferencePage_0=Primitive Display Options JavaPrimitivesPreferencePage_1=Display options for primitive values +JavaStackFramesPreferencePage_title=Stack Frame Colors +JavaStackFramesPreferencePage_description=Color of the Stack Frames set accordingly to these filters when 'Mark Stack Frames differently' toggle is activated. +JavaStackFramesPreferencePage__Color_stack_frames=Mark stack frames differently +JavaStackFramesPreferencePage__Collapse_stack_frames=Collapse non relevant stack frames +JavaStackFramesPreferencePage_Defined_stack_frame_filters_for_platform=Filters to define &platform methods: +JavaStackFramesPreferencePage_Defined_custom_stack_frame_filters=Filters for highlighted stack frames: +JavaStackFramesPreferencePage_Appearance_of_stack_frames=&Appearance of the stack frames: +JavaStackFramesPreferencePage_fg_color=&Foreground color +JavaStackFramesPreferencePage_bg_color=&Background color +JavaStackFramesPreferencePage_category_custom_filter=Highlighted methods +JavaStackFramesPreferencePage_category_synthetic=Synthetic methods +JavaStackFramesPreferencePage_category_platform=Platform methods +JavaStackFramesPreferencePage_category_test=Your test code +JavaStackFramesPreferencePage_category_production=Your source code +JavaStackFramesPreferencePage_category_library=Library code +JavaStackFramesPreferencePage_Filter_custom=Mark &highlighted stack frames differently +JavaStackFramesPreferencePage_Filter_synthetic=Mark s&ynthetic methods differently +JavaStackFramesPreferencePage_Filter_platform=Mark &platform methods differently +JavaStackFramesPreferencePage_Filter_test=Mark your &test code differently +JavaStackFramesPreferencePage_Filter_production=Mark your &source code differently +JavaStackFramesPreferencePage_Filter_library=Mark &library code differently + +JavaStackFramesPreferencePage_Add__Filter=Add &Filter... +JavaStackFramesPreferencePage_Add__Package=Add &Packages... +JavaStackFramesPreferencePage_Add__Type=Add &Class... +JavaStackFramesPreferencePage_Remove=&Remove +JavaStackFramesPreferencePage_Enable_All=&Enable All +JavaStackFramesPreferencePage_Disable_All=D&isable All + +JavaStackFramesPreferencePage_Add_package_for_stack_filters=Add Packages to Stack Filters +JavaStackFramesPreferencePage_Select_a_package_for_stack_filter=&Select a package for stack filtering: +JavaStackFramesPreferencePage_Add_type_for_stack_filters=Add Class to Stack Filters +JavaStackFramesPreferencePage_Select_a_type_for_stack_filter=&Select a class for stack filtering: + JavaStepFilterPreferencePage_Filter_co_nstructors_19=Filter co&nstructors JavaStepFilterPreferencePage_Filter_s_ynthetic_methods__requires_VM_support__17=Filter s&ynthetic methods (requires VM support) JavaStepFilterPreferencePage_Filter_static__initializers_18=Filter static &initializers diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/IJDIPreferencesConstants.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/IJDIPreferencesConstants.java index efe86cf430..2a04584b1f 100644 --- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/IJDIPreferencesConstants.java +++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/IJDIPreferencesConstants.java @@ -81,6 +81,12 @@ public interface IJDIPreferencesConstants { public static final String PREF_COLLAPSE_STACK_FRAMES = IJavaDebugUIConstants.PLUGIN_ID + ".collapse_stack_frames"; //$NON-NLS-1$ + /** + * Boolean preference indicating whether to colorize the stack frames in the debug view. + * + */ + public static final String PREF_COLORIZE_STACK_FRAMES = IJavaDebugUIConstants.PLUGIN_ID + ".colorize_stack_frames"; //$NON-NLS-1$ + /** * List of active step filters. A String containing a comma separated list of fully qualified type names/patterns. */ @@ -251,6 +257,25 @@ public interface IJDIPreferencesConstants { public static final String PREF_THREAD_MONITOR_IN_DEADLOCK_COLOR= "org.eclipse.jdt.debug.ui.InDeadlockColor"; //$NON-NLS-1$ public static final String PREF_LABELED_OBJECT_COLOR = IJavaDebugUIConstants.PLUGIN_ID + ".LabeledObject"; //$NON-NLS-1$ + + public static final String PREF_TEST_STACK_FRAME_FG_COLOR = IJavaDebugUIConstants.PLUGIN_ID + ".TestStackFrameFgColor"; //$NON-NLS-1$ + public static final String PREF_TEST_STACK_FRAME_BG_COLOR = IJavaDebugUIConstants.PLUGIN_ID + ".TestStackFrameBgColor"; //$NON-NLS-1$ + + public static final String PREF_LIB_STACK_FRAME_FG_COLOR = IJavaDebugUIConstants.PLUGIN_ID + ".LibraryStackFrameFgColor"; //$NON-NLS-1$ + public static final String PREF_LIB_STACK_FRAME_BG_COLOR = IJavaDebugUIConstants.PLUGIN_ID + ".LibraryStackFrameBgColor"; //$NON-NLS-1$ + + public static final String PREF_SYNT_STACK_FRAME_FG_COLOR = IJavaDebugUIConstants.PLUGIN_ID + ".SyntheticStackFrameFgColor"; //$NON-NLS-1$ + public static final String PREF_SYNT_STACK_FRAME_BG_COLOR = IJavaDebugUIConstants.PLUGIN_ID + ".SyntheticStackFrameBgColor"; //$NON-NLS-1$ + + public static final String PREF_PLATFORM_STACK_FRAME_FG_COLOR = IJavaDebugUIConstants.PLUGIN_ID + ".PlatformStackFrameFgColor"; //$NON-NLS-1$ + public static final String PREF_PLATFORM_STACK_FRAME_BG_COLOR = IJavaDebugUIConstants.PLUGIN_ID + ".PlatformStackFrameBgColor"; //$NON-NLS-1$ + + public static final String PREF_PRODUCTION_STACK_FRAME_FG_COLOR = IJavaDebugUIConstants.PLUGIN_ID + ".ProductionStackFrameFgColor"; //$NON-NLS-1$ + public static final String PREF_PRODUCTION_STACK_FRAME_BG_COLOR = IJavaDebugUIConstants.PLUGIN_ID + ".ProductionStackFrameBgColor"; //$NON-NLS-1$ + + public static final String PREF_CUSTOM_FILTERED_STACK_FRAME_FG_COLOR = IJavaDebugUIConstants.PLUGIN_ID + ".CustomFilteredStackFrameFgColor"; //$NON-NLS-1$ + public static final String PREF_CUSTOM_FILTERED_STACK_FRAME_BG_COLOR = IJavaDebugUIConstants.PLUGIN_ID + ".CustomFilteredStackFrameBgColor"; //$NON-NLS-1$ + /** * @since 3.2 */ diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/IJavaDebugHelpContextIds.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/IJavaDebugHelpContextIds.java index 33b90bcef7..9e17bc4656 100644 --- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/IJavaDebugHelpContextIds.java +++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/IJavaDebugHelpContextIds.java @@ -52,6 +52,7 @@ public interface IJavaDebugHelpContextIds { public static final String LAUNCH_JRE_PROPERTY_PAGE= PREFIX + "launch_jre_property_page_context"; //$NON-NLS-1$ public static final String JAVA_DEBUG_PREFERENCE_PAGE= PREFIX + "java_debug_preference_page_context"; //$NON-NLS-1$ public static final String JAVA_STEP_FILTER_PREFERENCE_PAGE= PREFIX + "java_step_filter_preference_page_context"; //$NON-NLS-1$ + public static final String JAVA_STACK_FRAMES_PREFERENCE_PAGE = PREFIX + "java_stack_frames_preference_page_context"; //$NON-NLS-1$ public static final String JAVA_BREAKPOINT_PREFERENCE_PAGE= PREFIX + "java_breakpoint_preference_page_context"; //$NON-NLS-1$ public static final String JAVA_DETAIL_FORMATTER_PREFERENCE_PAGE= PREFIX + "java_detail_formatter_preference_page_context"; //$NON-NLS-1$ public static final String JAVA_PRIMITIVES_PREFERENCE_PAGE= PREFIX + "java_primitives_preference_page_context"; //$NON-NLS-1$ diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JDIDebugUIPreferenceInitializer.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JDIDebugUIPreferenceInitializer.java index f7e2ce8a1c..fd7715affe 100644 --- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JDIDebugUIPreferenceInitializer.java +++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JDIDebugUIPreferenceInitializer.java @@ -48,6 +48,7 @@ public void initializeDefaultPreferences() { // Grouping stack frames store.setDefault(IJDIPreferencesConstants.PREF_COLLAPSE_STACK_FRAMES, true); + store.setDefault(IJDIPreferencesConstants.PREF_COLORIZE_STACK_FRAMES, true); store.setDefault(IJDIPreferencesConstants.PREF_ACTIVE_PLATFORM_FRAME_FILTER_LIST, "java.*,javax.*,jdk.*,sun.*,sunw.*,org.junit.*,org.eclipse.jdt.internal.*"); //$NON-NLS-1$ store.setDefault(IJDIPreferencesConstants.PREF_INACTIVE_PLATFORM_FRAME_FILTER_LIST, ""); //$NON-NLS-1$ 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 04c8d85fef..721b32c74c 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 @@ -1241,6 +1241,10 @@ protected boolean isShowVariableTypeNames() { } } + protected boolean isColorizeStackFrames() { + return getStackFrameProvider().isColorizeStackFrames(); + } + protected boolean isShowHexValues() { return JDIDebugUIPlugin.getDefault().getPreferenceStore().getBoolean(IJDIPreferencesConstants.PREF_SHOW_HEX); } @@ -2175,6 +2179,9 @@ public Color getForeground(Object element) { if (element instanceof IJavaThread javaThread && ThreadMonitorManager.getDefault().isInDeadlock(javaThread)) { return getColorFromRegistry(IJDIPreferencesConstants.PREF_THREAD_MONITOR_IN_DEADLOCK_COLOR); } + if (element instanceof IJavaStackFrame frame && isColorizeStackFrames()) { + return getStackFrameProvider().getForeground(frame); + } return null; } @@ -2190,6 +2197,9 @@ protected Color getColorFromRegistry(String symbolicName) { */ @Override public Color getBackground(Object element) { + if (element instanceof IJavaStackFrame frame && isColorizeStackFrames()) { + return getStackFrameProvider().getBackground(frame); + } return null; } diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JavaStackFramePreferencePage.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JavaStackFramePreferencePage.java new file mode 100644 index 0000000000..c11bee639f --- /dev/null +++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/JavaStackFramePreferencePage.java @@ -0,0 +1,523 @@ +/******************************************************************************* + * Copyright (c) 2022, 2025 Zsombor Gegesy 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: + * Zsombor Gegesy - initial API and implementation + *******************************************************************************/ +package org.eclipse.jdt.internal.debug.ui; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import org.eclipse.debug.internal.ui.SWTFactory; +import org.eclipse.jdt.debug.core.IJavaStackFrame; +import org.eclipse.jdt.debug.core.IJavaStackFrame.Category; +import org.eclipse.jdt.internal.ui.filtertable.Filter; +import org.eclipse.jdt.internal.ui.filtertable.JavaFilterTable; +import org.eclipse.jdt.internal.ui.filtertable.JavaFilterTable.ButtonLabel; +import org.eclipse.jdt.internal.ui.filtertable.JavaFilterTable.DialogLabels; +import org.eclipse.jdt.internal.ui.filtertable.JavaFilterTable.FilterTableConfig; +import org.eclipse.jface.preference.ColorSelector; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.preference.PreferencePage; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.jface.viewers.IColorProvider; +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.RGB; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPreferencePage; +import org.eclipse.ui.PlatformUI; + +/** + * The preference page for Java stack frame categorization, located at the node Java > Debug > Stack Frames + * + * @since 3.19 + */ +public class JavaStackFramePreferencePage extends PreferencePage implements IWorkbenchPreferencePage, IPropertyChangeListener { + + private static class CategoryColors { + final IJavaStackFrame.Category category; + final String key; + Color fgColor; + Color bgColor; + + CategoryColors(Category category, String key) { + this.category = category; + this.key = key; + } + + Color getForegroundColor(StackFramePresentationProvider stackFramePresentationProvider) { + if (fgColor != null) { + return fgColor; + } + return stackFramePresentationProvider.getForegroundColor(category); + } + + Color getBackgroundColor(StackFramePresentationProvider stackFramePresentationProvider) { + if (bgColor != null) { + return bgColor; + } + return stackFramePresentationProvider.getBackgroundColor(category); + } + + void setToDefault() { + this.fgColor = null; + this.bgColor = null; + } + } + + private class StackFrameCategoryLabelProvider extends LabelProvider implements IColorProvider { + + @Override + public String getText(Object element) { + return ((CategoryColors) element).key; + } + + @Override + public Color getForeground(Object element) { + return ((CategoryColors) element).getForegroundColor(stackFramePresentationProvider); + } + + @Override + public Color getBackground(Object element) { + return ((CategoryColors) element).getBackgroundColor(stackFramePresentationProvider); + } + } + + private static class StackFrameCategoryContentProvider implements IStructuredContentProvider { + @Override + public Object[] getElements(Object inputElement) { + return ((java.util.List) inputElement).toArray(); + } + } + + private static class ButtonWrapper { + private final Button button; + protected final A key; + + ButtonWrapper(Composite container, String label, A key, boolean checked, Consumer stateChangeConsumer) { + this.button = SWTFactory.createCheckButton(container, label, null, checked, 2); + this.key = key; + if (stateChangeConsumer != null) { + widgetSelected(stateChangeConsumer); + } + } + + void setChecked(boolean checked) { + button.setSelection(checked); + } + + boolean isChecked() { + return button.getSelection(); + } + + void setEnabled(boolean enabled) { + this.button.setEnabled(enabled); + } + + ButtonWrapper widgetSelected(Consumer consumer) { + this.button.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + consumer.accept(button.getSelection()); + } + }); + return this; + } + + } + + private static class PreferenceButton extends ButtonWrapper { + + PreferenceButton(Composite container, String label, IPreferenceStore store, String flag, Consumer stateChangeConsumer) { + super(container, label, flag, store.getBoolean(flag), stateChangeConsumer); + } + + boolean performDefault(IPreferenceStore store) { + var defValue = store.getBoolean(key); + setChecked(defValue); + return defValue; + } + + void performOk(IPreferenceStore store) { + store.setValue(key, isChecked()); + } + } + + private static class CategoryButton extends ButtonWrapper { + + CategoryButton(Composite container, String label, StackFrameCategorizer categorizer, Category category, Consumer stateChangeConsumer) { + super(container, label, category, categorizer.isEnabled(category), stateChangeConsumer); + } + + boolean performDefault(StackFrameCategorizer store) { + var defValue = store.isEnabled(key); + setChecked(defValue); + return defValue; + } + + void performOk(StackFrameCategorizer store) { + store.setEnabled(key, isChecked()); + } + + } + public static final String PAGE_ID = "org.eclipse.jdt.debug.ui.JavaStackFramePreferencePage"; //$NON-NLS-1$ + + //widgets + private PreferenceButton fColorizeStackFrames; + private PreferenceButton fCollapseStackFrames; + private List categoryButtons; + private CategoryButton fEnablePlatformButton; + private CategoryButton fEnableCustomButton; + private JavaFilterTable fPlatformStackFilterTable; + private JavaFilterTable fCustomStackFilterTable; + private TableViewer fAppearanceList; + private List colors; + private ColorSelector fFgColorSelector; + private ColorSelector fBgColorSelector; + private StackFramePresentationProvider stackFramePresentationProvider; + private StackFrameCategorizer categorizer; + + /** + * Constructor + */ + public JavaStackFramePreferencePage() { + super(); + setPreferenceStore(JDIDebugUIPlugin.getDefault().getPreferenceStore()); + setTitle(DebugUIMessages.JavaStackFramesPreferencePage_title); + setDescription(DebugUIMessages.JavaStackFramesPreferencePage_description); + stackFramePresentationProvider = new StackFramePresentationProvider(getPreferenceStore()); + this.categorizer = JDIDebugUIPlugin.getDefault().getStackFrameCategorizer(); + categoryButtons = new ArrayList<>(); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.preference.PreferencePage#createContents(org.eclipse.swt.widgets.Composite) + */ + @Override + protected Control createContents(Composite parent) { + PlatformUI.getWorkbench().getHelpSystem().setHelp(getControl(), IJavaDebugHelpContextIds.JAVA_STACK_FRAMES_PREFERENCE_PAGE); + // The main composite + Composite composite = SWTFactory.createComposite(parent, parent.getFont(), 1, 1, GridData.FILL_BOTH, 0, 0); + createStepFilterPreferences(composite); + return composite; + } + + /* (non-Javadoc) + * @see org.eclipse.ui.IWorkbenchPreferencePage#init(org.eclipse.ui.IWorkbench) + */ + @Override + public void init(IWorkbench workbench) {} + + /** + * Create a group to contain the step filter related widgets + */ + private void createStepFilterPreferences(Composite parent) { + var store = getPreferenceStore(); + Composite container = SWTFactory.createComposite(parent, parent.getFont(), 2, 1, GridData.FILL_BOTH, 0, 0); + fColorizeStackFrames = new PreferenceButton(container, DebugUIMessages.JavaStackFramesPreferencePage__Color_stack_frames, store, IJDIPreferencesConstants.PREF_COLORIZE_STACK_FRAMES, this::updateCheckboxes); + + fCollapseStackFrames = new PreferenceButton(container, DebugUIMessages.JavaStackFramesPreferencePage__Collapse_stack_frames, store, IJDIPreferencesConstants.PREF_COLLAPSE_STACK_FRAMES, this::updateCheckboxes); + initializeDialogUnits(container); + + this.fEnableCustomButton = new CategoryButton(container, DebugUIMessages.JavaStackFramesPreferencePage_Filter_custom, categorizer, StackFrameCategorizer.CATEGORY_CUSTOM_FILTERED, // + selected -> fCustomStackFilterTable.setEnabled(selected && isCategoryHandlingEnabled())); + categoryButtons.add(this.fEnableCustomButton); + + fCustomStackFilterTable = new JavaFilterTable(new JavaFilterTable.FilterStorage() { + + @Override + public Filter[] getStoredFilters(boolean defaults) { + return combineFilterLists(categorizer.getActiveCustomStackFilter(), categorizer.getInactiveCustomStackFilter()); + } + + @Override + public void setStoredFilters(IPreferenceStore store, Filter[] filters) { + categorizer.setCustomFilters(filters); + } + + }, + new FilterTableConfig() // configuration for the table + .setAddFilter(new ButtonLabel(DebugUIMessages.JavaStackFramesPreferencePage_Add__Filter)) + .setAddPackage(new ButtonLabel(DebugUIMessages.JavaStackFramesPreferencePage_Add__Package)) + .setAddType(new ButtonLabel(DebugUIMessages.JavaStackFramesPreferencePage_Add__Type)) + .setRemove(new ButtonLabel(DebugUIMessages.JavaStackFramesPreferencePage_Remove)) + .setSelectAll(new ButtonLabel(DebugUIMessages.JavaStackFramesPreferencePage_Enable_All)) + .setDeselectAll(new ButtonLabel(DebugUIMessages.JavaStackFramesPreferencePage_Disable_All)) + .setAddPackageDialog(new DialogLabels(DebugUIMessages.JavaStackFramesPreferencePage_Add_package_for_stack_filters, DebugUIMessages.JavaStackFramesPreferencePage_Select_a_package_for_stack_filter)) + .setAddTypeDialog(new DialogLabels(DebugUIMessages.JavaStackFramesPreferencePage_Add_type_for_stack_filters, DebugUIMessages.JavaStackFramesPreferencePage_Select_a_package_for_stack_filter)) + .setLabelText(DebugUIMessages.JavaStackFramesPreferencePage_Defined_custom_stack_frame_filters) + .setHelpContextId(IJavaDebugHelpContextIds.JAVA_STACK_FRAMES_PREFERENCE_PAGE)); + fCustomStackFilterTable.createTable(container); + + categoryButtons.add(new CategoryButton(container, DebugUIMessages.JavaStackFramesPreferencePage_Filter_synthetic, categorizer, StackFrameCategorizer.CATEGORY_SYNTHETIC, null)); + + this.fEnablePlatformButton = new CategoryButton(container, DebugUIMessages.JavaStackFramesPreferencePage_Filter_platform, categorizer, StackFrameCategorizer.CATEGORY_PLATFORM, // + selected -> fPlatformStackFilterTable.setEnabled(selected && isCategoryHandlingEnabled())); + categoryButtons.add(fEnablePlatformButton); + + fPlatformStackFilterTable = new JavaFilterTable(new JavaFilterTable.FilterStorage() { + @Override + public Filter[] getStoredFilters(boolean defaults) { + return combineFilterLists(categorizer.getActivePlatformStackFilter(), categorizer.getInactivePlatformStackFilter()); + } + + @Override + public void setStoredFilters(IPreferenceStore store, Filter[] filters) { + categorizer.setPlatformFilters(filters); + } + + }, + new FilterTableConfig() + .setAddFilter(new ButtonLabel(DebugUIMessages.JavaStackFramesPreferencePage_Add__Filter)) + .setAddPackage(new ButtonLabel(DebugUIMessages.JavaStackFramesPreferencePage_Add__Package)) + .setAddType(new ButtonLabel(DebugUIMessages.JavaStackFramesPreferencePage_Add__Type)) + .setRemove(new ButtonLabel(DebugUIMessages.JavaStackFramesPreferencePage_Remove)) + .setSelectAll(new ButtonLabel(DebugUIMessages.JavaStackFramesPreferencePage_Enable_All)) + .setDeselectAll(new ButtonLabel(DebugUIMessages.JavaStackFramesPreferencePage_Disable_All)) + .setAddPackageDialog(new DialogLabels(DebugUIMessages.JavaStackFramesPreferencePage_Add_package_for_stack_filters, + DebugUIMessages.JavaStackFramesPreferencePage_Select_a_package_for_stack_filter)) + .setAddTypeDialog(new DialogLabels(DebugUIMessages.JavaStackFramesPreferencePage_Add_type_for_stack_filters, + DebugUIMessages.JavaStackFramesPreferencePage_Select_a_package_for_stack_filter)) + .setLabelText(DebugUIMessages.JavaStackFramesPreferencePage_Defined_stack_frame_filters_for_platform) + .setHelpContextId(IJavaDebugHelpContextIds.JAVA_STACK_FRAMES_PREFERENCE_PAGE) + ); + fPlatformStackFilterTable.createTable(container); + + categoryButtons.add(new CategoryButton(container, DebugUIMessages.JavaStackFramesPreferencePage_Filter_test, categorizer, StackFrameCategorizer.CATEGORY_TEST, null)); + categoryButtons.add(new CategoryButton(container, DebugUIMessages.JavaStackFramesPreferencePage_Filter_production, categorizer, StackFrameCategorizer.CATEGORY_PRODUCTION, null)); + categoryButtons.add(new CategoryButton(container, DebugUIMessages.JavaStackFramesPreferencePage_Filter_library, categorizer, StackFrameCategorizer.CATEGORY_LIBRARY, null)); + + createAppearanceList(container); + + setPageEnablement(isCategoryHandlingEnabled()); + initList(); + } + + private static Filter[] combineFilterLists(String[] activefilters, String[] inactivefilters) { + Filter[] filters = new Filter[activefilters.length + inactivefilters.length]; + for (int i = 0; i < activefilters.length; i++) { + filters[i] = new Filter(activefilters[i], true); + } + for (int i = 0; i < inactivefilters.length; i++) { + filters[i + activefilters.length] = new Filter(inactivefilters[i], false); + } + return filters; + } + + private void createAppearanceList(Composite container) { + + SWTFactory.createLabel(container, DebugUIMessages.JavaStackFramesPreferencePage_Appearance_of_stack_frames, 2); + + var editorComposite = new Composite(container, SWT.NONE); + var layout = new GridLayout(); + layout.numColumns = 2; + layout.marginHeight = 0; + layout.marginWidth = 0; + editorComposite.setLayout(layout); + var gd = new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.FILL_VERTICAL); + gd.horizontalSpan = 2; + editorComposite.setLayoutData(gd); + + fAppearanceList = new TableViewer(editorComposite, SWT.SINGLE | SWT.V_SCROLL | SWT.BORDER | SWT.FULL_SELECTION); + fAppearanceList.setLabelProvider(new StackFrameCategoryLabelProvider()); + fAppearanceList.setContentProvider(new StackFrameCategoryContentProvider()); + + gd = new GridData(SWT.BEGINNING, SWT.FILL, false, true); + gd.heightHint = convertHeightInCharsToPixels(8); + fAppearanceList.getControl().setLayoutData(gd); + fAppearanceList.addSelectionChangedListener(event -> { + var selection = event.getStructuredSelection(); + var valid = !selection.isEmpty(); + fFgColorSelector.getButton().setEnabled(valid); + fBgColorSelector.getButton().setEnabled(valid); + if (valid) { + CategoryColors category = (CategoryColors) selection.getFirstElement(); + var color = category.getForegroundColor(stackFramePresentationProvider); + if (color != null) { + fFgColorSelector.setColorValue(color.getRGB()); + } + var bgColor = category.getBackgroundColor(stackFramePresentationProvider); + if (bgColor != null) { + fBgColorSelector.setColorValue(bgColor.getRGB()); + } + } + }); + + + Composite stylesComposite = new Composite(editorComposite, SWT.NONE); + layout = new GridLayout(); + layout.marginHeight = 0; + layout.marginWidth = 0; + layout.numColumns = 2; + stylesComposite.setLayout(layout); + stylesComposite.setLayoutData(new GridData(GridData.FILL_BOTH)); + + SWTFactory.createLabel(stylesComposite, DebugUIMessages.JavaStackFramesPreferencePage_fg_color, 1); + + fFgColorSelector = createColorSelector(stylesComposite); + fFgColorSelector.addListener(event -> { + var selection = getSelected(); + if (selection != null) { + selection.fgColor = toColor(event); + fAppearanceList.update(selection, null); + } + }); + + SWTFactory.createLabel(stylesComposite, DebugUIMessages.JavaStackFramesPreferencePage_bg_color, 1); + + fBgColorSelector = createColorSelector(stylesComposite); + fBgColorSelector.addListener(event -> { + var selection = getSelected(); + if (selection != null) { + selection.bgColor = toColor(event); + fAppearanceList.update(selection, null); + } + }); + + PlatformUI.getWorkbench().getThemeManager().getCurrentTheme().getColorRegistry().addListener(this); + } + + private CategoryColors getSelected() { + var selection = fAppearanceList.getStructuredSelection().getFirstElement(); + if (selection instanceof CategoryColors) { + return (CategoryColors) selection; + } + return null; + } + + private Color toColor(PropertyChangeEvent event) { + return new Color((RGB) event.getNewValue()); + } + + private ColorSelector createColorSelector(Composite stylesComposite) { + var colorSelector = new ColorSelector(stylesComposite); + Button button = colorSelector.getButton(); + var gd = new GridData(GridData.FILL_HORIZONTAL); + gd.horizontalAlignment = GridData.BEGINNING; + button.setLayoutData(gd); + return colorSelector; + } + + private void initList() { + colors = List.of( + new CategoryColors(StackFrameCategorizer.CATEGORY_CUSTOM_FILTERED, DebugUIMessages.JavaStackFramesPreferencePage_category_custom_filter), // + new CategoryColors(StackFrameCategorizer.CATEGORY_SYNTHETIC, DebugUIMessages.JavaStackFramesPreferencePage_category_synthetic), // + new CategoryColors(StackFrameCategorizer.CATEGORY_PLATFORM, DebugUIMessages.JavaStackFramesPreferencePage_category_platform), // + new CategoryColors(StackFrameCategorizer.CATEGORY_TEST, DebugUIMessages.JavaStackFramesPreferencePage_category_test), // + new CategoryColors(StackFrameCategorizer.CATEGORY_PRODUCTION, DebugUIMessages.JavaStackFramesPreferencePage_category_production), // + new CategoryColors(StackFrameCategorizer.CATEGORY_LIBRARY, DebugUIMessages.JavaStackFramesPreferencePage_category_library) // + ); + fAppearanceList.setInput(colors); + fAppearanceList.setSelection(new StructuredSelection(colors.get(0))); + } + + protected void updateCheckboxes(@SuppressWarnings("unused") boolean flag) { + setPageEnablement(isCategoryHandlingEnabled()); + } + + private boolean isCategoryHandlingEnabled() { + return fCollapseStackFrames.isChecked() || fColorizeStackFrames.isChecked(); + } + + /** + * Enables or disables the widgets on the page, with the + * exception of fUseStepFiltersButton according + * to the passed boolean + * @param enabled the new enablement status of the page's widgets + * @since 3.2 + */ + protected void setPageEnablement(boolean enabled) { + fPlatformStackFilterTable.setEnabled(enabled && fEnablePlatformButton.isChecked()); + fCustomStackFilterTable.setEnabled(enabled && fEnableCustomButton.isChecked()); + for (var categoryButton : categoryButtons) { + categoryButton.setEnabled(enabled); + } + fAppearanceList.getControl().setEnabled(enabled); + fFgColorSelector.getButton().setEnabled(enabled); + fBgColorSelector.getButton().setEnabled(enabled); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.preference.PreferencePage#performOk() + */ + @Override + public boolean performOk() { + IPreferenceStore store = getPreferenceStore(); + fPlatformStackFilterTable.performOk(store); + fCustomStackFilterTable.performOk(store); + fColorizeStackFrames.performOk(store); + for (var categoryButton : categoryButtons) { + categoryButton.performOk(categorizer); + } + for (var color : colors) { + if (color.fgColor != null) { + stackFramePresentationProvider.setForegroundColor(color.category, color.fgColor.getRGB()); + } + if (color.bgColor != null) { + stackFramePresentationProvider.setBackgroundColor(color.category, color.bgColor.getRGB()); + } + } + return super.performOk(); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.preference.PreferencePage#performDefaults() + */ + @Override + protected void performDefaults() { + var store = getPreferenceStore(); + boolean enabled = fColorizeStackFrames.performDefault(store) || fCollapseStackFrames.performDefault(store); + + for (var categoryButton : categoryButtons) { + categoryButton.performDefault(categorizer); + } + for (var color : colors) { + color.setToDefault(); + } + fAppearanceList.update(colors.toArray(), null); + fAppearanceList.setSelection(new StructuredSelection(colors.get(0))); + + setPageEnablement(enabled); + + fPlatformStackFilterTable.performDefaults(); + fCustomStackFilterTable.performDefaults(); + super.performDefaults(); + } + + @Override + public void propertyChange(PropertyChangeEvent event) { + var propertyName = event.getProperty(); + if (StackFramePresentationProvider.isColorName(propertyName)) { + fAppearanceList.update(colors.toArray(), null); + fAppearanceList.setSelection(new StructuredSelection(colors.get(0))); + } + } + + @Override + public void dispose() { + PlatformUI.getWorkbench().getThemeManager().getCurrentTheme().getColorRegistry().removeListener(this); + super.dispose(); + } +} diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/StackFrameCategorizer.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/StackFrameCategorizer.java index 3bad077417..f2155ef1f5 100644 --- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/StackFrameCategorizer.java +++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/StackFrameCategorizer.java @@ -41,27 +41,27 @@ public class StackFrameCategorizer implements IPreferenceChangeListener { /** * The user specified a filter, can be used for highlighting specific, very important code layers. */ - static final Category CATEGORY_CUSTOM_FILTERED = new Category("CUSTOM_FILTERED", false); //$NON-NLS-1$ + public static final Category CATEGORY_CUSTOM_FILTERED = new Category("CUSTOM_FILTERED", false); //$NON-NLS-1$ /** * The stack frame represents a synthetic function call, which is not based on actual Java source code. */ - static final Category CATEGORY_SYNTHETIC = new Category("SYNTHETIC", true); //$NON-NLS-1$ + public static final Category CATEGORY_SYNTHETIC = new Category("SYNTHETIC", true); //$NON-NLS-1$ /** * Methods in classes that considered as platform, like code in 'java.*' packages. */ - static final Category CATEGORY_PLATFORM = new Category("PLATFORM", true); //$NON-NLS-1$ + public static final Category CATEGORY_PLATFORM = new Category("PLATFORM", true); //$NON-NLS-1$ /** * Classes found in a test source folder in the project. */ - static final Category CATEGORY_TEST = new Category("TEST", false); //$NON-NLS-1$ + public static final Category CATEGORY_TEST = new Category("TEST", false); //$NON-NLS-1$ /** * Classes found in a non-test source folder in the project. */ - static final Category CATEGORY_PRODUCTION = new Category("PRODUCTION", false); //$NON-NLS-1$ + public static final Category CATEGORY_PRODUCTION = new Category("PRODUCTION", false); //$NON-NLS-1$ /** * Classes coming from a library, not from the actual project. @@ -71,7 +71,7 @@ public class StackFrameCategorizer implements IPreferenceChangeListener { /** * Classes with unknown origin. */ - static final Category CATEGORY_UNKNOWN = new Category("UNKNOWN", true); //$NON-NLS-1$ + public static final Category CATEGORY_UNKNOWN = new Category("UNKNOWN", true); //$NON-NLS-1$ /** * Class to decide if a particular class name is part of a list of classes and list of packages. diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/StackFramePresentationProvider.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/StackFramePresentationProvider.java index 707e8841a4..0a347737f3 100644 --- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/StackFramePresentationProvider.java +++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/StackFramePresentationProvider.java @@ -13,14 +13,20 @@ *******************************************************************************/ package org.eclipse.jdt.internal.debug.ui; +import java.util.Map; import java.util.Set; import org.eclipse.jdt.debug.core.IJavaStackFrame; +import org.eclipse.jdt.debug.core.IJavaStackFrame.Category; import org.eclipse.jdt.internal.ui.JavaPluginImages; import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.resource.ColorRegistry; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.RGB; +import org.eclipse.ui.PlatformUI; /** * Provides foreground and background colors for stack frames in a debug view. After usage, it needs to be closed, so it could unregister itself from @@ -30,12 +36,29 @@ public final class StackFramePresentationProvider implements IPropertyChangeListener { private static final Set JAR_ICON = Set.of(StackFrameCategorizer.CATEGORY_LIBRARY.name(), StackFrameCategorizer.CATEGORY_SYNTHETIC.name(), StackFrameCategorizer.CATEGORY_PLATFORM.name()); + + private final static Map fgKeyMap = Map.of(StackFrameCategorizer.CATEGORY_CUSTOM_FILTERED, IJDIPreferencesConstants.PREF_CUSTOM_FILTERED_STACK_FRAME_FG_COLOR, // + StackFrameCategorizer.CATEGORY_SYNTHETIC, IJDIPreferencesConstants.PREF_SYNT_STACK_FRAME_FG_COLOR, // + StackFrameCategorizer.CATEGORY_PLATFORM, IJDIPreferencesConstants.PREF_PLATFORM_STACK_FRAME_FG_COLOR, // + StackFrameCategorizer.CATEGORY_TEST, IJDIPreferencesConstants.PREF_TEST_STACK_FRAME_FG_COLOR, // + StackFrameCategorizer.CATEGORY_PRODUCTION, IJDIPreferencesConstants.PREF_PRODUCTION_STACK_FRAME_FG_COLOR, // + StackFrameCategorizer.CATEGORY_LIBRARY, IJDIPreferencesConstants.PREF_LIB_STACK_FRAME_FG_COLOR); + + private final static Map bgKeyMap = Map.of(StackFrameCategorizer.CATEGORY_CUSTOM_FILTERED, IJDIPreferencesConstants.PREF_CUSTOM_FILTERED_STACK_FRAME_BG_COLOR, // + StackFrameCategorizer.CATEGORY_SYNTHETIC, IJDIPreferencesConstants.PREF_SYNT_STACK_FRAME_BG_COLOR, // + StackFrameCategorizer.CATEGORY_PLATFORM, IJDIPreferencesConstants.PREF_PLATFORM_STACK_FRAME_BG_COLOR, // + StackFrameCategorizer.CATEGORY_TEST, IJDIPreferencesConstants.PREF_TEST_STACK_FRAME_BG_COLOR, // + StackFrameCategorizer.CATEGORY_PRODUCTION, IJDIPreferencesConstants.PREF_PRODUCTION_STACK_FRAME_BG_COLOR, // + StackFrameCategorizer.CATEGORY_LIBRARY, IJDIPreferencesConstants.PREF_LIB_STACK_FRAME_BG_COLOR); + private final IPreferenceStore store; private boolean collapseStackFrames; + private boolean colorizeStackFrames; public StackFramePresentationProvider(IPreferenceStore store) { this.store = store; store.addPropertyChangeListener(this); + colorizeStackFrames = store.getBoolean(IJDIPreferencesConstants.PREF_COLORIZE_STACK_FRAMES); collapseStackFrames = store.getBoolean(IJDIPreferencesConstants.PREF_COLLAPSE_STACK_FRAMES); } @@ -43,11 +66,57 @@ public StackFramePresentationProvider() { this(JDIDebugUIPlugin.getDefault().getPreferenceStore()); } + /** + * @return the foreground color of the stack frame's category, or null, if not defined. + */ + public Color getForeground(IJavaStackFrame frame) { + Category cat = frame.getCategory(); + if (cat == null) { + return null; + } + return getForegroundColor(cat); + } + + /** + * @return the foreground color of the category, or null, if it's not defined. + */ + Color getForegroundColor(Category cat) { + var key = fgKeyMap.get(cat); + if (key != null) { + return getColorRegistry().get(key); + } + return null; + } + + private ColorRegistry getColorRegistry() { + return PlatformUI.getWorkbench().getThemeManager().getCurrentTheme().getColorRegistry(); + } + + /** + * Sets the foreground color for the given {@link IJavaStackFrame.Category}. + */ + void setForegroundColor(Category cat, RGB color) { + var key = fgKeyMap.get(cat); + if (key != null) { + getColorRegistry().put(key, color); + } + } + + /** + * Sets the background color for the given {@link IJavaStackFrame.Category}. + */ + void setBackgroundColor(Category cat, RGB color) { + var key = bgKeyMap.get(cat); + if (key != null) { + getColorRegistry().put(key, color); + } + } + /** * @return the category specific image for the stack frame, or null, if there is no one defined. */ public ImageDescriptor getStackFrameImage(IJavaStackFrame frame) { - if (collapseStackFrames) { + if (collapseStackFrames || colorizeStackFrames) { var category = frame.getCategory(); if (category != null) { if (JAR_ICON.contains(category.name())) { @@ -58,6 +127,26 @@ public ImageDescriptor getStackFrameImage(IJavaStackFrame frame) { return null; } + /** + * @return the background color assigned to the stack frame's category, or null, if there is no category matching. + */ + public Color getBackground(IJavaStackFrame frame) { + Category cat = frame.getCategory(); + if (cat == null) { + return null; + } + return getBackgroundColor(cat); + } + + Color getBackgroundColor(Category cat) { + var key = bgKeyMap.get(cat); + if (key != null) { + return getColorRegistry().get(key); + } + return null; + } + + /** * Unsubscribes from the {@link IPreferenceStore} to not receive notifications. */ @@ -70,6 +159,8 @@ public void propertyChange(PropertyChangeEvent event) { String prop = event.getProperty(); if (IJDIPreferencesConstants.PREF_COLLAPSE_STACK_FRAMES.equals(prop)) { collapseStackFrames = (Boolean) event.getNewValue(); + } else if (IJDIPreferencesConstants.PREF_COLORIZE_STACK_FRAMES.equals(prop)) { + colorizeStackFrames = (Boolean) event.getNewValue(); } } @@ -80,4 +171,14 @@ public boolean isCollapseStackFrames() { return collapseStackFrames; } + /** + * @return if stack frames should be colored differently based on their category. + */ + public boolean isColorizeStackFrames() { + return colorizeStackFrames; + } + + static boolean isColorName(String propertyName) { + return fgKeyMap.containsValue(propertyName) || bgKeyMap.containsValue(propertyName); + } } diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/actions/ColorizeStackFramesAction.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/actions/ColorizeStackFramesAction.java new file mode 100644 index 0000000000..dfa8de008a --- /dev/null +++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/actions/ColorizeStackFramesAction.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2022, 2025 Zsombor Gegesy 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: + * Zsombor Gegesy - initial API and implementation + *******************************************************************************/ +package org.eclipse.jdt.internal.debug.ui.actions; + +import org.eclipse.jdt.internal.debug.ui.IJDIPreferencesConstants; + +/** + * Enable/disable the coloring of the stack frames in debug views. + * + */ +public class ColorizeStackFramesAction extends ToggleBooleanPreferenceAction { + + /* + * (non-Javadoc) + * + * @see org.eclipse.jdt.internal.debug.ui.actions.ViewFilterAction#getPreferenceKey() + */ + @Override + protected String getPreferenceKey() { + return IJDIPreferencesConstants.PREF_COLORIZE_STACK_FRAMES; + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jdt.internal.debug.ui.actions.ViewFilterAction#getCompositeKey() + */ + @Override + protected String getCompositeKey() { + return getPreferenceKey(); + } + +} diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/actions/HighlightStackFrameAction.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/actions/HighlightStackFrameAction.java new file mode 100644 index 0000000000..bd525f3fdb --- /dev/null +++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/actions/HighlightStackFrameAction.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2023 Zsombor Gegesy 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: + * Zsombor Gegesy - initial API and implementation + *******************************************************************************/ +package org.eclipse.jdt.internal.debug.ui.actions; + +import java.util.HashSet; + +import org.eclipse.debug.core.DebugException; +import org.eclipse.debug.core.model.IStackFrame; +import org.eclipse.debug.core.model.IThread; +import org.eclipse.jdt.debug.core.IJavaStackFrame; +import org.eclipse.jdt.internal.debug.ui.JDIDebugUIPlugin; +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.viewers.IStructuredSelection; + +/** + * Add the current stack frame to the highlighted frames. + * + */ +public class HighlightStackFrameAction extends ObjectActionDelegate { + + @Override + public void run(IAction action) { + // Make sure there is a current selection + IStructuredSelection selection = getCurrentSelection(); + if (selection == null) { + return; + } + + final var stackFrameCategorizer = JDIDebugUIPlugin.getDefault().getStackFrameCategorizer(); + final var types = new HashSet(); + final var threads = new HashSet(); + for (Object selected : selection) { + if (selected instanceof IJavaStackFrame frame) { + try { + types.add(frame.getDeclaringTypeName()); + } catch (DebugException e) { + JDIDebugUIPlugin.log(e); + } + threads.add(frame.getThread()); + } + } + stackFrameCategorizer.addTypesToActiveCustomFilters(types); + for (IThread thread : threads) { + try { + for (IStackFrame frame : thread.getStackFrames()) { + if (frame instanceof IJavaStackFrame javaFrame) { + javaFrame.resetCategory(); + } + } + } catch (DebugException e) { + JDIDebugUIPlugin.log(e); + } + } + + } + +}