Skip to content

Commit 92483a2

Browse files
authored
Merge pull request #46 from simple-scala-tooling/diags
Diagnostics from PC and from build server
2 parents 524d7b1 + 5c2fa00 commit 92483a2

16 files changed

+418
-174
lines changed

build.mill

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ object sls extends CommonScalaModule {
2828
ivy"com.github.plokhotnyuk.jsoniter-scala:jsoniter-scala-core_2.13:2.35.2".forceVersion(),
2929
ivy"co.fs2::fs2-io:3.13.0-M2",
3030
ivy"tech.neander::jsonrpclib-fs2::0.0.8+26-13de833b-SNAPSHOT".forceVersion(),
31+
ivy"ch.qos.logback:logback-classic:1.4.14",
3132
ivy"tech.neander::langoustine-app::0.0.22",
3233
ivy"com.lihaoyi::os-lib:0.11.4",
3334
ivy"org.polyvariant.smithy4s-bsp::bsp4s:0.4.1",

sls/resources/logback.xml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<configuration>
2+
3+
<!-- Appender for dotty.tools.pc -->
4+
<appender name="PC_LOG" class="ch.qos.logback.core.FileAppender">
5+
<file>./.logs/pc.log</file>
6+
<append>false</append>
7+
<encoder>
8+
<pattern>%date %-5level [%thread] %logger{36} - %msg%n</pattern>
9+
</encoder>
10+
</appender>
11+
12+
<!-- Appender for org.scala.abusers.pc -->
13+
<appender name="SLS_LOG" class="ch.qos.logback.core.FileAppender">
14+
<file>./.logs/sls.log</file>
15+
<append>false</append>
16+
<encoder>
17+
<pattern>%date %-5level [%thread] %logger{36} - %msg%n</pattern>
18+
</encoder>
19+
</appender>
20+
21+
<!-- Logger for dotty.tools.pc -->
22+
<logger name="dotty.tools.pc" level="DEBUG" additivity="false">
23+
<appender-ref ref="PC_LOG"/>
24+
</logger>
25+
26+
<!-- Logger for org.scala.abusers.pc -->
27+
<logger name="org.scala.abusers" level="DEBUG" additivity="false">
28+
<appender-ref ref="SLS_LOG"/>
29+
</logger>
30+
31+
<root level="INFO">
32+
</root>
33+
34+
</configuration>

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

Lines changed: 22 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -44,51 +44,35 @@ def makeBspClient(path: String, channel: FS2Channel[IO], report: String => IO[Un
4444
)
4545
)
4646

47-
def bspClientHandler(lspClient: Communicate[IO]): List[Endpoint[IO]] = BSPCodecs.serverEndpoints(
48-
new BuildClient[IO] {
49-
private def notify(msg: String) =
50-
lspClient.notification(
51-
window.showMessage(
52-
langoustine.lsp.structures
53-
.ShowMessageParams(`type` = langoustine.lsp.enumerations.MessageType.Info, message = msg)
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)
55+
)
5456
)
55-
)
5657

57-
def onBuildLogMessage(input: LogMessageParams): IO[Unit] = notify(
58-
s"handling onBuildLogMessage: $input"
59-
)
58+
def onBuildLogMessage(input: LogMessageParams): IO[Unit] = IO.unit
6059

61-
def onBuildPublishDiagnostics(input: PublishDiagnosticsParams): IO[Unit] = notify(
62-
s"handling onBuildPublishDiagnostics: $input"
63-
)
60+
def onBuildPublishDiagnostics(input: PublishDiagnosticsParams): IO[Unit] =
61+
notify(s"We've just got $input") >>
62+
diagnosticManager.onBuildPublishDiagnostics(lspClient, input)
6463

65-
def onBuildShowMessage(input: ShowMessageParams): IO[Unit] =
66-
notify(
67-
s"handling onBuildShowMessage: $input"
68-
)
64+
def onBuildShowMessage(input: ShowMessageParams): IO[Unit] = IO.unit
6965

70-
def onBuildTargetDidChange(input: DidChangeBuildTarget): IO[Unit] = notify(
71-
s"handling onBuildTargetDidChange: $input"
72-
)
66+
def onBuildTargetDidChange(input: DidChangeBuildTarget): IO[Unit] = IO.unit
7367

74-
def onBuildTaskFinish(input: OnBuildTaskFinishInput): IO[Unit] = notify(
75-
s"handling onBuildTaskFinish: $input"
76-
)
68+
def onBuildTaskFinish(input: OnBuildTaskFinishInput): IO[Unit] = IO.unit
7769

78-
def onBuildTaskProgress(input: TaskProgressParams): IO[Unit] = notify(
79-
s"handling onBuildTaskProgress: $input"
80-
)
70+
def onBuildTaskProgress(input: TaskProgressParams): IO[Unit] = IO.unit
8171

82-
def onBuildTaskStart(input: OnBuildTaskStartInput): IO[Unit] = notify(
83-
s"handling onBuildTaskStart: $input"
84-
)
72+
def onBuildTaskStart(input: OnBuildTaskStartInput): IO[Unit] = IO.unit
8573

86-
def onRunPrintStderr(input: PrintParams): IO[Unit] = notify(
87-
s"handling onRunPrintStderr: $input"
88-
)
74+
def onRunPrintStderr(input: PrintParams): IO[Unit] = IO.unit
8975

90-
def onRunPrintStdout(input: PrintParams): IO[Unit] = notify(
91-
s"handling onRunPrintStdout: $input"
92-
)
93-
}
94-
)
76+
def onRunPrintStdout(input: PrintParams): IO[Unit] = IO.unit
77+
}
78+
)

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

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ import bsp.BuildTarget.BuildTargetScalaBuildTarget
66
import bsp.CompileParams
77
import bsp.InverseSourcesParams
88
import cats.effect.kernel.Ref
9-
import cats.effect.std.MapRef
9+
import cats.effect.std.AtomicCell
1010
import cats.effect.IO
1111
import langoustine.lsp.*
1212
import langoustine.lsp.structures.*
1313
import org.scala.abusers.pc.ScalaVersion
14-
import org.scala.abusers.sls.LspNioConverter.asNio
14+
import org.scala.abusers.sls.NioConverter.asNio
15+
import org.scala.abusers.sls.LoggingUtils.*
1516

