Skip to content
Open
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
1 change: 0 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ jobs:
run: sbt ci buildWebsite

- name: Publish ${{ github.ref }}
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || (github.ref == 'refs/heads/main'))
run: sbt ci-release
env:
PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }}
Expand Down
20 changes: 15 additions & 5 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ inThisBuild(
"keynmol@gmail.com",
url("https://blog.indoorvivants.com")
)
)
),
resolvers ++= Resolver.sonatypeOssRepos("snapshots")
)
)

Expand All @@ -30,7 +31,7 @@ val V = new {
val scribe = "3.13.2"
val upickle = "2.0.0"
val cats = "2.10.0"
val jsonrpclib = "0.0.7"
val jsonrpclib = "0.1.0"
val fs2 = "3.10.0"
val http4s = "0.23.26"
val laminar = "0.14.5"
Expand Down Expand Up @@ -147,7 +148,7 @@ lazy val app = projectMatrix
libraryDependencies += "tech.neander" %%% "jsonrpclib-fs2" % V.jsonrpclib,
libraryDependencies += "co.fs2" %%% "fs2-io" % V.fs2,
libraryDependencies += "com.outr" %%% "scribe-cats" % V.scribe,
test := {},
test := {},
scalacOptions ++= commonScalacOptions
)
.jvmPlatform(V.scalaVersions)
Expand Down Expand Up @@ -255,12 +256,16 @@ lazy val tracer = projectMatrix
.settings(
name := "langoustine-tracer",
libraryDependencies += "tech.neander" %%% "jsonrpclib-fs2" % V.jsonrpclib,
libraryDependencies += "co.fs2" %%% "fs2-io" % V.fs2,
libraryDependencies += "co.fs2" %%% "fs2-io" % V.fs2,
libraryDependencies += "org.http4s" %%% "http4s-ember-server" % V.http4s,
libraryDependencies += "org.http4s" %%% "http4s-dsl" % V.http4s,
libraryDependencies += "com.monovore" %%% "decline" % V.decline,
libraryDependencies += "com.outr" %%% "scribe-cats" % V.scribe,
libraryDependencies += "com.indoorvivants.detective" %% "platform" % V.detective,
libraryDependencies ++= Seq(
// Use the "provided" scope instead when the "compile-internal" scope is not supported
"com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-macros" % V.jsoniter % "compile-internal"
),
scalacOptions ++= commonScalacOptions,
Compile / doc / sources := Seq.empty,
// embedding frontend in backend's resources
Expand Down Expand Up @@ -322,7 +327,11 @@ lazy val tracerFrontend = projectMatrix
libraryDependencies += "com.raquo" %%% "laminar" % V.laminar,
libraryDependencies += "io.circe" %%% "circe-scalajs" % V.circe,
libraryDependencies += "com.lihaoyi" %%% "fansi" % V.fansi,
scalaJSUseMainModuleInitializer := true,
libraryDependencies ++= Seq(
// Use the "provided" scope instead when the "compile-internal" scope is not supported
"com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-macros" % "2.35.3" % "compile-internal"
),
scalaJSUseMainModuleInitializer := true,
scalacOptions ++= commonScalacOptions
)
.jsPlatform(V.scalaVersions)
Expand All @@ -335,6 +344,7 @@ lazy val tracerShared = projectMatrix
name := "langoustine-tracer-shared",
libraryDependencies ++= Seq(
"com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-core" % V.jsoniter,
"com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-circe" % V.jsoniter,
"com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-macros" % V.jsoniter % "compile-internal",
"tech.neander" %%% "jsonrpclib-core" % V.jsonrpclib
),
Expand Down
8 changes: 3 additions & 5 deletions modules/app/src/main/scala/LSPCancelRequest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,15 @@

package langoustine.lsp.app

import io.circe.Codec
import jsonrpclib.CallId
import jsonrpclib.Codec
import com.github.plokhotnyuk.jsoniter_scala.core.JsonValueCodec
import jsonrpclib.fs2.CancelTemplate

private case class LSPCancelRequest(id: CallId)

object LSPCancelRequest:
import com.github.plokhotnyuk.jsoniter_scala.macros.*
given JsonValueCodec[LSPCancelRequest] = JsonCodecMaker.make[LSPCancelRequest]
given Codec[LSPCancelRequest] = Codec.fromJsonCodec
given Codec[LSPCancelRequest] = io.circe.Codec
.forProduct1[LSPCancelRequest, CallId]("id")(LSPCancelRequest(_))(_.id)

