调试适配器协议和 Scala_tiknovel-最新最全的nft,web3,AI技术资讯技术社区

调试适配器协议和 Scala

2022-03-21 21:10:08  浏览:368  作者:管理员
调试适配器协议和 Scala

在 Scala 的过去几年中,我们看到各种工具协议的使用激增。越来越多的开发人员至少熟悉语言服务器协议 (LSP),特别是如果您是Metals用户或者是 Dotty 的早期用户,因为它有一个内置的 LSP 服务器。即使只是通过看到sbt create a.bsp/Scala 工作区中的目录。另一个流行的协议是调试适配器协议,如果您使用 Metals,您可能会再次遇到这种情况。我已经看到了很多关于 DAP 如何与 Metals 一起工作的问题,而这个实际上比其他的有更多的活动部件,以使它们一起工作。所以,我想记下一些笔记,以确保我理解所有活动部分,作为对它们如何协同工作的详细解释,并希望能帮助你了解全貌。

请记住,每个客户都会有所不同。这里的客户端也可能有点模棱两可,因为我们将讨论 Metals 客户端扩展和 DAP 客户端,所以我会尝试通过说“Metals 客户端”或“DAP 客户端”来区分它们,即使它们可能是同一个东西.

另请记住,这并不是对表达式评估或断点等实际工作方式的技术解释而是对所有移动部分的更多概述,以更好地了解当您触发run或时所有工具之间发生的情况debug.

什么是 DAP

如果您熟悉 LSP 的目标,那么您已经熟悉了 DAP 的一些目标。基本上,不要为每一种想要实现调试的新语言和工具一遍又一遍地重新实现所有调试器功能。取自DAP 网站

为 IDE 或编辑器添加新语言的调试器不仅是一项重大工作,而且令人沮丧的是,这种工作不能轻易地分摊到多个开发工具上,因为每个工具使用不同的 API 来实现相同的功能。调试适配器协议 (DAP) 背后的想法是将开发工具的调试支持与调试器或运行时通信的方式抽象为协议。由于假设现有调试器或运行时很快就会采用此协议是不现实的,我们宁愿假设中间组件 - 所谓的调试适配器 - 使现有调试器或运行时适应调试适配器协议。

调试适配器协议使得为开发工具实现通用调试器成为可能,该工具可以通过调试适配器与不同的调试器进行通信。调试适配器可以在多个开发工具中重复使用,这大大减少了在不同工具中支持新调试器的工作量。

此描述还提出了实际调试适配器的一个重要点,因为在许多语言中确实具有直接内置到语言或平台中的现有调试器接口,例如Java 调试接口,这是java-debug使用的,这是scala- debug-adapter使用,这是大多数构建服务器将使用的,这是 Metals 连接的......你明白了。但是,我们将在下面进一步讨论。总而言之,目标是在 X 编辑器用户想要转换到另一个编辑器的情况下,比如 Neovim,只要该新客户端具有内置或作为插件提供的 DAP 客户端实现,他们仍然可以拥有相同的基本调试体验。如果 Metals 用户决定使用 Bloop 作为他们的构建服务器或 sbt 作为他们的构建服务器,那么服务器端也可以这样说,他们可以共享协议服务器端的通用实现,而不必完全重新实现它两次。服务器端示例正是scala-debug-adapter的用途。

两种不同类型的客户

我在上面提到过,但也想在这里重申一下。不同的语言服务器客户端可能会以不同的方式处理 DAP 客户端部分。与 VS Code 的情况一样,DAP 客户端直接包含在编辑器中。你可以在scalameta/metals-vscode扩展中看到一个例子。请注意,导入来自vscode这提供了一个非常紧密的集成,对用户来说是完全抽象的。然后可以在此之上构建其他东西,例如Metals 最近添加的 VS Code 测试资源管理器 API 支持在幕后,这些通信仍在通过 DAP。我喜欢将这些视为 DAP 的“扩展”,类似于 LSP 扩展,它们不一定是协议的一部分,但遵循相同的模式,甚至可以重用协议一部分。然后,这些需要其他客户端执行更多工作,并且它们不会像其他 DAP 功能那样开箱即用。目前,我们不会关注任何这些,我们只会坚持核心 DAP 功能。

