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

New Language Server API Implementations / Mocks #1875

Merged
merged 27 commits into from
Jul 17, 2021
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Enso Next

## Tooling

- Implement parts of the new Language Server API related to library support
([#1875](https://github.com/enso-org/enso/pull/1875)). Parts of the API are
still mocked, but they are supported too for testing purposes.
radeusgd marked this conversation as resolved.
Show resolved Hide resolved

# Enso 0.2.14 (2021-07-15)

## Interpreter/Runtime
Expand Down
19 changes: 19 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,20 @@ lazy val cli = project
Test / parallelExecution := false
)

lazy val `task-progress-notifications` = project
radeusgd marked this conversation as resolved.
Show resolved Hide resolved
.in(file("lib/scala/task-progress-notifications"))
.configs(Test)
.settings(
version := "0.1",
libraryDependencies ++= Seq(
"com.beachape" %% "enumeratum-circe" % enumeratumCirceVersion,
"org.scalatest" %% "scalatest" % scalatestVersion % Test
),
Test / parallelExecution := false
)
.dependsOn(cli)
.dependsOn(`json-rpc-server`)

lazy val `version-output` = (project in file("lib/scala/version-output"))
.settings(
version := "0.1"
Expand Down Expand Up @@ -798,6 +812,7 @@ lazy val `project-manager` = (project in file("lib/scala/project-manager"))
.dependsOn(`version-output`)
.dependsOn(editions)
.dependsOn(cli)
.dependsOn(`task-progress-notifications`)
.dependsOn(`polyglot-api`)
.dependsOn(`runtime-version-manager`)
.dependsOn(`library-manager`)
Expand Down Expand Up @@ -977,6 +992,7 @@ lazy val `language-server` = (project in file("engine/language-server"))
),
Test / testOptions += Tests
.Argument(TestFrameworks.ScalaCheck, "-minSuccessfulTests", "1000"),
Test / envVars ++= distributionEnvironmentOverrides,
GenerateFlatbuffers.flatcVersion := flatbuffersVersion,
Compile / sourceGenerators += GenerateFlatbuffers.task
)
Expand All @@ -991,13 +1007,16 @@ lazy val `language-server` = (project in file("engine/language-server"))
)
.dependsOn(`json-rpc-server-test` % Test)
.dependsOn(`json-rpc-server`)
.dependsOn(`task-progress-notifications`)
.dependsOn(`library-manager`)
.dependsOn(`logging-service`)
.dependsOn(`polyglot-api`)
.dependsOn(`searcher`)
.dependsOn(`text-buffer`)
.dependsOn(`version-output`)
.dependsOn(pkg)
.dependsOn(testkit % Test)
.dependsOn(`runtime-version-manager-test` % Test)

