Skip to content

Commit 36b0409

Browse files
rochalakubukoz
andauthored
lsp-smithy integration (#50)
* Add smithyLsp module * Migrate to lsp-smithy * Scalafix, fmt + test * Cleanup * Small refactor to use more Resource --------- Co-authored-by: Jakub Kozłowski <kubukoz@gmail.com>
1 parent 2105187 commit 36b0409

18 files changed

+474
-503
lines changed

.mill-version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.12.10
1+
0.12.11

build.mill

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
1+
import mill.eval.Evaluator
2+
import smithy4s.codegen.LSP
13
import com.goyeau.mill.scalafix.ScalafixModule
4+
import scala.collection.immutable.ListSet
25
import coursier.core.Repository
36
import coursier.maven.MavenRepository
47
import mill._
58
import mill.define.Sources
69
import scalalib._
10+
import smithy4s.codegen.mill._
711

12+
import $ivy.`com.disneystreaming.smithy4s::smithy4s-mill-codegen-plugin_mill0.12:0.18.37`
813
import $ivy.`com.goyeau::mill-scalafix::0.5.0`
914

15+
val jsonrpcVersion = "0.1.0"
16+
val smithyVersion = "1.57.1"
17+
1018
trait CommonScalaModule extends ScalaModule with ScalafixModule {
1119
override def repositoriesTask: Task[Seq[Repository]] = T.task {
1220
Seq(
@@ -16,28 +24,39 @@ trait CommonScalaModule extends ScalaModule with ScalafixModule {
1624
) ++ super.repositoriesTask()
1725
}
1826

19-
def scalaVersion = "3.7.0"
27+
def scalaVersion = "3.7.2-RC1-bin-20250616-61d9887-NIGHTLY"
2028
def scalacOptions = Seq("-no-indent", "-Wunused:all")
2129
}
2230

31+
object slsSmithy extends CommonScalaModule with Smithy4sModule {
32+
33+
override def ivyDeps = Agg(
34+
ivy"com.disneystreaming.smithy4s::smithy4s-core:${smithy4sVersion()}",
35+
ivy"tech.neander:jsonrpclib-smithy:$jsonrpcVersion",
36+
ivy"io.github.simple-scala-tooling:lsp-smithy-definitions:1f4b6e9",
37+
)
38+
}
39+
2340
object sls extends CommonScalaModule {
2441

2542
def mainClass = Some("org.scala.abusers.sls.SimpleScalaServer")
2643

44+
def moduleDeps: Seq[JavaModule] = Seq(slsSmithy)
45+
2746
def ivyDeps = Agg(
28-
ivy"com.github.plokhotnyuk.jsoniter-scala:jsoniter-scala-core_2.13:2.35.2".forceVersion(),
47+
ivy"org.typelevel::cats-effect:3.6.2",
2948
ivy"co.fs2::fs2-io:3.13.0-M2",
30-
ivy"tech.neander::jsonrpclib-fs2::0.0.8+26-13de833b-SNAPSHOT".forceVersion(),
49+
ivy"io.scalaland::chimney:1.8.1",
50+
ivy"io.scalaland::chimney-java-collections:1.8.1",
51+
ivy"tech.neander::jsonrpclib-fs2::$jsonrpcVersion",
3152
ivy"ch.qos.logback:logback-classic:1.4.14",
32-
ivy"tech.neander::langoustine-app::0.0.22",
3353
ivy"com.lihaoyi::os-lib:0.11.4",
34-
ivy"org.polyvariant.smithy4s-bsp::bsp4s:0.4.1",
35-
ivy"org.scalameta:mtags-interfaces:1.5.1:",
54+
ivy"org.polyvariant.smithy4s-bsp::bsp4s:0.5.0",
55+
ivy"org.scalameta:mtags-interfaces:1.5.1",
3656
ivy"com.evolution::scache:5.1.2",
3757
ivy"org.typelevel::cats-parse:1.1.0",
38-
ivy"io.get-coursier:coursier_2.13:2.1.24"
39-
.excludeOrg("org.scala-lang"),
40-
).map(_.exclude("com.github.plokhotnyuk.jsoniter-scala" -> "jsoniter-scala-core_3"))
58+
ivy"io.get-coursier:interface:1.0.28"
59+
)
4160

4261
object test extends ScalaTests {
4362
def ivyDeps = Agg(

sls/src/org/scala/abusers/sls/BspClient.scala

Lines changed: 29 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,8 @@ import cats.syntax.all.*
1414
import com.comcast.ip4s.*
1515
import fs2.io.*
1616
import fs2.io.net.Network
17-
import jsonrpclib.fs2.*
1817
import jsonrpclib.Endpoint
19-
import langoustine.lsp.requests.window
20-
import langoustine.lsp.Communicate
18+
import jsonrpclib.fs2.{lsp => jsonrpclibLsp, *}
2119
import smithy4sbsp.bsp4s.BSPCodecs
2220

2321
def makeBspClient(path: String, channel: FS2Channel[IO], report: String => IO[Unit]): Resource[IO, BuildServer] =
@@ -27,52 +25,53 @@ def makeBspClient(path: String, channel: FS2Channel[IO], report: String => IO[Un
2725
fs2.Stream
2826
.eval(IO.never)
2927
.concurrently(
30-
socket.reads.through(lsp.decodeMessages).evalTap(m => report(m.toString)).through(channel.inputOrBounce)
28+
socket.reads.through(jsonrpclibLsp.decodeMessages).evalTap(m => report(m.toString)).through(channel.inputOrBounce)
3129
)
32-
.concurrently(channel.output.through(lsp.encodeMessages).through(socket.writes))
30+
.concurrently(channel.output.through(jsonrpclibLsp.encodeMessages).through(socket.writes))
3331
.compile
3432
.drain
3533
.guarantee(IO.consoleForIO.errorln("Terminating server"))
3634
.background
3735
}
3836
.as(
3937
BuildServer(
40-
BSPCodecs.clientStub(bsp.BuildServer, channel),
41-
BSPCodecs.clientStub(bsp.jvm.JvmBuildServer, channel),
42-
BSPCodecs.clientStub(bsp.scala_.ScalaBuildServer, channel),
43-
BSPCodecs.clientStub(bsp.java_.JavaBuildServer, channel),
38+
BSPCodecs.clientStub(bsp.BuildServer, channel).toTry.get,
39+
BSPCodecs.clientStub(bsp.jvm.JvmBuildServer, channel).toTry.get,
40+
BSPCodecs.clientStub(bsp.scala_.ScalaBuildServer, channel).toTry.get,
41+
BSPCodecs.clientStub(bsp.java_.JavaBuildServer, channel).toTry.get,
4442
)
4543
)
4644

47-
def bspClientHandler(lspClient: Communicate[IO], diagnosticManager: DiagnosticManager): List[Endpoint[IO]] =
48-
BSPCodecs.serverEndpoints(
49-
new BuildClient[IO] {
50-
private def notify(msg: String) =
51-
lspClient.notification(
52-
window.showMessage(
53-
langoustine.lsp.structures
54-
.ShowMessageParams(`type` = langoustine.lsp.enumerations.MessageType.Info, message = msg)
45+
def bspClientHandler(lspClient: SlsLanguageClient[IO], diagnosticManager: DiagnosticManager): List[Endpoint[IO]] =
46+
BSPCodecs
47+
.serverEndpoints(
48+
new BuildClient[IO] {
49+
50+
private def notify(msg: String) =
51+
lspClient.windowShowMessage(
52+
lsp.ShowMessageParams(_type = lsp.MessageType.INFO, message = msg)
5553
)
56-
)
5754

58-
def onBuildLogMessage(input: LogMessageParams): IO[Unit] = IO.unit
55+
def onBuildLogMessage(input: LogMessageParams): IO[Unit] = IO.unit // we want some logging to file here
5956

60-
def onBuildPublishDiagnostics(input: PublishDiagnosticsParams): IO[Unit] =
61-
notify(s"We've just got $input") >>
57+
def onBuildPublishDiagnostics(input: PublishDiagnosticsParams): IO[Unit] =
58+
// notify(s"We've just got $input") >>
6259
diagnosticManager.onBuildPublishDiagnostics(lspClient, input)
6360

64-
def onBuildShowMessage(input: ShowMessageParams): IO[Unit] = IO.unit
61+
def onBuildShowMessage(input: ShowMessageParams): IO[Unit] = IO.unit
6562

66-
def onBuildTargetDidChange(input: DidChangeBuildTarget): IO[Unit] = IO.unit
63+
def onBuildTargetDidChange(input: DidChangeBuildTarget): IO[Unit] = IO.unit
6764

68-
def onBuildTaskFinish(input: OnBuildTaskFinishInput): IO[Unit] = IO.unit
65+
def onBuildTaskFinish(input: OnBuildTaskFinishInput): IO[Unit] = IO.unit
6966

70-
def onBuildTaskProgress(input: TaskProgressParams): IO[Unit] = IO.unit
67+
def onBuildTaskProgress(input: TaskProgressParams): IO[Unit] = IO.unit
7168

72-
def onBuildTaskStart(input: OnBuildTaskStartInput): IO[Unit] = IO.unit
69+
def onBuildTaskStart(input: OnBuildTaskStartInput): IO[Unit] = IO.unit
7370

74-
def onRunPrintStderr(input: PrintParams): IO[Unit] = IO.unit
71+
def onRunPrintStderr(input: PrintParams): IO[Unit] = IO.unit
7572

76-
def onRunPrintStdout(input: PrintParams): IO[Unit] = IO.unit
77-
}
78-
)
73+
def onRunPrintStdout(input: PrintParams): IO[Unit] = IO.unit
74+
}
75+
)
76+
.toTry
77+
.get

sls/src/org/scala/abusers/sls/BspStateManager.scala

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,7 @@ import bsp.InverseSourcesParams
88
import cats.effect.kernel.Ref
99
import cats.effect.std.AtomicCell
1010
import cats.effect.IO
11-
import langoustine.lsp.*
12-
import langoustine.lsp.structures.*
1311
import org.scala.abusers.pc.ScalaVersion
14-
import org.scala.abusers.sls.NioConverter.asNio
1512
import org.scala.abusers.sls.LoggingUtils.*
1613

1714
import java.net.URI
@@ -32,12 +29,12 @@ object ScalaBuildTargetInformation {
3229

3330
object BspStateManager {
3431

35-
def instance(bspServer: BuildServer): IO[BspStateManager] =
32+
def instance(lspClient: SlsLanguageClient[IO], bspServer: BuildServer): IO[BspStateManager] =
3633
// We should track this in progress bar. Think of this as `Import Build`
3734
for {
3835
sourcesToTargets <- AtomicCell[IO].of(Map[URI, ScalaBuildTargetInformation]())
3936
buildTargets <- Ref.of[IO, Set[ScalaBuildTargetInformation]](Set.empty)
40-
} yield BspStateManager(bspServer, sourcesToTargets, buildTargets)
37+
} yield BspStateManager(lspClient, bspServer, sourcesToTargets, buildTargets)
4138
}
4239

4340
/** Class responsible for tracking and handling map between file and target we want to compile it against
@@ -47,19 +44,20 @@ object BspStateManager {
4744
* feature Another option will be to default to latest version which after all I'll default to right now
4845
*/
4946
class BspStateManager(
47+
lspClient: SlsLanguageClient[IO],
5048
val bspServer: BuildServer,
5149
sourcesToTargets: AtomicCell[IO, Map[URI, ScalaBuildTargetInformation]],
5250
targets: Ref[IO, Set[ScalaBuildTargetInformation]],
5351
) {
5452
import ScalaBuildTargetInformation.*
5553

56-
def importBuild(back: Communicate[IO]) =
54+
def importBuild =
5755
for {
58-
_ <- back.logMessage("Starting build import.") // in the future this should be a task with progress
56+
_ <- lspClient.logMessage("Starting build import.") // in the future this should be a task with progress
5957
importedBuild <- getBuildInformation(bspServer)
6058
_ <- bspServer.generic.buildTargetCompile(CompileParams(targets = importedBuild.map(_.buildTarget.id).toList))
6159
_ <- targets.set(importedBuild)
62-
_ <- back.logMessage("Build import finished.")
60+
_ <- lspClient.logMessage("Build import finished.")
6361
} yield ()
6462

6563
private val byScalaVersion: Ordering[ScalaBuildTargetInformation] = new Ordering[ScalaBuildTargetInformation] {
@@ -115,15 +113,15 @@ class BspStateManager(
115113
state.getOrElse(uri, throw new IllegalStateException("Get should always be called after didOpen"))
116114
}
117115

118-
def didOpen(in: Invocation[DidOpenTextDocumentParams, IO]): IO[Unit] = {
119-
val uri = in.params.textDocument.uri.asNio
116+
def didOpen(client: SlsLanguageClient[IO], params: lsp.DidOpenTextDocumentParams): IO[Unit] = {
117+
val uri = URI.create(params.textDocument.uri)
120118
sourcesToTargets.evalUpdate(state =>
121119
for {
122120
possibleIds <- buildTargetInverseSources(uri)
123121
targets0 <- targets.get
124122
possibleBuildTargets = possibleIds.flatMap(id => targets0.find(_.buildTarget.id == id))
125123
bestBuildTarget = possibleBuildTargets.maxBy(_.buildTarget.project.scala.map(_.data.scalaVersion))
126-
_ <- in.toClient.logDebug(s"Best build target for $uri is ${bestBuildTarget.toString}")
124+
_ <- client.logDebug(s"Best build target for $uri is ${bestBuildTarget.toString}")
127125
} yield state.updated(uri, bestBuildTarget)
128126
)
129127
}
Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
package org.scala.abusers.sls
22

3-
import scala.concurrent.duration.FiniteDuration
4-
import cats.effect.Temporal
5-
import cats.effect.Ref
63
import cats.effect.kernel.Fiber
7-
import cats.effect.kernel.Sync
84
import cats.effect.IO
5+
import cats.effect.Ref
6+
import cats.effect.Temporal
97
import cats.syntax.all.*
108

9+
import scala.concurrent.duration.FiniteDuration
1110

1211
class Debouncer(delay: FiniteDuration) {
1312

@@ -20,7 +19,7 @@ class Debouncer(delay: FiniteDuration) {
2019
fiber <- Temporal[IO].start(
2120
IO.sleep(delay) *> run
2221
)
23-
_ <- ref.set(Some(fiber))
22+
_ <- ref.set(Some(fiber))
2423
} yield ()
2524

2625
}

sls/src/org/scala/abusers/sls/DiagnosticManager.scala

Lines changed: 22 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,65 +5,59 @@ import bsp.PublishDiagnosticsParams as BspPublishDiagnosticsParams
55
import cats.effect.std.MapRef
66
import cats.effect.IO
77
import cats.syntax.all.*
8-
import langoustine.lsp
9-
import langoustine.lsp.requests.textDocument.publishDiagnostics
10-
import langoustine.lsp.runtime.*
11-
import langoustine.lsp.structures.Diagnostic as LspDiagnostic
12-
import langoustine.lsp.structures.DidChangeTextDocumentParams
13-
import langoustine.lsp.structures.PublishDiagnosticsParams as LspPublishDiagnosticsParams
14-
import langoustine.lsp.Communicate
15-
import langoustine.lsp.Invocation
16-
import org.scala.abusers.sls.NioConverter.*
178
import smithy4s.json.Json
189

19-
import java.net.URI
20-
2110
/** Diagnostic State Manager
2211
*
2312
* This class is reposnobile for holding the state of the diagnostic displayed on the client
2413
*
2514
* Proposed heuristic is: On file save we trigger compilation, and this results in notifications being sent from BSP
26-
* server. We will keep adding diagnostics, and will clean them only when [[PublishDiagnosticsParams.reset]] is
27-
* set to true.
15+
* server. We will keep adding diagnostics, and will clean them only when [[PublishDiagnosticsParams.reset]] is set to
16+
* true.
2817
*
2918
* @param publishedDiagnostics
3019
*/
31-
class DiagnosticManager(publishedDiagnostics: MapRef[IO, URI, Option[Set[LspDiagnostic]]]) {
32-
private def convertDiagnostic(bspDiag: BspDiagnostic): LspDiagnostic = {
20+
// FIXME revert this to URI
21+
class DiagnosticManager(publishedDiagnostics: MapRef[IO, String, Option[Set[lsp.Diagnostic]]]) {
22+
private def convertDiagnostic(bspDiag: BspDiagnostic): lsp.Diagnostic = {
3323
val data = Json.writeBlob(bspDiag)
34-
upickle.default.read[LspDiagnostic](data.asByteBuffer)
24+
// to be chimneyed
25+
Json.read[lsp.Diagnostic](data).toOption.getOrElse(sys.error(s"Failed to convert BSP diagnostic to LSP: $data"))
3526
}
3627

37-
def didChange(in: Invocation[DidChangeTextDocumentParams, IO], pcDiags: Vector[LspDiagnostic]): IO[Unit] =
28+
def didChange(client: SlsLanguageClient[IO], uri: String, pcDiags: List[lsp.Diagnostic]): IO[Unit] =
3829
// remove diagnostic on modified lines
3930
// ask presentation compiler for diagnostics
4031
for {
41-
_ <- publishedDiagnostics(in.params.textDocument.uri.asNio).set(pcDiags.toSet.some)
42-
request = LspPublishDiagnosticsParams(in.params.textDocument.uri, Opt.empty, pcDiags.toVector)
43-
_ <- in.toClient.notification(publishDiagnostics(request))
32+
_ <- publishedDiagnostics(uri).set(pcDiags.toSet.some)
33+
request = lsp.PublishDiagnosticsParams(uri, pcDiags, None)
34+
_ <- client.textDocumentPublishDiagnostics(request)
4435
} yield ()
4536

46-
def onBuildPublishDiagnostics(lspClient: Communicate[IO], input: BspPublishDiagnosticsParams): IO[Unit] = {
37+
def onBuildPublishDiagnostics(client: SlsLanguageClient[IO], input: BspPublishDiagnosticsParams): IO[Unit] = {
4738
val bspUri = input.textDocument.uri
4839
val lspDiags = input.diagnostics.toSet.map(convertDiagnostic)
49-
def request(diags: Set[LspDiagnostic]) =
50-
LspPublishDiagnosticsParams(DocumentUri(bspUri.value), Opt.empty, diags.toVector)
40+
def request(diags: Set[lsp.Diagnostic]) =
41+
lsp.PublishDiagnosticsParams(bspUri.value, diags.toList, None)
42+
5143
if input.reset then {
5244
for {
53-
_ <- publishedDiagnostics(input.textDocument.uri.asNio).set(lspDiags.some)
54-
_ <- lspClient.notification(publishDiagnostics(request(lspDiags)))
45+
_ <- publishedDiagnostics(input.textDocument.uri.value).set(lspDiags.some)
46+
_ <- client.textDocumentPublishDiagnostics(
47+
lsp.PublishDiagnosticsParams(input.textDocument.uri.value, lspDiags.toList)
48+
)
5549
} yield ()
5650

5751
} else {
5852
for {
59-
currentDiags <- publishedDiagnostics(bspUri.asNio).updateAndGet(_.foldLeft(lspDiags)(_ ++ _).some)
60-
_ <- lspClient.notification(publishDiagnostics(request(currentDiags.get)))
53+
currentDiags <- publishedDiagnostics(bspUri.value).updateAndGet(_.foldLeft(lspDiags)(_ ++ _).some)
54+
_ <- client.textDocumentPublishDiagnostics(request(currentDiags.get))
6155
} yield ()
6256
}
6357
}
6458
}
6559

6660
object DiagnosticManager {
6761
def instance: IO[DiagnosticManager] =
68-
MapRef.ofScalaConcurrentTrieMap[IO, URI, Set[LspDiagnostic]].map(DiagnosticManager.apply)
62+
MapRef.ofScalaConcurrentTrieMap[IO, String, Set[lsp.Diagnostic]].map(DiagnosticManager.apply)
6963
}
Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,16 @@
11
package org.scala.abusers.sls
22

33
import cats.effect.IO
4-
import langoustine.lsp.*
5-
import langoustine.lsp.structures.LogMessageParams
6-
import langoustine.lsp.structures.ShowMessageParams
7-
import langoustine.lsp.Communicate
84

95
object LoggingUtils {
10-
extension (back: Communicate[IO]) {
6+
extension (client: SlsLanguageClient[IO]) {
117
def sendMessage(msg: String): IO[Unit] =
12-
back.notification(
13-
requests.window.showMessage,
14-
ShowMessageParams(enumerations.MessageType.Info, msg),
15-
) *> logMessage(msg)
8+
client.windowShowMessage(lsp.ShowMessageParams(lsp.MessageType.INFO, msg)) *> logMessage(msg)
169

17-
def logMessage(message: String): IO[Unit] =
18-
back.notification(
19-
requests.window.logMessage,
20-
LogMessageParams(enumerations.MessageType.Info, message),
21-
)
10+
def logMessage(msg: String): IO[Unit] =
11+
client.windowLogMessage(lsp.LogMessageParams(lsp.MessageType.INFO, msg))
2212

23-
def logDebug(message: String): IO[Unit] =
24-
back.notification(
25-
requests.window.logMessage,
26-
LogMessageParams(enumerations.MessageType.Debug, message),
27-
)
13+
def logDebug(msg: String): IO[Unit] =
14+
client.windowLogMessage(lsp.LogMessageParams(lsp.MessageType.LOG, msg))
2815
}
2916
}
Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
package org.scala.abusers.sls
22

3-
import langoustine.lsp.runtime.DocumentUri
4-
53
import java.net.URI
64
import scala.annotation.targetName
75

86
object NioConverter {
9-
extension (documentUri: DocumentUri) @targetName("lspAsNio") def asNio: URI = URI.create(documentUri.value)
10-
extension (uri: bsp.URI) @targetName("bspAsNio") def asNio: URI = URI.create(uri.value)
7+
extension (uri: bsp.URI) @targetName("bspAsNio") def asNio: URI = URI.create(uri.value)
118
}

0 commit comments

Comments
 (0)