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

LSP support #54

Merged
merged 29 commits into from
Jul 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
ca4c68f
wip
kubukoz Apr 8, 2022
599ba9f
Merge branch 'main' into lsp
kubukoz May 14, 2022
24efca3
Initial support for formatting via LSP
kubukoz May 14, 2022
a994c66
Rename server class
kubukoz May 14, 2022
4991230
error handling in formatter
kubukoz May 14, 2022
64fdf30
Merge branch 'main' into lsp
kubukoz Jul 22, 2022
d230c5a
Clean up formatting support via LSP
kubukoz Jul 23, 2022
74ffe0a
Bring back non-LSP functionality
kubukoz Jul 23, 2022
234fcfe
Implement completions via LSP
kubukoz Jul 27, 2022
a738114
Remove js completions
kubukoz Jul 27, 2022
f679282
Move diagnostic code to core
kubukoz Jul 27, 2022
1c2805b
Implement diagnostics in LSP
kubukoz Jul 27, 2022
7604099
Move lenses to LSP
kubukoz Jul 27, 2022
ad415f1
Cleanup utils, add auth middleware to LSP client
kubukoz Jul 27, 2022
69f93a2
Initial support for running via LSP + remove all extension customization
kubukoz Jul 27, 2022
91499a5
cleanup
kubukoz Jul 27, 2022
59ed8f9
Remove node dep
kubukoz Jul 27, 2022
a4d11ff
Fix parsing ranges
kubukoz Jul 27, 2022
a134f67
add todo
kubukoz Jul 27, 2022
0154c4e
Cleanup
kubukoz Jul 27, 2022
6abe585
Implement missing logs
kubukoz Jul 27, 2022
7be57f5
Revamp configuration API
kubukoz Jul 27, 2022
6b40e0b
Add custom extensions for output panel & command runner
kubukoz Jul 27, 2022
dd48631
Unhardcode workspace paths
kubukoz Jul 27, 2022
8018181
Prepare for version/artifact replacement
kubukoz Jul 27, 2022
7c38d9f
Drop scala.js
kubukoz Jul 27, 2022
360b3d1
work on publishing
kubukoz Jul 28, 2022
038dc76
Merge branch 'main' into lsp
kubukoz Jul 28, 2022
b136b14
run yarn first
kubukoz Jul 28, 2022
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
10 changes: 9 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ on:
push:
branches: ['**']
tags: ['**']

env:
PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }}
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
PGP_SECRET: ${{ secrets.PGP_SECRET }}

jobs:
build:
name: "Build"
Expand All @@ -28,7 +35,8 @@ jobs:
~/AppData/Local/Coursier/Cache/v1
~/Library/Caches/Coursier/v1
key: ${{ runner.os }}-sbt-cache-v2-${{ hashFiles('**/*.sbt') }}-${{ hashFiles('project/build.properties') }}
- run: nix develop --command sbt test fastLinkJS
- run: nix develop --command bash -c 'cd vscode-extension && yarn && yarn compile'
- run: nix develop --command sbt test
-
name: release
if: startsWith(github.ref, 'refs/tags/v')
Expand Down
12 changes: 10 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,23 @@
"configurations": [
{
"args": ["--extensionDevelopmentPath=${workspaceFolder}"],
"name": "Launch Extension",
"name": "Client",
"outFiles": [
"${workspaceFolder}/vscode-extension/out/*.js",
"${workspaceFolder}/vscode-extension/out/*.js.map"
],
"preLaunchTask": "fastOptJS",
// "preLaunchTask": "build",
"request": "launch",
"type": "extensionHost",
"sourceMaps": true
},
{
"type": "scala",
"request": "attach",
"name": "Server",
"buildTarget": "lsp",
"hostName": "localhost",
"port": 5005
}
]
}
9 changes: 6 additions & 3 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
"version": "2.0.0",
"tasks": [
{
"command": "sbtn",
"args": ["fastOptJS"],
"label": "fastOptJS"
"command": "bash",
"args": [
"-c",
"(cd vscode-extension && yarn compile) && sbtn publishLocal"
],
"label": "build"
}
]
}
96 changes: 48 additions & 48 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,41 +1,63 @@
inThisBuild(
List(
organization := "com.kubukoz",
homepage := Some(url("https://github.com/kubukoz/smithy-playground")),
licenses := List("Apache-2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0")),
developers := List(
Developer(
"kubukoz",
"Jakub Kozłowski",
"kubukoz@gmail.com",
url("https://kubukoz.com"),
)
),
)
)

import scala.sys.process._

def crossPlugin(x: sbt.librarymanagement.ModuleID) = compilerPlugin(x.cross(CrossVersion.full))

val compilerPlugins = List(
crossPlugin("org.polyvariant" % "better-tostring" % "0.3.15"),
crossPlugin("org.typelevel" % "kind-projector" % "0.13.2"),
)
val compilerPlugins =
libraryDependencies ++= List(
crossPlugin("org.polyvariant" % "better-tostring" % "0.3.15")
) ++ (if (scalaVersion.value.startsWith("3"))
Nil
else
List(
crossPlugin("org.typelevel" % "kind-projector" % "0.13.2")
))
Comment on lines +21 to +29
Copy link
Owner Author

