Skip to content

Commit

Permalink
Merge pull request #78 from alephium/boiler
Browse files Browse the repository at this point in the history
Boiler
  • Loading branch information
simerplaha authored Jan 9, 2024
2 parents d8a16e8 + 0f9d73f commit 0ee5f8e
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 96 deletions.
4 changes: 2 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ lazy val `presentation-compiler` =

lazy val `lsp-server` =
project
.dependsOn(`presentation-compiler`)
.settings(
scalaVersion := Version.scala213,
scalacOptions += "-Xmixin-force-forwarders:false", //Duplicate RPC method initialized.
Expand All @@ -39,6 +38,7 @@ lazy val `lsp-server` =
case x if x.endsWith("module-info.class") => MergeStrategy.discard
case other => assemblyMergeStrategy.value(other)
},
Test / scalacOptions += "-Xmixin-force-forwarders:true", // reset to default for tests.
libraryDependencies ++=
Seq(
Dependencies.lsp4j,
Expand All @@ -48,7 +48,7 @@ lazy val `lsp-server` =
Dependencies.logback,
Dependencies.scalaLogging
)
)
).dependsOn(`presentation-compiler`)

lazy val downloadWeb3AndInstallStd = taskKey[Unit]("Download alephium-web3 source code and copy std interface to the correct resource folder")

Expand Down
7 changes: 4 additions & 3 deletions lsp-server/src/main/scala/org/alephium/ralph/lsp/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ package org.alephium.ralph.lsp
import com.typesafe.scalalogging.StrictLogging
import org.alephium.ralph.lsp.access.compiler.CompilerAccess
import org.alephium.ralph.lsp.access.file.FileAccess
import org.alephium.ralph.lsp.server.{RalphLangClient, RalphLangServer}
import org.alephium.ralph.lsp.server.RalphLangServer
import org.eclipse.lsp4j.jsonrpc.Launcher
import org.eclipse.lsp4j.services.LanguageClient

import java.io.{InputStream, OutputStream}

