Merge pull request #5618 from adpi2/topic/metals-support

Add suggestion about semanticdb when Metals connect to sbt
This commit is contained in:
eugene yokota 2020-06-24 14:19:04 -04:00 committed by GitHub
commit 778264a319
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 166 additions and 14 deletions

View File

@ -208,7 +208,8 @@ object Defaults extends BuildCommon {
fullServerHandlers := {
Seq(
LanguageServerProtocol.handler(fileConverter.value),
BuildServerProtocol.handler(sbtVersion.value)
BuildServerProtocol
.handler(sbtVersion.value, semanticdbEnabled.value, semanticdbVersion.value)
) ++ serverHandlers.value :+ ServerHandler.fallback
},
uncachedStamper := Stamps.uncachedStamps(fileConverter.value),

View File

@ -20,9 +20,12 @@ import sbt.internal.bsp._
import sbt.internal.langserver.ErrorCodes
import sbt.internal.protocol.JsonRpcRequestMessage
import sbt.librarymanagement.Configuration
import sbt.util.Logger
import sjsonnew.shaded.scalajson.ast.unsafe.JValue
import sjsonnew.support.scalajson.unsafe.Converter
import scala.util.control.NonFatal
object BuildServerProtocol {
import sbt.internal.bsp.codec.JsonProtocol._
private val capabilities = BuildServerCapabilities(
@ -123,11 +126,17 @@ object BuildServerProtocol {
bspInternalDependencyConfigurations := internalDependencyConfigurationsSetting.value
)
def handler(sbtVersion: String): ServerHandler = ServerHandler { callback =>
def handler(
sbtVersion: String,
semanticdbEnabled: Boolean,
semanticdbVersion: String
): ServerHandler = ServerHandler { callback =>
ServerIntent(
{
case r: JsonRpcRequestMessage if r.method == "build/initialize" =>
val _ = Converter.fromJson[InitializeBuildParams](json(r)).get
val params = Converter.fromJson[InitializeBuildParams](json(r)).get
checkMetalsCompatibility(semanticdbEnabled, semanticdbVersion, params, callback.log)
val response = InitializeBuildResult(
"sbt",
sbtVersion,
@ -175,6 +184,39 @@ object BuildServerProtocol {
)
}
private def checkMetalsCompatibility(
semanticdbEnabled: Boolean,
semanticdbVersion: String,
params: InitializeBuildParams,
log: Logger
): Unit = {
for {
data <- params.data
// try parse metadata as MetalsMetadata
metalsMetadata <- Converter.fromJson[MetalsMetadata](data).toOption
} {
if (!semanticdbEnabled) {
log.warn(s"${params.displayName} requires the semanticdb compiler plugin")
log.warn(
s"consider setting 'Global / semanticdbEnabled := true' in your global sbt settings ($$HOME/.sbt/1.0)"
)
}
for {
requiredVersion <- SemanticVersion.tryParse(metalsMetadata.semanticdbVersion)
currentVersion <- SemanticVersion.tryParse(semanticdbVersion)
if requiredVersion > currentVersion
} {
log.warn(
s"${params.displayName} requires semanticdb version ${metalsMetadata.semanticdbVersion}, current version is $semanticdbVersion"
)
log.warn(
s"""consider setting 'Global / semanticdbVersion := "${metalsMetadata.semanticdbVersion}"' in your global sbt settings ($$HOME/.sbt/1.0)"""
)
}
}
}
private def json(r: JsonRpcRequestMessage): JValue =
r.params.getOrElse(
throw LangServerError(
@ -315,4 +357,22 @@ object BuildServerProtocol {
BuildTargetIdentifier(new URI(s"$build#$project/${config.id}"))
case _ => sys.error(s"unexpected $ref")
}
private case class SemanticVersion(major: Int, minor: Int) extends Ordered[SemanticVersion] {
override def compare(that: SemanticVersion): Int = {
if (that.major != major) major.compare(that.major)
else minor.compare(minor)
}
}
private object SemanticVersion {
def tryParse(versionStr: String): Option[SemanticVersion] = {
try {
val parts = versionStr.split('.')
Some(SemanticVersion(parts(0).toInt, parts(1).toInt))
} catch {
case NonFatal(_) => None
}
}
}
}

View File

@ -11,28 +11,30 @@ package sbt.internal.bsp
* @param bspVersion The BSP version that the client speaks
* @param rootUri The rootUri of the workspace
* @param capabilities The capabilities of the client
* @param data Additional metadata about the client
*/
final class InitializeBuildParams private (
val displayName: String,
val version: String,
val bspVersion: String,
val rootUri: java.net.URI,
val capabilities: sbt.internal.bsp.BuildClientCapabilities) extends Serializable {
val capabilities: sbt.internal.bsp.BuildClientCapabilities,
val data: Option[sjsonnew.shaded.scalajson.ast.unsafe.JValue]) extends Serializable {
override def equals(o: Any): Boolean = o match {
case x: InitializeBuildParams => (this.displayName == x.displayName) && (this.version == x.version) && (this.bspVersion == x.bspVersion) && (this.rootUri == x.rootUri) && (this.capabilities == x.capabilities)
case x: InitializeBuildParams => (this.displayName == x.displayName) && (this.version == x.version) && (this.bspVersion == x.bspVersion) && (this.rootUri == x.rootUri) && (this.capabilities == x.capabilities) && (this.data == x.data)
case _ => false
}
override def hashCode: Int = {
37 * (37 * (37 * (37 * (37 * (37 * (17 + "sbt.internal.bsp.InitializeBuildParams".##) + displayName.##) + version.##) + bspVersion.##) + rootUri.##) + capabilities.##)
37 * (37 * (37 * (37 * (37 * (37 * (37 * (17 + "sbt.internal.bsp.InitializeBuildParams".##) + displayName.##) + version.##) + bspVersion.##) + rootUri.##) + capabilities.##) + data.##)
}
override def toString: String = {
"InitializeBuildParams(" + displayName + ", " + version + ", " + bspVersion + ", " + rootUri + ", " + capabilities + ")"
"InitializeBuildParams(" + displayName + ", " + version + ", " + bspVersion + ", " + rootUri + ", " + capabilities + ", " + data + ")"
}
private[this] def copy(displayName: String = displayName, version: String = version, bspVersion: String = bspVersion, rootUri: java.net.URI = rootUri, capabilities: sbt.internal.bsp.BuildClientCapabilities = capabilities): InitializeBuildParams = {
new InitializeBuildParams(displayName, version, bspVersion, rootUri, capabilities)
private[this] def copy(displayName: String = displayName, version: String = version, bspVersion: String = bspVersion, rootUri: java.net.URI = rootUri, capabilities: sbt.internal.bsp.BuildClientCapabilities = capabilities, data: Option[sjsonnew.shaded.scalajson.ast.unsafe.JValue] = data): InitializeBuildParams = {
new InitializeBuildParams(displayName, version, bspVersion, rootUri, capabilities, data)
}
def withDisplayName(displayName: String): InitializeBuildParams = {
copy(displayName = displayName)
@ -49,8 +51,15 @@ final class InitializeBuildParams private (
def withCapabilities(capabilities: sbt.internal.bsp.BuildClientCapabilities): InitializeBuildParams = {
copy(capabilities = capabilities)
}
def withData(data: Option[sjsonnew.shaded.scalajson.ast.unsafe.JValue]): InitializeBuildParams = {
copy(data = data)
}
def withData(data: sjsonnew.shaded.scalajson.ast.unsafe.JValue): InitializeBuildParams = {
copy(data = Option(data))
}
}
object InitializeBuildParams {
def apply(displayName: String, version: String, bspVersion: String, rootUri: java.net.URI, capabilities: sbt.internal.bsp.BuildClientCapabilities): InitializeBuildParams = new InitializeBuildParams(displayName, version, bspVersion, rootUri, capabilities)
def apply(displayName: String, version: String, bspVersion: String, rootUri: java.net.URI, capabilities: sbt.internal.bsp.BuildClientCapabilities, data: Option[sjsonnew.shaded.scalajson.ast.unsafe.JValue]): InitializeBuildParams = new InitializeBuildParams(displayName, version, bspVersion, rootUri, capabilities, data)
def apply(displayName: String, version: String, bspVersion: String, rootUri: java.net.URI, capabilities: sbt.internal.bsp.BuildClientCapabilities, data: sjsonnew.shaded.scalajson.ast.unsafe.JValue): InitializeBuildParams = new InitializeBuildParams(displayName, version, bspVersion, rootUri, capabilities, Option(data))
}

View File

@ -0,0 +1,41 @@
/**
* This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]].
*/
// DO NOT EDIT MANUALLY
package sbt.internal.bsp
/**
* Metals metadata in the initialization request
* @param semanticdbVersion The semanticdb plugin version that should be enabled for Metals code navigation
* @param supportedScalaVersions The list of scala versions that are supported by Metals
*/
final class MetalsMetadata private (
val semanticdbVersion: String,
val supportedScalaVersions: Vector[String]) extends Serializable {
override def equals(o: Any): Boolean = o match {
case x: MetalsMetadata => (this.semanticdbVersion == x.semanticdbVersion) && (this.supportedScalaVersions == x.supportedScalaVersions)
case _ => false
}
override def hashCode: Int = {
37 * (37 * (37 * (17 + "sbt.internal.bsp.MetalsMetadata".##) + semanticdbVersion.##) + supportedScalaVersions.##)
}
override def toString: String = {
"MetalsMetadata(" + semanticdbVersion + ", " + supportedScalaVersions + ")"
}
private[this] def copy(semanticdbVersion: String = semanticdbVersion, supportedScalaVersions: Vector[String] = supportedScalaVersions): MetalsMetadata = {
new MetalsMetadata(semanticdbVersion, supportedScalaVersions)
}
def withSemanticdbVersion(semanticdbVersion: String): MetalsMetadata = {
copy(semanticdbVersion = semanticdbVersion)
}
def withSupportedScalaVersions(supportedScalaVersions: Vector[String]): MetalsMetadata = {
copy(supportedScalaVersions = supportedScalaVersions)
}
}
object MetalsMetadata {
def apply(semanticdbVersion: String, supportedScalaVersions: Vector[String]): MetalsMetadata = new MetalsMetadata(semanticdbVersion, supportedScalaVersions)
}

View File

@ -5,7 +5,7 @@
// DO NOT EDIT MANUALLY
package sbt.internal.bsp.codec
import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError }
trait InitializeBuildParamsFormats { self: sbt.internal.bsp.codec.BuildClientCapabilitiesFormats with sjsonnew.BasicJsonProtocol =>
trait InitializeBuildParamsFormats { self: sbt.internal.bsp.codec.BuildClientCapabilitiesFormats with sbt.internal.util.codec.JValueFormats with sjsonnew.BasicJsonProtocol =>
implicit lazy val InitializeBuildParamsFormat: JsonFormat[sbt.internal.bsp.InitializeBuildParams] = new JsonFormat[sbt.internal.bsp.InitializeBuildParams] {
override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.bsp.InitializeBuildParams = {
__jsOpt match {
@ -16,8 +16,9 @@ implicit lazy val InitializeBuildParamsFormat: JsonFormat[sbt.internal.bsp.Initi
val bspVersion = unbuilder.readField[String]("bspVersion")
val rootUri = unbuilder.readField[java.net.URI]("rootUri")
val capabilities = unbuilder.readField[sbt.internal.bsp.BuildClientCapabilities]("capabilities")
val data = unbuilder.readField[Option[sjsonnew.shaded.scalajson.ast.unsafe.JValue]]("data")
unbuilder.endObject()
sbt.internal.bsp.InitializeBuildParams(displayName, version, bspVersion, rootUri, capabilities)
sbt.internal.bsp.InitializeBuildParams(displayName, version, bspVersion, rootUri, capabilities, data)
case None =>
deserializationError("Expected JsObject but found None")
}
@ -29,6 +30,7 @@ implicit lazy val InitializeBuildParamsFormat: JsonFormat[sbt.internal.bsp.Initi
builder.addField("bspVersion", obj.bspVersion)
builder.addField("rootUri", obj.rootUri)
builder.addField("capabilities", obj.capabilities)
builder.addField("data", obj.data)
builder.endObject()
}
}

View File

@ -40,4 +40,5 @@ trait JsonProtocol extends sjsonnew.BasicJsonProtocol
with sbt.internal.bsp.codec.ScalacOptionsItemFormats
with sbt.internal.bsp.codec.ScalacOptionsResultFormats
with sbt.internal.bsp.codec.BspConnectionDetailsFormats
with sbt.internal.bsp.codec.MetalsMetadataFormats
object JsonProtocol extends JsonProtocol

View File

@ -0,0 +1,29 @@
/**
* This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]].
*/
// DO NOT EDIT MANUALLY
package sbt.internal.bsp.codec
import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError }
trait MetalsMetadataFormats { self: sjsonnew.BasicJsonProtocol =>
implicit lazy val MetalsMetadataFormat: JsonFormat[sbt.internal.bsp.MetalsMetadata] = new JsonFormat[sbt.internal.bsp.MetalsMetadata] {
override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.bsp.MetalsMetadata = {
__jsOpt match {
case Some(__js) =>
unbuilder.beginObject(__js)
val semanticdbVersion = unbuilder.readField[String]("semanticdbVersion")
val supportedScalaVersions = unbuilder.readField[Vector[String]]("supportedScalaVersions")
unbuilder.endObject()
sbt.internal.bsp.MetalsMetadata(semanticdbVersion, supportedScalaVersions)
case None =>
deserializationError("Expected JsObject but found None")
}
}
override def write[J](obj: sbt.internal.bsp.MetalsMetadata, builder: Builder[J]): Unit = {
builder.beginObject()
builder.addField("semanticdbVersion", obj.semanticdbVersion)
builder.addField("supportedScalaVersions", obj.supportedScalaVersions)
builder.endObject()
}
}
}

View File

@ -135,8 +135,8 @@ type InitializeBuildParams {
## The capabilities of the client
capabilities: sbt.internal.bsp.BuildClientCapabilities!
# Additional metadata about the client
# data: any
## Additional metadata about the client
data: sjsonnew.shaded.scalajson.ast.unsafe.JValue
}
type BuildClientCapabilities {
@ -457,3 +457,12 @@ type BspConnectionDetails {
## Command arguments runnable via system processes to start a BSP server
argv: [String]
}
## Metals metadata in the initialization request
type MetalsMetadata {
## The semanticdb plugin version that should be enabled for Metals code navigation
semanticdbVersion: String!
## The list of scala versions that are supported by Metals
supportedScalaVersions: [String]
}