val cancelTemplate: CancelTemplate = CancelTemplate
.make[LSPCancelRequest](
Expand Down
30 changes: 12 additions & 18 deletions modules/app/src/main/scala/LangoustineApp.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,6 @@ import cats.effect.kernel.Resource
import cats.effect.std.Dispatcher
import cats.syntax.all.*
import jsonrpclib.Channel
import jsonrpclib.Endpoint
import jsonrpclib.Endpoint.NotificationEndpoint
import jsonrpclib.Endpoint.RequestResponseEndpoint
import jsonrpclib.fs2.*
import langoustine.lsp.Communicate
import langoustine.lsp.LSPBuilder
Expand Down Expand Up @@ -76,18 +73,13 @@ object LangoustineApp:
val futureComms = DispatcherCommunicate(disp, comms)
val endpoints = builder.build(futureComms)

(endpoints: @unchecked)
(endpoints)
.map {
case n @ NotificationEndpoint[Future, Any](method, run, inCodec) =>
n.copy(run = (msg, in) => IO.fromFuture(IO(n.run(msg, in))))
case r @ RequestResponseEndpoint[Future, Any, Any, Any](
method,
run,
inCodec,
errCodec,
outCodec
) =>
r.copy(run = (msg, in) => IO.fromFuture(IO(r.run(msg, in))))
_.mapK(
new jsonrpclib.PolyFunction[Future, IO]:
def apply[A0](fa: => Future[A0]): IO[A0] =
IO.fromFuture(IO(fa))
)
}
.traverse(ep => to.mountEndpoint(ep))
}.void
Expand Down Expand Up @@ -131,13 +123,15 @@ object LangoustineApp:
fs2.Stream
.eval(IO.deferred[Boolean])
.flatMap { latch =>
FS2Channel[IO](bufferSize, Some(LSPCancelRequest.cancelTemplate))
FS2Channel
.stream[IO](bufferSize, Some(LSPCancelRequest.cancelTemplate))
.flatMap { channel =>
(builder: @unchecked) match
case l: LSPBuilder[IO] =>
FS2.Stream.resource(
Resource.eval(l.bind(channel, latch.complete(true).void))
)
FS2.Stream
.resource(
Resource.eval(l.bind(channel, latch.complete(true).void))
)
case other: LSPServerBuilder[IO] =>
FS2.Stream
.resource(other(channel, latch.complete(true).void))
Expand Down
27 changes: 8 additions & 19 deletions modules/e2e-tests/src/test/scala/EndToEndTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,16 @@

package tests.e2e

import cats.effect.IO
import com.github.plokhotnyuk.jsoniter_scala.circe.JsoniterScalaCodec.*
import com.github.plokhotnyuk.jsoniter_scala.core.*
import jsonrpclib.Payload
import langoustine.example.MyCustomNotification
import langoustine.example.MyCustomRequest
import langoustine.lsp.all.*

import weaver.*

import com.github.plokhotnyuk.jsoniter_scala.core.*
import upickle.default.ReadWriter
import cats.effect.IO
import scala.sys.process.ProcessIO
import concurrent.duration.*
import scala.sys.process.BasicIO
import jsonrpclib.Payload

import cats.syntax.all.*
import scala.concurrent.Promise
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])

Expand Down Expand Up @@ -166,7 +155,7 @@ transparent inline def asNotification[T <: LSPNotification](
p: Payload
): Option[t.In] =
for
js <- scala.util.Try(ujson.read(writeToArray(p))).toOption
js <- scala.util.Try(ujson.read(writeToArray(p.data))).toOption
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: would it be possible for langoustine to switch from ujson to circe? That would help a lot with all these conversions.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@keynmol thoughts?

o <- js.objOpt
p <- o.get("params")
res <- scala.util.Try(upickle.default.read[t.In](p)).toOption
Expand All @@ -179,7 +168,7 @@ transparent inline def asResponse[T <: LSPRequest](
p: Payload
): Option[t.Out] =
for
js <- scala.util.Try(ujson.read(writeToArray(p))).toOption
js <- scala.util.Try(ujson.read(writeToArray(p.data))).toOption
o <- js.objOpt
p <- o.get("result")
res <- scala.util.Try(upickle.default.read[t.Out](p)).toOption
Expand Down
53 changes: 32 additions & 21 deletions modules/lsp/src/main/scala/JSONRPC.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,31 +23,42 @@ import upickle.core.TraceVisitor.TraceException
import upickle.default.*

import requests.*
import io.circe.Codec
import com.github.plokhotnyuk.jsoniter_scala.circe.JsoniterScalaCodec.*
import com.github.plokhotnyuk.jsoniter_scala.core.readFromString
import com.github.plokhotnyuk.jsoniter_scala.core.writeToString
import io.circe.Json
import io.circe.HCursor
import io.circe.Decoder.Result
import io.circe.DecodingFailure

private[langoustine] object jsonrpcIntegration:
object jsonrpcIntegration:
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought: needed to make this public so that it can be used in the test.core.testkit class

val nullArray = "null".getBytes()
given codec[T: Reader: Writer]: Codec[T] =
new Codec[T]:
override def decode(
payload: Option[Payload]
): Either[ProtocolError, T] =
payload
.map(_.stripNull.map(_.array).getOrElse(nullArray))
.toRight(ProtocolError.InvalidParams("missing payload"))
.flatMap: arr =>
Try(read[T](arr, trace = true)).toEither.left.map {
case te: TraceException =>
val e = te.getCause()
ProtocolError.InvalidParams(
s"invalid payload at ${te.jsonPath}: " + e.getMessage
)

case e =>
ProtocolError.InternalError("oopsie daisy: " + e.getMessage)
}

override def encode(a: T): Payload =
Payload(write(a).getBytes)

override def apply(c: HCursor): Result[T] =
Try(read[T](writeToString[Json](c.value))).toEither.left.map {
case te: TraceException =>
val e = te.getCause()
DecodingFailure(
DecodingFailure.Reason.CustomReason(
s"invalid payload at ${te.jsonPath}: " + e.getMessage
),
c
)

case e =>
DecodingFailure(
DecodingFailure.Reason.CustomReason(
"oopsie daisy: " + e.getMessage
),
c
)
}

override def apply(a: T): Json =
readFromString[Json](write(a))

def handlerToEndpoint[F[_]: Monadic, T <: LSPRequest](req: T)(
f: req.In => F[req.Out]
Expand Down
5 changes: 0 additions & 5 deletions modules/tests/src/test/scala/CodecTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,9 @@ package tests.core

import upickle.default.*
import langoustine.lsp.*
import jsonrpclib.Monadic
import scala.util.*
import cats.MonadThrow

import langoustine.lsp.all.*
import cats.Monad

import jsonrpclib.*
import org.scalacheck.*
import org.scalacheck.rng.Seed

Expand Down
Loading
Loading