1. sbt 服务器

sbt 服务器 

sbt 服务器是 sbt 1.x 中新引入的功能,它仍在开发中。你可能最初认为服务器是运行在远程服务器上的东西,并能完成很多很棒的事情,但就目前而言,sbt 服务器并不是那样。

实际上,sbt 服务器只是在 sbt 的 shell 命令中添加了网络访问,因此除了接受来自终端的输入之外,服务器还可以接受来自网络的输入。这允许多个客户端连接到一个 *单个 sbt 会话*。我们对客户端的主要用例是工具集成,例如编辑器和 IDE。请参见 IDE 集成 页面。

配置 

有几个设置可用于配置服务器。以下列出了一些设置及其默认值。可以针对每个项目更改设置,也可以在 ~/.sbt/1.0/global.sbt 中设置值。

// If set to a defined value, sbt server will exit if it goes at least the
// specified duration without receiving any commands.
Global / serverIdleTimeout := Some(new FiniteDuration(5, TimeUnit.MINUTES))

语言服务器协议 3.0 

我们使用的线协议是 语言服务器协议 3.0 (LSP),它基于 JSON-RPC

基本协议由标头和内容部分组成(类似于 HTTP)。标头和内容部分用 \r\n 分隔。

目前支持以下标头字段

  • Content-Length:内容部分的长度(以字节为单位)。如果你没有提供此标头,我们将读取直到行尾。
  • Content-Type:必须设置为 application/vscode-jsonrpc; charset=utf-8 或省略。

以下是一个示例

Content-Type: application/vscode-jsonrpc; charset=utf-8\r\n
Content-Length: ...\r\n
\r\n
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "textDocument/didSave",
  "params": {
    ...
  }
}

一个 JSON-RPC 请求由一个 id 号、一个 method 名称和一个可选的 params 对象组成。因此所有 LSP 请求都是方法名称和 params JSON 的对。

对 JSON-RPC 请求的示例响应是

Content-Type: application/vscode-jsonrpc; charset=utf-8\r\n
Content-Length: ...\r\n
\r\n
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    ...
  }
}

或者服务器可能返回错误响应

Content-Type: application/vscode-jsonrpc; charset=utf-8\r\n
Content-Length: ...\r\n
\r\n
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32602,
    "message": "some error message"
  }
}

除了响应之外,服务器还可以发送事件(在 LSP 术语中称为“通知”)。

Content-Type: application/vscode-jsonrpc; charset=utf-8\r\n
Content-Length: ...\r\n
\r\n
{
  "jsonrpc": "2.0",
  "method": "textDocument/publishDiagnostics",
  "params": {
    ...
  }
}

服务器模式 

Sbt 服务器可以运行在两种模式下,这两种模式在线协议和初始化方面有所不同。自 sbt 1.1.x 以来,默认模式是 *域套接字模式*,它使用 Unix 域套接字(在 Unix 上)或命名管道(在 Windows 上)进行服务器和客户端之间的数据传输。此外,还有一种 *TCP 模式*,它使用 TCP 进行数据传输。

sbt 服务器启动的模式由键 serverConnectionType 决定,该键可以设置为 ConnectionType.Local(用于域套接字/命名管道模式),也可以设置为 ConnectionType.Tcp(用于 TCP 模式)。

服务器发现和身份验证 

为了发现正在运行的服务器,我们使用 *端口文件*。

默认情况下,当 sbt shell 会话处于活动状态时,sbt 服务器将运行。当服务器启动时,它将创建一个名为端口文件的文件。端口文件位于 ./project/target/active.json。端口文件的内容将根据服务器是在 TCP 模式下运行还是在域套接字/命名管道模式下运行而有所不同。它们看起来像这样

在域套接字/命名管道模式下,在 Unix 上

{"uri":"local:///Users/someone/.sbt/1.0/server/0845deda85cb41abdb9f/sock"}

其中 uri 键将包含一个以 local:// 开头的字符串,后跟 sbt 服务器正在监听的套接字地址。

在域套接字/命名管道模式下,在 Windows 上,它看起来像这样

{"uri":"local:sbt-server-0845deda85cb41abdb9f"}

其中 uri 键将包含一个以 local: 开头的字符串,后跟命名管道的名称。在此示例中,命名管道的路径将为 \.\pipe\sbt-server-0845deda85cb41abdb9f

在 TCP 模式下,它看起来像这样

{
  "uri":"tcp://127.0.0.1:5010",
  "tokenfilePath":"/Users/xxx/.sbt/1.0/server/0845deda85cb41abdb9f/token.json",
  "tokenfileUri":"file:/Users/xxx/.sbt/1.0/server/0845deda85cb41abdb9f/token.json"
}

在这种情况下,uri 键将包含一个 TCP uri,其中包含服务器正在监听的地址。在此模式下,端口文件将包含两个额外的键,tokenfilePathtokenfileUri。它们指向 *令牌文件* 的位置。

