diff --git a/docs/dap/DAPSupport.md b/docs/dap/DAPSupport.md index 588b6a81e..4255f2b68 100644 --- a/docs/dap/DAPSupport.md +++ b/docs/dap/DAPSupport.md @@ -64,7 +64,7 @@ Current state of [Requests](https://microsoft.github.io/debug-adapter-protocol// * ✅ [StackTrace](https://microsoft.github.io/debug-adapter-protocol//specification.html#Requests_StackTrace). * ❌ [StepBack](https://microsoft.github.io/debug-adapter-protocol//specification.html#Requests_StepBack). * ✅ [StepIn](https://microsoft.github.io/debug-adapter-protocol//specification.html#Requests_StepIn). -* ❌ [StepInTargets](https://microsoft.github.io/debug-adapter-protocol//specification.html#Requests_StepInTargets). +* ✅ [StepInTargets](https://microsoft.github.io/debug-adapter-protocol//specification.html#Requests_StepInTargets). * ✅ [StepOut](https://microsoft.github.io/debug-adapter-protocol//specification.html#Requests_StepOut). * ✅ [Terminate](https://microsoft.github.io/debug-adapter-protocol//specification.html#Requests_Terminate). * ❌ [TerminateThreads](https://microsoft.github.io/debug-adapter-protocol//specification.html#Requests_TerminateThreads). diff --git a/docs/dap/UserGuide.md b/docs/dap/UserGuide.md index 89fc72e1c..e085edcec 100644 --- a/docs/dap/UserGuide.md +++ b/docs/dap/UserGuide.md @@ -83,6 +83,34 @@ Take a sample JavaScript file containing an error: In this example, no breakpoints are defined. However, when you start the DAP server, it stops at the line with the line error: +### Smart Step Into + +[Smart Step Into](https://www.jetbrains.com/help/idea/stepping-through-the-program.html#smart-step-into) is available +if the DAP server supports [StepInTargets](https://microsoft.github.io/debug-adapter-protocol//specification.html#Requests_StepInTargets). + +When you're stopped at a breakpoint on a line with multiple function calls, press **Shift+F7** (or select **Smart Step Into** from the debug menu). +A popup will appear showing all available functions you can step into. +Use the arrow keys to navigate between functions, and press **Enter** to step into the selected one. +You can also click directly on a function name in the editor. + +For example, on this line: +```python +result = multiply(add(x, y), subtract(x, y)) +``` + +Smart Step Into will let you choose between `multiply`, `add`, and `subtract`. + +The feature also handles duplicate function names on the same line correctly. For instance: +```python +result = add(1, 2) + add(3, 4) +``` + +Each `add` call will be highlighted separately, allowing you to step into the specific occurrence you want. + +Here's a demo with [Python Debugpy](./user-defined-dap/python-debugpy.md) which supports [StepInTargets](https://microsoft.github.io/debug-adapter-protocol//specification.html#Requests_StepInTargets): + +![DAP Smart Step Into](./images/DAP_SmartStepIntoDemo.gif) + ![DAP exception breakpoint / Syntax error](./images/DAP_exception_breakpoint_sample_stop.png) This happens because `Caught Exceptions` is selected. diff --git a/docs/dap/images/DAP_SmartStepIntoDemo.gif b/docs/dap/images/DAP_SmartStepIntoDemo.gif new file mode 100644 index 000000000..cd01fe718 Binary files /dev/null and b/docs/dap/images/DAP_SmartStepIntoDemo.gif differ diff --git a/src/main/java/com/redhat/devtools/lsp4ij/dap/DAPDebugProcess.java b/src/main/java/com/redhat/devtools/lsp4ij/dap/DAPDebugProcess.java index fa6057d91..0501c27eb 100644 --- a/src/main/java/com/redhat/devtools/lsp4ij/dap/DAPDebugProcess.java +++ b/src/main/java/com/redhat/devtools/lsp4ij/dap/DAPDebugProcess.java @@ -35,6 +35,7 @@ import com.intellij.xdebugger.evaluation.XDebuggerEditorsProvider; import com.intellij.xdebugger.frame.XStackFrame; import com.intellij.xdebugger.frame.XSuspendContext; +import com.intellij.xdebugger.stepping.XSmartStepIntoHandler; import com.intellij.xdebugger.ui.XDebugTabLayouter; import com.redhat.devtools.lsp4ij.dap.breakpoints.DAPBreakpointHandlerBase; import com.redhat.devtools.lsp4ij.dap.breakpoints.DAPExceptionBreakpointsPanel; @@ -47,6 +48,8 @@ import com.redhat.devtools.lsp4ij.dap.disassembly.DAPAlternativeSourceHandler; import com.redhat.devtools.lsp4ij.dap.disassembly.DisassemblyFile; import com.redhat.devtools.lsp4ij.dap.disassembly.breakpoints.DisassemblyBreakpointHandlerBase; +import com.redhat.devtools.lsp4ij.dap.stepping.DAPSmartStepIntoHandler; +import com.redhat.devtools.lsp4ij.dap.stepping.DAPStepIntoVariant; import com.redhat.devtools.lsp4ij.dap.threads.ThreadsPanel; import com.redhat.devtools.lsp4ij.internal.CancellationSupport; import com.redhat.devtools.lsp4ij.internal.CompletableFutures; @@ -86,6 +89,9 @@ public class DAPDebugProcess extends XDebugProcess implements Disposable { private final @NotNull DAPAlternativeSourceHandler alternativeSourceHandler; private final long sessionId; + // Smart Step Into + private DAPSmartStepIntoHandler smartStepIntoHandler; + private @Nullable CompletableFuture connectToServerFuture; private Status status; private Supplier streamsSupplier; @@ -317,6 +323,14 @@ public void startStepInto(@Nullable XSuspendContext context) { } } + @Override + public @Nullable XSmartStepIntoHandler getSmartStepIntoHandler() { + if (smartStepIntoHandler == null) { + smartStepIntoHandler = new DAPSmartStepIntoHandler(getSession()); + } + return smartStepIntoHandler; + } + private @Nullable SteppingGranularity getSteppingGranularity(@NotNull DAPClient client) { return client.canDisassemble() && getAlternativeSourceHandler() .getAlternativeSourceKindState().getValue() ? SteppingGranularity.INSTRUCTION : null; diff --git a/src/main/java/com/redhat/devtools/lsp4ij/dap/client/DAPClient.java b/src/main/java/com/redhat/devtools/lsp4ij/dap/client/DAPClient.java index 675400ef6..0e4fb20cb 100644 --- a/src/main/java/com/redhat/devtools/lsp4ij/dap/client/DAPClient.java +++ b/src/main/java/com/redhat/devtools/lsp4ij/dap/client/DAPClient.java @@ -563,15 +563,29 @@ public void stepOut(int threadId, SteppingGranularity granularity) { } public void stepIn(int threadId, SteppingGranularity granularity) { + stepIn(threadId, null, granularity); + } + + public void stepIn(int threadId, @Nullable Integer targetId, @Nullable SteppingGranularity granularity) { if (debugProtocolServer == null) { return; } StepInArguments args = new StepInArguments(); args.setThreadId(threadId); + args.setTargetId(targetId); args.setGranularity(granularity); debugProtocolServer.stepIn(args); } + public CompletableFuture stepInTargets(int frameId) { + if (debugProtocolServer == null) { + return CompletableFuture.completedFuture(null); + } + StepInTargetsArguments args = new StepInTargetsArguments(); + args.setFrameId(frameId); + return debugProtocolServer.stepInTargets(args); + } + public CompletableFuture evaluate(@NotNull String expression, @Nullable Integer frameId, @NotNull String context) { @@ -680,6 +694,15 @@ public boolean isSupportsEvaluateForHovers() { return Boolean.TRUE.equals(getCapabilities().getSupportsEvaluateForHovers()); } + /** + * Returns true if the debug adapter supports the 'stepInTargets' request and false otherwise. + * + * @return true if the debug adapter supports the 'stepInTargets' request and false otherwise. + */ + public boolean isSupportsStepInTargetsRequest() { + return Boolean.TRUE.equals(getCapabilities().getSupportsStepInTargetsRequest()); + } + public boolean canDisassemble() { return Boolean.TRUE.equals(getCapabilities().getSupportsDisassembleRequest()); } diff --git a/src/main/java/com/redhat/devtools/lsp4ij/dap/stepping/DAPSmartStepIntoHandler.java b/src/main/java/com/redhat/devtools/lsp4ij/dap/stepping/DAPSmartStepIntoHandler.java new file mode 100644 index 000000000..9fd93b20a --- /dev/null +++ b/src/main/java/com/redhat/devtools/lsp4ij/dap/stepping/DAPSmartStepIntoHandler.java @@ -0,0 +1,222 @@ +/******************************************************************************* + * Copyright (c) 2026 Red Hat, Inc. + * Distributed under license by Red Hat, Inc. All rights reserved. + * This program is made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, + * and is available at https://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ +package com.redhat.devtools.lsp4ij.dap.stepping; + +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.xdebugger.XDebugSession; +import com.intellij.xdebugger.XSourcePosition; +import com.intellij.xdebugger.frame.XSuspendContext; +import com.intellij.xdebugger.stepping.XSmartStepIntoHandler; +import com.redhat.devtools.lsp4ij.LSPIJUtils; +import com.redhat.devtools.lsp4ij.dap.client.DAPClient; +import com.redhat.devtools.lsp4ij.dap.client.DAPStackFrame; +import com.redhat.devtools.lsp4ij.dap.client.DAPSuspendContext; +import com.redhat.devtools.lsp4ij.internal.CompletableFutures; +import org.eclipse.lsp4j.debug.StepInTarget; +import org.eclipse.lsp4j.debug.StepInTargetsResponse; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.concurrency.AsyncPromise; +import org.jetbrains.concurrency.Promise; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +/** + * Handles "Smart Step Into" functionality for DAP debug sessions. + * When multiple function calls exist on a single line, this handler allows the user + * to choose which function to step into via the DAP stepInTargets request. + */ +public class DAPSmartStepIntoHandler extends XSmartStepIntoHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(DAPSmartStepIntoHandler.class); + + private final @NotNull XDebugSession session; + + /** + * Creates a new Smart Step Into handler. + * + * @param session the debug session + */ + public DAPSmartStepIntoHandler(@NotNull XDebugSession session) { + this.session = session; + } + + @Override + public @NotNull List computeSmartStepVariants(@NotNull XSourcePosition position) { + CompletableFuture> future = getStepInTargetsFuture(position); + + try { + CompletableFutures.waitUntilDone(future, (com.intellij.psi.PsiFile) null, 5000); + } catch (ProcessCanceledException e) { + throw e; + } catch (ExecutionException e) { + LOGGER.warn("Error getting smart step variants", e); + return Collections.emptyList(); + } catch (java.util.concurrent.TimeoutException e) { + return Collections.emptyList(); + } + + List result = future.getNow(null); + return result != null ? result : Collections.emptyList(); + } + + /** + * Helper method to get the CompletableFuture for step-in targets. + * Extracted to avoid code duplication between sync and async versions. + */ + private CompletableFuture> getStepInTargetsFuture(@NotNull XSourcePosition position) { + XSuspendContext suspendContext = session.getSuspendContext(); + if (!(suspendContext instanceof DAPSuspendContext dapContext)) { + return CompletableFuture.completedFuture(Collections.emptyList()); + } + + var activeStack = dapContext.getActiveExecutionStack(); + if (activeStack == null) { + return CompletableFuture.completedFuture(Collections.emptyList()); + } + + var topFrame = activeStack.getTopFrame(); + if (!(topFrame instanceof DAPStackFrame dapFrame)) { + return CompletableFuture.completedFuture(Collections.emptyList()); + } + + DAPClient client = dapFrame.getClient(); + + // Check capability + if (!client.isSupportsStepInTargetsRequest()) { + return CompletableFuture.completedFuture(Collections.emptyList()); + } + + int frameId = dapFrame.getFrameId(); + CompletableFuture responseFuture = client.stepInTargets(frameId); + + // Transform the response into variants + Document document = LSPIJUtils.getDocument(position.getFile()); + int zeroBasedLine = position.getLine(); + + return responseFuture.handle((response, throwable) -> { + if (throwable != null) { + LOGGER.error("Error while fetching step-in targets from DAP server", throwable); + return Collections.emptyList(); + } + + if (response == null || response.getTargets() == null || response.getTargets().length == 0) { + return Collections.emptyList(); + } + + List variants = new ArrayList<>(); + + // Track how many times we've seen each function name to handle duplicates + Map functionNameCounts = new java.util.HashMap<>(); + + for (StepInTarget target : response.getTargets()) { + // Extract function name from label to count occurrences + String label = target.getLabel(); + String functionName = label; + int parenIndex = label.indexOf('('); + if (parenIndex > 0) { + functionName = label.substring(0, parenIndex); + } + + // Get the occurrence index for this function name + int occurrenceIndex = functionNameCounts.getOrDefault(functionName, 0); + functionNameCounts.put(functionName, occurrenceIndex + 1); + + DAPStepIntoVariant variant = new DAPStepIntoVariant(target, document, zeroBasedLine, occurrenceIndex); + variants.add(variant); + } + return variants; + }); + } + + @Override + public @NotNull Promise> computeSmartStepVariantsAsync( + @NotNull XSourcePosition position) { + + // Get the CompletableFuture and wrap it in an AsyncPromise + CompletableFuture> future = getStepInTargetsFuture(position); + + AsyncPromise> promise = new AsyncPromise<>(); + future.whenComplete((result, throwable) -> { + if (throwable != null) { + promise.setResult(Collections.emptyList()); + } else { + promise.setResult(result != null ? result : Collections.emptyList()); + } + }); + + return promise; + } + + @Override + public void startStepInto(@NotNull DAPStepIntoVariant variant, @Nullable XSuspendContext context) { + if (!(context instanceof DAPSuspendContext dapContext)) { + return; + } + + Integer threadId = dapContext.getThreadId(); + if (threadId == null) { + return; + } + + var activeStack = dapContext.getActiveExecutionStack(); + if (activeStack == null) { + return; + } + + var topFrame = activeStack.getTopFrame(); + if (!(topFrame instanceof DAPStackFrame dapFrame)) { + return; + } + + DAPClient client = dapFrame.getClient(); + + // Get stepping granularity (null for normal stepping, INSTRUCTION for disassembly) + var granularity = getSteppingGranularity(client); + + // Execute stepIn with targetId + Integer targetId = variant.getTargetId(); + client.stepIn(threadId, targetId, granularity); + } + + @Override + public void stepIntoEmpty(XDebugSession session) { + // Fallback to regular step into when no variants available + session.stepInto(); + } + + @Override + public @Nullable String getPopupTitle(@NotNull XSourcePosition position) { + return "Choose Method to Step Into"; + } + + /** + * Gets the stepping granularity for the current session. + * Returns INSTRUCTION granularity when disassembly mode is active, null otherwise. + * + * @param client the DAP client + * @return the stepping granularity, or null for default (source line) granularity + */ + private @Nullable org.eclipse.lsp4j.debug.SteppingGranularity getSteppingGranularity(@NotNull DAPClient client) { + // Note: For Smart Step Into, we typically use source-level granularity + // Disassembly-level stepping with target selection is rarely needed + // If needed in the future, this could access DAPDebugProcess.getAlternativeSourceHandler() + return null; + } +} diff --git a/src/main/java/com/redhat/devtools/lsp4ij/dap/stepping/DAPStepIntoVariant.java b/src/main/java/com/redhat/devtools/lsp4ij/dap/stepping/DAPStepIntoVariant.java new file mode 100644 index 000000000..efb8ab94f --- /dev/null +++ b/src/main/java/com/redhat/devtools/lsp4ij/dap/stepping/DAPStepIntoVariant.java @@ -0,0 +1,144 @@ +/******************************************************************************* + * Copyright (c) 2026 Red Hat, Inc. + * Distributed under license by Red Hat, Inc. All rights reserved. + * This program is made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, + * and is available at https://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ +package com.redhat.devtools.lsp4ij.dap.stepping; + +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.util.TextRange; +import com.intellij.xdebugger.stepping.XSmartStepIntoVariant; +import org.eclipse.lsp4j.debug.StepInTarget; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a step-into target from the Debug Adapter Protocol (DAP). + * Wraps {@link StepInTarget} to integrate with IntelliJ's Smart Step Into UI. + */ +public class DAPStepIntoVariant extends XSmartStepIntoVariant { + + private final @NotNull StepInTarget target; + private final @Nullable Document document; + private final int zeroBasedLine; + private final int occurrenceIndex; + + /** + * Creates a new step-into variant. + * + * @param target the DAP step-in target + * @param document the document containing the target (for highlighting) + * @param zeroBasedLine the 0-based line number in IntelliJ coordinates + * @param occurrenceIndex the occurrence index when multiple targets have the same function name (0-based) + */ + public DAPStepIntoVariant(@NotNull StepInTarget target, + @Nullable Document document, + int zeroBasedLine, + int occurrenceIndex) { + this.target = target; + this.document = document; + this.zeroBasedLine = zeroBasedLine; + this.occurrenceIndex = occurrenceIndex; + } + + @Override + public @NotNull String getText() { + return target.getLabel(); + } + + @Override + public @Nullable TextRange getHighlightRange() { + // If DAP provides line/column, use it for precise highlighting + Integer line = target.getLine(); + Integer column = target.getColumn(); + + if (document != null && line != null && column != null) { + // DAP uses 1-based line numbers + int oneBasedLine = line; + int lineNumber = oneBasedLine - 1; + + if (lineNumber >= 0 && lineNumber < document.getLineCount()) { + int lineStartOffset = document.getLineStartOffset(lineNumber); + int lineEndOffset = document.getLineEndOffset(lineNumber); + + // Column is 1-based, convert to offset + int columnOffset = column - 1; + if (columnOffset >= 0) { + int startOffset = lineStartOffset + columnOffset; + + // Try to use endColumn if provided for precise range + Integer endColumn = target.getEndColumn(); + int endOffset; + if (endColumn != null && endColumn > column) { + endOffset = Math.min(lineStartOffset + (endColumn - 1), lineEndOffset); + } else { + // Highlight the function name (estimate ~20 chars or to end of line) + endOffset = Math.min(startOffset + 20, lineEndOffset); + } + + return new TextRange(startOffset, endOffset); + } + } + } + + // Fallback: try to find the function name in the current line + if (document != null && zeroBasedLine >= 0 && zeroBasedLine < document.getLineCount()) { + int lineStartOffset = document.getLineStartOffset(zeroBasedLine); + int lineEndOffset = document.getLineEndOffset(zeroBasedLine); + String lineText = document.getText(new TextRange(lineStartOffset, lineEndOffset)); + + // Extract function name from label (e.g., "add(x, y)" -> "add") + // This avoids overlapping ranges when labels are nested (e.g., "multiply(add(x, y), subtract(x, y))") + String label = target.getLabel(); + String functionName = label; + int parenIndex = label.indexOf('('); + if (parenIndex > 0) { + functionName = label.substring(0, parenIndex); + } + + // Search for the N-th occurrence of the function name in the line + // to handle cases like: add(1, 2) + add(3, 4) + int index = -1; + for (int i = 0; i <= occurrenceIndex; i++) { + index = lineText.indexOf(functionName, index + 1); + if (index < 0) { + break; + } + } + + if (index >= 0) { + int startOffset = lineStartOffset + index; + int endOffset = startOffset + functionName.length(); + return new TextRange(startOffset, endOffset); + } + + // Ultimate fallback: highlight entire line + return new TextRange(lineStartOffset, lineEndOffset); + } + + return null; + } + + /** + * Gets the target ID for use with the DAP stepIn request. + * + * @return the target ID + */ + public int getTargetId() { + return target.getId(); + } + + /** + * Gets the underlying DAP step-in target. + * + * @return the step-in target + */ + public @NotNull StepInTarget getTarget() { + return target; + } +} diff --git a/src/test/java/com/redhat/devtools/lsp4ij/dap/stepping/DAPSmartStepIntoHandlerTest.java b/src/test/java/com/redhat/devtools/lsp4ij/dap/stepping/DAPSmartStepIntoHandlerTest.java new file mode 100644 index 000000000..1a77f1ea0 --- /dev/null +++ b/src/test/java/com/redhat/devtools/lsp4ij/dap/stepping/DAPSmartStepIntoHandlerTest.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2026 Red Hat, Inc. + * Distributed under license by Red Hat, Inc. All rights reserved. + * This program is made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ +package com.redhat.devtools.lsp4ij.dap.stepping; + +import com.intellij.testFramework.fixtures.BasePlatformTestCase; +import org.eclipse.lsp4j.debug.StepInTarget; +import org.jetbrains.annotations.NotNull; + +/** + * Unit tests for DAPSmartStepIntoHandler. + * + * Note: These are basic structural tests. Full integration tests require a running + * DAP server and debug session, which should be covered by manual testing or + * integration test suites. + */ +public class DAPSmartStepIntoHandlerTest extends BasePlatformTestCase { + + public void testGetPopupTitle() { + // The popup title is a simple method that doesn't require mocking + // We can test it in isolation once we have a session mock implementation + String expectedTitle = "Choose Method to Step Into"; + // Test will be completed when integration testing framework is ready + assertNotNull(expectedTitle); + } + + /** + * Test that verifies the basic structure of the handler. + * Full testing requires DAP server integration. + */ + public void testHandlerStructure() { + // Verify the handler class exists and can be instantiated + // Full functional tests will be done via integration tests + assertNotNull(DAPSmartStepIntoHandler.class); + } + + @NotNull + @Override + protected String getTestDataPath() { + return "src/test/resources/testData/dap/stepping"; + } +} diff --git a/src/test/java/com/redhat/devtools/lsp4ij/dap/stepping/DAPStepIntoVariantTest.java b/src/test/java/com/redhat/devtools/lsp4ij/dap/stepping/DAPStepIntoVariantTest.java new file mode 100644 index 000000000..eea414ab1 --- /dev/null +++ b/src/test/java/com/redhat/devtools/lsp4ij/dap/stepping/DAPStepIntoVariantTest.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * Copyright (c) 2026 Red Hat, Inc. + * Distributed under license by Red Hat, Inc. All rights reserved. + * This program is made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ +package com.redhat.devtools.lsp4ij.dap.stepping; + +import com.intellij.testFramework.LightPlatformTestCase; +import org.eclipse.lsp4j.debug.StepInTarget; +import org.jetbrains.annotations.NotNull; + +/** + * Unit tests for DAPStepIntoVariant. + */ +public class DAPStepIntoVariantTest extends LightPlatformTestCase { + + public void testGetText() { + StepInTarget target = createTarget(1, "foo(x, y)"); + DAPStepIntoVariant variant = new DAPStepIntoVariant(target, null, 0, 0); + + assertEquals("foo(x, y)", variant.getText()); + } + + public void testGetTargetId() { + StepInTarget target = createTarget(42, "bar()"); + DAPStepIntoVariant variant = new DAPStepIntoVariant(target, null, 0, 0); + + assertEquals(42, variant.getTargetId()); + } + + public void testGetHighlightRange_withNullDocument() { + StepInTarget target = createTarget(1, "foo()"); + target.setLine(1); + target.setColumn(5); + + DAPStepIntoVariant variant = new DAPStepIntoVariant(target, null, 0, 0); + var range = variant.getHighlightRange(); + + assertNull(range); + } + + public void testGetTarget() { + StepInTarget target = createTarget(10, "test()"); + DAPStepIntoVariant variant = new DAPStepIntoVariant(target, null, 0, 0); + + assertSame(target, variant.getTarget()); + } + + @NotNull + private static StepInTarget createTarget(int id, String label) { + StepInTarget target = new StepInTarget(); + target.setId(id); + target.setLabel(label); + return target; + } +} +