Java Bindings
Installation
The ch.epfl.scala:bsp4j
module is a Java library that is available from Maven
Central. The module has one external dependency on the
eclipse/lsp4j library.
Gradle
compile group: 'ch.epfl.scala', name: 'bsp4j', version: '2.0.0-M4+13-b3992f30'
Maven
<dependency>
<groupId>ch.epfl.scala</groupId>
<artifactId>bsp4j</artifactId>
<version>2.0.0-M4+13-b3992f30</version>
</dependency>
sbt
libraryDependencies += "ch.epfl.scala" % "bsp4j" % "2.0.0-M4+13-b3992f30"
Examples
Client
First, begin by obtaining an input and output stream to communicate with the build server.
val output: java.io.OutputStream = buildOutputStream()
val input: java.io.InputStream = buildInputStream()
Next, implement the BuildClient
interface. Replace the ???
dummy
implementations with the logic of your build client.
import java.util.concurrent._
import ch.epfl.scala.bsp4j._
import org.eclipse.lsp4j.jsonrpc.Launcher
class MyClient extends BuildClient {
def onBuildLogMessage(params: LogMessageParams): Unit = ???
def onBuildPublishDiagnostics(params: PublishDiagnosticsParams): Unit = ???
def onBuildShowMessage(params: ShowMessageParams): Unit = ???
def onBuildTargetDidChange(params: DidChangeBuildTarget): Unit = ???
def onBuildTaskFinish(params: TaskFinishParams): Unit = ???
def onBuildTaskProgress(params: TaskProgressParams): Unit = ???
def onBuildTaskStart(params: TaskStartParams): Unit = ???
}
val localClient = new MyClient()
Optionally, create a custom ExecutorService
to run client responses
import java.util.concurrent._
val es = Executors.newFixedThreadPool(1)
// es: ExecutorService = java.util.concurrent.ThreadPoolExecutor@6196c03d[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]
Next, wire the client implementation together with the remote build server.
val launcher = new Launcher.Builder[BuildServer]()
.setOutput(output)
.setInput(input)
.setLocalService(localClient)
.setExecutorService(es)
.setRemoteInterface(classOf[BuildServer])
.create()
// launcher: Launcher[BuildServer] = org.eclipse.lsp4j.jsonrpc.Launcher$Builder$1@2cb06e98
Next, obtain an instance of the remote BuildServer
via getRemoteProxy()
.
val server = launcher.getRemoteProxy
// server: BuildServer = EndpointProxy for org.eclipse.lsp4j.jsonrpc.RemoteEndpoint@62b19302
Next, start listening to the remote build server on a separate thread. The
.get()
method call is blocking during the lifetime of BSP session.
new Thread {
override def run() = launcher.startListening().get()
}
// res0: Thread = Thread[Thread-212,5,run-main-group-3]
Next, trigger the initialize handshake with the remote server.
val workspace = java.nio.file.Paths.get(".").toAbsolutePath().normalize()
// workspace: java.nio.file.Path = /Users/lgeirsson/dev/bsp
val initializeResult = server.buildInitialize(new InitializeBuildParams(
"MyClient", // name of this client
"1.0.0", // version of this client
"2.0.0-M4+13-b3992f30", // BSP version
workspace.toUri().toString(),
new BuildClientCapabilities(java.util.Collections.singletonList("scala"))
))
// initializeResult: CompletableFuture[InitializeBuildResult] = org.eclipse.lsp4j.jsonrpc.RemoteEndpoint$1@21a852ee[Not completed, 1 dependents]
After receiving the initialize response, send the build/initialized
notification.
initializeResult.thenAccept(_ => server.onBuildInitialized())
// res1: CompletableFuture[Void] = java.util.concurrent.CompletableFuture@169f9003[Not completed]
After sending the build/initialized
notification, you can send any BSP
requests and notications such as workspace/buildTargets
,
buildTarget/compile
.
To close the BSP session, send the build/shutdown
request followed by a
build/exit
notification.
server.buildShutdown().thenAccept(_ => server.onBuildExit())
// res2: CompletableFuture[Void] = java.util.concurrent.CompletableFuture@3974e8e6[Not completed]
Server
First, implement the BuildServer
interface.
import java.util.concurrent._
import ch.epfl.scala.bsp4j._
import org.eclipse.lsp4j.jsonrpc.Launcher
class MyBuildServer extends BuildServer {
var client: BuildClient = null // will be updated later
def buildInitialize(params: InitializeBuildParams): CompletableFuture[InitializeBuildResult] = ???
def buildShutdown(): CompletableFuture[Object] = ???
def buildTargetCleanCache(params: CleanCacheParams): CompletableFuture[CleanCacheResult] = ???
def buildTargetCompile(params: CompileParams): CompletableFuture[CompileResult] = ???
def buildTargetDependencySources(params: DependencySourcesParams): CompletableFuture[DependencySourcesResult] = ???
def buildTargetInverseSources(params: InverseSourcesParams): CompletableFuture[InverseSourcesResult] = ???
def buildTargetResources(params: ResourcesParams): CompletableFuture[ResourcesResult] = ???
def buildTargetRun(params: RunParams): CompletableFuture[RunResult] = ???
def buildTargetSources(params: SourcesParams): CompletableFuture[SourcesResult] = ???
def buildTargetTest(params: TestParams): CompletableFuture[TestResult] = ???
def onBuildExit(): Unit = ???
def onBuildInitialized(): Unit = ???
def workspaceBuildTargets(): CompletableFuture[WorkspaceBuildTargetsResult] = ???
}
val localServer = new MyBuildServer()
// localServer: MyBuildServer = repl.Session$App4$MyBuildServer@40b72398
Next, construct a launcher for the remote build client.
val launcher = new Launcher.Builder[BuildClient]()
.setOutput(System.out)
.setInput(System.in)
.setLocalService(localServer)
.setRemoteInterface(classOf[BuildClient])
.create()
// launcher: Launcher[BuildClient] = org.eclipse.lsp4j.jsonrpc.Launcher$Builder$1@d63afda
Next, update the remote build client reference in localServer
.
localServer.client = launcher.getRemoteProxy()
Finally, in a main
method wire everything together.
def main(args: Array[String]): Unit = {
launcher.startListening().get() // listen until BSP session is over.
}