Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract lsp4j-agnostic code from lsp module #527

Merged
merged 14 commits into from
Mar 22, 2025
4 changes: 3 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ You'll find these in [`modules/lsp/src/test`](modules/lsp/src/test), and they ru

This doesn't exercise the standard I/O interface of the server, but it's still a decent way to test the composition of all the modules with a filesystem directory ([`modules/lsp/src/test/resources/test-workspaces`](modules/lsp/src/test/resources/test-workspaces)).

To run integration tests, run `lsp/test`.
These are working without any external LSP library like lsp4j or Langoustine.

To run integration tests, run `lsp-kernel/test`.

## Extension integration tests

Expand Down
24 changes: 18 additions & 6 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -195,23 +195,21 @@ lazy val core = module("core")
)

// LSP-like interfaces like CodeLensProvider, which are later adapted into actual lsp
// but can be used in other, non-LSP contexts
lazy val languageSupport = module("language-support")
.dependsOn(core % "test->test;compile->compile", parser)

// Adapters for language services to LSP, actual LSP server binding, entrypoint
lazy val lsp = module("lsp")
// LSP features that aren't specific to any given lsp library (lsp4j, langoustine)
lazy val lspKernel = module("lsp-kernel")
.settings(
libraryDependencies ++= Seq(
"org.eclipse.lsp4j" % "org.eclipse.lsp4j" % "0.24.0",
"io.circe" %% "circe-core" % "0.14.12",
"org.http4s" %% "http4s-ember-client" % "0.23.30",
"org.http4s" %% "http4s-ember-server" % "0.23.30" % Test,
("io.get-coursier" % "coursier_2.13" % "2.1.24")
.exclude("org.scala-lang.modules", "scala-collection-compat_2.13"),
"org.typelevel" %% "cats-tagless-core" % "0.16.3",
"org.http4s" %% "http4s-ember-server" % "0.23.30" % Test,
).pipe(jsoniterFix),
buildInfoPackage := "playground.lsp.buildinfo",
buildInfoKeys ++= Seq(version, scalaBinaryVersion),
(Test / test) := {
(pluginCore / publishLocal).value
(pluginSample / publishLocal).value
Expand All @@ -220,8 +218,20 @@ lazy val lsp = module("lsp")
},
)
.enablePlugins(BuildInfoPlugin)
.settings(
buildInfoPackage := "playground.lsp.buildinfo",
buildInfoKeys ++= Seq(version, scalaBinaryVersion),
)
.dependsOn(languageSupport)

lazy val lsp = module("lsp")
.settings(
libraryDependencies ++= Seq(
"org.eclipse.lsp4j" % "org.eclipse.lsp4j" % "0.24.0"
)
)
.dependsOn(lspKernel)

lazy val e2e = module("e2e")
.enablePlugins(BuildInfoPlugin)
.settings(
Expand All @@ -233,6 +243,7 @@ lazy val e2e = module("e2e")
}
// todo: replace with a full publishLocal before e2e in particular gets run (but not before tests run normally)
.dependsOn(
lspKernel / publishLocal,
lsp / publishLocal,
languageSupport / publishLocal,
core / publishLocal,
Expand Down Expand Up @@ -271,6 +282,7 @@ lazy val root = project
parser,
formatter,
languageSupport,
lspKernel,
lsp,
protocol4s,
pluginCore,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ case class CodeLens(
command: Command,
)

// note: could be made a bit more type safe sometime
case class Command(
title: String,
command: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ import playground.types.*
trait DiagnosticProvider[F[_]] {

def getDiagnostics(
fileName: String,
documentText: String,
documentText: String
): List[CompilationError]

}
Expand All @@ -37,8 +36,7 @@ object DiagnosticProvider {
new DiagnosticProvider[F] {

def getDiagnostics(
fileName: String,
documentText: String,
documentText: String
): List[CompilationError] = compilationErrors(documentText).fold(
_.toList,
parsed => runnerErrors(parsed),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ trait Feedback[F[_]] {
msg: String
): F[Unit]

// custom smithyql/showOutputPanel notification in the client
def showOutputPanel: F[Unit]

def logOutput(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,13 @@ object DiagnosticProviderTests extends SimpleIOSuite {
)

pureTest("empty file - no diagnostics") {
assertNoDiff(provider.getDiagnostics("test.smithyql", ""), Nil)
assertNoDiff(provider.getDiagnostics(""), Nil)
}

pureTest("file doesn't parse at all") {
val input = "horrendous <parsing mistake>"
assertNoDiff(
provider.getDiagnostics("test.smithyql", input),
provider.getDiagnostics(input),
List(
CompilationError(
err = CompilationErrorDetails.ParseError(expectationString = "{"),
Expand All @@ -117,7 +117,7 @@ object DiagnosticProviderTests extends SimpleIOSuite {
val input = "AnyOp {}"

assertNoDiff(
provider.getDiagnostics("test.smithyql", input),
provider.getDiagnostics(input),
List(
CompilationError(
err = CompilationErrorDetails.AmbiguousService(
Expand All @@ -138,8 +138,7 @@ object DiagnosticProviderTests extends SimpleIOSuite {

assertNoDiff(
provider.getDiagnostics(
"test.smithyql",
input,
input
),
List(
CompilationError.error(
Expand All @@ -165,7 +164,7 @@ object DiagnosticProviderTests extends SimpleIOSuite {
|noop#NoRunnerService.Noop {}""".stripMargin

assertNoDiff(
provider.getDiagnostics("test.smithyql", input),
provider.getDiagnostics(input),
List(
CompilationError.info(
err = CompilationErrorDetails.UnsupportedProtocols(
Expand All @@ -184,7 +183,7 @@ object DiagnosticProviderTests extends SimpleIOSuite {
"""playground.std#Random.NextUUID {}
|playground.std#Clock.CurrentTimestamp {}""".stripMargin

assertNoDiff(provider.getDiagnostics("test.smithyql", input), Nil)
assertNoDiff(provider.getDiagnostics(input), Nil)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package playground.lsp

import cats.FlatMap
import cats.data.Kleisli
import cats.syntax.all.*
import cats.tagless.Derive
import cats.tagless.FunctorK
import cats.tagless.implicits.*
import cats.~>
import playground.language.Feedback

trait LanguageClient[F[_]] extends Feedback[F] {

def configuration[A](
v: ConfigurationValue[A]
): F[A]

def showMessage(
tpe: MessageType,
msg: String,
): F[Unit]

def refreshDiagnostics: F[Unit]
def refreshCodeLenses: F[Unit]

def showInfoMessage(
msg: String
): F[Unit] = showMessage(MessageType.Info, msg)

def showWarnMessage(
msg: String
): F[Unit] = showMessage(MessageType.Warning, msg)

def showErrorMessage(
msg: String
): F[Unit] = showMessage(MessageType.Error, msg)

}

object LanguageClient {

def apply[F[_]](
implicit F: LanguageClient[F]
): LanguageClient[F] = F

implicit val functorK: FunctorK[LanguageClient] = Derive.functorK[LanguageClient]

def defer[F[_]: FlatMap](
fa: F[LanguageClient[F]]
): LanguageClient[F] = Derive
.readerT[LanguageClient, F]
.mapK(new (Kleisli[F, LanguageClient[F], *] ~> F) {

def apply[A](
k: Kleisli[F, LanguageClient[F], A]
): F[A] = fa.flatMap(k.run)

})

}

enum MessageType {
case Info, Warning, Error

def name: String = toString
}
Loading
Loading