Choose a reason for hiding this comment

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

Thought I was gonna do the LSP in Scala 3, but I was getting too annoyed by the tooling.


ThisBuild / versionScheme := Some("early-semver")

Global / onChangedBuildSource := ReloadOnSourceChanges

val commonScalaVersions = Seq("2.13.8")
ThisBuild / scalaVersion := "2.13.8"
ThisBuild / crossScalaVersions := Seq("2.13.8")

val commonSettings = Seq(
organization := "com.kubukoz.playground",
libraryDependencies ++= Seq(
"org.typelevel" %%% "cats-effect" % "3.3.14",
"com.disneystreaming" %%% "weaver-cats" % "0.7.13" % Test,
"com.disneystreaming" %%% "weaver-discipline" % "0.7.13" % Test,
"com.disneystreaming" %%% "weaver-scalacheck" % "0.7.13" % Test,
"org.typelevel" %% "cats-effect" % "3.3.14",
"com.disneystreaming" %% "weaver-cats" % "0.7.13" % Test,
"com.disneystreaming" %% "weaver-discipline" % "0.7.13" % Test,
"com.disneystreaming" %% "weaver-scalacheck" % "0.7.13" % Test,
),
testFrameworks += new TestFramework("weaver.framework.CatsEffect"),
libraryDependencies ++= compilerPlugins,
compilerPlugins,
scalacOptions -= "-Xfatal-warnings",
scalacOptions -= "-Vtype-diffs",
scalacOptions ++= Seq("-Xsource:3.0"),
)

lazy val core = projectMatrix
lazy val core = project
.settings(
libraryDependencies ++= Seq(
"com.disneystreaming.smithy4s" %%% "smithy4s-dynamic" % smithy4sVersion.value,
"com.disneystreaming.smithy4s" %%% "smithy4s-http4s" % smithy4sVersion.value,
"com.disneystreaming.smithy4s" %%% "smithy4s-aws-http4s" % smithy4sVersion.value,
"org.typelevel" %%% "cats-parse" % "0.3.8",
"org.typelevel" %%% "paiges-cats" % "0.4.2",
"com.disneystreaming.smithy4s" %% "smithy4s-dynamic" % smithy4sVersion.value,
"com.disneystreaming.smithy4s" %% "smithy4s-http4s" % smithy4sVersion.value,
"com.disneystreaming.smithy4s" %% "smithy4s-aws-http4s" % smithy4sVersion.value,
"org.typelevel" %% "cats-parse" % "0.3.8",
"org.typelevel" %% "paiges-cats" % "0.4.2",
),
commonSettings,
buildInfoPackage := "playground.buildinfo",
Expand All @@ -44,57 +66,35 @@ lazy val core = projectMatrix
),
Smithy4sCodegenPlugin.defaultSettings(Test),
)
.jvmPlatform(commonScalaVersions)
.jsPlatform(
commonScalaVersions,
Seq(
libraryDependencies += "org.scala-js" %%% "scalajs-java-securerandom" % "1.0.0"
),
)
.enablePlugins(Smithy4sCodegenPlugin)
.enablePlugins(BuildInfoPlugin)

lazy val vscode = projectMatrix
.in(file("vscode-extension"))
lazy val lsp = project
.settings(
crossScalaVersions := commonScalaVersions,
moduleName := "smithy-playground-vscode",
libraryDependencies ++= Seq(
"org.http4s" %%% "http4s-ember-client" % "0.23.14"
"com.disneystreaming.smithy4s" %% "smithy4s-codegen-cli" % smithy4sVersion.value,
"org.eclipse.lsp4j" % "org.eclipse.lsp4j" % "0.14.0",
"io.circe" %% "circe-core" % "0.14.2",
"org.http4s" %% "http4s-ember-client" % "0.23.14",
),
commonSettings,
)
.enablePlugins(ScalablyTypedConverterExternalNpmPlugin)
.enablePlugins(JavaAppPackaging)
.dependsOn(core)
.jsPlatform(
commonScalaVersions,
Seq(
externalNpm := {
Process(
List("yarn", "--cwd", ((ThisBuild / baseDirectory).value / "vscode-extension").toString)
).!
(ThisBuild / baseDirectory).value / "vscode-extension"
},
Compile / fastOptJS / artifactPath := (ThisBuild / baseDirectory).value / "vscode-extension" / "out" / "extension.js",
Compile / fullOptJS / artifactPath := (ThisBuild / baseDirectory).value / "vscode-extension" / "out" / "extension.js",
test := {},
scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.CommonJSModule) },
),
)

lazy val cli = projectMatrix
lazy val cli = project
.in(file("cli"))
.settings(
commonSettings,
libraryDependencies ++= Seq(
"org.http4s" %%% "http4s-ember-client" % "0.23.14",
"org.http4s" %% "http4s-ember-client" % "0.23.14",
"com.monovore" %% "decline-effect" % "2.3.0",
"com.disneystreaming.smithy4s" %% "smithy4s-codegen-cli" % smithy4sVersion.value,
),
)
.dependsOn(core)
.jvmPlatform(commonScalaVersions)

