Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
233 changes: 156 additions & 77 deletions src/main/java/com/redhat/devtools/lsp4ij/dap/client/DAPStackFrame.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,48 +10,57 @@
******************************************************************************/
package com.redhat.devtools.lsp4ij.dap.client;

import com.intellij.icons.AllIcons;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.ColoredTextContainer;
import com.intellij.ui.SimpleTextAttributes;
import com.intellij.xdebugger.XDebuggerBundle;
import com.intellij.xdebugger.XDebuggerUtil;
import com.intellij.xdebugger.XSourcePosition;
import com.intellij.xdebugger.evaluation.XDebuggerEvaluator;
import com.intellij.xdebugger.frame.XCompositeNode;
import com.intellij.xdebugger.frame.XStackFrame;
import com.intellij.xdebugger.frame.XValueChildrenList;
import com.redhat.devtools.lsp4ij.dap.client.files.DAPFileRegistry;
import com.redhat.devtools.lsp4ij.dap.client.files.DAPSourceReferencePosition;
import com.redhat.devtools.lsp4ij.dap.client.variables.DAPValueGroup;
import com.redhat.devtools.lsp4ij.dap.client.variables.providers.DebugVariableContext;
import com.redhat.devtools.lsp4ij.dap.disassembly.DisassemblyDeferredSourcePosition;
import com.redhat.devtools.lsp4ij.dap.evaluation.DAPDebuggerEvaluator;
import com.redhat.devtools.lsp4ij.internal.StringUtils;
import org.eclipse.lsp4j.debug.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.nio.file.Path;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;

import static com.redhat.devtools.lsp4ij.dap.DAPIJUtils.getValidFilePath;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.Navigatable;
import com.intellij.ui.ColoredTextContainer;
import com.intellij.ui.SimpleTextAttributes;
import com.intellij.xdebugger.XDebuggerBundle;
import com.intellij.xdebugger.XDebuggerUtil;
import com.intellij.xdebugger.XSourcePosition;
import com.intellij.xdebugger.evaluation.XDebuggerEvaluator;
import com.intellij.xdebugger.frame.XCompositeNode;
import com.intellij.xdebugger.frame.XStackFrame;
import com.intellij.xdebugger.frame.XValueChildrenList;
import com.intellij.xdebugger.impl.XSourcePositionEx;
import com.redhat.devtools.lsp4ij.dap.client.files.DAPFileRegistry;
import com.redhat.devtools.lsp4ij.dap.client.files.DAPSourceReferencePosition;
import com.redhat.devtools.lsp4ij.dap.client.variables.DAPValueGroup;
import com.redhat.devtools.lsp4ij.dap.client.variables.providers.DebugVariableContext;
import com.redhat.devtools.lsp4ij.dap.disassembly.DisassemblyDeferredSourcePosition;
import com.redhat.devtools.lsp4ij.dap.evaluation.DAPDebuggerEvaluator;
import com.redhat.devtools.lsp4ij.internal.StringUtils;
import kotlinx.coroutines.flow.Flow;
import kotlinx.coroutines.flow.MutableStateFlow;
import kotlinx.coroutines.flow.StateFlowKt;
import org.eclipse.lsp4j.debug.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.nio.file.Path;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;

import static com.redhat.devtools.lsp4ij.dap.DAPIJUtils.getValidFilePath;

