Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Collapse skipped frames #6240

Merged
merged 4 commits into from Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -2,10 +2,14 @@ package scala.meta.internal.metals.debug

import java.nio.file.Paths

import scala.util.control.NonFatal

import scala.meta.internal.metals.MetalsEnrichments._
import scala.meta.internal.metals.SourceMapper
import scala.meta.io.AbsolutePath

import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import org.eclipse.lsp4j.Position
import org.eclipse.lsp4j.debug.InitializeRequestArguments
import org.eclipse.lsp4j.debug.InitializeRequestArgumentsPathFormat
Expand All @@ -19,6 +23,7 @@ import org.eclipse.lsp4j.debug.SourceBreakpoint
* @param linesStartAt1 true if client line numbers start at 1
*/
private[debug] final case class ClientConfigurationAdapter(
clientId: Option[String],
pathFormat: String,
val linesStartAt1: Boolean,
sourceMapper: SourceMapper,
Expand All @@ -35,6 +40,38 @@ private[debug] final case class ClientConfigurationAdapter(
sourceMapper.mappedLineForClient(path, adaptedLine)
}

/**
* In the DAP specification, the presentationHint of a StackFrame can be
* 'normal', 'label' or 'subtle'. Most DAP implementations use 'subtle' to
* indicate that a frame is skipped by the debugger. The problem is that
* VSCode does not collapse 'subtle' frames, as other DAP clients do.
* Instead it collapses 'deemphasize' frames, even if it is not part of the
* spec.
*
* See https://github.com/microsoft/vscode/issues/206801
*/
def adaptStackTraceResponse(result: JsonObject): JsonObject = {
adpi2 marked this conversation as resolved.
Show resolved Hide resolved
if (clientId.contains("vscode")) {
try {
// For VSCode only, we hack the json result of the stack trace response
// to replace all occurrences of 'subtle' by 'deemphasize'.
val frames = result.get("stackFrames").getAsJsonArray()
for (i <- 0.until(frames.size)) {
val frame = frames.get(i).getAsJsonObject()
val presentationHint = Option(frame.get("presentationHint"))
.map(_.getAsJsonPrimitive.getAsString)
if (presentationHint.contains("subtle")) {
frame.add("presentationHint", new JsonPrimitive("deemphasize"))
}
}
} catch {
case NonFatal(t) =>
scribe.error("unexpected error when adapting stack trace response", t)
}
}
result
}

def toLspPosition(breakpoint: SourceBreakpoint): Position = {
val line = breakpoint.getLine
// LSP Position is 0-based
Expand Down Expand Up @@ -79,6 +116,7 @@ private[debug] object ClientConfigurationAdapter {

def default(sourceMapper: SourceMapper): ClientConfigurationAdapter = {
ClientConfigurationAdapter(
None,
defautlPathFormat,
defaultLinesStartAt1,
sourceMapper,
Expand All @@ -95,6 +133,7 @@ private[debug] object ClientConfigurationAdapter {
.map(_.booleanValue)
.getOrElse(defaultLinesStartAt1)
ClientConfigurationAdapter(
Option(initRequest.getClientID),
pathFormat,
linesStartAt1,
sourceMapper,
Expand Down
Expand Up @@ -29,7 +29,7 @@ import org.eclipse.lsp4j.{debug => dap}
import org.eclipse.{lsp4j => l}

object DebugProtocol {
import scala.meta.internal.metals.debug.DapJsonParser._
import DapJsonParser._
val FirstMessageId = 1

val serverName = "dap-server"
Expand Down
Expand Up @@ -239,7 +239,7 @@ private[debug] final class DebugProxy(
// output window gets refreshed resulting in stale messages being printed on top, before
// any actual logs from the restarted process
case response @ DebugProtocol.StackTraceResponse(args) =>
import scala.meta.internal.metals.JsonParser._
import DapJsonParser._
for {
stackFrame <- args.getStackFrames
frameSource <- Option(stackFrame.getSource)
Expand All @@ -256,7 +256,8 @@ private[debug] final class DebugProxy(
mappedSourcePath
)
} frameSource.setPath(clientAdapter.adaptPathForClient(metalsSource))
response.setResult(args.toJson)
val result = clientAdapter.adaptStackTraceResponse(args.toJsonObject)
response.setResult(result)
for (frame <- args.getStackFrames()) {
frameIdToFrame.put(frame.getId, frame)
}
Expand Down