Expand All @@ -27,10 +28,10 @@ object Main extends StrictLogging {

// configure LSP server
val launcher =
new Launcher.Builder[RalphLangClient]()
new Launcher.Builder[LanguageClient]()
.setInput(in)
.setOutput(out)
.setRemoteInterface(classOf[RalphLangClient])
.setRemoteInterface(classOf[LanguageClient])
.setLocalService(server)
.create()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,25 @@ import org.eclipse.lsp4j.services.LanguageClient

import java.util.concurrent.CompletableFuture

object RalphLangClient {

/** Implements functions that are an extension to LSP4J's [[LanguageClient]]. */
implicit class RalphLangClientExtension(val client: RalphLangClient) extends AnyVal {

def show(error: ResponseError): ResponseError = {
client.showMessage(new MessageParams(MessageType.Error, error.getMessage))
error
}

/**
* @see [[RalphLangClient.registerCapability]]
*/
def register(registration: Registration): CompletableFuture[Void] =
client.registerCapability(new RegistrationParams(java.util.Arrays.asList(registration)))

/**
* @see [[RalphLangClient.publishDiagnostics]]
*/
def publish(diagnostics: Iterable[PublishDiagnosticsParams]): Unit =
diagnostics foreach client.publishDiagnostics
}
}

/**
* The Ralph-LSP client.
*/
trait RalphLangClient extends LanguageClient
case class RalphLangClient(private val client: LanguageClient) {

def show(error: ResponseError): ResponseError = {
client.showMessage(new MessageParams(MessageType.Error, error.getMessage))
error
}

/**
* @see [[RalphLangClient.registerCapability]]
*/
def register(registration: Registration): CompletableFuture[Void] =
client.registerCapability(new RegistrationParams(java.util.Arrays.asList(registration)))

/**
* @see [[RalphLangClient.publishDiagnostics]]
*/
def publish(diagnostics: Iterable[PublishDiagnosticsParams]): Unit =
diagnostics foreach client.publishDiagnostics
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,16 @@ object RalphLangServer {

/** Start server with pre-configured client */
def apply(client: RalphLangClient,
listener: JFuture[Void])(implicit compiler: CompilerAccess,
listener: JFuture[Void],
clientAllowsWatchedFilesDynamicRegistration: Boolean = false)(implicit compiler: CompilerAccess,
file: FileAccess): RalphLangServer = {
val initialState =
ServerState(
client = Some(client),
listener = Some(listener),
workspace = None,
buildErrors = None,
clientAllowsWatchedFilesDynamicRegistration = false,
clientAllowsWatchedFilesDynamicRegistration = clientAllowsWatchedFilesDynamicRegistration,
trace = Trace.Off,
shutdownReceived = false
)
Expand Down Expand Up @@ -109,15 +110,16 @@ class RalphLangServer private(@volatile private var state: ServerState)(implicit
* Client must be known before a connection is initialised.
* @param listener LSP connection listener function.
*/
def setInitialState(client: RalphLangClient,
def setInitialState(client: LanguageClient,
listener: () => JFuture[Void]): Unit =
runSync {
require(state.client.isEmpty, "Client is already set")
require(state.listener.isEmpty, "Listener is already set")

// Client must be set first, before running the request listener,
// so that it is available for responding to requests.
thisServer.state = state.copy(client = Some(client))
val ralphClient = RalphLangClient(client)
thisServer.state = state.copy(client = Some(ralphClient))
thisServer.state = state.copy(listener = Some(listener()))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,10 @@ import org.scalatest.wordspec.AnyWordSpec

import java.net.URI
import java.util.concurrent.{CompletableFuture, Future => JFuture}
import java.util.concurrent.atomic.AtomicBoolean
import scala.concurrent.Promise
import scala.jdk.FutureConverters._

class RalphLangServerSpec extends AnyWordSpec with Matchers with MockFactory with ScalaFutures{
class RalphLangServerSpec extends AnyWordSpec with Matchers with MockFactory with ScalaFutures {

"initialize" should {
"set server workspaces and respond with capabilities" in {
Expand Down Expand Up @@ -65,12 +64,12 @@ class RalphLangServerSpec extends AnyWordSpec with Matchers with MockFactory wit
}

"shutdown" should {
implicit val compiler: CompilerAccess = CompilerAccess.ralphc
implicit val file: FileAccess = FileAccess.disk
val client : RalphLangClient = new EmptyRalphLangClient{}
implicit val compiler: CompilerAccess = null
implicit val file: FileAccess = null
val listener = CompletableFuture.runAsync(() => ())

"return true, set `shutdownReceived` to true, but not cancel the listener" in {
val client = RalphLangClient(null)
val server = RalphLangServer(client, listener)

server.getState().shutdownReceived shouldBe false
Expand All @@ -80,6 +79,7 @@ class RalphLangServerSpec extends AnyWordSpec with Matchers with MockFactory wit
}

"return an `InvalidRequest` when receiving more than one shutdown request" in {
val client = RalphLangClient(null)
val server = RalphLangServer(client, listener)

server.shutdown().asScala.futureValue shouldBe true
Expand All @@ -91,7 +91,7 @@ class RalphLangServerSpec extends AnyWordSpec with Matchers with MockFactory wit
"exit" should {
implicit val compiler: CompilerAccess = CompilerAccess.ralphc
implicit val file: FileAccess = FileAccess.disk
val client : RalphLangClient = new EmptyRalphLangClient{}
val client: RalphLangClient = RalphLangClient(null)

//We use a Promise to have a non-completed future
val listener = Promise[Void]().future.asJava.asInstanceOf[JFuture[Void]]
Expand All @@ -104,80 +104,62 @@ class RalphLangServerSpec extends AnyWordSpec with Matchers with MockFactory wit

server.exitWithCode() shouldBe 0
server.getState().listener.get.isCancelled shouldBe true
}
}

"exit with error if shutdown wasn't requested" in {
val server = RalphLangServer(client, listener)

server.exitWithCode() shouldBe 1
server.getState().listener.get.isCancelled shouldBe true
}
}
}

"registerClientCapabilities" should {
"register watched files if dynamic registration is true" in {
implicit val compiler: CompilerAccess = null // compile is never accessed
implicit val file: FileAccess = null // file/disk IO is never accessed

def buildServer(dynamicRegistration: Option[Boolean], registered: AtomicBoolean) = {
implicit val compiler: CompilerAccess = CompilerAccess.ralphc
implicit val file: FileAccess = FileAccess.disk
val client: RalphLangClient = new EmptyRalphLangClient {
override def registerCapability(params: RegistrationParams): CompletableFuture[Void] = {
registered.set(true)
new CompletableFuture[Void]()
}
}
val listener = CompletableFuture.runAsync(() => ())
val server = RalphLangServer(client, listener)

val capabilities = dynamicRegistration match {
case None => new ClientCapabilities()
case Some(dynamicRegistration) =>
val watchedFilesCapabilities = new DidChangeWatchedFilesCapabilities(dynamicRegistration)
val workspaceCapabilities = new WorkspaceClientCapabilities()
workspaceCapabilities.setDidChangeWatchedFiles(watchedFilesCapabilities)
val result = new ClientCapabilities()
result.setWorkspace(workspaceCapabilities)
result
}
val client = mock[RalphLangClient]

val initialise = new InitializeParams()
val workspaceURI = new URI("file://test")
initialise.setRootUri(workspaceURI.toString)
initialise.setCapabilities(capabilities)
val server =
RalphLangServer(
client = client,
listener = CompletableFuture.runAsync(() => ()),
clientAllowsWatchedFilesDynamicRegistration = true
)

server.initialize(initialise).get()
server
}
server.getState().clientAllowsWatchedFilesDynamicRegistration shouldBe true

"register watched files if dynamic registration is true" in {
val registered = new AtomicBoolean(false)
val server = buildServer(Some(true), registered)
server.registerClientCapabilities()
// register is called once with the default client-capabilities
(client.register _)
.expects(RalphLangServer.clientCapabilities())
.returning(new CompletableFuture[Void]())
.once()

registered.get shouldBe true
server.registerClientCapabilities() shouldBe (())
}

"not register watched files if dynamic registration is false" in {
val registered = new AtomicBoolean(false)
val server = buildServer(Some(false), registered)
server.registerClientCapabilities()
implicit val compiler: CompilerAccess = null // compile is never accessed
implicit val file: FileAccess = null // file/disk IO is never accessed

registered.get shouldBe false
}
val client = mock[RalphLangClient]

"not register watched files if dynamic registration is not set" in {
val registered = new AtomicBoolean(false)
val server = buildServer(None, registered)
server.registerClientCapabilities()
val server =
RalphLangServer(
client = client,
listener = CompletableFuture.runAsync(() => ()),
clientAllowsWatchedFilesDynamicRegistration = false
)

server.getState().clientAllowsWatchedFilesDynamicRegistration shouldBe false

// register is never called
(client.register _)
.expects(*)
.never()

registered.get shouldBe false
server.registerClientCapabilities() shouldBe (())
}
}
}

trait EmptyRalphLangClient extends RalphLangClient {
def logMessage(x: org.eclipse.lsp4j.MessageParams): Unit = ()
def publishDiagnostics(x: org.eclipse.lsp4j.PublishDiagnosticsParams): Unit = ()
def showMessage(x: org.eclipse.lsp4j.MessageParams): Unit = ()
def showMessageRequest(x: org.eclipse.lsp4j.ShowMessageRequestParams): java.util.concurrent.CompletableFuture[org.eclipse.lsp4j.MessageActionItem] = ???
def telemetryEvent(x: Object): Unit = ()
}
4 changes: 2 additions & 2 deletions project/Dependencies.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import sbt._

object Version {
val scala213 = "2.13.11"
val scala213 = "2.13.12"
val web3 = "0.22.0"
}

Expand All @@ -13,7 +13,7 @@ object Dependencies {
lazy val scalaMock = "org.scalamock" %% "scalamock" % "5.2.0" % Test

/** Core */
lazy val ralphc = "org.alephium" %% "alephium-ralphc" % "2.5.8"
lazy val ralphc = "org.alephium" %% "alephium-ralphc" % "2.5.9"
lazy val lsp4j = "org.eclipse.lsp4j" % "org.eclipse.lsp4j" % "0.21.1"

/** Logging */
Expand Down

0 comments on commit 0ee5f8e

Please sign in to comment.