Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.mill
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ trait CommonScalaModule extends ScalaModule with ScalafixModule {
MavenRepository("https://s01.oss.sonatype.org/content/repositories/snapshots"),
MavenRepository("https://s01.oss.sonatype.org/content/repositories/releases"),
MavenRepository("https://evolution.jfrog.io/artifactory/public"),
MavenRepository("https://central.sonatype.com/repository/maven-snapshots/")
) ++ super.repositoriesTask()
}

Expand Down
145 changes: 87 additions & 58 deletions sls/src/org/scala/abusers/sls/ServerImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ import scala.meta.pc.VirtualFileParams

import LoggingUtils.*
import ScalaBuildTargetInformation.*
import scala.meta.pc.RawPresentationCompiler
import lsp.CompletionTriggerKind.TRIGGER_CHARACTER
import lsp.CompletionTriggerKind.TRIGGER_FOR_INCOMPLETE_COMPLETIONS

class ServerImpl(
pcProvider: PresentationCompilerProvider,
Expand All @@ -57,7 +60,7 @@ class ServerImpl(
val rootPath = os.Path(java.net.URI.create(rootUri).getPath())
(for {
_ <- lspClient.logMessage("Ready to initialise!")
_ <- importMillBsp(rootPath, lspClient)
_ <- importMillBsp(rootPath)
bspClient <- connectWithBloop(steward, diagnosticManager)
_ <- lspClient.logMessage("Connection with bloop estabilished")
response <- bspClient.generic.buildInitialize(
Expand Down Expand Up @@ -86,54 +89,85 @@ class ServerImpl(
computationQueue.synchronously(bspStateManager.importBuild)

def textDocumentCompletionOp(params: lsp.CompletionParams): IO[lsp.TextDocumentCompletionOpOutput] =
Tracer[IO].span("completion").profilingSurround(handleCompletion(params))
computationQueue.synchronously {
Tracer[IO].span("completion").profilingSurround(handleCompletion(params))
}
def textDocumentDefinitionOp(params: lsp.DefinitionParams): IO[lsp.TextDocumentDefinitionOpOutput] =
Tracer[IO].span("go-to-defintion").profilingSurround(handleDefinition(params))
computationQueue.synchronously {
Tracer[IO].span("go-to-defintion").profilingSurround(handleDefinition(params))
}
def textDocumentDidChange(params: lsp.DidChangeTextDocumentParams): IO[Unit] =
Tracer[IO].span("did-change").profilingSurround(handleDidChange(params))
computationQueue.synchronously {
Tracer[IO].span("did-change").profilingSurround(handleDidChange(params))
}
def textDocumentDidClose(params: lsp.DidCloseTextDocumentParams): IO[Unit] =
Tracer[IO].span("did-close").profilingSurround(handleDidClose(params))
computationQueue.synchronously {
Tracer[IO].span("did-close").profilingSurround(handleDidClose(params))
}
def textDocumentDidOpen(params: lsp.DidOpenTextDocumentParams): IO[Unit] =
Tracer[IO].span("did-open").profilingSurround(handleDidOpen(params))
computationQueue.synchronously {
Tracer[IO].span("did-open").profilingSurround(handleDidOpen(params))
}
def textDocumentDidSave(params: lsp.DidSaveTextDocumentParams): IO[Unit] =
Tracer[IO].span("did-save").profilingSurround(handleDidSave(params))
computationQueue.synchronously {
Tracer[IO].span("did-save").profilingSurround(handleDidSave(params))
}
def textDocumentHoverOp(params: lsp.HoverParams): IO[lsp.TextDocumentHoverOpOutput] =
Tracer[IO].span("hover").profilingSurround(handleHover(params))
computationQueue.synchronously {
Tracer[IO].span("hover").profilingSurround(handleHover(params))
}
def textDocumentInlayHintOp(params: lsp.InlayHintParams): IO[lsp.TextDocumentInlayHintOpOutput] =
Tracer[IO].span("inlay-hints").profilingSurround(handleInlayHints(params))
computationQueue.synchronously {
Tracer[IO].span("inlay-hints").profilingSurround(handleInlayHints(params))
}
def textDocumentSignatureHelpOp(params: lsp.SignatureHelpParams): IO[lsp.TextDocumentSignatureHelpOpOutput] =
Tracer[IO].span("signature-help").profilingSurround(handleSignatureHelp(params))
computationQueue.synchronously {
Tracer[IO].span("signature-help").profilingSurround(handleSignatureHelp(params))
}

def toPC(completionTriggerKind: Option[lsp.CompletionTriggerKind]): lsp4j.CompletionTriggerKind =
completionTriggerKind match {
case Some(lsp.CompletionTriggerKind.INVOKED) => lsp4j.CompletionTriggerKind.Invoked
case Some(lsp.CompletionTriggerKind.TRIGGER_CHARACTER) => lsp4j.CompletionTriggerKind.TriggerCharacter
case Some(lsp.CompletionTriggerKind.TRIGGER_FOR_INCOMPLETE_COMPLETIONS) => lsp4j.CompletionTriggerKind.TriggerForIncompleteCompletions
case None => lsp4j.CompletionTriggerKind.TriggerCharacter
}

// // TODO: goto type definition with container types
def handleCompletion(params: lsp.CompletionParams) =
computationQueue.synchronously {
offsetParamsRequest(params)(_.complete).map { result =>
lsp.TextDocumentCompletionOpOutput(
convert[lsp4j.CompletionList, lsp.ListCompletionUnion](result).some
)
}
def handleCompletion(params: lsp.CompletionParams)(using SynchronizedState) =
cancelTokens.mkCancelToken.use { token =>
val uri = summon[PositionWithURI[lsp.CompletionParams]].uri(params)
val position = summon[PositionWithURI[lsp.CompletionParams]].position(params)
for {
docState <- textDocumentSyncManager.get(uri)
offsetParams = toOffsetParams(position, docState, token)
triggerKind = toPC(params.context.map(_.triggerKind))
result <- pcParamsRequest(params, (offsetParams, triggerKind))(pc => params => pc.complete(params._1, params._2))
} yield lsp.TextDocumentCompletionOpOutput(
convert[lsp4j.CompletionList, lsp.ListCompletionUnion](result).some
)
}

def handleHover(params: lsp.HoverParams) =
computationQueue.synchronously {
def handleHover(params: lsp.HoverParams)(using SynchronizedState) =
{
offsetParamsRequest(params)(_.hover).map { result =>
lsp.TextDocumentHoverOpOutput(
result.toScala.map(hoverSig => convert[lsp4j.Hover, lsp.Hover](hoverSig.toLsp()))
)
}
}

def handleSignatureHelp(params: lsp.SignatureHelpParams) =
computationQueue.synchronously {
def handleSignatureHelp(params: lsp.SignatureHelpParams)(using SynchronizedState) =
{
offsetParamsRequest(params)(_.signatureHelp).map { result =>
lsp.TextDocumentSignatureHelpOpOutput(
convert[lsp4j.SignatureHelp, lsp.SignatureHelp](result).some
)
}
}

def handleDefinition(params: lsp.DefinitionParams) =
computationQueue.synchronously {
def handleDefinition(params: lsp.DefinitionParams)(using SynchronizedState) =
{
offsetParamsRequest(params)(_.definition).map { result =>
lsp.TextDocumentDefinitionOpOutput(
result
Expand All @@ -155,7 +189,7 @@ class ServerImpl(

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

def handleInlayHints(params: lsp.InlayHintParams) = computationQueue.synchronously {
def handleInlayHints(params: lsp.InlayHintParams)(using SynchronizedState) = {
val uri0 = summon[WithURI[lsp.InlayHintParams]].uri(params)

cancelTokens.mkCancelToken.use { token0 =>
Expand Down Expand Up @@ -183,7 +217,7 @@ class ServerImpl(
// def handleInlayHintsRefresh(in: Invocation[Unit, IO]) = IO.pure(null)

private def offsetParamsRequest[Params: PositionWithURI, Result](params: Params)(
thunk: PresentationCompiler => OffsetParams => CompletableFuture[Result]
thunk: RawPresentationCompiler => OffsetParams => Result
)(using SynchronizedState): IO[Result] = { // TODO Completion on context bound inserts []
val uri = summon[WithURI[Params]].uri(params)
val position = summon[WithPosition[Params]].position(params)
Expand All @@ -197,24 +231,23 @@ class ServerImpl(
}

private def pcParamsRequest[Params: WithURI, Result, PcParams](params: Params, pcParams: PcParams)(
thunk: PresentationCompiler => PcParams => CompletableFuture[Result]
thunk: RawPresentationCompiler => PcParams => Result
)(using SynchronizedState): IO[Result] = { // TODO Completion on context bound inserts []
val uri = summon[WithURI[Params]].uri(params)
for {
info <- bspStateManager.get(uri)
pc <- pcProvider.get(info)
result <- IO.fromCompletableFuture(IO(thunk(pc)(pcParams)))
result <- IO.interruptible(thunk(pc)(pcParams))
} yield result
}

def handleDidSave(params: lsp.DidSaveTextDocumentParams) =
computationQueue
.synchronously {
for {
_ <- textDocumentSyncManager.didSave(params)
info <- bspStateManager.get(URI(params.textDocument.uri))
} yield info
}
def handleDidSave(params: lsp.DidSaveTextDocumentParams)(using SynchronizedState) =
{
for {
_ <- textDocumentSyncManager.didSave(params)
info <- bspStateManager.get(URI(params.textDocument.uri))
} yield info
}
.flatMap { info =>
bspStateManager.bspServer.generic.buildTargetCompile(
bsp.CompileParams(targets = List(info.buildTarget.id))
Expand All @@ -224,11 +257,11 @@ class ServerImpl(
}
.void

def handleDidOpen(params: lsp.DidOpenTextDocumentParams) = computationQueue.synchronously {
textDocumentSyncManager.didOpen(params) *> bspStateManager.didOpen(lspClient, params)
def handleDidOpen(params: lsp.DidOpenTextDocumentParams)(using SynchronizedState) = {
lspClient.logMessage("DID OPEN") *> textDocumentSyncManager.didOpen(params) *> bspStateManager.didOpen(lspClient, params)
}

def handleDidClose(params: lsp.DidCloseTextDocumentParams) = computationQueue.synchronously {
def handleDidClose(params: lsp.DidCloseTextDocumentParams)(using SynchronizedState) = {
textDocumentSyncManager.didClose(params)
}

Expand All @@ -238,12 +271,12 @@ class ServerImpl(
implicit val rangeTransformer: Transformer[lsp4j.Range, lsp.Range] =
Transformer.define[lsp4j.Range, lsp.Range].enableBeanGetters.buildTransformer

val handleDidChange: lsp.DidChangeTextDocumentParams => IO[Unit] = {
val debounce = Debouncer(300.millis)
val handleDidChange: SynchronizedState ?=> lsp.DidChangeTextDocumentParams => IO[Unit] = {
val debounce = Debouncer(250.millis)

def isSupported(info: ScalaBuildTargetInformation): Boolean = {
import scala.math.Ordered.orderingToOrdered
info.scalaVersion > ScalaVersion("3.7.9")
info.scalaVersion > ScalaVersion("3.7.2")
}

/** We want to debounce compiler diagnostics as they are expensive to compute and we can't really cancel them as
Expand All @@ -253,11 +286,10 @@ class ServerImpl(
computationQueue.synchronously {
cancelTokens.mkCancelToken.use { token =>
for {
_ <- lspClient.logDebug("Getting PresentationCompiler diagnostics")
textDocument <- textDocumentSyncManager.get(uri)
pc <- pcProvider.get(info)
params = virtualFileParams(uri, textDocument.content, token)
diags <- IO.fromCompletableFuture(IO(pc.didChange(params)))
diags <- IO(pc.didChange(params))
lspDiags = diags
.into[List[lsp.Diagnostic]]
.withFieldRenamed(_.everyItem.getRange, _.everyItem.range)
Expand All @@ -269,19 +301,16 @@ class ServerImpl(
}
}

params =>
computationQueue
.synchronously {
for {
_ <- textDocumentSyncManager.didChange(params)
_ <- lspClient.logDebug("Updated DocumentState")
uri = URI(params.textDocument.uri)
info <- bspStateManager.get(uri)
} yield (uri, info)
}
.flatMap { (uri, info) =>
if isSupported(info) then debounce.debounce(pcDiagnostics(info, uri)) else IO.unit
}
params => {
for {
_ <- textDocumentSyncManager.didChange(params)
uri = URI(params.textDocument.uri)
info <- bspStateManager.get(uri)
} yield (uri, info)
}
.flatMap { (uri, info) =>
if isSupported(info) then debounce.debounce(pcDiagnostics(info, uri)) else IO.unit
}
}

private def serverCapabilities: lsp.ServerCapabilities =
Expand Down Expand Up @@ -349,7 +378,7 @@ class ServerImpl(
steward.acquire(bspClientRes)
}

def importMillBsp(rootPath: os.Path, client: SlsLanguageClient[IO]) = {
def importMillBsp(rootPath: os.Path) = {
val millExec = "./mill" // TODO if mising then findMillExec()
ProcessBuilder(millExec, "--import", "ivy:com.lihaoyi::mill-contrib-bloop:", "mill.contrib.bloop.Bloop/install")
.withWorkingDirectory(fs2.io.file.Path.fromNioPath(rootPath.toNIO))
Expand All @@ -364,7 +393,7 @@ class ServerImpl(
.through(text.lines)

allOutput
.evalMap(client.logMessage)
.evalMap(lspClient.logMessage)
.compile
.drain
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ import os.Path
import java.net.URLClassLoader
import scala.concurrent.duration.*
import scala.jdk.CollectionConverters.*
import scala.meta.pc.PresentationCompiler
import scala.meta.pc.RawPresentationCompiler

class PresentationCompilerProvider(
serviceLoader: BlockingServiceLoader,
compilers: SCache[IO, BuildTargetIdentifier, PresentationCompiler],
compilers: SCache[IO, BuildTargetIdentifier, RawPresentationCompiler],
) {
private val cache = Cache.create() // .withLogger TODO No completions here

Expand All @@ -31,7 +31,7 @@ class PresentationCompilerProvider(
.create()
.withCache(cache)
.addDependencies(dep)
.addRepositories( /* load from user config */ )
.addRepositories(MavenRepository.of("https://central.sonatype.com/repository/maven-snapshots/"))
.fetch()
.asScala
.map(os.Path.apply)
Expand All @@ -53,21 +53,21 @@ class PresentationCompilerProvider(
for {
compilerClasspath <- fetchPresentationCompilerJars(scalaVersion)
classloader <- freshPresentationCompilerClassloader(projectClasspath, compilerClasspath)
pc <- serviceLoader.load(classOf[PresentationCompiler], PresentationCompilerProvider.classname, classloader)
} yield pc.newInstance("random", projectClasspath.map(_.toNIO).asJava, scalacOptions.asJava)
pc <- serviceLoader.load(classOf[RawPresentationCompiler], PresentationCompilerProvider.classname, classloader)
} yield pc.newInstance("pc-id-replace", projectClasspath.map(_.toNIO).asJava, scalacOptions.asJava)

def get(info: ScalaBuildTargetInformation)(using SynchronizedState): IO[PresentationCompiler] =
def get(info: ScalaBuildTargetInformation)(using SynchronizedState): IO[RawPresentationCompiler] =
compilers.getOrUpdate(info.buildTarget.id)(createPC(info.scalaVersion, info.classpath, info.compilerOptions))
}

object PresentationCompilerProvider {
val classname = "dotty.tools.pc.ScalaPresentationCompiler"
val classname = "dotty.tools.pc.RawScalaPresentationCompiler"

def instance: IO[PresentationCompilerProvider] =
for {
serviceLoader <- BlockingServiceLoader.instance
pcProvider <- SCache
.expiring[IO, BuildTargetIdentifier, PresentationCompiler]( // we will need to move this out because other services will want to manage the state of the cache and invalidate when configuration changes also this shoul be ModuleFingerprint or something like that
.expiring[IO, BuildTargetIdentifier, RawPresentationCompiler]( // we will need to move this out because other services will want to manage the state of the cache and invalidate when configuration changes also this shoul be ModuleFingerprint or something like that
ExpiringCache.Config(expireAfterRead = 5.minutes),
None,
)
Expand Down