这可以在客户端中查看的另一种方式是,当您的语言服务器客户端本身没有实现 DAP 客户端,但您使用扩展来实现它时。你可以在scalameta/nvim-metals中看到一个例子请注意,在setup_dap函数中,我们实际做的第一件事是需要mfussenegger/nvim-dap,这是一个出色的插件,它实现了 Neovim 协议的客户端部分。

因此,无论您是使用带有内置 DAP 客户端集成的 VS Code 还是使用 Neovim 和类似的插件nvim-dap,核心客户端功能应该大致相同。继续前进,所有示例都将假设第二个设置使用nvim-dap,因为这是我最熟悉的。

一切如何设置

我不希望这一定是“入门nvim-dap”指南,因为那里已经有指南,而且文档非常nvim-dap详细。相反,我想专注于在使用nvim-metals其中大部分也可以转移到其他支持 DAP 的 Metals 扩展。

让我们从一段简单的代码开始:

@main def dapExample() =  println("hello people interested in DAP")

如果您已经nvim-dap安装并使用 main 方法打开了一个 Scala 项目,您应该会看到代码镜头出现在您的 main 方法上。nvim-metals看起来像这样:

一些带有代码镜头的scala代码

我们需要回答的第一个问题是“这些是怎么来的?” 然后是“这实际上如何触发我的代码的运行或调试?”。在幕后实际发生的是,Metals 将与您的构建服务器通信,并通过buildTarget/scalaMainClasses请求获取构建目标中的任何主要方法并缓存这些结果。然后,当 LSP 请求到达 Metals 时,textDocument/codeLensMetals 会通过SemanticDB查找当前文档并查找任何主要方法。如果找到它们,它会将它们与之前检索到的缓存的进行比较,然后为它们创建带有附加特殊命令的代码镜头。

以下是上面的一些插图:

buildTarget/scalaMainClasses构建服务器的请求和响应示例

[Trace - 10:50:29 AM] Sending request 'buildTarget/scalaMainClasses - (7)'Params: {  "targets": [    {      "uri": "file:/Users/ckipp/Documents/scala-workspace/sanity/Sanity/test/?id\u003dSanity.test"    },    {      "uri": "file:/Users/ckipp/Documents/scala-workspace/sanity/Sanity/?id\u003dSanity"    }  ]}[Trace - 10:50:29 AM] Received response 'buildTarget/scalaMainClasses - (7)' in 6msResult: {  "items": [    {      "target": {        "uri": "file:/Users/ckipp/Documents/scala-workspace/sanity/Sanity/?id\u003dSanity"      },      "classes": [        {          "class": "dapExample",          "arguments": [],          "jvmOptions": [],          "environmentVariables": []        }      ]    },    {      "target": {        "uri": "file:/Users/ckipp/Documents/scala-workspace/sanity/Sanity/test/?id\u003dSanity.test"      },      "classes": []    }  ]}

我们代码片段的 SemanticDB 的示例。注意第一个出现的是scala/main#一旦找到,我们将获得该事件的符号,然后将其与上面返回的内容进行检查。

Sanity/src/example/Hello.scala------------------------------Summary:Schema => SemanticDB v4Uri => Sanity/src/example/Hello.scalaText => emptyLanguage => ScalaSymbols => 3 entriesOccurrences => 3 entriesSymbols:_empty_/Hello$package. => final package object _empty_ extends Object { self: _empty_.type => +2 decls }_empty_/Hello$package.dapExample(). => @main method dapExample(): Unit_empty_/dapExample# => final class dapExample extends Object { self: dapExample => +2 decls }Occurrences:[0:1..0:5) => scala/main#[0:10..0:20) <= _empty_/Hello$package.dapExample().[1:2..1:9) => scala/Predef.println(+1).

代码镜头请求和响应示例。

评论区

共 0 条评论
  • 这篇文章还没有收到评论,赶紧来抢沙发吧~

【随机内容】

返回顶部