lazy val ast = (project in file("lib/scala/ast"))
.settings(
Expand Down
4 changes: 1 addition & 3 deletions docs/language-server/protocol-language-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -4014,13 +4014,11 @@ the repositories and include them in the result as well.
> available. In the future it should emit warnings using proper notification
> channels.

The `update` field is optional and if it is not provided, it defaults to false.

#### Parameters

```typescript
{
update?: Boolean;
update: Boolean;
}
```

Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,26 @@
package org.enso.languageserver.boot

import java.io.File
import java.net.URI
import java.time.Clock

import akka.actor.ActorSystem
import org.enso.distribution.{
DistributionManager,
EditionManager,
Environment,
LanguageHome
}
import org.enso.editions.EditionResolver
import org.enso.jsonrpc.JsonRpcServer
import org.enso.languageserver.boot.DeploymentType.{Azure, Desktop}
import org.enso.languageserver.capability.CapabilityRouter
import org.enso.languageserver.data._
import org.enso.languageserver.effect.ZioExec
import org.enso.languageserver.filemanager.{
ContentRoot,
ContentRootManager,
ContentRootManagerActor,
ContentRootManagerWrapper,
ContentRootWithFile,
FileManager,
FileSystem,
ReceivesTreeUpdatesHandler
}
import org.enso.languageserver.filemanager._
import org.enso.languageserver.http.server.BinaryWebSocketServer
import org.enso.languageserver.io._
import org.enso.languageserver.libraries.{
EditionReferenceResolver,
LocalLibraryManager,
ProjectSettingsManager
}
import org.enso.languageserver.monitoring.{
HealthCheckEndpoint,
IdlenessEndpoint,
Expand Down Expand Up @@ -49,6 +48,9 @@ import org.graalvm.polyglot.Context
import org.graalvm.polyglot.io.MessageEndpoint
import org.slf4j.LoggerFactory

import java.io.File
import java.net.URI
import java.time.Clock
import scala.concurrent.duration._

/** A main module containing all components of the server.
Expand Down Expand Up @@ -270,20 +272,48 @@ class MainModule(serverConfig: LanguageServerConfig, logLevel: LogLevel) {
context
)(system.dispatcher)

val environment = new Environment {}
val languageHome = LanguageHome.detectFromExecutableLocation(environment)
val distributionManager = new DistributionManager(environment)

val editionProvider =
EditionManager.makeEditionProvider(distributionManager, Some(languageHome))
val editionResolver = EditionResolver(editionProvider)
val editionReferenceResolver = new EditionReferenceResolver(
contentRoot.file,
editionProvider,
editionResolver
)
val editionManager = EditionManager(distributionManager, Some(languageHome))

val projectSettingsManager = system.actorOf(
ProjectSettingsManager.props(contentRoot.file, editionResolver),
"project-settings-manager"
)

val localLibraryManager = system.actorOf(
LocalLibraryManager.props(contentRoot.file, distributionManager),
"local-library-manager"
)

val jsonRpcControllerFactory = new JsonConnectionControllerFactory(
initializationComponent,
bufferRegistry,
capabilityRouter,
fileManager,
contentRootManagerActor,
contextRegistry,
suggestionsHandler,
stdOutController,
stdErrController,
stdInController,
runtimeConnector,
idlenessMonitor,
languageServerConfig
mainComponent = initializationComponent,
bufferRegistry = bufferRegistry,
capabilityRouter = capabilityRouter,
fileManager = fileManager,
contentRootManager = contentRootManagerActor,
contextRegistry = contextRegistry,
suggestionsHandler = suggestionsHandler,
stdOutController = stdOutController,
stdErrController = stdErrController,
stdInController = stdInController,
runtimeConnector = runtimeConnector,
idlenessMonitor = idlenessMonitor,
projectSettingsManager = projectSettingsManager,
localLibraryManager = localLibraryManager,
editionReferenceResolver = editionReferenceResolver,
editionManager = editionManager,
config = languageServerConfig
)
log.trace(
"Created JSON connection controller factory [{}].",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package org.enso.languageserver.libraries

import io.circe.{Decoder, DecodingFailure, Encoder, Json}
import io.circe.syntax._
import io.circe.generic.auto._

/** A reference to an edition - either a named edition or an unnamed one
* associated with the current project.
*/
sealed trait EditionReference
object EditionReference {

/** An edition identified by its name. */
case class NamedEdition(editionName: String) extends EditionReference

/** The edition associated with the current project. */
case object CurrentProjectEdition extends EditionReference

object CodecField {
val Type = "type"
val EditionName = "editionName"
}

object CodecType {
val NamedEdition = "NamedEdition"
val CurrentProjectEdition = "CurrentProjectEdition"
}

implicit val encoder: Encoder[EditionReference] = {
case NamedEdition(editionName) =>
Json.obj(
CodecField.Type -> CodecType.NamedEdition.asJson,
CodecField.EditionName -> editionName.asJson
)
case CurrentProjectEdition =>
Json.obj(CodecField.Type -> CodecType.CurrentProjectEdition.asJson)
}

implicit val decoder: Decoder[EditionReference] = { json =>
val typeCursor = json.downField(CodecField.Type)
typeCursor.as[String].flatMap {
case CodecType.NamedEdition =>
Decoder[NamedEdition].tryDecode(json)
case CodecType.CurrentProjectEdition => Right(CurrentProjectEdition)
case unknownType =>
Left(
DecodingFailure(
s"Unknown EditionReference type [$unknownType].",
typeCursor.history
)
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package org.enso.languageserver.libraries

import org.enso.editions.provider.EditionProvider
import org.enso.editions.{DefaultEdition, EditionResolver, Editions}
import org.enso.languageserver.libraries.EditionReference.NamedEdition
import org.enso.pkg.PackageManager

import java.io.File
import scala.util.Try

/** Resolves [[EditionReference]] to a raw or resolved edition. */
class EditionReferenceResolver(
projectRoot: File,
editionProvider: EditionProvider,
editionResolver: EditionResolver
) {
private lazy val projectPackage =
PackageManager.Default.loadPackage(projectRoot).get

/** Loads the raw edition corresponding to the given [[EditionReference]]. */
def resolveReference(
editionReference: EditionReference
): Try[Editions.RawEdition] = editionReference match {
case EditionReference.NamedEdition(editionName) =>
editionProvider.findEditionForName(editionName)
case EditionReference.CurrentProjectEdition =>
Try {
projectPackage.config.edition.getOrElse {
// TODO [RW] default edition from config (#1864)
DefaultEdition.getDefaultEdition
}
}
}

/** Resolves all edition dependencies of an edition identified by
* [[EditionReference]].
*/
def resolveEdition(
editionReference: EditionReference
): Try[Editions.ResolvedEdition] = for {
raw <- resolveReference(editionReference)
resolved <- editionResolver.resolve(raw).toTry
} yield resolved

/** Resolves all edition dependencies of an edition identified by its name. */
def resolveEdition(name: String): Try[Editions.ResolvedEdition] =
resolveEdition(NamedEdition(name))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package org.enso.languageserver.libraries

import org.enso.cli.task.{
ProgressReporter,
ProgressUnit,
TaskProgress,
TaskProgressImplementation
}

import scala.util.Success

/** A temporary helper for mocked parts of the API.
*
* It should be removed soon, when the missing parts are implemented.
*/
object FakeDownload {

/** Creates a [[TaskProgress]] which reports progress updates for a few seconds.
*
* Intended for mocking download-like endpoints.
*/
def make(seconds: Int = 10): TaskProgress[Unit] = {
val tracker = new TaskProgressImplementation[Unit](ProgressUnit.Bytes)
val thread = new Thread(() => {
val n = (seconds * 10).toLong
for (i <- 0L to n) {
tracker.reportProgress(i, Some(n))
Thread.sleep(100)
}
tracker.setComplete(Success(()))
})
thread.start()
tracker
}

/** Simulates a download operation reporting progress updates to the
* [[ProgressReporter]].
*/
def simulateDownload(
message: String,
progressReporter: ProgressReporter,
seconds: Int = 10
): Unit = {
val download = make(seconds = seconds)
progressReporter.trackProgress(message, download)
download.force()
}
}