diff --git a/metals/src/main/scala/scala/meta/internal/metals/debug/ClientConfigurationAdapter.scala b/metals/src/main/scala/scala/meta/internal/metals/debug/ClientConfigurationAdapter.scala index db97f3ac5bf..8f285ca7c15 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/debug/ClientConfigurationAdapter.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/debug/ClientConfigurationAdapter.scala @@ -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 @@ -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, @@ -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 = { + 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 @@ -79,6 +116,7 @@ private[debug] object ClientConfigurationAdapter { def default(sourceMapper: SourceMapper): ClientConfigurationAdapter = { ClientConfigurationAdapter( + None, defautlPathFormat, defaultLinesStartAt1, sourceMapper, @@ -95,6 +133,7 @@ private[debug] object ClientConfigurationAdapter { .map(_.booleanValue) .getOrElse(defaultLinesStartAt1) ClientConfigurationAdapter( + Option(initRequest.getClientID), pathFormat, linesStartAt1, sourceMapper, diff --git a/metals/src/main/scala/scala/meta/internal/metals/debug/DebugProtocol.scala b/metals/src/main/scala/scala/meta/internal/metals/debug/DebugProtocol.scala index 487bb2048ef..e8998913c60 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/debug/DebugProtocol.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/debug/DebugProtocol.scala @@ -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" diff --git a/metals/src/main/scala/scala/meta/internal/metals/debug/DebugProxy.scala b/metals/src/main/scala/scala/meta/internal/metals/debug/DebugProxy.scala index 8d248ae0f41..a46d61f01d6 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/debug/DebugProxy.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/debug/DebugProxy.scala @@ -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) @@ -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) }