lazy val root = project
.in(file("."))
.aggregate(List(core, vscode, cli).flatMap(_.projectRefs): _*)
.settings(publish / skip := true)
.aggregate(core, cli, lsp)
48 changes: 48 additions & 0 deletions core/src/main/scala/playground/CodeLensProvider.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package playground

import playground.smithyql.SourceRange
import playground.smithyql.SmithyQLParser
import cats.implicits._
import types._

trait CodeLensProvider[F[_]] {
def provide(documentUri: String, documentText: String): List[CodeLens]
}

object CodeLensProvider {

def instance[F[_]](
compiler: Compiler[IorThrow],
runner: Runner.Optional[F],
): CodeLensProvider[F] =
new CodeLensProvider[F] {

def provide(documentUri: String, documentText: String): List[CodeLens] =
SmithyQLParser.parseFull(documentText) match {
case Right(parsed) if runner.get(parsed).toEither.isRight =>
compiler
.compile(parsed)
.as {
CodeLens(
range = parsed.operationName.range,
Command(
title = "Run query",
command = Command.RUN_QUERY,
args = documentUri :: Nil,
),
)
}
.toList
case _ => Nil
}

}

}

case class CodeLens(range: SourceRange, command: Command)
case class Command(title: String, command: String, args: List[String])

object Command {
val RUN_QUERY: String = "smithyql.runQuery"
}
123 changes: 123 additions & 0 deletions core/src/main/scala/playground/CommandProvider.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package playground

import cats.MonadThrow
import cats.implicits._
import playground.smithyql.Formatter
import playground.smithyql.InputNode
import playground.smithyql.SmithyQLParser
import playground.smithyql.WithSource

import java.util.concurrent.atomic.AtomicInteger
import scala.collection.immutable.ListMap

trait Feedback[F[_]] {
def showErrorMessage(msg: String): F[Unit]
def showOutputPanel: F[Unit]
def logOutput(msg: String): F[Unit]
}

object Feedback {
def apply[F[_]](implicit F: Feedback[F]): Feedback[F] = F
}

trait CommandProvider[F[_]] {
def runCommand(name: String, args: List[String]): F[Unit]
}

object CommandProvider {

def instance[F[_]: MonadThrow: TextDocumentProvider: Feedback](
compiler: Compiler[F],
runner: Runner.Optional[F],
): CommandProvider[F] =
new CommandProvider[F] {
// todo mutability
private val requestCount = new AtomicInteger(0)

private def runQuery(documentUri: String): F[Unit] = TextDocumentProvider[F]
.get(documentUri)
.flatMap { documentText =>
SmithyQLParser
.parseFull(documentText)
.liftTo[F]
.flatMap { parsed =>
runner
.get(parsed)
.toEither
.leftMap(Runner.Issue.squash(_))
.leftMap {
case Left(protocols) =>
Feedback[F].showErrorMessage(
s"""The service uses an unsupported protocol.
|Supported protocols: ${protocols
.supported
.map(_.show)
.mkString_(", ")}
|Found protocols: ${protocols
.found
.map(_.show)
.mkString(", ")}""".stripMargin
)

case Right(others) =>
Feedback[F].showErrorMessage(
others.map(_.toString).mkString_("\n\n")
)
}
.map { runner =>
compiler
.compile(parsed)
.flatMap { compiled =>
val requestId = requestCount.addAndGet(1)

Feedback[F].showOutputPanel *>
Feedback[F].logOutput(
s"// Calling ${parsed.operationName.value.text} ($requestId)"
) *>
runner
.run(compiled)
.onError { case e =>
val rendered =
compiled
.catchError(e)
.flatMap(err => compiled.writeError.map(_.toNode(err))) match {
case Some(e) => "\n" + writeOutput(e)
case None => e.toString
}

Feedback[F].logOutput(s"// ERROR ($requestId) $rendered")
}
.flatMap { out =>
Feedback[F].logOutput(
s"// Succeeded ${parsed.operationName.value.text} ($requestId), response:\n"
+ writeOutput(out)
)
}
}
}
.merge
}
}

private def writeOutput(
node: InputNode[cats.Id]
) = Formatter.writeAst(node.mapK(WithSource.liftId)).renderTrim(80)

private val commandMap: Map[String, List[String] => F[Unit]] = ListMap(
Command.RUN_QUERY -> {
case documentUri :: Nil => runQuery(documentUri)
case s => new Throwable("Unsupported arguments: " + s).raiseError[F, Unit]
}
)

def runCommand(
name: String,
args: List[String],
): F[Unit] = commandMap
.get(name)
.liftTo[F](new Throwable("Unsupported command: " + name))
.flatMap(_.apply(args))

}

}
2 changes: 1 addition & 1 deletion core/src/main/scala/playground/CompletionProvider.scala
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ object CompletionProvider {

case Right(q) =>
val matchingNode = WithSource.atPosition(q)(pos)
println("matchingNode: " + matchingNode.map(_.render))
// println("matchingNode: " + matchingNode.map(_.render))

val serviceIdOpt =
MultiServiceResolver
Expand Down
Loading