Skip to content
Draft
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
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"${workspaceFolder}/vscode-extension/out/*.js",
"${workspaceFolder}/vscode-extension/out/*.js.map"
],
"preLaunchTask": "build",
// "preLaunchTask": "build",
"request": "launch",
"type": "extensionHost",
"sourceMaps": true
Expand Down
10 changes: 7 additions & 3 deletions modules/lsp/src/main/scala/playground/lsp/LanguageClient.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import cats.tagless.FunctorK
import cats.tagless.implicits.*
import cats.~>
import com.google.gson.JsonElement
import io.circe.Json
import org.eclipse.lsp4j.ConfigurationItem
import org.eclipse.lsp4j.ConfigurationParams
import org.eclipse.lsp4j.MessageParams
Expand All @@ -23,7 +24,7 @@ trait LanguageClient[F[_]] extends Feedback[F] {

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

def showMessage(
tpe: MessageType,
Expand Down Expand Up @@ -68,7 +69,7 @@ object LanguageClient {

def configuration[A](
v: ConfigurationValue[A]
): F[A] = withClientF(
): F[Option[A]] = withClientF(
_.configuration(
new ConfigurationParams(
(new ConfigurationItem().tap(_.setSection(v.key)) :: Nil).asJava
Expand All @@ -85,7 +86,10 @@ object LanguageClient {
case e: JsonElement => converters.gsonToCirce(e)
case e => throw new RuntimeException(s"Unexpected configuration value: $e")
}
.flatMap(_.as[A](v.codec).liftTo[F])
.flatMap {
case Json.Null => none[A].pure[F]
case other => other.as[A](v.codec).liftTo[F].map(_.some)
}

def showMessage(
tpe: MessageType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ object LanguageServer {

private val getFormatterWidth: F[Int] = LanguageClient[F]
.configuration(ConfigurationValue.maxWidth)
.flatMap(_.liftTo[F](new Exception("couldn't find a formatter width")))

val formattingProvider: Uri => F[List[language.TextEdit]] = FormattingProvider.provider(
getFormatterWidth
Expand Down
63 changes: 37 additions & 26 deletions modules/lsp/src/main/scala/playground/lsp/ServerBuilder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ import cats.syntax.all.*
import fs2.compression.Compression
import fs2.io.file.Files
import fs2.io.net.Network
import org.http4s.Uri
import org.http4s.client.Client
import org.http4s.client.middleware.Logger
import org.http4s.ember.client.EmberClientBuilder
import org.http4s.headers.Authorization
import org.http4s.implicits.*
import playground.FileRunner
import playground.OperationRunner
import playground.ServiceIndex
Expand Down Expand Up @@ -43,19 +45,23 @@ object ServerBuilder {

implicit val stdlibRuntime: StdlibRuntime[F] = StdlibRuntime.instance[F]

val logger = Logger[F](
logHeaders = true,
logBody = true,
logAction = Some(msg =>
LanguageClient[F].logOutput(msg.linesWithSeparators.map("// " + _).mkString)
),
)

val makeClient = EmberClientBuilder
.default[F]
.build
.map(middleware.BaseUri[F])
.map(middleware.AuthorizationHeader[F])
.map(
Logger[F](
logHeaders = true,
logBody = true,
logAction = Some(msg =>
LanguageClient[F].logOutput(msg.linesWithSeparators.map("// " + _).mkString)
),
)
// logger gets applied first, so that it sees the final result
identity[Client[F]]
.compose(middleware.BaseUri[F])
.compose(middleware.AuthorizationHeader[F])
.compose(logger)
)

for {
Expand Down Expand Up @@ -101,21 +107,26 @@ object ServerBuilder {

def BaseUri[F[_]: LanguageClient: MonadCancelThrow]: Client[F] => Client[F] =
client =>
Client[F] { req =>
LanguageClient[F].configuration(ConfigurationValue.baseUri).toResource.flatMap { uri =>
client.run(
req.withUri(
req
.uri
.copy(
scheme = uri.scheme,
authority = uri.authority,
// prefixing with uri.path
path = uri.path.addSegments(req.uri.path.segments),
)
)
)
}
Client[F] {
case req if req.uri != uri"/" => client.run(req)
case req =>
LanguageClient[F].configuration(ConfigurationValue.baseUri).toResource.flatMap {
case None => client.run(req)
case Some(uri) =>
client.run(
req
.withUri(
req
.uri
.copy(
scheme = uri.scheme,
authority = uri.authority,
// prefixing with uri.path
path = uri.path.addSegments(req.uri.path.segments),
)
)
)
}
}

def AuthorizationHeader[F[_]: LanguageClient: MonadCancelThrow]: Client[F] => Client[F] =
Expand All @@ -125,8 +136,8 @@ object ServerBuilder {
LanguageClient[F]
.configuration(ConfigurationValue.authorizationHeader)
.flatMap {
case v if v.trim.isEmpty() => request.pure[F]
case v => Authorization.parse(v).liftTo[F].map(request.putHeaders(_))
case None => request.pure[F]
case Some(v) => Authorization.parse(v).liftTo[F].map(request.putHeaders(_))
}
.toResource

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package playground.lsp.harness

import cats.data.Chain
import cats.data.OptionT
import cats.effect.IO
import cats.effect.IOLocal
import cats.syntax.all.*
Expand Down Expand Up @@ -77,10 +78,14 @@ object TestClient {

def configuration[A](
v: ConfigurationValue[A]
): IO[A] = state
.get
.flatMap(_.configuration.get(v.key).liftTo[IO](new Throwable(s"key not found: ${v.key}")))
.flatMap(_.as[A](v.codec).liftTo[IO])
): IO[Option[A]] =
OptionT {
state
.get
.map(_.configuration.get(v.key))
}
.semiflatMap(_.as[A](v.codec).liftTo[IO])
.value

def showMessage(
tpe: MessageType,
Expand Down
4 changes: 2 additions & 2 deletions vscode-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,12 @@
"type": "string",
"format": "uri",
"description": "The base URL of the HTTP server to use for running queries.",
"default": "http://localhost:8080"
"default": null
},
"smithyql.http.authorizationHeader": {
"type": "string",
"description": "The value of the Authorization header to use for running queries. Empty/whitespace strings will result in no header.",
"default": ""
"default": null
},
"smithyql.server.artifact": {
"type": "string",
Expand Down
Loading