/**
* Debug Adapter Protocol (DAP) stack frame.
*/
public class DAPStackFrame extends XStackFrame {
private final @NotNull StackFrame stackFrame;
private final @NotNull DAPClient client;
private @Nullable XSourcePosition sourcePosition;
private @Nullable DisassemblyDeferredSourcePosition disassemblyInstructionSourcePosition;
private XDebuggerEvaluator evaluator;
private CompletableFuture<DebugVariableContext> variablesContext;

private final @NotNull StackFrame stackFrame;
private final @NotNull DAPClient client;
private volatile @Nullable XSourcePosition sourcePosition;
private @Nullable DisassemblyDeferredSourcePosition disassemblyInstructionSourcePosition;
private XDebuggerEvaluator evaluator;
private CompletableFuture<DebugVariableContext> variablesContext;

public DAPStackFrame(@NotNull DAPClient client,
@NotNull StackFrame stackFrame) {
Expand All @@ -76,46 +85,116 @@ public void customizePresentation(@NotNull ColoredTextContainer component) {
}
component.setIcon(AllIcons.Debugger.Frame);
}

@Override
public @Nullable XSourcePosition getSourcePosition() {
var source = stackFrame.getSource();
if (sourcePosition == null && source != null) {
sourcePosition = doGetSourcePosition(source, stackFrame.getLine() - 1);
}
return sourcePosition;
}

private @Nullable XSourcePosition doGetSourcePosition(@NotNull Source source, int line) {
int sourceReference = source.getSourceReference() != null ? source.getSourceReference() : 0;
if (sourceReference > 0) {
// If the value &gt; 0 the contents of the source must be retrieved through
// the SourceRequest (even if a path is specified).
var file = DAPFileRegistry.getInstance().getOrCreateDAPFile(client.getConfigName(), getValidSourceName(source), client.getProject());
if (file.shouldReload(getClient().getSessionId())) {
return new DAPSourceReferencePosition(file, sourceReference, line, client);
}
return XDebuggerUtil.getInstance().createPosition(file, line);
}

Path filePath = getValidFilePath(source);
if (filePath == null) {
return null;
}
try {
VirtualFile file = VfsUtil.findFile(filePath, true);
return XDebuggerUtil.getInstance().createPosition(file, line);
} catch (Exception e) {
// Invalid path...
// ex: <node_internals>/internal/modules/cjs/loader
}
return null;
}

public CompletableFuture<XSourcePosition> getSourcePositionFor(@NotNull Variable variable) {
var sourcePosition = getSourcePosition();
if (sourcePosition == null) {
return CompletableFuture.completedFuture(null);

@Override
public @Nullable XSourcePosition getSourcePosition() {
XSourcePosition cached = sourcePosition;
if (cached != null) {
return cached;
}

var source = stackFrame.getSource();
if (source == null) {
return null;
}

sourcePosition = doGetSourcePosition(source, stackFrame.getLine() - 1);
return sourcePosition;
}

private @Nullable XSourcePosition doGetSourcePosition(@NotNull Source source, int line) {
int sourceReference = source.getSourceReference() != null ? source.getSourceReference() : 0;
if (sourceReference > 0) {
// If the value > 0 the contents of the source must be retrieved through
// the SourceRequest (even if a path is specified).
var file = DAPFileRegistry.getInstance().getOrCreateDAPFile(client.getConfigName(), getValidSourceName(source), client.getProject());
if (file.shouldReload(getClient().getSessionId())) {
return new DAPSourceReferencePosition(file, sourceReference, line, client);
}
return XDebuggerUtil.getInstance().createPosition(file, line);
}

Path filePath = getValidFilePath(source);
if (filePath == null) {
return null;
}

VirtualFile file = LocalFileSystem.getInstance().findFileByPath(filePath.toString());
if (file == null || !file.isValid()) {
return null;
}

if (ApplicationManager.getApplication().isDispatchThread()) {
return new DeferredLocalFileSourcePosition(file, line);
}

return XDebuggerUtil.getInstance().createPosition(file, line);
}

private static final class DeferredLocalFileSourcePosition implements XSourcePositionEx {
private final @NotNull VirtualFile file;
private final int line;
private final @NotNull MutableStateFlow<Boolean> positionUpdateFlow = StateFlowKt.MutableStateFlow(false);
private volatile int offset = -1;

private DeferredLocalFileSourcePosition(@NotNull VirtualFile file, int line) {
this.file = file;
this.line = line;
// `getSourcePosition()` may be called on EDT; precompute offset in background and notify the debugger UI.
ApplicationManager.getApplication().executeOnPooledThread(() -> {
int resolvedOffset = com.intellij.openapi.application.ReadAction.compute(() -> computeOffset(file, line));
offset = resolvedOffset;
positionUpdateFlow.setValue(true);
});
}

@Override
public int getLine() {
return line;
}

@Override
public int getOffset() {
return offset;
}

@Override
public @NotNull VirtualFile getFile() {
return file;
}

@Override
public @NotNull Navigatable createNavigatable(@NotNull Project project) {
int resolvedOffset = offset;
if (resolvedOffset >= 0) {
return new OpenFileDescriptor(project, file, resolvedOffset);
}
return new OpenFileDescriptor(project, file, Math.max(0, line), 0);
}

@Override
public @NotNull Flow<Boolean> getPositionUpdateFlow() {
return positionUpdateFlow;
}

private static int computeOffset(@NotNull VirtualFile file, int line) {
Document document = FileDocumentManager.getInstance().getDocument(file);
if (document == null) {
return -1;
}
int l = Math.max(0, line);
int offset = l < document.getLineCount() ? document.getLineStartOffset(l) : -1;
if (offset >= document.getTextLength()) {
offset = document.getTextLength() - 1;
}
return offset;
}
}

public CompletableFuture<XSourcePosition> getSourcePositionFor(@NotNull Variable variable) {
var sourcePosition = getSourcePosition();
if (sourcePosition == null) {
return CompletableFuture.completedFuture(null);
}
return getVariablesContext()
.thenApply(context -> context.getSourcePositionFor(variable));
Expand Down