diff --git a/build.sc b/build.sc index a322d8ab4c..0f0fa5b6f8 100644 --- a/build.sc +++ b/build.sc @@ -611,12 +611,13 @@ trait Build extends ScalaCliSbtModule with ScalaCliPublishModule with HasTests Deps.collectionCompat, Deps.javaClassName, Deps.jsoniterCore, - Deps.scalametaTrees, + Deps.lsp4j, Deps.nativeTestRunner, Deps.osLib, Deps.pprint, Deps.scalaJsEnvNodeJs, Deps.scalaJsTestAdapter, + Deps.scalametaTrees, Deps.swoval, Deps.zipInputStream ) @@ -775,6 +776,7 @@ trait Cli extends SbtModule with ProtoBuildModule with CliLaunchers Deps.jniUtils, Deps.jsoniterCore, Deps.libsodiumjni, + Deps.lsp4j, Deps.metaconfigTypesafe, Deps.pythonNativeLibs, Deps.scalaPackager, @@ -856,6 +858,7 @@ trait CliIntegration extends SbtModule with ScalaCliPublishModule with HasTests Deps.dockerClient, Deps.jsoniterCore, Deps.libsodiumjni, + Deps.lsp4j, Deps.pprint, Deps.scalaAsync, Deps.slf4jNop, diff --git a/modules/build/src/main/scala/scala/build/bsp/BspClient.scala b/modules/build/src/main/scala/scala/build/bsp/BspClient.scala index c203f53769..c7c7185c1e 100644 --- a/modules/build/src/main/scala/scala/build/bsp/BspClient.scala +++ b/modules/build/src/main/scala/scala/build/bsp/BspClient.scala @@ -1,6 +1,8 @@ package scala.build.bsp import ch.epfl.scala.{bsp4j => b} +import org.eclipse.lsp4j.WorkspaceEdit +import org.eclipse.lsp4j as l import java.lang.Boolean as JBoolean import java.net.URI @@ -8,7 +10,7 @@ import java.nio.file.Paths import java.util.concurrent.{ConcurrentHashMap, ExecutorService} import scala.build.Position.File -import scala.build.bsp.protocol.TextEdit +import scala.build.bsp.protocol.* import scala.build.errors.{BuildException, CompositeBuildException, Diagnostic, Severity} import scala.build.postprocessing.LineConversion import scala.build.{BloopBuildClient, GeneratedSource, Logger} @@ -214,16 +216,24 @@ class BspClient( )(diag: Diagnostic): Seq[os.Path] = diag.positions.flatMap { case File(Right(path), (startLine, startC), (endL, endC)) => - val id = new b.TextDocumentIdentifier(path.toNIO.toUri.toASCIIString) - val startPos = new b.Position(startLine, startC) - val endPos = new b.Position(endL, endC) - val range = new b.Range(startPos, endPos) + val id = new b.TextDocumentIdentifier(path.toNIO.toUri.toASCIIString) + val bstartPos = new b.Position(startLine, startC) + val bendPos = new b.Position(endL, endC) + val brange = new b.Range(bstartPos, bendPos) val bDiag = - new b.Diagnostic(range, diag.message) + new b.Diagnostic(brange, diag.message) diag.textEdit.foreach { textEdit => - val bTextEdit = TextEdit(range, textEdit.newText) - bDiag.setData(bTextEdit.toJsonTree()) + val lstartPos = new l.Position(startLine, startC) + val lendPos = new l.Position(endL, endC) + val lrange = new l.Range(lstartPos, lendPos) + val lTextEdit = l.TextEdit(lrange, textEdit.newText) + val workspaceEdit = new WorkspaceEdit() + workspaceEdit.setChanges( + Map(path.toNIO.toUri().toString() -> List(lTextEdit).asJava).asJava + ) + val data = DiagnosticData(edits = Array(workspaceEdit)) + bDiag.setData(data.toJsonTree()) } bDiag.setSeverity(diag.severity.toBsp4j) bDiag.setSource("scala-cli") diff --git a/modules/build/src/main/scala/scala/build/bsp/protocol/DiagnosticData.scala b/modules/build/src/main/scala/scala/build/bsp/protocol/DiagnosticData.scala new file mode 100644 index 0000000000..c54a37dc4e --- /dev/null +++ b/modules/build/src/main/scala/scala/build/bsp/protocol/DiagnosticData.scala @@ -0,0 +1,22 @@ +package scala.build.bsp.protocol + +import com.google.gson.{Gson, JsonElement} +import org.eclipse.lsp4j.WorkspaceEdit + +/** Representation for the data field in a bsp.Diagnostic. + * + * For now this only contains an edit, but there is nothing from keeping it having more field in + * the future. + * @param edit + * The Workspace edit attatched to the Diagnostic. + */ +case class DiagnosticData(edits: Array[WorkspaceEdit]) { + + /** Go from the DiagnosticData to Json. This will result in each param being a field in the + * object. + * + * @return + * the JsonElement + */ + def toJsonTree(): JsonElement = new Gson().toJsonTree(this) +} diff --git a/modules/build/src/main/scala/scala/build/bsp/protocol/TextEdit.scala b/modules/build/src/main/scala/scala/build/bsp/protocol/TextEdit.scala deleted file mode 100644 index 89b8543f4e..0000000000 --- a/modules/build/src/main/scala/scala/build/bsp/protocol/TextEdit.scala +++ /dev/null @@ -1,8 +0,0 @@ -package scala.build.bsp.protocol - -import ch.epfl.scala.bsp4j as b -import com.google.gson.Gson - -case class TextEdit(range: b.Range, newText: String) { - def toJsonTree() = new Gson().toJsonTree(this) -} diff --git a/modules/cli/src/main/resources/META-INF/native-image/org.virtuslab/scala-cli-core/reflect-config.json b/modules/cli/src/main/resources/META-INF/native-image/org.virtuslab/scala-cli-core/reflect-config.json index 560b64654d..da06add088 100644 --- a/modules/cli/src/main/resources/META-INF/native-image/org.virtuslab/scala-cli-core/reflect-config.json +++ b/modules/cli/src/main/resources/META-INF/native-image/org.virtuslab/scala-cli-core/reflect-config.json @@ -930,6 +930,118 @@ } ] }, + { + "name": "org.eclipse.lsp4j.ChangeAnnotation", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + }, + { + "name": "org.eclipse.lsp4j.CreateFile", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + }, + { + "name": "org.eclipse.lsp4j.CreateFileOptions", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + }, + { + "name": "org.eclipse.lsp4j.DeleteFile", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + }, + { + "name": "org.eclipse.lsp4j.DeleteFileOptions", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + }, + { + "name": "org.eclipse.lsp4j.Position", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + }, + { + "name": "org.eclipse.lsp4j.Range", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + }, + { + "name": "org.eclipse.lsp4j.RenameFile", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + }, + { + "name": "org.eclipse.lsp4j.RenameFileOptions", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + }, + { + "name": "org.eclipse.lsp4j.ResourceOperation", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + }, + { + "name": "org.eclipse.lsp4j.TextDocumentEdit", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + }, + { + "name": "org.eclipse.lsp4j.TextEdit", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + }, + { + "name": "org.eclipse.lsp4j.WorkspaceEdit", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + }, + { + "name": "org.eclipse.lsp4j.adapters.DocumentChangeListAdapter", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + }, + { + "name": "org.eclipse.lsp4j.adapters.ResourceOperationTypeAdapter", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + }, + { + "name": "org.eclipse.lsp4j.adapters.VersionedTextDocumentIdentifierTypeAdapter$Factory", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + }, { "name": "org.eclipse.lsp4j.jsonrpc.json.adapters.JsonElementTypeAdapter$Factory", "allDeclaredConstructors": true, @@ -1096,7 +1208,7 @@ "allDeclaredFields": true }, { - "name": "scala.build.bsp.protocol.TextEdit", + "name": "scala.build.bsp.protocol.DiagnosticData", "allDeclaredConstructors": true, "allPublicConstructors": true, "allDeclaredMethods": true, diff --git a/modules/cli/src/main/scala/scala/cli/internal/CliLogger.scala b/modules/cli/src/main/scala/scala/cli/internal/CliLogger.scala index a099a006c3..73d8f935c8 100644 --- a/modules/cli/src/main/scala/scala/cli/internal/CliLogger.scala +++ b/modules/cli/src/main/scala/scala/cli/internal/CliLogger.scala @@ -5,11 +5,13 @@ import ch.epfl.scala.bsp4j.Location import ch.epfl.scala.{bsp4j => b} import coursier.cache.CacheLogger import coursier.cache.loggers.{FallbackRefreshDisplay, RefreshLogger} +import org.eclipse.lsp4j.WorkspaceEdit +import org.eclipse.lsp4j as l import org.scalajs.logging.{Level => ScalaJsLevel, Logger => ScalaJsLogger, ScalaConsoleLogger} import java.io.PrintStream -import scala.build.bsp.protocol.TextEdit +import scala.build.bsp.protocol.* import scala.build.errors.{BuildException, CompositeBuildException, Diagnostic, Severity} import scala.build.internal.CustomProgressBarRefreshDisplay import scala.build.{ConsoleBloopBuildClient, Logger, Position} @@ -83,9 +85,19 @@ class CliLogger( diag.setSeverity(severity.toBsp4j) diag.setSource("scala-cli") - for (textEdit <- textEditOpt) { - val bTextEdit = TextEdit(range, textEdit.newText) - diag.setData(bTextEdit.toJsonTree()) + (textEditOpt, f.path) match { + case (Some(textEdit), Right(path)) => + val lstartPos = new l.Position(f.startPos._1, f.startPos._2) + val lendPos = new l.Position(f.endPos._1, f.endPos._2) + val lrange = new l.Range(lstartPos, lendPos) + val bTextEdit = l.TextEdit(lrange, textEdit.newText) + val workspaceEdit = new WorkspaceEdit() + workspaceEdit.setChanges( + Map(path.toNIO.toUri().toString() -> List(bTextEdit).asJava).asJava + ) + val data = DiagnosticData(edits = Array(workspaceEdit)) + diag.setData(data.toJsonTree()) + case _ => () } for (file <- f.path) { diff --git a/modules/integration/src/test/scala/scala/cli/integration/BspTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/BspTestDefinitions.scala index ecec4c620c..e7629cf369 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/BspTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/BspTestDefinitions.scala @@ -7,7 +7,9 @@ import com.github.plokhotnyuk.jsoniter_scala.core.* import com.github.plokhotnyuk.jsoniter_scala.macros.* import com.google.gson.internal.LinkedTreeMap import com.google.gson.{Gson, JsonElement} +import org.eclipse.lsp4j.WorkspaceEdit import org.eclipse.lsp4j.jsonrpc.messages.ResponseError +import org.eclipse.lsp4j as l import java.net.URI import java.nio.file.Paths @@ -1293,16 +1295,21 @@ abstract class BspTestDefinitions(val scalaVersionOpt: Option[String]) strictlyCheckMessage = false ) - val textEdit = new Gson().fromJson[TextEdit]( + val data = new Gson().fromJson[DiagnosticData]( updateActionableDiagnostic.getData().asInstanceOf[JsonElement], - classOf[TextEdit] + classOf[DiagnosticData] ) - expect(textEdit.newText.contains("com.lihaoyi::os-lib:")) - expect(textEdit.range.getStart.getLine == 0) - expect(textEdit.range.getStart.getCharacter == 15) - expect(textEdit.range.getEnd.getLine == 0) - expect(textEdit.range.getEnd.getCharacter == 40) + val edits = data.edits.head.getChanges().asScala + expect(edits.size == 1) + + val (_, textEdits) = edits.head + val edit: l.TextEdit = textEdits.asScala.head + expect(edit.getNewText().contains("com.lihaoyi::os-lib:")) + expect(edit.getRange().getStart().getLine() == 0) + expect(edit.getRange().getStart().getCharacter() == 15) + expect(edit.getRange().getEnd().getLine() == 0) + expect(edit.getRange().getEnd().getCharacter() == 40) } } } @@ -1390,4 +1397,5 @@ object BspTestDefinitions { private final case class TextEdit(range: b.Range, newText: String) + private final case class DiagnosticData(edits: Array[WorkspaceEdit]) } diff --git a/project/deps.sc b/project/deps.sc index 9149a6c482..9338fe0a01 100644 --- a/project/deps.sc +++ b/project/deps.sc @@ -85,6 +85,7 @@ object Deps { def coursierM1Cli = "2.1.0-RC4" def jsoniterScala = "2.20.3" def jsoniterScalaJava8 = "2.13.5.2" + def lsp4j = "0.20.1" def scalaMeta = "4.7.1" def scalaNative = "0.4.12" def scalaPackager = "0.1.29" @@ -126,6 +127,7 @@ object Deps { def jsoniterMacrosJava8 = ivy"com.github.plokhotnyuk.jsoniter-scala::jsoniter-scala-macros:${Versions.jsoniterScalaJava8}" def libsodiumjni = ivy"org.virtuslab.scala-cli:libsodiumjni:0.0.3" + def lsp4j = ivy"org.eclipse.lsp4j:org.eclipse.lsp4j:${Versions.lsp4j}" def macroParadise = ivy"org.scalamacros:::paradise:2.1.1" def metaconfigTypesafe = ivy"com.geirsson::metaconfig-typesafe-config:0.11.1"