Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix --bsp #1146

Merged
merged 2 commits into from
Jan 18, 2021
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import coursierapi.{Dependency, Repository}
import org.eclipse.lsp4j.jsonrpc.Launcher

import scala.collection.JavaConverters._
import scala.concurrent.{ExecutionContext, Future}
import scala.concurrent.{ExecutionContext, Future, Promise}
import scala.util.{Failure, Success}
import scala.util.control.NonFatal

Expand Down Expand Up @@ -458,6 +458,15 @@ class AmmoniteBuildServer(
new CleanCacheResult("", true)
}

private val shutdownPromise = Promise[Unit]()
def buildShutdown(): CompletableFuture[Object] =
nonBlocking {
if (!shutdownPromise.isCompleted)
shutdownPromise.success(())
null
}

def initiateShutdown: Future[Unit] = shutdownPromise.future
}

object AmmoniteBuildServer {
Expand Down Expand Up @@ -537,12 +546,29 @@ object AmmoniteBuildServer {
.map(_.split('/').toSeq)
.toSet

private def naiveJavaFutureToScalaFuture[T](
f: java.util.concurrent.Future[T]
): Future[T] = {
val p = Promise[T]()
val t = new Thread {
setDaemon(true)
setName("ammonite-bsp-wait-for-exit")
override def run(): Unit =
p.complete {
try Success(f.get())
catch { case t: Throwable => Failure(t) }
}
}
t.start()
p.future
}

def start(
server: AmmoniteBuildServer,
input: InputStream = System.in,
output: OutputStream = System.out
): Launcher[BuildClient] = {
val ec = Executors.newFixedThreadPool(4) // FIXME Daemon threads
): (Launcher[BuildClient], Future[Unit]) = {
val ec = Executors.newFixedThreadPool(4, threadFactory("ammonite-bsp-jsonrpc"))
val launcher = new Launcher.Builder[BuildClient]()
.setExecutorService(ec)
.setInput(input)
Expand All @@ -552,7 +578,14 @@ object AmmoniteBuildServer {
.create()
val client = launcher.getRemoteProxy
server.onConnectWithClient(client)
launcher
}
val f = launcher.startListening()

val scalaEc = ExecutionContext.fromExecutorService(ec)
val futures = Seq(
naiveJavaFutureToScalaFuture(f).map(_ => ())(scalaEc),
server.initiateShutdown
)
val shutdownFuture = Future.firstCompletedOf(futures)(scalaEc)
(launcher, shutdownFuture)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ import scala.collection.JavaConverters._

private[script] trait DummyBuildServerImplems extends BuildServer with ScalaBuildServer {

def buildShutdown(): CompletableFuture[Object] =
CompletableFuture.completedFuture(null)

def buildTargetResources(params: ResourcesParams): CompletableFuture[ResourcesResult] = {
val items = params.getTargets.asScala.toList.map { target =>
new ResourcesItem(target, List.empty[String].asJava)
Expand Down
10 changes: 5 additions & 5 deletions amm/src/main/scala/ammonite/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import ammonite.util._
import scala.annotation.tailrec
import ammonite.runtime.ImportHook
import coursierapi.Dependency
import scala.concurrent.Await
import scala.concurrent.duration.Duration



Expand Down Expand Up @@ -301,20 +303,18 @@ object Main{
case Right(cliConfig) =>
if (cliConfig.core.bsp.value) {
val buildServer = new AmmoniteBuildServer(
???,
ammonite.compiler.CompilerBuilder,
ammonite.compiler.Parsers,
ammonite.compiler.DefaultCodeWrapper,
initialScripts = cliConfig.rest.map(os.Path(_)),
initialImports = PredefInitialization.initBridges(
Seq("ammonite.interp.api.InterpBridge" -> "interp")
) ++ AmmoniteBuildServer.defaultImports
)
val launcher = AmmoniteBuildServer.start(buildServer)
printErr.println("Starting BSP server")
val f = launcher.startListening()
f.get()
val (launcher, shutdownFuture) = AmmoniteBuildServer.start(buildServer)
Await.result(shutdownFuture, Duration.Inf)
printErr.println("BSP server done")
// FIXME Doesn't exit for now
true
}else{

Expand Down
13 changes: 13 additions & 0 deletions integration/src/test/scala/ammonite/integration/BasicTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -270,5 +270,18 @@ object BasicTests extends TestSuite{
assert(out.contains(expected))
}
}

test("BSP"){
val jsonrpc = """{"jsonrpc": "2.0", "id": 1, "method": "build/shutdown", "params": null}"""
.getBytes("UTF-8")
val input = Array(
s"Content-Length: ${jsonrpc.length}",
"\r\n" * 2
).flatMap(_.getBytes("UTF-8")) ++ jsonrpc
val res = os.proc(TestUtils.executable, "--bsp").call(
stdin = input
)
assert(res.exitCode == 0)
}
}
}