令牌文件的位置在运行之间不会改变。它的内容看起来像这样

{
  "uri":"tcp://127.0.0.1:5010",
  "token":"12345678901234567890123456789012345678"
}

uri 字段相同,token 字段包含一个 128 位非负整数。

初始化请求 

为了启动与 sbt 服务器的通信,客户端(例如 VS Code 等工具)必须首先发送一个 `initialize` 请求。这意味着客户端必须发送一个方法设置为“initialize”、params 字段为 InitializeParams 数据类型的请求。

如果服务器在 TCP 模式下运行,则为了验证自己,必须在 initializationOptions 中传递令牌,如下所示

type InitializationOptionsParams {
  token: String!
}

在 telnet 上,它看起来像这样

$ telnet 127.0.0.1 5010
Content-Type: application/vscode-jsonrpc; charset=utf-8
Content-Length: 149

{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "initializationOptions": { "token": "84046191245433876643612047032303751629" } } }

如果服务器在命名管道模式下运行,则不需要令牌,initializationOptions 应该为空对象 {}

在 Unix 上,使用 netcat 在域套接字/命名管道模式下发送初始化消息看起来像这样

$ nc -U /Users/foo/.sbt/1.0/server/0845deda85cb41abcdef/sock
Content-Length: 99^M
^M
{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "initializationOptions": { } } }^M

当服务器在命名管道模式下运行时,对服务器的连接是独占的,只允许第一个连接到套接字或管道的进程访问。

sbt 接收请求后,将发送一个 `initialized` 事件

textDocument/publishDiagnostics 事件 

编译器警告和错误使用 textDocument/publishDiagnostics 事件发送到客户端。

以下是一个示例输出(省略了 JSON-RPC 标头)

{
  "jsonrpc": "2.0",
  "method": "textDocument/publishDiagnostics",
  "params": {
    "uri": "file:/Users/xxx/work/hellotest/Hello.scala",
    "diagnostics": [
      {
        "range": {
          "start": {
            "line": 2,
            "character": 0
          },
          "end": {
            "line": 2,
            "character": 1
          }
        },
        "severity": 1,
        "source": "sbt",
        "message": "')' expected but '}' found."
      }
    ]
  }
}

textDocument/didSave 事件 

从 sbt 1.1.0 开始,sbt 将在收到 textDocument/didSave 通知后执行 compile 任务。此行为可能会发生变化。

sbt/exec 请求 

sbt/exec 请求模拟用户在 shell 中输入内容。

  • 方法:sbt/exec
  • 参数
type SbtExecParams {
  commandLine: String!
}

在 telnet 上,它看起来像这样

Content-Length: 91

{ "jsonrpc": "2.0", "id": 2, "method": "sbt/exec", "params": { "commandLine": "clean" } }

请注意,可能还有其他命令正在构建上运行,在这种情况下,请求将被排队。

sbt/setting 请求 

sbt/setting 请求可用于查询设置。

  • 方法:sbt/setting
  • 参数
type SettingQuery {
  setting: String!
}

在 telnet 上,它看起来像这样

Content-Length: 102

{ "jsonrpc": "2.0", "id": 3, "method": "sbt/setting", "params": { "setting": "root/scalaVersion" } }
Content-Length: 87
Content-Type: application/vscode-jsonrpc; charset=utf-8

{"jsonrpc":"2.0","id":"3","result":{"value":"2.12.2","contentType":"java.lang.String"}}

与命令执行不同,它会立即响应。

sbt/completion 请求 

(sbt 1.3.0+)

sbt/completion 请求用于模拟 sbt shell 的制表符补全。

  • 方法:sbt/completion
  • 参数:` type CompletionParams { query: String! } `

在 telnet 上,它看起来像这样

Content-Length: 100

{ "jsonrpc": "2.0", "id": 15, "method": "sbt/completion", "params": { "query": "testOnly org." } }
Content-Length: 79
Content-Type: application/vscode-jsonrpc; charset=utf-8

{"jsonrpc":"2.0","id":15,"result":{"items":["testOnly org.sbt.ExampleSpec"]}}

它将根据 sbt 的最后一个可用状态立即响应。

sbt/cancelRequest 

(sbt 1.3.0+)

sbt/cancelRequest 请求可用于终止正在进行的任务的执行。

  • 方法:sbt/cancelRequest
  • 参数:` type CancelRequestParams { id: String! } `

在 telnet 上,它看起来像这样(假设一个名为“foo”的任务正在运行)

Content-Length: 93

{ "jsonrpc": "2.0", "id": "bar", "method": "sbt/cancelRequest", "params": { "id": "foo" } }
Content-Length: 126
Content-Type: application/vscode-jsonrpc; charset=utf-8

{"jsonrpc":"2.0","id":"bar","result":{"status":"Task cancelled","channelName":"network-1","execId":"foo","commandQueue":[]}}

它将响应操作的结果。