diff --git a/build.sbt b/build.sbt index f92122270..31b5c6abf 100644 --- a/build.sbt +++ b/build.sbt @@ -150,7 +150,7 @@ lazy val app = projectMatrix lazy val `e2e-tests` = projectMatrix .in(file("modules/e2e-tests")) - .dependsOn(app) + .dependsOn(app, example) .defaultAxes(V.default*) .settings(enableSnapshots) .jvmPlatform( diff --git a/docs/_docs/examples.md b/docs/_docs/examples.md index 1b9b83f52..9f7a26e0b 100644 --- a/docs/_docs/examples.md +++ b/docs/_docs/examples.md @@ -26,3 +26,13 @@ Designed to serve as _the_ testbed for Langoustine changes, it's a small primiti arithmetic language with associated set of tooling and built-in LSP. This particular example runs on the JVM and uses Scala CLI exclusively for packaging. + +## Another LSP for a toy language + +[Badlang](https://github.com/kubukoz/badlang) + +Made by [Jakub Kozłowski](https://twitter.com/kubukoz/) for various versions of his talk about +building a language and a LSP for it. + +Check out the [talk at Lambda Days 2023](https://www.youtube.com/watch?v=HF0xVrBZqtI) which also +showcases the usage of Langoustine Tracer. diff --git a/modules/e2e-tests/src/test/scala/EndToEndTests.scala b/modules/e2e-tests/src/test/scala/EndToEndTests.scala index 20f7a9451..3ad968742 100644 --- a/modules/e2e-tests/src/test/scala/EndToEndTests.scala +++ b/modules/e2e-tests/src/test/scala/EndToEndTests.scala @@ -14,15 +14,14 @@ import jsonrpclib.Payload import cats.syntax.all.* import scala.concurrent.Promise -import cats.effect.std.Dispatcher.apply -import cats.effect.std.Dispatcher -import cats.effect.std.Semaphore.apply -import cats.effect.std.Semaphore +import cats.effect.std.* import cats.effect.kernel.Ref import cats.effect.kernel.Deferred import cats.effect.kernel.Resource import java.lang.management.ManagementFactory +import langoustine.example.{MyCustomRequest, MyCustomNotification} + case class Result(code: Int, stdout: List[String], stderr: List[String]) object EndToEndTests extends SimpleIOSuite: @@ -78,7 +77,10 @@ object EndToEndTests extends SimpleIOSuite: ) ) - val send = str + str1 + str2 + val str3 = sendNotification(MyCustomNotification, "hello!") + val str4 = sendRequest(MyCustomRequest, "hello request!") + + val send = str + str1 + str2 + str3 + str4 val timeout = 5.seconds @@ -117,6 +119,14 @@ object EndToEndTests extends SimpleIOSuite: ShowMessageParams( MessageType.Info, "In total, 2 files registered!" + ), + ShowMessageParams( + MessageType.Info, + "Received custom notification: hello!" + ), + ShowMessageParams( + MessageType.Info, + "Received custom request: hello request!" ) ), initialized == Vector( diff --git a/modules/example/src/main/scala/Example.scala b/modules/example/src/main/scala/Example.scala index 8e17a4419..9472d609d 100644 --- a/modules/example/src/main/scala/Example.scala +++ b/modules/example/src/main/scala/Example.scala @@ -1,3 +1,5 @@ +package langoustine.example + import langoustine.lsp.* import langoustine.lsp.all.* import langoustine.lsp.app.* @@ -29,6 +31,20 @@ def myLSP(files: Ref[IO, Set[String]]) = sendMessage(in.toClient, s"In total, $count files registered!") } } + .handleNotification(MyCustomNotification) { in => + sendMessage(in.toClient, s"Received custom notification: ${in.params}") + } + .handleRequest(MyCustomRequest) { in => + sendMessage(in.toClient, s"Received custom request: ${in.params}") *> + files.get.map(_.size) + } + +object MyCustomNotification + extends CustomNotification[String]("custom/myNotification") + +object MyCustomRequest extends CustomRequest[String, Int]("custom/myRequest") def sendMessage(back: Communicate[IO], msg: String) = - back.notification(window.showMessage(ShowMessageParams(MessageType.Info, msg))) + back.notification( + window.showMessage(ShowMessageParams(MessageType.Info, msg)) + ) diff --git a/modules/generate/src/main/scala/render.scala b/modules/generate/src/main/scala/render.scala index 94c788647..af896e399 100644 --- a/modules/generate/src/main/scala/render.scala +++ b/modules/generate/src/main/scala/render.scala @@ -52,22 +52,48 @@ class Render(manager: Manager, packageName: String = "langoustine.lsp"): } val requestPrelude = """ - |sealed abstract class LSPRequest(val requestMethod: String): - | type In - | type Out - | - | given inputReader: Reader[In] - | given inputWriter: Writer[In] - | given outputWriter: Writer[Out] - | given outputReader: Reader[Out] - | - | def apply(in: In): PreparedRequest[this.type] = PreparedRequest(this,in) - """.stripMargin.trim + |sealed abstract class LSPRequest(val requestMethod: String): + | type In + | type Out + | + | given inputReader: Reader[In] + | given inputWriter: Writer[In] + | given outputWriter: Writer[Out] + | given outputReader: Reader[Out] + | + | def apply(in: In): PreparedRequest[this.type] = PreparedRequest(this,in) + """.stripMargin.trim requestPrelude.linesIterator.foreach(line) line("") + val customRequestPrelude = + """ + |abstract class CustomRequest[I, O](method: String)(using ir: ReadWriter[I], or: ReadWriter[O]) extends LSPRequest(method): + | override type In = I + | override type Out = O + | + | override given inputReader: Reader[In] = ir + | + | override given inputWriter: Writer[In] = ir + | + | override given outputWriter: Writer[Out] = or + | + | override given outputReader: Reader[Out] = or + | + |abstract class CustomNotification[I](method: String)(using ir: ReadWriter[I]) extends LSPNotification(method): + | override type In = I + | + | override given inputReader: Reader[In] = ir + | + | override given inputWriter: Writer[In] = ir + """.stripMargin.trim + + customRequestPrelude.linesIterator.foreach(line) + + line("") + val notificationPrelude = """ |sealed abstract class LSPNotification(val notificationMethod: String): | type In @@ -170,7 +196,9 @@ class Render(manager: Manager, packageName: String = "langoustine.lsp"): line("") - line(s"override def apply(in: $inTypeStr): PreparedRequest[this.type] = super.apply(in)") + line( + s"override def apply(in: $inTypeStr): PreparedRequest[this.type] = super.apply(in)" + ) summon[Context].inModified( _.copy(definitionScope = "requests" :: req.segs) @@ -285,7 +313,9 @@ class Render(manager: Manager, packageName: String = "langoustine.lsp"): line("") - line(s"override def apply(in: $inTypeStr): PreparedNotification[this.type] = super.apply(in)") + line( + s"override def apply(in: $inTypeStr): PreparedNotification[this.type] = super.apply(in)" + ) codecsOut.topLevel { codecsLine("") diff --git a/modules/lsp/src/main/scala/generated/requests.scala b/modules/lsp/src/main/scala/generated/requests.scala index c1ee462ea..587d1c989 100644 --- a/modules/lsp/src/main/scala/generated/requests.scala +++ b/modules/lsp/src/main/scala/generated/requests.scala @@ -38,6 +38,25 @@ object requests: def apply(in: In): PreparedRequest[this.type] = PreparedRequest(this,in) + abstract class CustomRequest[I, O](method: String)(using ir: ReadWriter[I], or: ReadWriter[O]) extends LSPRequest(method): + override type In = I + override type Out = O + + override given inputReader: Reader[In] = ir + + override given inputWriter: Writer[In] = ir + + override given outputWriter: Writer[Out] = or + + override given outputReader: Reader[Out] = or + + abstract class CustomNotification[I](method: String)(using ir: ReadWriter[I]) extends LSPNotification(method): + override type In = I + + override given inputReader: Reader[In] = ir + + override given inputWriter: Writer[In] = ir + sealed abstract class LSPNotification(val notificationMethod: String): type In