1617
import java.net.URI
1718

@@ -34,7 +35,7 @@ object BspStateManager {
3435
def instance(bspServer: BuildServer): IO[BspStateManager] =
3536
// We should track this in progress bar. Think of this as `Import Build`
3637
for {
37-
sourcesToTargets <- MapRef.ofScalaConcurrentTrieMap[IO, URI, ScalaBuildTargetInformation]
38+
sourcesToTargets <- AtomicCell[IO].of(Map[URI, ScalaBuildTargetInformation]())
3839
buildTargets <- Ref.of[IO, Set[ScalaBuildTargetInformation]](Set.empty)
3940
} yield BspStateManager(bspServer, sourcesToTargets, buildTargets)
4041
}
@@ -47,24 +48,26 @@ object BspStateManager {
4748
*/
4849
class BspStateManager(
4950
val bspServer: BuildServer,
50-
sourcesToTargets: MapRef[IO, URI, Option[ScalaBuildTargetInformation]],
51+
sourcesToTargets: AtomicCell[IO, Map[URI, ScalaBuildTargetInformation]],
5152
targets: Ref[IO, Set[ScalaBuildTargetInformation]],
5253
) {
5354
import ScalaBuildTargetInformation.*
5455

55-
def importBuild =
56+
def importBuild(back: Communicate[IO]) =
5657
for {
58+
_ <- back.logMessage("Starting build import.") // in the future this should be a task with progress
5759
importedBuild <- getBuildInformation(bspServer)
5860
_ <- bspServer.generic.buildTargetCompile(CompileParams(targets = importedBuild.map(_.buildTarget.id).toList))
5961
_ <- targets.set(importedBuild)
62+
_ <- back.logMessage("Build import finished.")
6063
} yield ()
6164

62-
val byScalaVersion: Ordering[ScalaBuildTargetInformation] = new Ordering[ScalaBuildTargetInformation] {
65+
private val byScalaVersion: Ordering[ScalaBuildTargetInformation] = new Ordering[ScalaBuildTargetInformation] {
6366
override def compare(x: ScalaBuildTargetInformation, y: ScalaBuildTargetInformation): Int =
6467
Ordering[ScalaVersion].compare(x.scalaVersion, y.scalaVersion)
6568
}
6669

67-
def getBuildInformation(bspServer: BuildServer): IO[Set[ScalaBuildTargetInformation]] =
70+
private def getBuildInformation(bspServer: BuildServer): IO[Set[ScalaBuildTargetInformation]] =
6871
for {
6972
workspaceBuildTargets <- bspServer.generic.workspaceBuildTargets()
7073
scalacOptions <- bspServer.scala.buildTargetScalacOptions(
@@ -75,7 +78,7 @@ class BspStateManager(
7578
.values
7679
.toSet
7780

78-
def buildTargetInverseSources(uri: URI): IO[List[bsp.BuildTargetIdentifier]] =
81+
private def buildTargetInverseSources(uri: URI): IO[List[bsp.BuildTargetIdentifier]] =
7982
for inverseSources <- bspServer.generic
8083
.buildTargetInverseSources(
8184
InverseSourcesParams(
@@ -108,19 +111,21 @@ class BspStateManager(
108111
* We want to fail fast if this is not the case because it is a way bigger problem that we may hide
109112
*/
110113
def get(uri: URI): IO[ScalaBuildTargetInformation] =
111-
sourcesToTargets(uri).get.map(
112-
_.getOrElse(throw new IllegalStateException("Get should always be called after didOpen"))
113-
)
114+
sourcesToTargets.get.map { state =>
115+
state.getOrElse(uri, throw new IllegalStateException("Get should always be called after didOpen"))
116+
}
114117

115118
def didOpen(in: Invocation[DidOpenTextDocumentParams, IO]): IO[Unit] = {
116119
val uri = in.params.textDocument.uri.asNio
117-
for {
118-
possibleIds <- buildTargetInverseSources(uri)
119-
targets0 <- targets.get
120-
possibleBuildTargets = possibleIds.flatMap(id => targets0.find(_.buildTarget.id == id))
121-
bestBuildTarget = possibleBuildTargets.maxBy(_.buildTarget.project.scala.map(_.data.scalaVersion))
122-
_ <- sourcesToTargets(uri).set(Some(bestBuildTarget)) // lets assume we will always update it
123-
} yield ()
120+
sourcesToTargets.evalUpdate(state =>
121+
for {
122+
possibleIds <- buildTargetInverseSources(uri)
123+
targets0 <- targets.get
124+
possibleBuildTargets = possibleIds.flatMap(id => targets0.find(_.buildTarget.id == id))
125+
bestBuildTarget = possibleBuildTargets.maxBy(_.buildTarget.project.scala.map(_.data.scalaVersion))
126+
_ <- in.toClient.logDebug(s"Best build target for $uri is ${bestBuildTarget.toString}")
127+
} yield state.updated(uri, bestBuildTarget)
128+
)
124129
}
125130
}
126131

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package org.scala.abusers.sls
2+
3+
import scala.concurrent.duration.FiniteDuration
4+
import cats.effect.Temporal
5+
import cats.effect.Ref
6+
import cats.effect.kernel.Fiber
7+
import cats.effect.kernel.Sync
8+
import cats.effect.IO
9+
import cats.syntax.all.*
10+
11+
12+
class Debouncer(delay: FiniteDuration) {
13+
14+
private val ref = Ref.unsafe[IO, Option[Fiber[IO, Throwable, Unit]]](None)
15+
16+
def debounce(run: => IO[Unit]): IO[Unit] =
17+
for {
18+
fiberOpt <- ref.getAndSet(None)
19+
_ <- fiberOpt.traverse_(_.cancel)
20+
fiber <- Temporal[IO].start(
21+
IO.sleep(delay) *> run
22+
)
23+
_ <- ref.set(Some(fiber))
24+
} yield ()
25+
26+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package org.scala.abusers.sls
2+
3+
import bsp.Diagnostic as BspDiagnostic
4+
import bsp.PublishDiagnosticsParams as BspPublishDiagnosticsParams
5+
import cats.effect.std.MapRef
6+
import cats.effect.IO
7+
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.*
17+
import smithy4s.json.Json
18+
19+
import java.net.URI
20+
21+
/** Diagnostic State Manager
22+
*
23+
* This class is reposnobile for holding the state of the diagnostic displayed on the client
24+
*
25+
* 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.
28+
*
29+
* @param publishedDiagnostics
30+
*/
31+
class DiagnosticManager(publishedDiagnostics: MapRef[IO, URI, Option[Set[LspDiagnostic]]]) {
32+
private def convertDiagnostic(bspDiag: BspDiagnostic): LspDiagnostic = {
33+
val data = Json.writeBlob(bspDiag)
34+
upickle.default.read[LspDiagnostic](data.asByteBuffer)
35+
}
36+
37+
def didChange(in: Invocation[DidChangeTextDocumentParams, IO], pcDiags: Vector[LspDiagnostic]): IO[Unit] =
38+
// remove diagnostic on modified lines
39+
// ask presentation compiler for diagnostics
40+
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))
44+
} yield ()
45+
46+
def onBuildPublishDiagnostics(lspClient: Communicate[IO], input: BspPublishDiagnosticsParams): IO[Unit] = {
47+
val bspUri = input.textDocument.uri
48+
val lspDiags = input.diagnostics.toSet.map(convertDiagnostic)
49+
def request(diags: Set[LspDiagnostic]) =
50+
LspPublishDiagnosticsParams(DocumentUri(bspUri.value), Opt.empty, diags.toVector)
51+
if input.reset then {
52+
for {
53+
_ <- publishedDiagnostics(input.textDocument.uri.asNio).set(lspDiags.some)
54+
_ <- lspClient.notification(publishDiagnostics(request(lspDiags)))
55+
} yield ()
56+
57+
} else {
58+
for {
59+
currentDiags <- publishedDiagnostics(bspUri.asNio).updateAndGet(_.foldLeft(lspDiags)(_ ++ _).some)
60+
_ <- lspClient.notification(publishDiagnostics(request(currentDiags.get)))
61+
} yield ()
62+
}
63+
}
64+
}
65+
66+
object DiagnosticManager {
67+
def instance: IO[DiagnosticManager] =
68+
MapRef.ofScalaConcurrentTrieMap[IO, URI, Set[LspDiagnostic]].map(DiagnosticManager.apply)
69+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package org.scala.abusers.sls
2+
3+
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
8+
9+
object LoggingUtils {
10+
extension (back: Communicate[IO]) {
11+
def sendMessage(msg: String): IO[Unit] =
12+
back.notification(
13+
requests.window.showMessage,
14+
ShowMessageParams(enumerations.MessageType.Info, msg),
15+
) *> logMessage(msg)
16+
17+
def logMessage(message: String): IO[Unit] =
18+
back.notification(
19+
requests.window.logMessage,
20+
LogMessageParams(enumerations.MessageType.Info, message),
21+
)
22+
23+
def logDebug(message: String): IO[Unit] =
24+
back.notification(
25+
requests.window.logMessage,
26+
LogMessageParams(enumerations.MessageType.Debug, message),
27+
)
28+
}
29+
}

sls/src/org/scala/abusers/sls/LspNioConverter.scala

Lines changed: 0 additions & 9 deletions
This file was deleted.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package org.scala.abusers.sls
2+
3+
import langoustine.lsp.runtime.DocumentUri
4+
5+
import java.net.URI
6+
import scala.annotation.targetName
7+
8+
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)
11+
}

0 commit comments

Comments
 (0)