Skip to content

Commit 0630b8f

Browse files
authored
New synchronisation proposal (#52)
* New synchronisation proposal * Fix test, apply review comment
1 parent 36b0409 commit 0630b8f

File tree

10 files changed

+133
-154
lines changed

10 files changed

+133
-154
lines changed

build.mill

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ trait CommonScalaModule extends ScalaModule with ScalafixModule {
2424
) ++ super.repositoriesTask()
2525
}
2626

27-
def scalaVersion = "3.7.2-RC1-bin-20250616-61d9887-NIGHTLY"
27+
def scalaVersion = "3.7.3-RC2"
2828
def scalacOptions = Seq("-no-indent", "-Wunused:all")
2929
}
3030

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

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,7 @@ def bspClientHandler(lspClient: SlsLanguageClient[IO], diagnosticManager: Diagno
4747
.serverEndpoints(
4848
new BuildClient[IO] {
4949

50-
private def notify(msg: String) =
51-
lspClient.windowShowMessage(
52-
lsp.ShowMessageParams(_type = lsp.MessageType.INFO, message = msg)
53-
)
50+
5451

5552
def onBuildLogMessage(input: LogMessageParams): IO[Unit] = IO.unit // we want some logging to file here
5653

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,12 +108,12 @@ class BspStateManager(
108108
*
109109
* We want to fail fast if this is not the case because it is a way bigger problem that we may hide
110110
*/
111-
def get(uri: URI): IO[ScalaBuildTargetInformation] =
111+
def get(uri: URI)(using SynchronizedState): IO[ScalaBuildTargetInformation] =
112112
sourcesToTargets.get.map { state =>
113113
state.getOrElse(uri, throw new IllegalStateException("Get should always be called after didOpen"))
114114
}
115115

116-
def didOpen(client: SlsLanguageClient[IO], params: lsp.DidOpenTextDocumentParams): IO[Unit] = {
116+
def didOpen(client: SlsLanguageClient[IO], params: lsp.DidOpenTextDocumentParams)(using SynchronizedState): IO[Unit] = {
117117
val uri = URI.create(params.textDocument.uri)
118118
sourcesToTargets.evalUpdate(state =>
119119
for {
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package org.scala.abusers.sls
2+
3+
import cats.effect.IO
4+
import cats.effect.std.Semaphore
5+
6+
sealed trait SynchronizedState
7+
8+
trait ComputationQueue {
9+
protected given SynchronizedState = new SynchronizedState {}
10+
def synchronously[A](computation: SynchronizedState ?=> IO[A]): IO[A]
11+
}
12+
13+
class ComputationQueueImpl(semaphore: Semaphore[IO]) extends ComputationQueue {
14+
def synchronously[A](computation: SynchronizedState ?=> IO[A]): IO[A] = {
15+
semaphore.permit.use(_ => computation(using new SynchronizedState {}))
16+
}
17+
}
18+
19+
object ComputationQueue {
20+
def instance: IO[ComputationQueue] =
21+
Semaphore[IO](1).map(ComputationQueueImpl.apply)
22+
}

sls/src/org/scala/abusers/sls/ServerImpl.scala

Lines changed: 90 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,15 @@ import LoggingUtils.*
3535
import ScalaBuildTargetInformation.*
3636

3737
class ServerImpl(
38-
stateManager: StateManager,
3938
pcProvider: PresentationCompilerProvider,
4039
cancelTokens: IOCancelTokens,
4140
diagnosticManager: DiagnosticManager,
4241
steward: ResourceSupervisor[IO],
4342
bspClientDeferred: Deferred[IO, BuildServer],
44-
lspClient: SlsLanguageClient[IO]
43+
lspClient: SlsLanguageClient[IO],
44+
computationQueue: ComputationQueue,
45+
textDocumentSyncManager: TextDocumentSyncManager,
46+
bspStateManager: BspStateManager,
4547
) extends SlsLanguageServer[IO] {
4648

4749
/* There can only be one client for one language-server */
@@ -76,57 +78,61 @@ class ServerImpl(
7678
)).guaranteeCase(s => lspClient.logMessage(s"closing initalize with $s"))
7779
}
7880

79-
def initialized(params: lsp.InitializedParams): IO[Unit] = stateManager.importBuild
80-
def textDocumentCompletionOp(params: lsp.CompletionParams): IO[lsp.TextDocumentCompletionOpOutput] = handleCompletion(
81-
params
82-
)
83-
def textDocumentDefinitionOp(params: lsp.DefinitionParams): IO[lsp.TextDocumentDefinitionOpOutput] = handleDefinition(
84-
params
85-
)
81+
def initialized(params: lsp.InitializedParams): IO[Unit] = computationQueue.synchronously {
82+
bspStateManager.importBuild
83+
}
84+
85+
def textDocumentCompletionOp(params: lsp.CompletionParams): IO[lsp.TextDocumentCompletionOpOutput] = handleCompletion(params)
86+
def textDocumentDefinitionOp(params: lsp.DefinitionParams): IO[lsp.TextDocumentDefinitionOpOutput] = handleDefinition(params)
8687
def textDocumentDidChange(params: lsp.DidChangeTextDocumentParams): IO[Unit] = handleDidChange(params)
8788
def textDocumentDidClose(params: lsp.DidCloseTextDocumentParams): IO[Unit] = handleDidClose(params)
8889
def textDocumentDidOpen(params: lsp.DidOpenTextDocumentParams): IO[Unit] = handleDidOpen(params)
8990
def textDocumentDidSave(params: lsp.DidSaveTextDocumentParams): IO[Unit] = handleDidSave(params)
9091
def textDocumentHoverOp(params: lsp.HoverParams): IO[lsp.TextDocumentHoverOpOutput] = handleHover(params)
91-
def textDocumentInlayHintOp(params: lsp.InlayHintParams): IO[lsp.TextDocumentInlayHintOpOutput] = handleInlayHints(
92-
params
93-
)
94-
def textDocumentSignatureHelpOp(params: lsp.SignatureHelpParams): IO[lsp.TextDocumentSignatureHelpOpOutput] =
95-
handleSignatureHelp(params)
92+
def textDocumentInlayHintOp(params: lsp.InlayHintParams): IO[lsp.TextDocumentInlayHintOpOutput] = handleInlayHints(params)
93+
def textDocumentSignatureHelpOp(params: lsp.SignatureHelpParams): IO[lsp.TextDocumentSignatureHelpOpOutput] = handleSignatureHelp(params)
9694

9795
// // TODO: goto type definition with container types
9896
def handleCompletion(params: lsp.CompletionParams) =
99-
offsetParamsRequest(params)(_.complete).map { result =>
100-
lsp.TextDocumentCompletionOpOutput(
101-
convert[lsp4j.CompletionList, lsp.ListCompletionUnion](result).some
102-
)
97+
computationQueue.synchronously {
98+
offsetParamsRequest(params)(_.complete).map { result =>
99+
lsp.TextDocumentCompletionOpOutput(
100+
convert[lsp4j.CompletionList, lsp.ListCompletionUnion](result).some
101+
)
102+
}
103103
}
104104

105105
def handleHover(params: lsp.HoverParams) =
106-
offsetParamsRequest(params)(_.hover).map { result =>
107-
lsp.TextDocumentHoverOpOutput(
108-
result.toScala.map(hoverSig => convert[lsp4j.Hover, lsp.Hover](hoverSig.toLsp()))
109-
)
106+
computationQueue.synchronously {
107+
offsetParamsRequest(params)(_.hover).map { result =>
108+
lsp.TextDocumentHoverOpOutput(
109+
result.toScala.map(hoverSig => convert[lsp4j.Hover, lsp.Hover](hoverSig.toLsp()))
110+
)
111+
}
110112
}
111113

112114
def handleSignatureHelp(params: lsp.SignatureHelpParams) =
113-
offsetParamsRequest(params)(_.signatureHelp).map { result =>
114-
lsp.TextDocumentSignatureHelpOpOutput(
115-
convert[lsp4j.SignatureHelp, lsp.SignatureHelp](result).some
116-
)
115+
computationQueue.synchronously {
116+
offsetParamsRequest(params)(_.signatureHelp).map { result =>
117+
lsp.TextDocumentSignatureHelpOpOutput(
118+
convert[lsp4j.SignatureHelp, lsp.SignatureHelp](result).some
119+
)
120+
}
117121
}
118122

119123
def handleDefinition(params: lsp.DefinitionParams) =
120-
offsetParamsRequest(params)(_.definition).map { result =>
121-
lsp.TextDocumentDefinitionOpOutput(
122-
result
123-
.locations()
124-
.asScala
125-
.headOption
126-
.map(definition =>
127-
convert[lsp4j.Location, lsp.DefinitionOrListOfDefinitionLink](definition)
128-
) // FIXME: missing completion on lsp.TextDocumentDefinitionOpOutput
129-
)
124+
computationQueue.synchronously {
125+
offsetParamsRequest(params)(_.definition).map { result =>
126+
lsp.TextDocumentDefinitionOpOutput(
127+
result
128+
.locations()
129+
.asScala
130+
.headOption
131+
.map(definition =>
132+
convert[lsp4j.Location, lsp.DefinitionOrListOfDefinitionLink](definition)
133+
) // FIXME: missing completion on lsp.TextDocumentDefinitionOpOutput
134+
)
135+
}
130136
}
131137

132138
def virtualFileParams(uri0: URI, content: String, token0: CancelToken): VirtualFileParams = new VirtualFileParams {
@@ -137,12 +143,12 @@ class ServerImpl(
137143

138144
given Schema[List[lsp.InlayHint]] = Schema.list(lsp.InlayHint.schema)
139145

140-
def handleInlayHints(params: lsp.InlayHintParams) = {
146+
def handleInlayHints(params: lsp.InlayHintParams) = computationQueue.synchronously {
141147
val uri0 = summon[WithURI[lsp.InlayHintParams]].uri(params)
142148

143149
cancelTokens.mkCancelToken.use { token0 =>
144150
for {
145-
docState <- stateManager.getDocumentState(uri0)
151+
docState <- textDocumentSyncManager.get(uri0)
146152
inalyHintsParams = new InlayHintsParams {
147153
import docState.*
148154
def implicitConversions(): Boolean = true
@@ -166,12 +172,12 @@ class ServerImpl(
166172

167173
private def offsetParamsRequest[Params: PositionWithURI, Result](params: Params)(
168174
thunk: PresentationCompiler => OffsetParams => CompletableFuture[Result]
169-
): IO[Result] = { // TODO Completion on context bound inserts []
175+
)(using SynchronizedState): IO[Result] = { // TODO Completion on context bound inserts []
170176
val uri = summon[WithURI[Params]].uri(params)
171177
val position = summon[WithPosition[Params]].position(params)
172178
cancelTokens.mkCancelToken.use { token =>
173179
for {
174-
docState <- stateManager.getDocumentState(uri)
180+
docState <- textDocumentSyncManager.get(uri)
175181
offsetParams = toOffsetParams(position, docState, token)
176182
result <- pcParamsRequest(params, offsetParams)(thunk)
177183
} yield result
@@ -180,20 +186,34 @@ class ServerImpl(
180186

181187
private def pcParamsRequest[Params: WithURI, Result, PcParams](params: Params, pcParams: PcParams)(
182188
thunk: PresentationCompiler => PcParams => CompletableFuture[Result]
183-
): IO[Result] = { // TODO Completion on context bound inserts []
189+
)(using SynchronizedState): IO[Result] = { // TODO Completion on context bound inserts []
184190
val uri = summon[WithURI[Params]].uri(params)
185191
for {
186-
info <- stateManager.getBuildTargetInformation(uri)
192+
info <- bspStateManager.get(uri)
187193
pc <- pcProvider.get(info)
188194
result <- IO.fromCompletableFuture(IO(thunk(pc)(pcParams)))
189195
} yield result
190196
}
191197

192-
def handleDidSave(params: lsp.DidSaveTextDocumentParams) = stateManager.didSave(params)
193-
194-
def handleDidOpen(params: lsp.DidOpenTextDocumentParams) = stateManager.didOpen(params)
198+
def handleDidSave(params: lsp.DidSaveTextDocumentParams) =
199+
computationQueue.synchronously {
200+
for {
201+
_ <- textDocumentSyncManager.didSave(params)
202+
info <- bspStateManager.get(URI(params.textDocument.uri))
203+
} yield info
204+
}.flatMap { info =>
205+
bspStateManager.bspServer.generic.buildTargetCompile(bsp.CompileParams(targets = List(info.buildTarget.id))) // we need to handle the case:
206+
// User saves, compilation starts, we don't want to block it, completion starts, during completion compilation ends new classfiles emmited. We need to know which classfiles are new.
207+
// I hardly believe that we should use straight to jar compilation for that
208+
}.void
209+
210+
def handleDidOpen(params: lsp.DidOpenTextDocumentParams) = computationQueue.synchronously {
211+
textDocumentSyncManager.didOpen(params) *> bspStateManager.didOpen(lspClient, params)
212+
}
195213

196-
def handleDidClose(params: lsp.DidCloseTextDocumentParams) = stateManager.didClose(params)
214+
def handleDidClose(params: lsp.DidCloseTextDocumentParams) = computationQueue.synchronously {
215+
textDocumentSyncManager.didClose(params)
216+
}
197217

198218
implicit val positionTransformer: Transformer[lsp4j.Position, lsp.Position] =
199219
Transformer.define[lsp4j.Position, lsp.Position].enableBeanGetters.buildTransformer
@@ -213,31 +233,35 @@ class ServerImpl(
213233
* they are triggered by notification and AFAIK, LSP cancellation only works for requests.
214234
*/
215235
def pcDiagnostics(info: ScalaBuildTargetInformation, uri: URI): IO[Unit] =
216-
cancelTokens.mkCancelToken.use { token =>
217-
for {
218-
_ <- lspClient.logDebug("Getting PresentationCompiler diagnostics")
219-
textDocument <- stateManager.getDocumentState(uri)
220-
pc <- pcProvider.get(info)
221-
params = virtualFileParams(uri, textDocument.content, token)
222-
diags <- IO.fromCompletableFuture(IO(pc.didChange(params)))
223-
lspDiags = diags
224-
.into[List[lsp.Diagnostic]]
225-
.withFieldRenamed(_.everyItem.getRange, _.everyItem.range)
226-
.withFieldRenamed(_.everyItem.getMessage, _.everyItem.message)
227-
.enableOptionDefaultsToNone
228-
.transform
229-
_ <- diagnosticManager.didChange(lspClient, uri.toString, lspDiags)
230-
} yield ()
236+
computationQueue.synchronously {
237+
cancelTokens.mkCancelToken.use { token =>
238+
for {
239+
_ <- lspClient.logDebug("Getting PresentationCompiler diagnostics")
240+
textDocument <- textDocumentSyncManager.get(uri)
241+
pc <- pcProvider.get(info)
242+
params = virtualFileParams(uri, textDocument.content, token)
243+
diags <- IO.fromCompletableFuture(IO(pc.didChange(params)))
244+
lspDiags = diags
245+
.into[List[lsp.Diagnostic]]
246+
.withFieldRenamed(_.everyItem.getRange, _.everyItem.range)
247+
.withFieldRenamed(_.everyItem.getMessage, _.everyItem.message)
248+
.enableOptionDefaultsToNone
249+
.transform
250+
_ <- diagnosticManager.didChange(lspClient, uri.toString, lspDiags)
251+
} yield ()
252+
}
231253
}
232254

233-
params =>
255+
params => computationQueue.synchronously {
234256
for {
235-
_ <- stateManager.didChange(params)
257+
_ <- textDocumentSyncManager.didChange(params)
236258
_ <- lspClient.logDebug("Updated DocumentState")
237259
uri = URI(params.textDocument.uri)
238-
info <- stateManager.getBuildTargetInformation(uri)
239-
_ <- if isSupported(info) then debounce.debounce(pcDiagnostics(info, uri)) else IO.unit
240-
} yield ()
260+
info <- bspStateManager.get(uri)
261+
} yield (uri, info)
262+
}.flatMap { (uri, info) =>
263+
if isSupported(info) then debounce.debounce(pcDiagnostics(info, uri)) else IO.unit
264+
}
241265
}
242266

243267
private def serverCapabilities: lsp.ServerCapabilities =

sls/src/org/scala/abusers/sls/SimpleLanguageServer.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ object SimpleScalaServer extends IOApp.Simple {
6666
textDocumentSync <- TextDocumentSyncManager.instance.toResource
6767
bspClientDeferred <- Deferred[IO, BuildServer].toResource
6868
bspStateManager <- BspStateManager.instance(lspClient, BuildServer.suspend(bspClientDeferred.get)).toResource
69-
stateManager <- StateManager.instance(lspClient, textDocumentSync, bspStateManager).toResource
7069
cancelTokens <- IOCancelTokens.instance
7170
diagnosticManager <- DiagnosticManager.instance.toResource
72-
} yield ServerImpl(stateManager, pcProvider, cancelTokens, diagnosticManager, steward, bspClientDeferred, lspClient)
71+
computationQueue <- ComputationQueue.instance.toResource
72+
} yield ServerImpl(pcProvider, cancelTokens, diagnosticManager, steward, bspClientDeferred, lspClient, computationQueue, textDocumentSync, bspStateManager)
7373
}

sls/src/org/scala/abusers/sls/StateManager.scala

Lines changed: 0 additions & 73 deletions
This file was deleted.

0 commit comments

Comments
 (0)