Java Bindings
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.
Installation
Add the following snippet to your build to add dependency on bsp4j
.
Gradle
compile group: 'ch.epfl.scala', name: 'bsp4j', version: '2.2.0-M4.TEST'
Maven
<dependency>
<groupId>ch.epfl.scala</groupId>
<artifactId>bsp4j</artifactId>
<version>2.2.0-M4.TEST</version>
</dependency>
sbt
libraryDependencies += "ch.epfl.scala" % "bsp4j" % "2.2.0-M4.TEST"
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 = ???
def onRunPrintStdout(params: PrintParams): Unit = ???
def onRunPrintStderr(params: PrintParams): Unit = ???
}
val localClient = new MyClient()
Optionally, create a custom ExecutorService
to run client responses
import java.util.concurrent._
val es = Executors.newFixedThreadPool(1)
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()
Next, obtain an instance of the remote BuildServer
via getRemoteProxy()
.
val server = launcher.getRemoteProxy
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()
}
Next, trigger the initialize handshake with the remote server.
val workspace = java.nio.file.Paths.get(".").toAbsolutePath().normalize()
val initializeResult = server.buildInitialize(new InitializeBuildParams(
"MyClient", // name of this client
"1.0.0", // version of this client
Bsp4j.PROTOCOL_VERSION,
workspace.toUri().toString(),
new BuildClientCapabilities(java.util.Collections.singletonList("scala"))
))
After receiving the initialize response, send the build/initialized
notification.
initializeResult.thenAccept(_ => server.onBuildInitialized())
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(new java.util.function.Consumer[Object] {
def accept(x: Object): Unit = {
server.onBuildExit()
}
})
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 buildTargetDependencyModules(params: DependencyModulesParams): CompletableFuture[DependencyModulesResult] = ???
def buildTargetInverseSources(params: InverseSourcesParams): CompletableFuture[InverseSourcesResult] = ???
def buildTargetResources(params: ResourcesParams): CompletableFuture[ResourcesResult] = ???
def buildTargetOutputPaths(params: OutputPathsParams): CompletableFuture[OutputPathsResult] = ???
def buildTargetRun(params: RunParams): CompletableFuture[RunResult] = ???
def buildTargetSources(params: SourcesParams): CompletableFuture[SourcesResult] = ???
def buildTargetTest(params: TestParams): CompletableFuture[TestResult] = ???
def debugSessionStart(params: DebugSessionParams): CompletableFuture[DebugSessionAddress] = ???
def onBuildExit(): Unit = ???
def onBuildInitialized(): Unit = ???
def workspaceBuildTargets(): CompletableFuture[WorkspaceBuildTargetsResult] = ???
def workspaceReload(): CompletableFuture[Object] = ???
def onRunReadStdin(params: ReadParams): Unit = ???
}
val localServer = new MyBuildServer()
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()
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.
}