New Language Server API Implementations / Mocks (#1875)

This commit is contained in:
Radosław Waśko 2021-07-17 16:49:51 +02:00 committed by GitHub
parent 4235d345aa
commit 86fcd86055
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
79 changed files with 2911 additions and 466 deletions

View File

@ -1,5 +1,12 @@
# Enso Next # 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 internally, but they are supported externally for testing
purposes.
# Enso 0.2.14 (2021-07-15) # Enso 0.2.14 (2021-07-15)
## Interpreter/Runtime ## Interpreter/Runtime

View File

@ -233,6 +233,7 @@ lazy val enso = (project in file("."))
logger.jvm, logger.jvm,
pkg, pkg,
cli, cli,
`task-progress-notifications`,
`logging-utils`, `logging-utils`,
`logging-service`, `logging-service`,
`akka-native`, `akka-native`,
@ -716,6 +717,20 @@ lazy val cli = project
Test / parallelExecution := false Test / parallelExecution := false
) )
lazy val `task-progress-notifications` = project
.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")) lazy val `version-output` = (project in file("lib/scala/version-output"))
.settings( .settings(
version := "0.1" version := "0.1"
@ -798,6 +813,7 @@ lazy val `project-manager` = (project in file("lib/scala/project-manager"))
.dependsOn(`version-output`) .dependsOn(`version-output`)
.dependsOn(editions) .dependsOn(editions)
.dependsOn(cli) .dependsOn(cli)
.dependsOn(`task-progress-notifications`)
.dependsOn(`polyglot-api`) .dependsOn(`polyglot-api`)
.dependsOn(`runtime-version-manager`) .dependsOn(`runtime-version-manager`)
.dependsOn(`library-manager`) .dependsOn(`library-manager`)
@ -977,6 +993,7 @@ lazy val `language-server` = (project in file("engine/language-server"))
), ),
Test / testOptions += Tests Test / testOptions += Tests
.Argument(TestFrameworks.ScalaCheck, "-minSuccessfulTests", "1000"), .Argument(TestFrameworks.ScalaCheck, "-minSuccessfulTests", "1000"),
Test / envVars ++= distributionEnvironmentOverrides,
GenerateFlatbuffers.flatcVersion := flatbuffersVersion, GenerateFlatbuffers.flatcVersion := flatbuffersVersion,
Compile / sourceGenerators += GenerateFlatbuffers.task Compile / sourceGenerators += GenerateFlatbuffers.task
) )
@ -991,6 +1008,8 @@ lazy val `language-server` = (project in file("engine/language-server"))
) )
.dependsOn(`json-rpc-server-test` % Test) .dependsOn(`json-rpc-server-test` % Test)
.dependsOn(`json-rpc-server`) .dependsOn(`json-rpc-server`)
.dependsOn(`task-progress-notifications`)
.dependsOn(`library-manager`)
.dependsOn(`logging-service`) .dependsOn(`logging-service`)
.dependsOn(`polyglot-api`) .dependsOn(`polyglot-api`)
.dependsOn(`searcher`) .dependsOn(`searcher`)
@ -998,6 +1017,7 @@ lazy val `language-server` = (project in file("engine/language-server"))
.dependsOn(`version-output`) .dependsOn(`version-output`)
.dependsOn(pkg) .dependsOn(pkg)
.dependsOn(testkit % Test) .dependsOn(testkit % Test)
.dependsOn(`runtime-version-manager-test` % Test)
lazy val ast = (project in file("lib/scala/ast")) lazy val ast = (project in file("lib/scala/ast"))
.settings( .settings(

View File

@ -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 > available. In the future it should emit warnings using proper notification
> channels. > channels.
The `update` field is optional and if it is not provided, it defaults to false.
#### Parameters #### Parameters
```typescript ```typescript
{ {
update?: Boolean; update: Boolean;
} }
``` ```

View File

@ -1,27 +1,26 @@
package org.enso.languageserver.boot package org.enso.languageserver.boot
import java.io.File
import java.net.URI
import java.time.Clock
import akka.actor.ActorSystem 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.jsonrpc.JsonRpcServer
import org.enso.languageserver.boot.DeploymentType.{Azure, Desktop} import org.enso.languageserver.boot.DeploymentType.{Azure, Desktop}
import org.enso.languageserver.capability.CapabilityRouter import org.enso.languageserver.capability.CapabilityRouter
import org.enso.languageserver.data._ import org.enso.languageserver.data._
import org.enso.languageserver.effect.ZioExec import org.enso.languageserver.effect.ZioExec
import org.enso.languageserver.filemanager.{ import org.enso.languageserver.filemanager._
ContentRoot,
ContentRootManager,
ContentRootManagerActor,
ContentRootManagerWrapper,
ContentRootWithFile,
FileManager,
FileSystem,
ReceivesTreeUpdatesHandler
}
import org.enso.languageserver.http.server.BinaryWebSocketServer import org.enso.languageserver.http.server.BinaryWebSocketServer
import org.enso.languageserver.io._ import org.enso.languageserver.io._
import org.enso.languageserver.libraries.{
EditionReferenceResolver,
LocalLibraryManager,
ProjectSettingsManager
}
import org.enso.languageserver.monitoring.{ import org.enso.languageserver.monitoring.{
HealthCheckEndpoint, HealthCheckEndpoint,
IdlenessEndpoint, IdlenessEndpoint,
@ -49,6 +48,9 @@ import org.graalvm.polyglot.Context
import org.graalvm.polyglot.io.MessageEndpoint import org.graalvm.polyglot.io.MessageEndpoint
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.io.File
import java.net.URI
import java.time.Clock
import scala.concurrent.duration._ import scala.concurrent.duration._
/** A main module containing all components of the server. /** A main module containing all components of the server.
@ -270,20 +272,48 @@ class MainModule(serverConfig: LanguageServerConfig, logLevel: LogLevel) {
context context
)(system.dispatcher) )(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( val jsonRpcControllerFactory = new JsonConnectionControllerFactory(
initializationComponent, mainComponent = initializationComponent,
bufferRegistry, bufferRegistry = bufferRegistry,
capabilityRouter, capabilityRouter = capabilityRouter,
fileManager, fileManager = fileManager,
contentRootManagerActor, contentRootManager = contentRootManagerActor,
contextRegistry, contextRegistry = contextRegistry,
suggestionsHandler, suggestionsHandler = suggestionsHandler,
stdOutController, stdOutController = stdOutController,
stdErrController, stdErrController = stdErrController,
stdInController, stdInController = stdInController,
runtimeConnector, runtimeConnector = runtimeConnector,
idlenessMonitor, idlenessMonitor = idlenessMonitor,
languageServerConfig projectSettingsManager = projectSettingsManager,
localLibraryManager = localLibraryManager,
editionReferenceResolver = editionReferenceResolver,
editionManager = editionManager,
config = languageServerConfig
) )
log.trace( log.trace(
"Created JSON connection controller factory [{}].", "Created JSON connection controller factory [{}].",

View File

@ -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
)
)
}
}
}

View File

@ -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))
}

View File

@ -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()
}
}

View File

@ -0,0 +1,235 @@
package org.enso.languageserver.libraries
import io.circe.Json
import io.circe.literal.JsonStringContext
import org.enso.editions.{LibraryName, LibraryVersion}
import org.enso.jsonrpc.{Error, HasParams, HasResult, Method, Unused}
object LibraryApi {
case object EditionsListAvailable extends Method("editions/listAvailable") {
self =>
case class Params(update: Boolean)
case class Result(editionNames: Seq[String])
implicit val hasParams = new HasParams[this.type] {
type Params = self.Params
}
implicit val hasResult = new HasResult[this.type] {
type Result = self.Result
}
}
case object EditionsResolve extends Method("editions/resolve") {
self =>
case class Params(edition: EditionReference)
case class Result(engineVersion: String)
implicit val hasParams = new HasParams[this.type] {
type Params = self.Params
}
implicit val hasResult = new HasResult[this.type] {
type Result = self.Result
}
}
case object EditionsGetProjectSettings
extends Method("editions/getProjectSettings") { self =>
case class Result(
parentEdition: Option[String],
preferLocalLibraries: Boolean
)
implicit val hasParams = new HasParams[this.type] {
type Params = Unused.type
}
implicit val hasResult = new HasResult[this.type] {
type Result = self.Result
}
}
case object EditionsSetParentEdition
extends Method("editions/setParentEdition") { self =>
case class Params(newEditionName: String)
case class Result(needsRestart: Option[Boolean])
implicit val hasParams = new HasParams[this.type] {
type Params = self.Params
}
implicit val hasResult = new HasResult[this.type] {
type Result = self.Result
}
}
case object EditionsSetLocalLibrariesPreference
extends Method("editions/setProjectLocalLibrariesPreference") { self =>
case class Params(preferLocalLibraries: Boolean)
case class Result(needsRestart: Option[Boolean])
implicit val hasParams = new HasParams[this.type] {
type Params = self.Params
}
implicit val hasResult = new HasResult[this.type] {
type Result = self.Result
}
}
case object EditionsListDefinedLibraries
extends Method("editions/listDefinedLibraries") { self =>
case class Params(edition: EditionReference)
case class Result(availableLibraries: Seq[LibraryEntry])
implicit val hasParams = new HasParams[this.type] {
type Params = self.Params
}
implicit val hasResult = new HasResult[this.type] {
type Result = self.Result
}
}
case object LibraryListLocal extends Method("library/listLocal") { self =>
case class Result(localLibraries: Seq[LibraryEntry])
implicit val hasParams = new HasParams[this.type] {
type Params = Unused.type
}
implicit val hasResult = new HasResult[this.type] {
type Result = self.Result
}
}
case object LibraryCreate extends Method("library/create") { self =>
case class Params(
namespace: String,
name: String,
authors: Seq[String],
maintainers: Seq[String],
license: String
)
implicit val hasParams = new HasParams[this.type] {
type Params = self.Params
}
implicit val hasResult = new HasResult[this.type] {
type Result = Unused.type
}
}
case object LibraryGetMetadata extends Method("library/getMetadata") { self =>
case class Params(namespace: String, name: String)
case class Result(description: Option[String], tagLine: Option[String])
implicit val hasParams = new HasParams[this.type] {
type Params = self.Params
}
implicit val hasResult = new HasResult[this.type] {
type Result = self.Result
}
}
case object LibrarySetMetadata extends Method("library/setMetadata") { self =>
case class Params(
namespace: String,
name: String,
description: Option[String],
tagLine: Option[String]
)
implicit val hasParams = new HasParams[this.type] {
type Params = self.Params
}
implicit val hasResult = new HasResult[this.type] {
type Result = Unused.type
}
}
case object LibraryPublish extends Method("library/publish") { self =>
case class Params(
namespace: String,
name: String,
authToken: String,
bumpVersionAfterPublish: Option[Boolean]
)
implicit val hasParams = new HasParams[this.type] {
type Params = self.Params
}
implicit val hasResult = new HasResult[this.type] {
type Result = Unused.type
}
}
case object LibraryPreinstall extends Method("library/preinstall") { self =>
case class Params(namespace: String, name: String)
implicit val hasParams = new HasParams[this.type] {
type Params = self.Params
}
implicit val hasResult = new HasResult[this.type] {
type Result = Unused.type
}
}
case class EditionNotFoundError(editionName: String)
extends Error(8001, s"Edition [$editionName] could not be found.") {
override def payload: Option[Json] = Some(
json""" { "editionName" : $editionName } """
)
}
case class LibraryAlreadyExists(libraryName: LibraryName)
extends Error(8002, s"Library [$libraryName] already exists.")
case class LibraryRepositoryAuthenticationError(reason: String)
extends Error(8003, s"Authentication failed: $reason.")
case class LibraryPublishError(reason: String)
extends Error(8004, s"Could not publish the library: $reason.")
case class LibraryUploadError(reason: String)
extends Error(8005, s"Could not upload the library: $reason.")
case class LibraryDownloadError(
name: LibraryName,
version: LibraryVersion,
reason: String
) extends Error(8006, s"Could not download the library: $reason.") {
override def payload: Option[Json] = Some(
json""" {
"namespace" : ${name.namespace},
"name" : ${name.name},
"version" : ${version.toString}
} """
)
}
case class LocalLibraryNotFound(libraryName: LibraryName)
extends Error(8007, s"Local library [$libraryName] has not been found.")
case class LibraryNotResolved(name: LibraryName)
extends Error(8008, s"Could not resolve [$name].") {
override def payload: Option[Json] = Some(
json""" {
"namespace" : ${name.namespace},
"name" : ${name.name}
} """
)
}
}

View File

@ -0,0 +1,91 @@
package org.enso.languageserver.libraries
import io.circe.generic.semiauto._
import io.circe.syntax._
import io.circe.{Decoder, DecodingFailure, Encoder, Json}
import org.enso.editions
/** An entry in library lists sent to the client.
*
* @param namespace namespace of the library
* @param name name of the library
* @param version version of the library
*/
case class LibraryEntry(
namespace: String,
name: String,
version: LibraryEntry.LibraryVersion
)
object LibraryEntry {
/** Version of a library. */
sealed trait LibraryVersion
/** A library version that references a locally editable version of the
* library.
*/
case object LocalLibraryVersion extends LibraryVersion
/** A library version that references a version of the library published in
* some repository.
*/
case class PublishedLibraryVersion(version: String, repositoryUrl: String)
extends LibraryVersion
/** Converts an instance of [[editions.LibraryVersion]] into one that is used
* in the Language Server protocol.
*/
implicit def convertLibraryVersion(
libraryVersion: editions.LibraryVersion
): LibraryVersion = libraryVersion match {
case editions.LibraryVersion.Local => LocalLibraryVersion
case editions.LibraryVersion.Published(version, repository) =>
PublishedLibraryVersion(version.toString, repository.url)
}
implicit val encoder: Encoder[LibraryEntry] = deriveEncoder[LibraryEntry]
implicit val decoder: Decoder[LibraryEntry] = deriveDecoder[LibraryEntry]
object CodecField {
val Type = "type"
val Version = "version"
val RepositoryUrl = "repositoryUrl"
}
object CodecType {
val LocalLibraryVersion = "LocalLibraryVersion"
val PublishedLibraryVersion = "PublishedLibraryVersion"
}
implicit val versionEncoder: Encoder[LibraryVersion] = {
case LocalLibraryVersion =>
Json.obj(CodecField.Type -> CodecType.LocalLibraryVersion.asJson)
case PublishedLibraryVersion(version, repositoryUrl) =>
Json.obj(
CodecField.Type -> CodecType.PublishedLibraryVersion.asJson,
CodecField.Version -> version.asJson,
CodecField.RepositoryUrl -> repositoryUrl.asJson
)
}
implicit val versionDecoder: Decoder[LibraryVersion] = { json =>
val typeCursor = json.downField(CodecField.Type)
typeCursor.as[String].flatMap {
case CodecType.LocalLibraryVersion =>
Right(LocalLibraryVersion)
case CodecType.PublishedLibraryVersion =>
for {
version <- json.get[String](CodecField.Version)
repositoryUrl <- json.get[String](CodecField.RepositoryUrl)
} yield PublishedLibraryVersion(version, repositoryUrl)
case unknownType =>
Left(
DecodingFailure(
s"Unknown LibraryVersion type [$unknownType].",
typeCursor.history
)
)
}
}
}

View File

@ -0,0 +1,123 @@
package org.enso.languageserver.libraries
import akka.actor.{Actor, Props}
import com.typesafe.scalalogging.LazyLogging
import org.enso.distribution.{DistributionManager, FileSystem}
import org.enso.editions.{Editions, LibraryName}
import org.enso.languageserver.libraries.LocalLibraryManagerProtocol._
import org.enso.librarymanager.local.LocalLibraryProvider
import org.enso.pkg.PackageManager
import java.io.File
import java.nio.file.Files
import scala.util.{Failure, Success, Try}
/** An Actor that manages local libraries. */
class LocalLibraryManager(
currentProjectRoot: File,
distributionManager: DistributionManager
) extends Actor
with LazyLogging {
override def receive: Receive = { case request: Request =>
request match {
case GetMetadata(_) =>
logger.warn(
"Getting local library metadata is currently not implemented."
)
sender() ! Success(GetMetadataResponse(None, None))
case SetMetadata(_, _, _) =>
logger.error(
"Setting local library metadata is currently not implemented."
)
sender() ! Failure(new NotImplementedError())
case ListLocalLibraries =>
sender() ! listLocalLibraries()
case Create(libraryName, authors, maintainers, license) =>
sender() ! createLibrary(libraryName, authors, maintainers, license)
case Publish(_, _, _) =>
logger.error("Publishing libraries is currently not implemented.")
sender() ! Failure(new NotImplementedError())
}
}
/** Creates a new local library project.
*
* The project is created in the first directory of the local library search
* path that is writable.
*/
private def createLibrary(
libraryName: LibraryName,
authors: Seq[String],
maintainers: Seq[String],
license: String
): Try[Unit] = Try {
// TODO [RW] modify protocol to be able to create Contact instances
val _ = (authors, maintainers)
// TODO [RW] make the exceptions more relevant
val possibleRoots = LazyList
.from(distributionManager.paths.localLibrariesSearchPaths)
.filter { path =>
Try { if (Files.notExists(path)) Files.createDirectories(path) }
Files.isWritable(path)
}
val librariesRoot = possibleRoots.headOption.getOrElse {
throw new RuntimeException(
"Cannot find a writable directory on local library path."
)
}
val libraryPath =
LocalLibraryProvider.resolveLibraryPath(librariesRoot, libraryName)
if (Files.exists(libraryPath)) {
throw new RuntimeException("Local library already exists")
}
PackageManager.Default.create(
libraryPath.toFile,
name = libraryName.name,
namespace = libraryName.namespace,
edition = findCurrentProjectEdition(),
license = license
)
}
/** Lists all local libraries. */
private def listLocalLibraries(): Try[ListLocalLibrariesResponse] = for {
libraryNames <- findLocalLibraries()
libraryEntries = libraryNames.distinct.map { name =>
LibraryEntry(name.namespace, name.name, LibraryEntry.LocalLibraryVersion)
}
} yield ListLocalLibrariesResponse(libraryEntries)
private def findLocalLibraries(): Try[Seq[LibraryName]] = Try {
for {
searchPathRoot <- distributionManager.paths.localLibrariesSearchPaths
namespaceDir <- FileSystem
.listDirectory(searchPathRoot)
.filter(Files.isDirectory(_))
nameDir <- FileSystem
.listDirectory(namespaceDir)
.filter(Files.isDirectory(_))
namespace = namespaceDir.getFileName.toString
name = nameDir.getFileName.toString
} yield LibraryName(namespace, name)
}
/** Finds the edition associated with the current project, if specified in its
* config.
*/
private def findCurrentProjectEdition(): Option[Editions.RawEdition] = {
val pkg = PackageManager.Default.loadPackage(currentProjectRoot).get
pkg.config.edition
}
}
object LocalLibraryManager {
def props(
currentProjectRoot: File,
distributionManager: DistributionManager
): Props = Props(
new LocalLibraryManager(currentProjectRoot, distributionManager)
)
}

View File

@ -0,0 +1,46 @@
package org.enso.languageserver.libraries
import org.enso.editions.LibraryName
object LocalLibraryManagerProtocol {
/** A top class representing any request to the [[LocalLibraryManager]]. */
sealed trait Request
/** A request to get metadata of a library. */
case class GetMetadata(libraryName: LibraryName) extends Request
/** Response to [[GetMetadata]]. */
case class GetMetadataResponse(
description: Option[String],
tagLine: Option[String]
)
/** A request to update metadata of a library. */
case class SetMetadata(
libraryName: LibraryName,
description: Option[String],
tagLine: Option[String]
) extends Request
/** A request to list local libraries. */
case object ListLocalLibraries extends Request
/** A response to [[ListLocalLibraries]]. */
case class ListLocalLibrariesResponse(libraries: Seq[LibraryEntry])
/** A request to create a new library project. */
case class Create(
libraryName: LibraryName,
authors: Seq[String],
maintainers: Seq[String],
license: String
) extends Request
/** A request to publish a library. */
case class Publish(
libraryName: LibraryName,
authToken: String,
bumpVersionAfterPublish: Boolean
) extends Request
}

View File

@ -0,0 +1,80 @@
package org.enso.languageserver.libraries
import akka.actor.{Actor, Props}
import org.enso.editions.{DefaultEdition, EditionResolver, Editions}
import org.enso.pkg.PackageManager
import java.io.File
import scala.util.Try
/** An Actor that manages edition-related settings of the current project. */
class ProjectSettingsManager(
projectRoot: File,
editionResolver: EditionResolver
) extends Actor {
import ProjectSettingsManager._
override def receive: Receive = { case request: Request =>
request match {
case GetSettings =>
sender() ! loadSettings()
case SetParentEdition(editionName) =>
sender() ! setParentEdition(editionName)
case SetPreferLocalLibraries(preferLocalLibraries) =>
sender() ! setPreferLocalLibraries(preferLocalLibraries)
}
}
private def loadSettings(): Try[SettingsResponse] = for {
pkg <- PackageManager.Default.loadPackage(projectRoot)
edition = pkg.config.edition.getOrElse(DefaultEdition.getDefaultEdition)
} yield SettingsResponse(edition.parent, pkg.config.preferLocalLibraries)
private def setParentEdition(editionName: String): Try[Unit] = for {
pkg <- PackageManager.Default.loadPackage(projectRoot)
newEdition = pkg.config.edition match {
case Some(edition) => edition.copy(parent = Some(editionName))
case None => Editions.Raw.Edition(parent = Some(editionName))
}
_ <- editionResolver.resolve(newEdition).toTry
updated = pkg.updateConfig { config =>
config.copy(edition = Some(newEdition))
}
_ <- updated.save()
} yield ()
private def setPreferLocalLibraries(
preferLocalLibraries: Boolean
): Try[Unit] = for {
pkg <- PackageManager.Default.loadPackage(projectRoot)
updated = pkg.updateConfig { config =>
config.copy(preferLocalLibraries = preferLocalLibraries)
}
_ <- updated.save()
} yield ()
}
object ProjectSettingsManager {
def props(projectRoot: File, editionResolver: EditionResolver): Props = Props(
new ProjectSettingsManager(projectRoot, editionResolver)
)
/** A request to the [[ProjectSettingsManager]]. */
sealed trait Request
/** A request to get the current project settings. */
case object GetSettings extends Request
/** Response to [[GetSettings]]. */
case class SettingsResponse(
parentEdition: Option[String],
preferLocalLibraries: Boolean
)
/** A request to set the parent edition for the project. */
case class SetParentEdition(editionName: String) extends Request
/** A request to set the local libraries preference. */
case class SetPreferLocalLibraries(preferLocalLibraries: Boolean)
extends Request
}

View File

@ -0,0 +1,81 @@
package org.enso.languageserver.libraries.handler
import akka.actor.{Actor, ActorRef, Cancellable, Props}
import com.typesafe.scalalogging.LazyLogging
import org.enso.jsonrpc.{Id, Request, ResponseError, ResponseResult}
import org.enso.languageserver.filemanager.FileManagerApi.FileSystemError
import org.enso.languageserver.libraries.LibraryApi._
import org.enso.languageserver.libraries.ProjectSettingsManager
import org.enso.languageserver.requesthandler.RequestTimeout
import org.enso.languageserver.util.UnhandledLogging
import scala.concurrent.duration.FiniteDuration
import scala.util.{Failure, Success}
/** A request handler for the `editions/getProjectSettings` endpoint.
*
* @param timeout request timeout
* @param projectSettingsManager a reference to the [[ProjectSettingsManager]]
*/
class EditionsGetProjectSettingsHandler(
timeout: FiniteDuration,
projectSettingsManager: ActorRef
) extends Actor
with LazyLogging
with UnhandledLogging {
import context.dispatcher
override def receive: Receive = requestStage
private def requestStage: Receive = {
case Request(EditionsGetProjectSettings, id, _) =>
projectSettingsManager ! ProjectSettingsManager.GetSettings
val cancellable =
context.system.scheduler.scheduleOnce(timeout, self, RequestTimeout)
context.become(responseStage(id, sender(), cancellable))
}
private def responseStage(
id: Id,
replyTo: ActorRef,
cancellable: Cancellable
): Receive = {
case RequestTimeout =>
replyTo ! RequestTimeout
context.stop(self)
case Success(settings: ProjectSettingsManager.SettingsResponse) =>
replyTo ! ResponseResult(
EditionsGetProjectSettings,
id,
EditionsGetProjectSettings.Result(
parentEdition = settings.parentEdition,
preferLocalLibraries = settings.preferLocalLibraries
)
)
cancellable.cancel()
context.stop(self)
case Failure(exception) =>
replyTo ! ResponseError(Some(id), FileSystemError(exception.getMessage))
cancellable.cancel()
context.stop(self)
}
}
object EditionsGetProjectSettingsHandler {
/** Creates a configuration object to create
* [[EditionsGetProjectSettingsHandler]].
*
* @param timeout request timeout
* @param projectSettingsManager a reference to the
* [[ProjectSettingsManager]]
*/
def props(timeout: FiniteDuration, projectSettingsManager: ActorRef): Props =
Props(
new EditionsGetProjectSettingsHandler(timeout, projectSettingsManager)
)
}

View File

@ -0,0 +1,52 @@
package org.enso.languageserver.libraries.handler
import akka.actor.{Actor, Props}
import com.typesafe.scalalogging.LazyLogging
import org.enso.distribution.EditionManager
import org.enso.jsonrpc.{Request, ResponseError, ResponseResult}
import org.enso.languageserver.filemanager.FileManagerApi.FileSystemError
import org.enso.languageserver.libraries.LibraryApi._
import org.enso.languageserver.util.UnhandledLogging
import scala.util.{Failure, Success, Try}
/** A request handler for the `editions/listAvailable` endpoint.
*
* It is a partial implementation - it already allows to list existing
* editions, but updating is not yet implemented.
*
* @param editionManager an edition manager instance
*/
class EditionsListAvailableHandler(editionManager: EditionManager)
extends Actor
with LazyLogging
with UnhandledLogging {
override def receive: Receive = {
case Request(EditionsListAvailable, id, _: EditionsListAvailable.Params) =>
// TODO [RW] once updating editions is implemented this should be made asynchronous
Try(editionManager.findAllAvailableEditions()) match {
case Success(editions) =>
sender() ! ResponseResult(
EditionsListAvailable,
id,
EditionsListAvailable.Result(editions)
)
case Failure(exception) =>
sender() ! ResponseError(
Some(id),
FileSystemError(exception.toString)
)
}
}
}
object EditionsListAvailableHandler {
/** Creates a configuration object to create [[EditionsListAvailableHandler]].
*
* @param editionManager an edition manager instance
*/
def props(editionManager: EditionManager): Props = Props(
new EditionsListAvailableHandler(editionManager)
)
}

View File

@ -0,0 +1,69 @@
package org.enso.languageserver.libraries.handler
import akka.actor.{Actor, Props}
import com.typesafe.scalalogging.LazyLogging
import org.enso.jsonrpc.{Request, ResponseError, ResponseResult}
import org.enso.languageserver.filemanager.FileManagerApi.FileSystemError
import org.enso.languageserver.libraries.{
EditionReferenceResolver,
LibraryEntry
}
import org.enso.languageserver.libraries.LibraryApi._
import org.enso.languageserver.util.UnhandledLogging
import scala.util.{Failure, Success}
/** A request handler for the `editions/listDefinedLibraries` endpoint.
*
* @param editionReferenceResolver an [[EditionReferenceResolver]] instance
*/
class EditionsListDefinedLibrariesHandler(
editionReferenceResolver: EditionReferenceResolver
) extends Actor
with LazyLogging
with UnhandledLogging {
override def receive: Receive = {
case Request(
EditionsListDefinedLibraries,
id,
EditionsListDefinedLibraries.Params(reference)
) =>
val result = for {
edition <- editionReferenceResolver.resolveEdition(reference)
} yield edition.getAllDefinedLibraries.toSeq.map { case (name, version) =>
LibraryEntry(
namespace = name.namespace,
name = name.name,
version = version
)
}
result match {
case Success(libraries) =>
sender() ! ResponseResult(
EditionsListDefinedLibraries,
id,
EditionsListDefinedLibraries.Result(libraries)
)
case Failure(exception) =>
// TODO [RW] more detailed errors
sender() ! ResponseError(
Some(id),
FileSystemError(exception.getMessage)
)
}
}
}
object EditionsListDefinedLibrariesHandler {
/** Creates a configuration object to create
* [[EditionsListDefinedLibrariesHandler]].
*
* @param editionReferenceResolver an [[EditionReferenceResolver]] instance
*/
def props(editionReferenceResolver: EditionReferenceResolver): Props = Props(
new EditionsListDefinedLibrariesHandler(editionReferenceResolver)
)
}

View File

@ -0,0 +1,54 @@
package org.enso.languageserver.libraries.handler
import akka.actor.{Actor, Props}
import com.typesafe.scalalogging.LazyLogging
import org.enso.jsonrpc.{Request, ResponseError, ResponseResult}
import org.enso.languageserver.filemanager.FileManagerApi.FileSystemError
import org.enso.languageserver.libraries.EditionReferenceResolver
import org.enso.languageserver.libraries.LibraryApi._
import org.enso.languageserver.util.UnhandledLogging
import scala.util.{Failure, Success}
/** A request handler for the `editions/resolve` endpoint.
*
* @param editionReferenceResolver an [[EditionReferenceResolver]] instance
*/
class EditionsResolveHandler(editionReferenceResolver: EditionReferenceResolver)
extends Actor
with LazyLogging
with UnhandledLogging {
override def receive: Receive = {
case Request(EditionsResolve, id, EditionsResolve.Params(reference)) =>
val result = for {
edition <- editionReferenceResolver.resolveEdition(reference)
} yield edition.getEngineVersion
result match {
case Success(engineVersion) =>
sender() ! ResponseResult(
EditionsResolve,
id,
EditionsResolve.Result(engineVersion.toString)
)
case Failure(exception) =>
// TODO [RW] more detailed errors
sender() ! ResponseError(
Some(id),
FileSystemError(exception.getMessage)
)
}
}
}
object EditionsResolveHandler {
/** Creates a configuration object to create [[EditionsResolveHandler]].
*
* @param editionReferenceResolver an [[EditionReferenceResolver]] instance
*/
def props(editionReferenceResolver: EditionReferenceResolver): Props = Props(
new EditionsResolveHandler(editionReferenceResolver)
)
}

View File

@ -0,0 +1,88 @@
package org.enso.languageserver.libraries.handler
import akka.actor.{Actor, ActorRef, Cancellable, Props}
import com.typesafe.scalalogging.LazyLogging
import org.enso.jsonrpc.{Id, Request, ResponseError, ResponseResult}
import org.enso.languageserver.filemanager.FileManagerApi.FileSystemError
import org.enso.languageserver.libraries.LibraryApi._
import org.enso.languageserver.libraries.ProjectSettingsManager
import org.enso.languageserver.requesthandler.RequestTimeout
import org.enso.languageserver.util.UnhandledLogging
import scala.concurrent.duration.FiniteDuration
import scala.util.{Failure, Success}
/** A request handler for the `editions/setParentEdition` endpoint.
*
* @param timeout request timeout
* @param projectSettingsManager a reference to the [[ProjectSettingsManager]]
*/
class EditionsSetParentEditionHandler(
timeout: FiniteDuration,
projectSettingsManager: ActorRef
) extends Actor
with LazyLogging
with UnhandledLogging {
import context.dispatcher
override def receive: Receive = requestStage
private def requestStage: Receive = {
case Request(
EditionsSetParentEdition,
id,
EditionsSetParentEdition.Params(newEditionName)
) =>
projectSettingsManager ! ProjectSettingsManager.SetParentEdition(
newEditionName
)
val cancellable =
context.system.scheduler.scheduleOnce(timeout, self, RequestTimeout)
context.become(responseStage(id, sender(), cancellable))
}
private def responseStage(
id: Id,
replyTo: ActorRef,
cancellable: Cancellable
): Receive = {
case RequestTimeout =>
replyTo ! RequestTimeout
context.stop(self)
case Success(_) =>
replyTo ! ResponseResult(
EditionsSetParentEdition,
id,
EditionsSetParentEdition.Result(needsRestart = Some(true))
)
cancellable.cancel()
context.stop(self)
case Failure(exception) =>
replyTo ! ResponseError(
Some(id),
FileSystemError(
s"Failed to update the settings: ${exception.getMessage}"
)
)
cancellable.cancel()
context.stop(self)
}
}
object EditionsSetParentEditionHandler {
/** Creates a configuration object to create
* [[EditionsSetParentEditionHandler]].
*
* @param timeout request timeout
* @param projectSettingsManager a reference to the
* [[ProjectSettingsManager]]
*/
def props(timeout: FiniteDuration, projectSettingsManager: ActorRef): Props =
Props(
new EditionsSetParentEditionHandler(timeout, projectSettingsManager)
)
}

View File

@ -0,0 +1,94 @@
package org.enso.languageserver.libraries.handler
import akka.actor.{Actor, ActorRef, Cancellable, Props}
import com.typesafe.scalalogging.LazyLogging
import org.enso.jsonrpc.{Id, Request, ResponseError, ResponseResult}
import org.enso.languageserver.filemanager.FileManagerApi.FileSystemError
import org.enso.languageserver.libraries.LibraryApi._
import org.enso.languageserver.libraries.ProjectSettingsManager
import org.enso.languageserver.requesthandler.RequestTimeout
import org.enso.languageserver.util.UnhandledLogging
import scala.concurrent.duration.FiniteDuration
import scala.util.{Failure, Success}
/** A request handler for the `editions/setProjectLocalLibrariesPreference`
* endpoint.
*
* @param timeout request timeout
* @param projectSettingsManager a reference to the [[ProjectSettingsManager]]
*/
class EditionsSetProjectLocalLibrariesPreferenceHandler(
timeout: FiniteDuration,
projectSettingsManager: ActorRef
) extends Actor
with LazyLogging
with UnhandledLogging {
import context.dispatcher
override def receive: Receive = requestStage
private def requestStage: Receive = {
case Request(
EditionsSetLocalLibrariesPreference,
id,
EditionsSetLocalLibrariesPreference.Params(preferLocalLibraries)
) =>
projectSettingsManager ! ProjectSettingsManager.SetPreferLocalLibraries(
preferLocalLibraries
)
val cancellable =
context.system.scheduler.scheduleOnce(timeout, self, RequestTimeout)
context.become(responseStage(id, sender(), cancellable))
}
private def responseStage(
id: Id,
replyTo: ActorRef,
cancellable: Cancellable
): Receive = {
case RequestTimeout =>
replyTo ! RequestTimeout
context.stop(self)
case Success(_) =>
replyTo ! ResponseResult(
EditionsSetLocalLibrariesPreference,
id,
EditionsSetLocalLibrariesPreference.Result(needsRestart = Some(true))
)
cancellable.cancel()
context.stop(self)
case Failure(exception) =>
replyTo ! ResponseError(
Some(id),
FileSystemError(
s"Failed to update the settings: ${exception.getMessage}"
)
)
cancellable.cancel()
context.stop(self)
}
}
object EditionsSetProjectLocalLibrariesPreferenceHandler {
/** Creates a configuration object to create
* [[EditionsSetProjectLocalLibrariesPreferenceHandler]].
*
* @param timeout request timeout
* @param projectSettingsManager a reference to the
* [[ProjectSettingsManager]]
*/
def props(
timeout: FiniteDuration,
projectSettingsManager: ActorRef
): Props = Props(
new EditionsSetProjectLocalLibrariesPreferenceHandler(
timeout,
projectSettingsManager
)
)
}

View File

@ -0,0 +1,81 @@
package org.enso.languageserver.libraries.handler
import akka.actor.{Actor, ActorRef, Cancellable, Props}
import com.typesafe.scalalogging.LazyLogging
import org.enso.editions.LibraryName
import org.enso.jsonrpc._
import org.enso.languageserver.filemanager.FileManagerApi.FileSystemError
import org.enso.languageserver.libraries.LibraryApi._
import org.enso.languageserver.libraries.LocalLibraryManagerProtocol
import org.enso.languageserver.requesthandler.RequestTimeout
import org.enso.languageserver.util.UnhandledLogging
import scala.concurrent.duration.FiniteDuration
import scala.util.{Failure, Success}
/** A request handler for the `library/create` endpoint.
*
* @param timeout request timeout
* @param localLibraryManager a reference to the LocalLibraryManager
*/
class LibraryCreateHandler(
timeout: FiniteDuration,
localLibraryManager: ActorRef
) extends Actor
with LazyLogging
with UnhandledLogging {
import context.dispatcher
override def receive: Receive = requestStage
private def requestStage: Receive = {
case Request(
LibraryCreate,
id,
LibraryCreate.Params(namespace, name, authors, maintainers, license)
) =>
localLibraryManager ! LocalLibraryManagerProtocol.Create(
LibraryName(namespace, name),
authors = authors,
maintainers = maintainers,
license = license
)
val cancellable =
context.system.scheduler.scheduleOnce(timeout, self, RequestTimeout)
context.become(responseStage(id, sender(), cancellable))
}
private def responseStage(
id: Id,
replyTo: ActorRef,
cancellable: Cancellable
): Receive = {
case RequestTimeout =>
replyTo ! RequestTimeout
context.stop(self)
case Success(_) =>
replyTo ! ResponseResult(LibraryCreate, id, Unused)
cancellable.cancel()
context.stop(self)
case Failure(exception) =>
// TODO [RW] handle LibraryAlreadyExists error
replyTo ! ResponseError(Some(id), FileSystemError(exception.getMessage))
cancellable.cancel()
context.stop(self)
}
}
object LibraryCreateHandler {
/** Creates a configuration object to create [[LibraryCreateHandler]].
*
* @param timeout request timeout
* @param localLibraryManager a reference to the LocalLibraryManager
*/
def props(timeout: FiniteDuration, localLibraryManager: ActorRef): Props =
Props(
new LibraryCreateHandler(timeout, localLibraryManager)
)
}

View File

@ -0,0 +1,32 @@
package org.enso.languageserver.libraries.handler
import akka.actor.{Actor, Props}
import com.typesafe.scalalogging.LazyLogging
import org.enso.jsonrpc.{Request, ResponseResult}
import org.enso.languageserver.libraries.LibraryApi._
import org.enso.languageserver.util.UnhandledLogging
/** A request handler for the `library/create` endpoint.
*
* It is currently a stub implementation which will be refined later on.
*/
class LibraryGetMetadataHandler
extends Actor
with LazyLogging
with UnhandledLogging {
override def receive: Receive = {
case Request(LibraryGetMetadata, id, _: LibraryGetMetadata.Params) =>
// TODO [RW] actual implementation
sender() ! ResponseResult(
LibraryGetMetadata,
id,
LibraryGetMetadata.Result(None, None)
)
}
}
object LibraryGetMetadataHandler {
/** Creates a configuration object to create [[LibraryGetMetadataHandler]]. */
def props(): Props = Props(new LibraryGetMetadataHandler)
}

View File

@ -0,0 +1,74 @@
package org.enso.languageserver.libraries.handler
import akka.actor.{Actor, ActorRef, Cancellable, Props}
import com.typesafe.scalalogging.LazyLogging
import org.enso.jsonrpc._
import org.enso.languageserver.filemanager.FileManagerApi.FileSystemError
import org.enso.languageserver.libraries.LibraryApi._
import org.enso.languageserver.libraries.LocalLibraryManagerProtocol
import org.enso.languageserver.requesthandler.RequestTimeout
import org.enso.languageserver.util.UnhandledLogging
import scala.concurrent.duration.FiniteDuration
import scala.util.{Failure, Success}
/** A request handler for the `library/listLocal` endpoint.
*
* @param timeout request timeout
* @param localLibraryManager a reference to the LocalLibraryManager
*/
class LibraryListLocalHandler(
timeout: FiniteDuration,
localLibraryManager: ActorRef
) extends Actor
with LazyLogging
with UnhandledLogging {
import context.dispatcher
override def receive: Receive = requestStage
private def requestStage: Receive = { case Request(LibraryListLocal, id, _) =>
localLibraryManager ! LocalLibraryManagerProtocol.ListLocalLibraries
val cancellable =
context.system.scheduler.scheduleOnce(timeout, self, RequestTimeout)
context.become(responseStage(id, sender(), cancellable))
}
private def responseStage(
id: Id,
replyTo: ActorRef,
cancellable: Cancellable
): Receive = {
case RequestTimeout =>
replyTo ! RequestTimeout
context.stop(self)
case Success(
LocalLibraryManagerProtocol.ListLocalLibrariesResponse(libraries)
) =>
replyTo ! ResponseResult(
LibraryListLocal,
id,
LibraryListLocal.Result(libraries)
)
cancellable.cancel()
context.stop(self)
case Failure(exception) =>
replyTo ! ResponseError(Some(id), FileSystemError(exception.getMessage))
cancellable.cancel()
context.stop(self)
}
}
object LibraryListLocalHandler {
/** Creates a configuration object to create [[LibraryListLocalHandler]].
*
* @param timeout request timeout
* @param localLibraryManager a reference to the LocalLibraryManager
*/
def props(timeout: FiniteDuration, localLibraryManager: ActorRef): Props =
Props(
new LibraryListLocalHandler(timeout, localLibraryManager)
)
}

View File

@ -0,0 +1,56 @@
package org.enso.languageserver.libraries.handler
import akka.actor.{Actor, Props}
import com.typesafe.scalalogging.LazyLogging
import org.enso.cli.task.notifications.ActorProgressNotificationForwarder
import org.enso.jsonrpc.{Request, ResponseError}
import org.enso.languageserver.filemanager.FileManagerApi.FileSystemError
import org.enso.languageserver.libraries.FakeDownload
import org.enso.languageserver.libraries.LibraryApi._
import org.enso.languageserver.util.UnhandledLogging
/** A request handler for the `library/preinstall` endpoint.
*
* It is currently a stub implementation which will be refined later on.
*/
class LibraryPreinstallHandler
extends Actor
with LazyLogging
with UnhandledLogging {
override def receive: Receive = {
case Request(LibraryPreinstall, id, LibraryPreinstall.Params(_, name)) =>
// TODO [RW] actual implementation
val progressReporter =
ActorProgressNotificationForwarder.translateAndForward(
LibraryPreinstall.name,
sender()
)
if (name == "Test") {
FakeDownload.simulateDownload(
"Download Test",
progressReporter,
seconds = 1
)
} else {
FakeDownload.simulateDownload(
"Downloading something...",
progressReporter
)
FakeDownload.simulateDownload(
"Downloading something else...",
progressReporter
)
}
sender() ! ResponseError(
Some(id),
FileSystemError("Feature not implemented")
)
}
}
object LibraryPreinstallHandler {
/** Creates a configuration object to create [[LibraryPreinstallHandler]]. */
def props(): Props = Props(new LibraryPreinstallHandler)
}

View File

@ -0,0 +1,32 @@
package org.enso.languageserver.libraries.handler
import akka.actor.{Actor, Props}
import com.typesafe.scalalogging.LazyLogging
import org.enso.jsonrpc.{Request, ResponseError}
import org.enso.languageserver.filemanager.FileManagerApi.FileSystemError
import org.enso.languageserver.libraries.LibraryApi._
import org.enso.languageserver.util.UnhandledLogging
/** A request handler for the `library/publish` endpoint.
*
* It is currently a stub implementation which will be refined later on.
*/
class LibraryPublishHandler
extends Actor
with LazyLogging
with UnhandledLogging {
override def receive: Receive = {
case Request(LibraryPublish, id, _: LibraryPublish.Params) =>
// TODO [RW] actual implementation
sender() ! ResponseError(
Some(id),
FileSystemError("Feature not implemented")
)
}
}
object LibraryPublishHandler {
/** Creates a configuration object to create [[LibraryPublishHandler]]. */
def props(): Props = Props(new LibraryPublishHandler)
}

View File

@ -0,0 +1,32 @@
package org.enso.languageserver.libraries.handler
import akka.actor.{Actor, Props}
import com.typesafe.scalalogging.LazyLogging
import org.enso.jsonrpc.{Request, ResponseError}
import org.enso.languageserver.filemanager.FileManagerApi.FileSystemError
import org.enso.languageserver.libraries.LibraryApi._
import org.enso.languageserver.util.UnhandledLogging
/** A request handler for the `library/setMetadata` endpoint.
*
* It is currently a stub implementation which will be refined later on.
*/
class LibrarySetMetadataHandler
extends Actor
with LazyLogging
with UnhandledLogging {
override def receive: Receive = {
case Request(LibrarySetMetadata, id, _: LibrarySetMetadata.Params) =>
// TODO [RW] actual implementation
sender() ! ResponseError(
Some(id),
FileSystemError("Feature not implemented")
)
}
}
object LibrarySetMetadataHandler {
/** Creates a configuration object to create [[LibrarySetMetadataHandler]]. */
def props(): Props = Props(new LibrarySetMetadataHandler)
}

View File

@ -1,11 +1,12 @@
package org.enso.languageserver.protocol.json package org.enso.languageserver.protocol.json
import java.util.UUID
import akka.actor.{Actor, ActorRef, Cancellable, Props, Stash, Status} import akka.actor.{Actor, ActorRef, Cancellable, Props, Stash, Status}
import akka.pattern.pipe import akka.pattern.pipe
import akka.util.Timeout import akka.util.Timeout
import com.typesafe.scalalogging.LazyLogging import com.typesafe.scalalogging.LazyLogging
import org.enso.cli.task.ProgressUnit
import org.enso.cli.task.notifications.TaskNotificationApi
import org.enso.distribution.EditionManager
import org.enso.jsonrpc._ import org.enso.jsonrpc._
import org.enso.languageserver.boot.resource.InitializationComponent import org.enso.languageserver.boot.resource.InitializationComponent
import org.enso.languageserver.capability.CapabilityApi.{ import org.enso.languageserver.capability.CapabilityApi.{
@ -26,6 +27,9 @@ import org.enso.languageserver.filemanager._
import org.enso.languageserver.io.InputOutputApi._ import org.enso.languageserver.io.InputOutputApi._
import org.enso.languageserver.io.OutputKind.{StandardError, StandardOutput} import org.enso.languageserver.io.OutputKind.{StandardError, StandardOutput}
import org.enso.languageserver.io.{InputOutputApi, InputOutputProtocol} import org.enso.languageserver.io.{InputOutputApi, InputOutputProtocol}
import org.enso.languageserver.libraries.EditionReferenceResolver
import org.enso.languageserver.libraries.LibraryApi._
import org.enso.languageserver.libraries.handler._
import org.enso.languageserver.monitoring.MonitoringApi.{InitialPing, Ping} import org.enso.languageserver.monitoring.MonitoringApi.{InitialPing, Ping}
import org.enso.languageserver.monitoring.MonitoringProtocol import org.enso.languageserver.monitoring.MonitoringProtocol
import org.enso.languageserver.refactoring.RefactoringApi.RenameProject import org.enso.languageserver.refactoring.RefactoringApi.RenameProject
@ -66,7 +70,10 @@ import org.enso.languageserver.text.TextApi._
import org.enso.languageserver.text.TextProtocol import org.enso.languageserver.text.TextProtocol
import org.enso.languageserver.util.UnhandledLogging import org.enso.languageserver.util.UnhandledLogging
import org.enso.languageserver.workspace.WorkspaceApi.ProjectInfo import org.enso.languageserver.workspace.WorkspaceApi.ProjectInfo
import org.enso.polyglot.runtime.Runtime.Api
import org.enso.polyglot.runtime.Runtime.Api.ProgressNotification
import java.util.UUID
import scala.concurrent.duration._ import scala.concurrent.duration._
/** An actor handling communications between a single client and the language /** An actor handling communications between a single client and the language
@ -81,6 +88,7 @@ import scala.concurrent.duration._
* @param contextRegistry a router that dispatches execution context requests * @param contextRegistry a router that dispatches execution context requests
* @param suggestionsHandler a reference to the suggestions requests handler * @param suggestionsHandler a reference to the suggestions requests handler
* @param idlenessMonitor a reference to the idleness monitor actor * @param idlenessMonitor a reference to the idleness monitor actor
* @param projectSettingsManager a reference to the project settings manager
* @param requestTimeout a request timeout * @param requestTimeout a request timeout
*/ */
class JsonConnectionController( class JsonConnectionController(
@ -97,6 +105,10 @@ class JsonConnectionController(
val stdInController: ActorRef, val stdInController: ActorRef,
val runtimeConnector: ActorRef, val runtimeConnector: ActorRef,
val idlenessMonitor: ActorRef, val idlenessMonitor: ActorRef,
val projectSettingsManager: ActorRef,
val localLibraryManager: ActorRef,
val editionReferenceResolver: EditionReferenceResolver,
val editionManager: EditionManager,
val languageServerConfig: Config, val languageServerConfig: Config,
requestTimeout: FiniteDuration = 10.seconds requestTimeout: FiniteDuration = 10.seconds
) extends Actor ) extends Actor
@ -230,8 +242,7 @@ class JsonConnectionController(
InitProtocolConnection.Result(allRoots.map(_.toContentRoot).toSet) InitProtocolConnection.Result(allRoots.map(_.toContentRoot).toSet)
) )
val requestHandlers = createRequestHandlers(rpcSession) initialize(webActor, rpcSession)
context.become(initialised(webActor, rpcSession, requestHandlers))
} else { } else {
context.become( context.become(
waitingForContentRoots( waitingForContentRoots(
@ -254,6 +265,17 @@ class JsonConnectionController(
stash() stash()
} }
private def initialize(
webActor: ActorRef,
rpcSession: JsonSession
): Unit = {
val requestHandlers = createRequestHandlers(rpcSession)
context.become(initialised(webActor, rpcSession, requestHandlers))
context.system.eventStream
.subscribe(self, classOf[Api.ProgressNotification])
}
private def initialised( private def initialised(
webActor: ActorRef, webActor: ActorRef,
rpcSession: JsonSession, rpcSession: JsonSession,
@ -364,6 +386,11 @@ class JsonConnectionController(
) )
} }
case Api.ProgressNotification(payload) =>
val translated: Notification[_, _] =
translateProgressNotification(payload)
webActor ! translated
case req @ Request(method, _, _) if requestHandlers.contains(method) => case req @ Request(method, _, _) if requestHandlers.contains(method) =>
refreshIdleTime(method) refreshIdleTime(method)
val handler = context.actorOf( val handler = context.actorOf(
@ -458,10 +485,57 @@ class JsonConnectionController(
RedirectStandardError -> RedirectStdErrHandler RedirectStandardError -> RedirectStdErrHandler
.props(stdErrController, rpcSession.clientId), .props(stdErrController, rpcSession.clientId),
FeedStandardInput -> FeedStandardInputHandler.props(stdInController), FeedStandardInput -> FeedStandardInputHandler.props(stdInController),
ProjectInfo -> ProjectInfoHandler.props(languageServerConfig) ProjectInfo -> ProjectInfoHandler.props(languageServerConfig),
EditionsGetProjectSettings -> EditionsGetProjectSettingsHandler
.props(requestTimeout, projectSettingsManager),
EditionsListAvailable -> EditionsListAvailableHandler.props(
editionManager
),
EditionsListDefinedLibraries -> EditionsListDefinedLibrariesHandler
.props(editionReferenceResolver),
EditionsResolve -> EditionsResolveHandler
.props(editionReferenceResolver),
EditionsSetParentEdition -> EditionsSetParentEditionHandler
.props(requestTimeout, projectSettingsManager),
EditionsSetLocalLibrariesPreference -> EditionsSetProjectLocalLibrariesPreferenceHandler
.props(requestTimeout, projectSettingsManager),
LibraryCreate -> LibraryCreateHandler
.props(requestTimeout, localLibraryManager),
LibraryListLocal -> LibraryListLocalHandler
.props(requestTimeout, localLibraryManager),
LibraryGetMetadata -> LibraryGetMetadataHandler.props(),
LibraryPreinstall -> LibraryPreinstallHandler.props(),
LibraryPublish -> LibraryPublishHandler.props(),
LibrarySetMetadata -> LibrarySetMetadataHandler.props()
) )
} }
private def translateProgressNotification(
progressNotification: ProgressNotification.NotificationType
): Notification[_, _] = progressNotification match {
case ProgressNotification.TaskStarted(
taskId,
relatedOperation,
unitStr,
total
) =>
val unit = ProgressUnit.fromString(unitStr)
Notification(
TaskNotificationApi.TaskStarted,
TaskNotificationApi.TaskStarted
.Params(taskId, relatedOperation, unit, total)
)
case ProgressNotification.TaskProgressUpdate(taskId, message, done) =>
Notification(
TaskNotificationApi.TaskProgressUpdate,
TaskNotificationApi.TaskProgressUpdate.Params(taskId, message, done)
)
case ProgressNotification.TaskFinished(taskId, message, success) =>
Notification(
TaskNotificationApi.TaskFinished,
TaskNotificationApi.TaskFinished.Params(taskId, message, success)
)
}
} }
object JsonConnectionController { object JsonConnectionController {
@ -493,26 +567,34 @@ object JsonConnectionController {
stdInController: ActorRef, stdInController: ActorRef,
runtimeConnector: ActorRef, runtimeConnector: ActorRef,
idlenessMonitor: ActorRef, idlenessMonitor: ActorRef,
projectSettingsManager: ActorRef,
localLibraryManager: ActorRef,
editionReferenceResolver: EditionReferenceResolver,
editionManager: EditionManager,
languageServerConfig: Config, languageServerConfig: Config,
requestTimeout: FiniteDuration = 10.seconds requestTimeout: FiniteDuration = 10.seconds
): Props = ): Props =
Props( Props(
new JsonConnectionController( new JsonConnectionController(
connectionId, connectionId = connectionId,
mainComponent, mainComponent = mainComponent,
bufferRegistry, bufferRegistry = bufferRegistry,
capabilityRouter, capabilityRouter = capabilityRouter,
fileManager, fileManager = fileManager,
contentRootManager, contentRootManager = contentRootManager,
contextRegistry, contextRegistry = contextRegistry,
suggestionsHandler, suggestionsHandler = suggestionsHandler,
stdOutController, stdOutController = stdOutController,
stdErrController, stdErrController = stdErrController,
stdInController, stdInController = stdInController,
runtimeConnector, runtimeConnector = runtimeConnector,
idlenessMonitor, idlenessMonitor = idlenessMonitor,
languageServerConfig, projectSettingsManager = projectSettingsManager,
requestTimeout localLibraryManager = localLibraryManager,
editionReferenceResolver = editionReferenceResolver,
editionManager = editionManager,
languageServerConfig = languageServerConfig,
requestTimeout = requestTimeout
) )
) )

View File

@ -1,9 +1,11 @@
package org.enso.languageserver.protocol.json package org.enso.languageserver.protocol.json
import akka.actor.{ActorRef, ActorSystem} import akka.actor.{ActorRef, ActorSystem}
import org.enso.distribution.EditionManager
import org.enso.jsonrpc.ClientControllerFactory import org.enso.jsonrpc.ClientControllerFactory
import org.enso.languageserver.boot.resource.InitializationComponent import org.enso.languageserver.boot.resource.InitializationComponent
import org.enso.languageserver.data.Config import org.enso.languageserver.data.Config
import org.enso.languageserver.libraries.EditionReferenceResolver
import java.util.UUID import java.util.UUID
@ -27,6 +29,10 @@ class JsonConnectionControllerFactory(
stdInController: ActorRef, stdInController: ActorRef,
runtimeConnector: ActorRef, runtimeConnector: ActorRef,
idlenessMonitor: ActorRef, idlenessMonitor: ActorRef,
projectSettingsManager: ActorRef,
localLibraryManager: ActorRef,
editionReferenceResolver: EditionReferenceResolver,
editionManager: EditionManager,
config: Config config: Config
)(implicit system: ActorSystem) )(implicit system: ActorSystem)
extends ClientControllerFactory { extends ClientControllerFactory {
@ -39,20 +45,24 @@ class JsonConnectionControllerFactory(
override def createClientController(clientId: UUID): ActorRef = override def createClientController(clientId: UUID): ActorRef =
system.actorOf( system.actorOf(
JsonConnectionController.props( JsonConnectionController.props(
clientId, connectionId = clientId,
mainComponent, mainComponent = mainComponent,
bufferRegistry, bufferRegistry = bufferRegistry,
capabilityRouter, capabilityRouter = capabilityRouter,
fileManager, fileManager = fileManager,
contentRootManager, contentRootManager = contentRootManager,
contextRegistry, contextRegistry = contextRegistry,
suggestionsHandler, suggestionsHandler = suggestionsHandler,
stdOutController, stdOutController = stdOutController,
stdErrController, stdErrController = stdErrController,
stdInController, stdInController = stdInController,
runtimeConnector, runtimeConnector = runtimeConnector,
idlenessMonitor, idlenessMonitor = idlenessMonitor,
config projectSettingsManager = projectSettingsManager,
localLibraryManager = localLibraryManager,
editionReferenceResolver = editionReferenceResolver,
editionManager = editionManager,
languageServerConfig = config
) )
) )
} }

View File

@ -1,6 +1,11 @@
package org.enso.languageserver.protocol.json package org.enso.languageserver.protocol.json
import io.circe.generic.auto._ import io.circe.generic.auto._
import org.enso.cli.task.notifications.TaskNotificationApi.{
TaskFinished,
TaskProgressUpdate,
TaskStarted
}
import org.enso.jsonrpc.Protocol import org.enso.jsonrpc.Protocol
import org.enso.languageserver.capability.CapabilityApi.{ import org.enso.languageserver.capability.CapabilityApi.{
AcquireCapability, AcquireCapability,
@ -17,6 +22,7 @@ import org.enso.languageserver.search.SearchApi._
import org.enso.languageserver.runtime.VisualisationApi._ import org.enso.languageserver.runtime.VisualisationApi._
import org.enso.languageserver.session.SessionApi.InitProtocolConnection import org.enso.languageserver.session.SessionApi.InitProtocolConnection
import org.enso.languageserver.text.TextApi._ import org.enso.languageserver.text.TextApi._
import org.enso.languageserver.libraries.LibraryApi._
import org.enso.languageserver.workspace.WorkspaceApi.ProjectInfo import org.enso.languageserver.workspace.WorkspaceApi.ProjectInfo
object JsonRpc { object JsonRpc {
@ -65,6 +71,21 @@ object JsonRpc {
.registerRequest(Import) .registerRequest(Import)
.registerRequest(RenameProject) .registerRequest(RenameProject)
.registerRequest(ProjectInfo) .registerRequest(ProjectInfo)
.registerRequest(EditionsListAvailable)
.registerRequest(EditionsResolve)
.registerRequest(EditionsGetProjectSettings)
.registerRequest(EditionsSetParentEdition)
.registerRequest(EditionsSetLocalLibrariesPreference)
.registerRequest(EditionsListDefinedLibraries)
.registerRequest(LibraryListLocal)
.registerRequest(LibraryCreate)
.registerRequest(LibraryGetMetadata)
.registerRequest(LibrarySetMetadata)
.registerRequest(LibraryPublish)
.registerRequest(LibraryPreinstall)
.registerNotification(TaskStarted)
.registerNotification(TaskProgressUpdate)
.registerNotification(TaskFinished)
.registerNotification(ForceReleaseCapability) .registerNotification(ForceReleaseCapability)
.registerNotification(GrantCapability) .registerNotification(GrantCapability)
.registerNotification(TextDidChange) .registerNotification(TextDidChange)

View File

@ -1,6 +1,5 @@
license: APLv2 license: APLv2
name: Standard name: Standard
enso-version: default
version: "0.1.0" version: "0.1.0"
author: "Enso Team <contact@enso.org>" author: "Enso Team <contact@enso.org>"
maintainer: "Enso Team <contact@enso.org>" maintainer: "Enso Team <contact@enso.org>"

View File

@ -0,0 +1,21 @@
package org.enso.languageserver.libraries
import io.circe.syntax._
import org.enso.languageserver.libraries.EditionReference.{
CurrentProjectEdition,
NamedEdition
}
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
class EditionNameSerializationSpec extends AnyWordSpec with Matchers {
"EditionName" should {
"serialize and deserialize to the same thing" in {
val edition1: EditionReference = CurrentProjectEdition
edition1.asJson.as[EditionReference] shouldEqual Right(edition1)
val edition2: EditionReference = NamedEdition("Foo-Bar")
edition2.asJson.as[EditionReference] shouldEqual Right(edition2)
}
}
}

View File

@ -0,0 +1,21 @@
package org.enso.languageserver.libraries
import io.circe.syntax._
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
class LibraryEntrySerializationSpec extends AnyWordSpec with Matchers {
"LibraryEntry" should {
"serialize and deserialize to the same thing" in {
val entry1 = LibraryEntry("Foo", "Bar", LibraryEntry.LocalLibraryVersion)
entry1.asJson.as[LibraryEntry] shouldEqual Right(entry1)
val entry2 = LibraryEntry(
"Foo",
"Bar",
LibraryEntry.PublishedLibraryVersion("1.2.3", "https://example.com/")
)
entry2.asJson.as[LibraryEntry] shouldEqual Right(entry2)
}
}
}

View File

@ -1,15 +1,15 @@
package org.enso.languageserver.websocket.json package org.enso.languageserver.websocket.json
import java.nio.file.Files
import java.util.UUID
import akka.testkit.TestProbe import akka.testkit.TestProbe
import io.circe.literal._ import io.circe.literal._
import io.circe.parser.parse import io.circe.parser.parse
import io.circe.syntax.EncoderOps import io.circe.syntax.EncoderOps
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
import org.enso.distribution.{DistributionManager, EditionManager, LanguageHome}
import org.enso.editions.EditionResolver
import org.enso.jsonrpc.test.JsonRpcServerTestKit import org.enso.jsonrpc.test.JsonRpcServerTestKit
import org.enso.jsonrpc.{ClientControllerFactory, Protocol} import org.enso.jsonrpc.{ClientControllerFactory, Protocol}
import org.enso.languageserver.TestClock
import org.enso.languageserver.boot.resource.{ import org.enso.languageserver.boot.resource.{
DirectoriesInitialization, DirectoriesInitialization,
RepoInitialization, RepoInitialization,
@ -21,6 +21,11 @@ import org.enso.languageserver.effect.ZioExec
import org.enso.languageserver.event.InitializedEvent import org.enso.languageserver.event.InitializedEvent
import org.enso.languageserver.filemanager._ import org.enso.languageserver.filemanager._
import org.enso.languageserver.io._ import org.enso.languageserver.io._
import org.enso.languageserver.libraries.{
EditionReferenceResolver,
LocalLibraryManager,
ProjectSettingsManager
}
import org.enso.languageserver.monitoring.IdlenessMonitor import org.enso.languageserver.monitoring.IdlenessMonitor
import org.enso.languageserver.protocol.json.{ import org.enso.languageserver.protocol.json.{
JsonConnectionControllerFactory, JsonConnectionControllerFactory,
@ -30,22 +35,28 @@ import org.enso.languageserver.refactoring.ProjectNameChangedEvent
import org.enso.languageserver.runtime.{ContextRegistry, RuntimeFailureMapper} import org.enso.languageserver.runtime.{ContextRegistry, RuntimeFailureMapper}
import org.enso.languageserver.search.SuggestionsHandler import org.enso.languageserver.search.SuggestionsHandler
import org.enso.languageserver.session.SessionRouter import org.enso.languageserver.session.SessionRouter
import org.enso.languageserver.TestClock
import org.enso.languageserver.text.BufferRegistry import org.enso.languageserver.text.BufferRegistry
import org.enso.pkg.PackageManager
import org.enso.polyglot.data.TypeGraph import org.enso.polyglot.data.TypeGraph
import org.enso.polyglot.runtime.Runtime.Api import org.enso.polyglot.runtime.Runtime.Api
import org.enso.runtimeversionmanager.test.{FakeEnvironment, HasTestDirectory}
import org.enso.searcher.sql.{SqlDatabase, SqlSuggestionsRepo, SqlVersionsRepo} import org.enso.searcher.sql.{SqlDatabase, SqlSuggestionsRepo, SqlVersionsRepo}
import org.enso.testkit.EitherValue import org.enso.testkit.EitherValue
import org.enso.text.Sha3_224VersionCalculator import org.enso.text.Sha3_224VersionCalculator
import org.scalatest.OptionValues import org.scalatest.OptionValues
import java.nio.file
import java.nio.file.Files
import java.util.UUID
import scala.concurrent.Await import scala.concurrent.Await
import scala.concurrent.duration._ import scala.concurrent.duration._
class BaseServerTest class BaseServerTest
extends JsonRpcServerTestKit extends JsonRpcServerTestKit
with EitherValue with EitherValue
with OptionValues { with OptionValues
with HasTestDirectory
with FakeEnvironment {
import system.dispatcher import system.dispatcher
@ -68,7 +79,12 @@ class BaseServerTest
graph graph
} }
private val testDirectory =
Files.createTempDirectory("enso-test").toRealPath()
override def getTestDirectory: file.Path = testDirectory
sys.addShutdownHook(FileUtils.deleteQuietly(testContentRoot.file)) sys.addShutdownHook(FileUtils.deleteQuietly(testContentRoot.file))
sys.addShutdownHook(FileUtils.deleteQuietly(testDirectory.toFile))
def mkConfig: Config = def mkConfig: Config =
Config( Config(
@ -204,30 +220,93 @@ class BaseServerTest
Api.VerifyModulesIndexResponse(Seq()) Api.VerifyModulesIndexResponse(Seq())
) )
locally {
val dataRoot = getTestDirectory.resolve("test_data")
val editions = dataRoot.resolve("editions")
Files.createDirectories(editions)
val distribution = file.Path.of("distribution")
val currentEdition = buildinfo.Info.currentEdition + ".yaml"
val dest = editions.resolve(currentEdition)
if (Files.notExists(dest)) {
Files.copy(
distribution.resolve("editions").resolve(currentEdition),
dest
)
}
}
val environment = fakeInstalledEnvironment()
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(
config.projectContentRoot.file,
editionProvider,
editionResolver
)
val editionManager = EditionManager(distributionManager, Some(languageHome))
val projectSettingsManager = system.actorOf(
ProjectSettingsManager.props(
config.projectContentRoot.file,
editionResolver
)
)
val localLibraryManager = system.actorOf(
LocalLibraryManager.props(
config.projectContentRoot.file,
distributionManager
)
)
new JsonConnectionControllerFactory( new JsonConnectionControllerFactory(
initializationComponent, mainComponent = initializationComponent,
bufferRegistry, bufferRegistry = bufferRegistry,
capabilityRouter, capabilityRouter = capabilityRouter,
fileManager, fileManager = fileManager,
contentRootManagerActor, contentRootManager = contentRootManagerActor,
contextRegistry, contextRegistry = contextRegistry,
suggestionsHandler, suggestionsHandler = suggestionsHandler,
stdOutController, stdOutController = stdOutController,
stdErrController, stdErrController = stdErrController,
stdInController, stdInController = stdInController,
runtimeConnectorProbe.ref, runtimeConnector = runtimeConnectorProbe.ref,
idlenessMonitor, idlenessMonitor = idlenessMonitor,
config projectSettingsManager = projectSettingsManager,
localLibraryManager = localLibraryManager,
editionReferenceResolver = editionReferenceResolver,
editionManager = editionManager,
config = config
) )
} }
def getInitialisedWsClient(): WsTestClient = { /** Specifies if the `package.yaml` at project root should be auto-created. */
val client = new WsTestClient(address) protected def initializeProjectPackage: Boolean = true
lazy val initPackage: Unit = {
if (initializeProjectPackage) {
PackageManager.Default.create(
config.projectContentRoot.file,
name = "TestProject"
)
}
}
def getInitialisedWsClient(debug: Boolean = false): WsTestClient = {
val client = new WsTestClient(address, debugMessages = debug)
initSession(client) initSession(client)
client client
} }
private def initSession(client: WsTestClient): UUID = { private def initSession(client: WsTestClient): UUID = {
initPackage
val clientId = UUID.randomUUID() val clientId = UUID.randomUUID()
client.send(json""" client.send(json"""
{ "jsonrpc": "2.0", { "jsonrpc": "2.0",
@ -252,5 +331,4 @@ class BaseServerTest
) )
clientId clientId
} }
} }

View File

@ -0,0 +1,245 @@
package org.enso.languageserver.websocket.json
import io.circe.literal._
import io.circe.{Json, JsonObject}
import org.enso.languageserver.libraries.LibraryEntry
import org.enso.languageserver.libraries.LibraryEntry.PublishedLibraryVersion
class LibrariesTest extends BaseServerTest {
"LocalLibraryManager" should {
"create a library project and include it on the list of local projects" in {
val client = getInitialisedWsClient()
client.send(json"""
{ "jsonrpc": "2.0",
"method": "library/listLocal",
"id": 0
}
""")
client.expectJson(json"""
{ "jsonrpc": "2.0",
"id": 0,
"result": {
"localLibraries": []
}
}
""")
client.send(json"""
{ "jsonrpc": "2.0",
"method": "library/create",
"id": 1,
"params": {
"namespace": "User",
"name": "MyLocalLib",
"authors": [],
"maintainers": [],
"license": ""
}
}
""")
client.expectJson(json"""
{ "jsonrpc": "2.0",
"id": 1,
"result": null
}
""")
client.send(json"""
{ "jsonrpc": "2.0",
"method": "library/listLocal",
"id": 2
}
""")
client.expectJson(json"""
{ "jsonrpc": "2.0",
"id": 2,
"result": {
"localLibraries": [
{
"namespace": "User",
"name": "MyLocalLib",
"version": {
"type": "LocalLibraryVersion"
}
}
]
}
}
""")
}
"fail with LibraryAlreadyExists when creating a library that already " +
"existed" ignore {
// TODO [RW] error handling (#1877)
}
}
"mocked library/preinstall" should {
"send progress notifications" in {
val client = getInitialisedWsClient()
client.send(json"""
{ "jsonrpc": "2.0",
"method": "library/preinstall",
"id": 0,
"params": {
"namespace": "Foo",
"name": "Test"
}
}
""")
val messages =
for (_ <- 0 to 3) yield {
val msg = client.expectSomeJson().asObject.value
val method = msg("method").map(_.asString.value).getOrElse("error")
val params =
msg("params").map(_.asObject.value).getOrElse(JsonObject())
(method, params)
}
val taskStart = messages.find(_._1 == "task/started").value
val taskId = taskStart._2("taskId").value.asString.value
taskStart
._2("relatedOperation")
.value
.asString
.value shouldEqual "library/preinstall"
taskStart._2("unit").value.asString.value shouldEqual "Bytes"
val updates = messages.filter { case (method, params) =>
method == "task/progress-update" &&
params("taskId").value.asString.value == taskId
}
updates should not be empty
updates.head
._2("message")
.value
.asString
.value shouldEqual "Download Test"
}
}
"editions/listAvailable" should {
"list editions on the search path" in {
val client = getInitialisedWsClient()
client.send(json"""
{ "jsonrpc": "2.0",
"method": "editions/listAvailable",
"id": 0,
"params": {
"update": false
}
}
""")
client.expectJson(json"""
{ "jsonrpc": "2.0",
"id": 0,
"result": {
"editionNames": [
${buildinfo.Info.currentEdition}
]
}
}
""")
}
"update the list of editions if requested" ignore {
// TODO [RW] updating editions
}
}
"editions/listDefinedLibraries" should {
"include Standard.Base in the list" in {
def containsBase(response: Json): Unit = {
val result = response.asObject.value("result").value
val libs = result.asObject.value("availableLibraries").value
val parsed = libs.asArray.value.map(_.as[LibraryEntry])
val bases = parsed.collect {
case Right(
LibraryEntry("Standard", "Base", PublishedLibraryVersion(_, _))
) =>
()
}
bases should have size 1
}
val client = getInitialisedWsClient()
client.send(json"""
{ "jsonrpc": "2.0",
"method": "editions/listDefinedLibraries",
"id": 0,
"params": {
"edition": {
"type": "CurrentProjectEdition"
}
}
}
""")
containsBase(client.expectSomeJson())
val currentEditionName = buildinfo.Info.currentEdition
client.send(json"""
{ "jsonrpc": "2.0",
"method": "editions/listDefinedLibraries",
"id": 0,
"params": {
"edition": {
"type": "NamedEdition",
"editionName": $currentEditionName
}
}
}
""")
containsBase(client.expectSomeJson())
}
}
"editions/resolve" should {
"resolve the engine version associated with an edition" in {
val currentVersion = buildinfo.Info.ensoVersion
val client = getInitialisedWsClient()
client.send(json"""
{ "jsonrpc": "2.0",
"method": "editions/resolve",
"id": 0,
"params": {
"edition": {
"type": "CurrentProjectEdition"
}
}
}
""")
client.expectJson(json"""
{ "jsonrpc": "2.0",
"id": 0,
"result": {
"engineVersion": $currentVersion
}
}
""")
client.send(json"""
{ "jsonrpc": "2.0",
"method": "editions/resolve",
"id": 1,
"params": {
"edition": {
"type": "NamedEdition",
"editionName": ${buildinfo.Info.currentEdition}
}
}
}
""")
client.expectJson(json"""
{ "jsonrpc": "2.0",
"id": 1,
"result": {
"engineVersion": $currentVersion
}
}
""")
}
}
}

View File

@ -0,0 +1,105 @@
package org.enso.languageserver.websocket.json
import io.circe.literal._
import org.enso.distribution.FileSystem
import java.nio.file.Files
class ProjectSettingsManagerTest extends BaseServerTest {
override def beforeAll(): Unit = {
super.beforeAll()
val editionsDir = getTestDirectory.resolve("test_data").resolve("editions")
Files.createDirectories(editionsDir)
FileSystem.writeTextFile(
editionsDir.resolve("some-edition.yaml"),
"""engine-version: 1.2.3
|""".stripMargin
)
FileSystem.writeTextFile(
editionsDir.resolve("broken.yaml"),
"""extends: non-existent
|""".stripMargin
)
}
"ProjectSettingsManager" should {
"get default settings" in {
val client = getInitialisedWsClient()
client.send(json"""
{ "jsonrpc": "2.0",
"method": "editions/getProjectSettings",
"id": 0
}
""")
client.expectJson(json"""
{ "jsonrpc": "2.0",
"id": 0,
"result": {
"parentEdition": ${buildinfo.Info.currentEdition},
"preferLocalLibraries": true
}
}
""")
}
"allow to set local libraries preference and parent edition and reflect " +
"these changes" in {
val client = getInitialisedWsClient()
client.send(json"""
{ "jsonrpc": "2.0",
"method": "editions/setProjectLocalLibrariesPreference",
"id": 0,
"params": {
"preferLocalLibraries": false
}
}
""")
client.expectJson(json"""
{ "jsonrpc": "2.0",
"id": 0,
"result": {
"needsRestart": true
}
}
""")
client.send(json"""
{ "jsonrpc": "2.0",
"method": "editions/setParentEdition",
"id": 1,
"params": {
"newEditionName": "some-edition"
}
}
""")
client.expectJson(json"""
{ "jsonrpc": "2.0",
"id": 1,
"result": {
"needsRestart": true
}
}
""")
client.send(json"""
{ "jsonrpc": "2.0",
"method": "editions/getProjectSettings",
"id": 2
}
""")
client.expectJson(json"""
{ "jsonrpc": "2.0",
"id": 2,
"result": {
"parentEdition": "some-edition",
"preferLocalLibraries": false
}
}
""")
}
"fail if the provided parent edition is not resolvable" ignore {}
}
}

View File

@ -9,6 +9,8 @@ import java.io.{File, FileOutputStream}
class WorkspaceOperationsTest extends BaseServerTest with FlakySpec { class WorkspaceOperationsTest extends BaseServerTest with FlakySpec {
override def initializeProjectPackage: Boolean = false
"workspace/projectInfo" must { "workspace/projectInfo" must {
val packageConfigName = Config.ensoPackageConfigName val packageConfigName = Config.ensoPackageConfigName
val testYamlPath = new File(testContentRoot.file, packageConfigName) val testYamlPath = new File(testContentRoot.file, packageConfigName)

View File

@ -46,9 +46,7 @@ case class Launcher(cliOptions: GlobalCLIOptions) {
} }
private lazy val configurationManager = private lazy val configurationManager =
new GlobalConfigurationManager(componentsManager, distributionManager) new GlobalConfigurationManager(componentsManager, distributionManager)
private lazy val editionManager = new EditionManager( private lazy val editionManager = EditionManager(distributionManager)
distributionManager.paths.editionSearchPaths.toList
)
private lazy val projectManager = new ProjectManager private lazy val projectManager = new ProjectManager
private lazy val runner = private lazy val runner =
new LauncherRunner( new LauncherRunner(

View File

@ -31,9 +31,7 @@ class LauncherRunnerSpec extends RuntimeVersionManagerTest {
new GlobalConfigurationManager(componentsManager, distributionManager) { new GlobalConfigurationManager(componentsManager, distributionManager) {
override def defaultVersion: SemVer = defaultEngineVersion override def defaultVersion: SemVer = defaultEngineVersion
} }
val editionManager = new EditionManager( val editionManager = EditionManager(distributionManager)
distributionManager.paths.editionSearchPaths.toList
)
val projectManager = new ProjectManager() val projectManager = new ProjectManager()
val cwd = cwdOverride.getOrElse(getTestDirectory) val cwd = cwdOverride.getOrElse(getTestDirectory)
val runner = val runner =

View File

@ -209,6 +209,10 @@ object Runtime {
new JsonSubTypes.Type( new JsonSubTypes.Type(
value = classOf[Api.LibraryLoaded], value = classOf[Api.LibraryLoaded],
name = "libraryLoaded" name = "libraryLoaded"
),
new JsonSubTypes.Type(
value = classOf[Api.ProgressNotification],
name = "progressNotification"
) )
) )
) )
@ -1350,6 +1354,40 @@ object Runtime {
location: File location: File
) extends ApiNotification ) extends ApiNotification
/** A notification containing updates on the progress of long-running tasks.
*
* @param payload the actual update contained within this notification
*/
case class ProgressNotification(
payload: ProgressNotification.NotificationType
) extends ApiNotification
object ProgressNotification {
sealed trait NotificationType
/** Indicates that a new task has been started. */
case class TaskStarted(
taskId: UUID,
relatedOperation: String,
unit: String,
total: Option[Long]
) extends NotificationType
/** Indicates that the task has progressed. */
case class TaskProgressUpdate(
taskId: UUID,
message: Option[String],
done: Long
) extends NotificationType
/** Indicates that the task has been finished. */
case class TaskFinished(
taskId: UUID,
message: Option[String],
success: Boolean
) extends NotificationType
}
private lazy val mapper = { private lazy val mapper = {
val factory = new CBORFactory() val factory = new CBORFactory()
val mapper = new ObjectMapper(factory) with ScalaObjectMapper val mapper = new ObjectMapper(factory) with ScalaObjectMapper

View File

@ -338,10 +338,7 @@ object PackageRepository {
.getOrElse(DefaultEdition.getDefaultEdition) .getOrElse(DefaultEdition.getDefaultEdition)
val homeManager = languageHome.map { home => LanguageHome(Path.of(home)) } val homeManager = languageHome.map { home => LanguageHome(Path.of(home)) }
val editionSearchPaths = val editionManager = EditionManager(distributionManager, homeManager)
homeManager.map(_.editions).toList ++
distributionManager.paths.editionSearchPaths
val editionManager = new EditionManager(editionSearchPaths)
val edition = editionManager.resolveEdition(rawEdition).get val edition = editionManager.resolveEdition(rawEdition).get
val resolvingLibraryProvider = val resolvingLibraryProvider =

View File

@ -2,7 +2,12 @@ package org.enso.interpreter.instrument
import com.typesafe.scalalogging.Logger import com.typesafe.scalalogging.Logger
import org.enso.cli.ProgressBar import org.enso.cli.ProgressBar
import org.enso.cli.task.{ProgressReporter, TaskProgress} import org.enso.cli.task.{
ProgressNotification,
ProgressNotificationForwarder,
ProgressReporter,
TaskProgress
}
import org.enso.editions.{LibraryName, LibraryVersion} import org.enso.editions.{LibraryName, LibraryVersion}
import org.enso.polyglot.runtime.Runtime.{Api, ApiResponse} import org.enso.polyglot.runtime.Runtime.{Api, ApiResponse}
@ -80,7 +85,9 @@ object NotificationHandler {
* notifications to the Language Server, which then should forward them to * notifications to the Language Server, which then should forward them to
* the IDE. * the IDE.
*/ */
class InteractiveMode(endpoint: Endpoint) extends NotificationHandler { class InteractiveMode(endpoint: Endpoint)
extends NotificationHandler
with ProgressNotificationForwarder {
private val logger = Logger[InteractiveMode] private val logger = Logger[InteractiveMode]
private def sendMessage(message: ApiResponse): Unit = { private def sendMessage(message: ApiResponse): Unit = {
@ -105,7 +112,16 @@ object NotificationHandler {
/** @inheritdoc */ /** @inheritdoc */
override def trackProgress(message: String, task: TaskProgress[_]): Unit = { override def trackProgress(message: String, task: TaskProgress[_]): Unit = {
logger.info(message) logger.info(message)
// TODO [RW] this should be implemented once progress tracking is used by downloads super.trackProgress(message, task)
} }
override def sendProgressNotification(
notification: ProgressNotification
): Unit = sendMessage(
ProgressNotificationTranslator.translate(
"compiler/downloadingDependencies",
notification
)
)
} }
} }

View File

@ -0,0 +1,49 @@
package org.enso.interpreter.instrument
import org.enso.cli.task.{
ProgressUnit,
ProgressNotification => TaskProgressNotification
}
import org.enso.polyglot.runtime.Runtime.Api.ProgressNotification
import org.enso.polyglot.runtime.Runtime.Api.ProgressNotification._
import org.enso.polyglot.runtime.Runtime.ApiResponse
/** A helper for translating notification formats. */
object ProgressNotificationTranslator {
/** Translates a notification as defined in the CLI module into the format
* that is used in the API of the runtime connector, so that it can be
* forwarded to the Language Server.
*
* @param relatedOperationName name of a related operation; these were
* originally tied to Project Manager or Language
* Server operations, but they can also be based
* on internal compiler operations
* @param progressNotification the notification to translate
*/
def translate(
relatedOperationName: String,
progressNotification: TaskProgressNotification
): ApiResponse = {
val payload = progressNotification match {
case TaskProgressNotification.TaskStarted(taskId, total, unit) =>
TaskStarted(
taskId = taskId,
relatedOperation = relatedOperationName,
unit = ProgressUnit.toString(unit),
total = total
)
case TaskProgressNotification.TaskUpdate(taskId, message, done) =>
TaskProgressUpdate(taskId, message, done)
case TaskProgressNotification.TaskSuccess(taskId) =>
TaskFinished(taskId, message = None, success = true)
case TaskProgressNotification.TaskFailure(taskId, throwable) =>
TaskFinished(
taskId,
message = Option(throwable.getMessage),
success = false
)
}
ProgressNotification(payload)
}
}

View File

@ -0,0 +1,44 @@
package org.enso.cli.task
import java.util.UUID
/** Internal representation of progress notifications. */
sealed trait ProgressNotification
object ProgressNotification {
/** Singals that a new task with progress has been started.
*
* @param taskId a unique id of the task
* @param total the total amount of units that the task is expected to take
* @param unit unit of that the progress is reported in
*/
case class TaskStarted(
taskId: UUID,
total: Option[Long],
unit: ProgressUnit
) extends ProgressNotification
/** Signals an update to task's progress.
*
* @param taskId the task id
* @param message an optional message to display
* @param done indication of how much progress has been done since the task
* started
*/
case class TaskUpdate(taskId: UUID, message: Option[String], done: Long)
extends ProgressNotification
/** Signals that a task has been finished successfully.
*
* @param taskId the task id
*/
case class TaskSuccess(taskId: UUID) extends ProgressNotification
/** Signals that a task has failed.
*
* @param taskId the task id
* @param throwable an exception associated with the failure
*/
case class TaskFailure(taskId: UUID, throwable: Throwable)
extends ProgressNotification
}

View File

@ -0,0 +1,66 @@
package org.enso.cli.task
import java.util.UUID
import scala.util.{Failure, Success, Try}
/** A [[ProgressReporter]] implementation that tracks tasks and sends
* [[ProgressNotification]]s using a generic interface.
*/
trait ProgressNotificationForwarder extends ProgressReporter {
/** The callback that is used to send the progress notification. */
def sendProgressNotification(notification: ProgressNotification): Unit
/** @inheritdoc */
override def trackProgress(message: String, task: TaskProgress[_]): Unit = {
var uuid: Option[UUID] = None
/** Initializes the task on first invocation and just returns the
* generated UUID on further invocations.
*/
def initializeTask(total: Option[Long]): UUID = uuid match {
case Some(value) => value
case None =>
val generated = UUID.randomUUID()
uuid = Some(generated)
sendProgressNotification(
ProgressNotification.TaskStarted(
generated,
total,
task.unit
)
)
generated
}
task.addProgressListener(new ProgressListener[Any] {
/** @inheritdoc */
override def progressUpdate(
done: Long,
total: Option[Long]
): Unit = {
val uuid = initializeTask(total)
sendProgressNotification(
ProgressNotification.TaskUpdate(
uuid,
Some(message),
done
)
)
}
/** @inheritdoc */
override def done(result: Try[Any]): Unit = result match {
case Failure(exception) =>
val uuid = initializeTask(None)
sendProgressNotification(
ProgressNotification.TaskFailure(uuid, exception)
)
case Success(_) =>
val uuid = initializeTask(None)
sendProgressNotification(ProgressNotification.TaskSuccess(uuid))
}
})
}
}

View File

@ -6,8 +6,21 @@ sealed trait ProgressUnit
object ProgressUnit { object ProgressUnit {
/** Specifies that progress amount is measured in bytes. */ /** Specifies that progress amount is measured in bytes. */
case object Bytes extends ProgressUnit case object Bytes extends ProgressUnit {
override val toString: String = "bytes"
}
/** Does not specify a particular progress unit. */ /** Does not specify a particular progress unit. */
case object Unspecified extends ProgressUnit case object Unspecified extends ProgressUnit {
override val toString: String = "unspecified"
}
/** Converts a unit to its string representation. */
def toString(unit: ProgressUnit): String = unit.toString
/** Creates a unit from its string representation, falling back to
* [[Unspecified]] if it cannot be recognized.
*/
def fromString(str: String): ProgressUnit =
if (str == Bytes.toString) Bytes else Unspecified
} }

View File

@ -1,21 +1,24 @@
package org.enso.distribution package org.enso.distribution
import nl.gn0s1s.bump.SemVer
import org.enso.editions import org.enso.editions
import org.enso.editions.provider.FileSystemEditionProvider import org.enso.editions.provider.{EditionProvider, FileSystemEditionProvider}
import org.enso.editions.{ import org.enso.editions.{EditionResolver, Editions}
DefaultEnsoVersion,
EditionResolver,
Editions,
EnsoVersion
}
import java.nio.file.Path import java.nio.file.Path
import scala.util.{Success, Try} import scala.annotation.unused
import scala.util.Try
/** A helper class for resolving editions. */
class EditionManager(searchPaths: List[Path]) {
private val editionProvider = FileSystemEditionProvider(searchPaths)
/** A helper class for resolving editions.
*
* @param primaryCachePath will be used for updating editions
* @param searchPaths all paths to search for editions, should include
* [[primaryCachePath]]
*/
class EditionManager(@unused primaryCachePath: Path, searchPaths: List[Path]) {
private val editionProvider = new FileSystemEditionProvider(
searchPaths
)
private val editionResolver = EditionResolver(editionProvider) private val editionResolver = EditionResolver(editionProvider)
private val engineVersionResolver = private val engineVersionResolver =
editions.EngineVersionResolver(editionProvider) editions.EngineVersionResolver(editionProvider)
@ -38,10 +41,46 @@ class EditionManager(searchPaths: List[Path]) {
* engine version * engine version
* @return the resolved engine version * @return the resolved engine version
*/ */
def resolveEngineVersion( def resolveEngineVersion(edition: Editions.RawEdition): Try[SemVer] =
edition: Option[Editions.RawEdition] engineVersionResolver.resolveEnsoVersion(edition).toTry
): Try[EnsoVersion] =
edition // TODO [RW] download edition updates, part of #1772
.map(engineVersionResolver.resolveEnsoVersion(_).toTry)
.getOrElse(Success(DefaultEnsoVersion)) /** Find all editions available in the [[searchPaths]]. */
def findAllAvailableEditions(): Seq[String] =
editionProvider.findAvailableEditions()
}
object EditionManager {
/** Create an [[EditionProvider]] that can locate editions from the
* distribution and the language home.
*/
def makeEditionProvider(
distributionManager: DistributionManager,
languageHome: Option[LanguageHome]
): EditionProvider = new FileSystemEditionProvider(
getSearchPaths(distributionManager, languageHome)
)
/** Get search paths associated with the distribution and language home. */
private def getSearchPaths(
distributionManager: DistributionManager,
languageHome: Option[LanguageHome]
): List[Path] = {
val paths = languageHome.map(_.editions).toList ++
distributionManager.paths.editionSearchPaths
paths.distinct
}
/** Create an [[EditionManager]] that can locate editions from the
* distribution and the language home.
*/
def apply(
distributionManager: DistributionManager,
languageHome: Option[LanguageHome] = None
): EditionManager = new EditionManager(
distributionManager.paths.cachedEditions,
getSearchPaths(distributionManager, languageHome)
)
} }

View File

@ -20,3 +20,15 @@ case class LanguageHome(languageHome: Path) {
def libraries: Path = def libraries: Path =
rootPath.resolve(DistributionManager.LIBRARIES_DIRECTORY) rootPath.resolve(DistributionManager.LIBRARIES_DIRECTORY)
} }
object LanguageHome {
/** Finds the [[LanguageHome]] based on the path of the runner JAR.
*
* Only guaranteed to work properly if used in a component that is started by the `engine-runner`.
*/
def detectFromExecutableLocation(environment: Environment): LanguageHome = {
val homePath = environment.getPathToRunningExecutable.getParent
LanguageHome(homePath)
}
}

View File

@ -29,7 +29,7 @@ object EditionResolutionError {
* reference is invalid. * reference is invalid.
*/ */
case class LibraryReferencesUndefinedRepository( case class LibraryReferencesUndefinedRepository(
libraryName: String, libraryName: LibraryName,
repositoryName: String repositoryName: String
) extends EditionResolutionError( ) extends EditionResolutionError(
s"A library `$libraryName` references a repository `$repositoryName` " + s"A library `$libraryName` references a repository `$repositoryName` " +

View File

@ -62,16 +62,16 @@ case class EditionResolver(provider: EditionProvider) {
* a mapping of resolved libraries * a mapping of resolved libraries
*/ */
private def resolveLibraries( private def resolveLibraries(
libraries: Map[String, Editions.Raw.Library], libraries: Map[LibraryName, Editions.Raw.Library],
currentRepositories: Map[String, Editions.Repository], currentRepositories: Map[String, Editions.Repository],
parent: Option[ResolvedEdition] parent: Option[ResolvedEdition]
): Either[ ): Either[
LibraryReferencesUndefinedRepository, LibraryReferencesUndefinedRepository,
Map[String, Editions.Resolved.Library] Map[LibraryName, Editions.Resolved.Library]
] = { ] = {
val resolvedPairs: Either[ val resolvedPairs: Either[
LibraryReferencesUndefinedRepository, LibraryReferencesUndefinedRepository,
List[(String, Editions.Resolved.Library)] List[(LibraryName, Editions.Resolved.Library)]
] = ] =
libraries.toList.traverse { case (name, library) => libraries.toList.traverse { case (name, library) =>
val resolved = resolveLibrary(library, currentRepositories, parent) val resolved = resolveLibrary(library, currentRepositories, parent)
@ -122,7 +122,7 @@ case class EditionResolver(provider: EditionProvider) {
case (None, None) => case (None, None) =>
Left( Left(
LibraryReferencesUndefinedRepository( LibraryReferencesUndefinedRepository(
libraryName = library.qualifiedName, libraryName = library.name,
repositoryName = repositoryName repositoryName = repositoryName
) )
) )

View File

@ -48,7 +48,7 @@ object EditionSerialization {
implicit val editionDecoder: Decoder[Raw.Edition] = { json => implicit val editionDecoder: Decoder[Raw.Edition] = { json =>
for { for {
parent <- json.get[Option[EditionName]](Fields.parent) parent <- json.get[Option[EditionName]](Fields.parent)
engineVersion <- json.get[Option[EnsoVersion]](Fields.engineVersion) engineVersion <- json.get[Option[SemVer]](Fields.engineVersion)
_ <- _ <-
if (parent.isEmpty && engineVersion.isEmpty) if (parent.isEmpty && engineVersion.isEmpty)
Left( Left(
@ -64,7 +64,7 @@ object EditionSerialization {
libraries <- json.getOrElse[Seq[Raw.Library]](Fields.libraries)(Seq()) libraries <- json.getOrElse[Seq[Raw.Library]](Fields.libraries)(Seq())
res <- { res <- {
val repositoryMap = Map.from(repositories.map(r => (r.name, r))) val repositoryMap = Map.from(repositories.map(r => (r.name, r)))
val libraryMap = Map.from(libraries.map(l => (l.qualifiedName, l))) val libraryMap = Map.from(libraries.map(l => (l.name, l)))
if (libraryMap.size != libraries.size) if (libraryMap.size != libraries.size)
Left( Left(
DecodingFailure( DecodingFailure(
@ -157,7 +157,11 @@ object EditionSerialization {
} }
implicit private val libraryDecoder: Decoder[Raw.Library] = { json => implicit private val libraryDecoder: Decoder[Raw.Library] = { json =>
def makeLibrary(name: String, repository: String, version: Option[SemVer]) = def makeLibrary(
name: LibraryName,
repository: String,
version: Option[SemVer]
) =
if (repository == Fields.localRepositoryName) if (repository == Fields.localRepositoryName)
if (version.isDefined) if (version.isDefined)
Left( Left(
@ -181,7 +185,7 @@ object EditionSerialization {
} }
} }
for { for {
name <- json.get[String](Fields.name) name <- json.get[LibraryName](Fields.name)
repository <- json.get[String](Fields.repository) repository <- json.get[String](Fields.repository)
version <- json.get[Option[SemVer]](Fields.version) version <- json.get[Option[SemVer]](Fields.version)
res <- makeLibrary(name, repository, version) res <- makeLibrary(name, repository, version)

View File

@ -42,22 +42,22 @@ trait Editions {
* It should consist of a prefix followed by a dot an the library name, for * It should consist of a prefix followed by a dot an the library name, for
* example `Prefix.Library_Name`. * example `Prefix.Library_Name`.
*/ */
def qualifiedName: String def name: LibraryName
} }
/** Represents a local library. */ /** Represents a local library. */
case class LocalLibrary(override val qualifiedName: String) extends Library case class LocalLibrary(override val name: LibraryName) extends Library
/** Represents a specific version of the library that is published in a /** Represents a specific version of the library that is published in a
* repository. * repository.
* *
* @param qualifiedName the qualified name of the library * @param name the qualified name of the library
* @param version the exact version of the library that should be used * @param version the exact version of the library that should be used
* @param repository the recommended repository to download the library from, * @param repository the recommended repository to download the library from,
* if it is not yet cached * if it is not yet cached
*/ */
case class PublishedLibrary( case class PublishedLibrary(
override val qualifiedName: String, override val name: LibraryName,
version: SemVer, version: SemVer,
repository: LibraryRepositoryType repository: LibraryRepositoryType
) extends Library ) extends Library
@ -75,9 +75,9 @@ trait Editions {
*/ */
case class Edition( case class Edition(
parent: Option[NestedEditionType] = None, parent: Option[NestedEditionType] = None,
engineVersion: Option[EnsoVersion] = None, engineVersion: Option[SemVer] = None,
repositories: Map[String, Editions.Repository] = Map.empty, repositories: Map[String, Editions.Repository] = Map.empty,
libraries: Map[String, Library] = Map.empty libraries: Map[LibraryName, Library] = Map.empty
) { ) {
if (parent.isEmpty && engineVersion.isEmpty) if (parent.isEmpty && engineVersion.isEmpty)
throw new IllegalArgumentException( throw new IllegalArgumentException(
@ -134,7 +134,7 @@ object Editions {
* is either the version override directly specified in the edition or the * is either the version override directly specified in the edition or the
* version implied by its parent. * version implied by its parent.
*/ */
def getEngineVersion: EnsoVersion = edition.engineVersion.getOrElse { def getEngineVersion: SemVer = edition.engineVersion.getOrElse {
val parent = edition.parent.getOrElse { val parent = edition.parent.getOrElse {
throw new IllegalStateException( throw new IllegalStateException(
"Internal error: Resolved edition does not imply an engine version." "Internal error: Resolved edition does not imply an engine version."
@ -142,6 +142,23 @@ object Editions {
} }
parent.getEngineVersion parent.getEngineVersion
} }
/** Returns a mapping of all libraries defined in the edition, including any
* libraries defined in parent editions (also taking into account the
* overrides).
*/
def getAllDefinedLibraries: Map[LibraryName, LibraryVersion] = {
val parent =
edition.parent.map(_.getAllDefinedLibraries).getOrElse(Map.empty)
edition.libraries.foldLeft(parent) { case (map, (name, lib)) =>
val version = lib match {
case Resolved.LocalLibrary(_) => LibraryVersion.Local
case Resolved.PublishedLibrary(_, version, repository) =>
LibraryVersion.Published(version, repository)
}
map.updated(name, version)
}
}
} }
/** Syntax helpers for a raw edition. */ /** Syntax helpers for a raw edition. */

View File

@ -1,5 +1,6 @@
package org.enso.editions package org.enso.editions
import nl.gn0s1s.bump.SemVer
import org.enso.editions.Editions.RawEdition import org.enso.editions.Editions.RawEdition
import org.enso.editions.provider.EditionProvider import org.enso.editions.provider.EditionProvider
@ -19,7 +20,7 @@ case class EngineVersionResolver(editionProvider: EditionProvider) {
*/ */
def resolveEnsoVersion( def resolveEnsoVersion(
edition: RawEdition edition: RawEdition
): Either[EditionResolutionError, EnsoVersion] = { ): Either[EditionResolutionError, SemVer] = {
for { for {
edition <- editionResolver.resolve(edition) edition <- editionResolver.resolve(edition)
} yield edition.getEngineVersion } yield edition.getEngineVersion

View File

@ -1,6 +1,7 @@
package org.enso.editions package org.enso.editions
import io.circe.{Decoder, DecodingFailure} import io.circe.syntax.EncoderOps
import io.circe.{Decoder, DecodingFailure, Encoder}
/** Represents a library name that should uniquely identify the library. /** Represents a library name that should uniquely identify the library.
* *
@ -31,6 +32,10 @@ object LibraryName {
} yield name } yield name
} }
implicit val encoder: Encoder[LibraryName] = { libraryName =>
libraryName.toString.asJson
}
private val separator = '.' private val separator = '.'
/** Creates a [[LibraryName]] from its string representation. /** Creates a [[LibraryName]] from its string representation.

View File

@ -5,12 +5,14 @@ import org.enso.editions.{EditionSerialization, Editions}
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.nio.file.{Files, Path} import java.nio.file.{Files, Path}
import scala.annotation.tailrec import scala.annotation.tailrec
import scala.util.{Failure, Success, Try} import scala.collection.Factory
import scala.jdk.StreamConverters.StreamHasToScala
import scala.util.{Failure, Success, Try, Using}
/** An implementation of [[EditionProvider]] that looks for the edition files in /** An implementation of [[EditionProvider]] that looks for the edition files in
* a list of filesystem paths. * a list of filesystem paths.
*/ */
case class FileSystemEditionProvider(searchPaths: List[Path]) class FileSystemEditionProvider(searchPaths: List[Path])
extends EditionProvider { extends EditionProvider {
/** @inheritdoc */ /** @inheritdoc */
@ -60,4 +62,23 @@ case class FileSystemEditionProvider(searchPaths: List[Path])
.map(EditionReadError) .map(EditionReadError)
} else Left(EditionNotFound) } else Left(EditionNotFound)
} }
/** Finds all editions available on the [[searchPaths]]. */
def findAvailableEditions(): Seq[String] =
searchPaths.flatMap(findEditionsAt).distinct
private def findEditionName(path: Path): Option[String] = {
val name = path.getFileName.toString
if (name.endsWith(editionSuffix)) {
Some(name.stripSuffix(editionSuffix))
} else None
}
private def findEditionsAt(path: Path): Seq[String] =
listDir(path).filter(Files.isRegularFile(_)).flatMap(findEditionName)
private def listDir(dir: Path): Seq[Path] =
if (Files.exists(dir))
Using(Files.list(dir))(_.toScala(Factory.arrayFactory).toSeq).get
else Seq()
} }

View File

@ -20,24 +20,28 @@ class EditionResolverSpec
val editions: Map[String, Editions.RawEdition] = Map( val editions: Map[String, Editions.RawEdition] = Map(
"2021.0" -> Editions.Raw.Edition( "2021.0" -> Editions.Raw.Edition(
parent = None, parent = None,
engineVersion = Some(SemVerEnsoVersion(SemVer(1, 2, 3))), engineVersion = Some(SemVer(1, 2, 3)),
repositories = Map( repositories = Map(
"main" -> mainRepo "main" -> mainRepo
), ),
libraries = Map( libraries = Map(
"Standard.Base" -> Editions.Raw LibraryName("Standard", "Base") -> Editions.Raw
.PublishedLibrary("Standard.Base", SemVer(1, 2, 3), "main") .PublishedLibrary(
LibraryName("Standard", "Base"),
SemVer(1, 2, 3),
"main"
)
) )
), ),
"cycleA" -> Editions.Raw.Edition( "cycleA" -> Editions.Raw.Edition(
parent = Some("cycleB"), parent = Some("cycleB"),
engineVersion = Some(SemVerEnsoVersion(SemVer(1, 2, 3))), engineVersion = Some(SemVer(1, 2, 3)),
repositories = Map(), repositories = Map(),
libraries = Map() libraries = Map()
), ),
"cycleB" -> Editions.Raw.Edition( "cycleB" -> Editions.Raw.Edition(
parent = Some("cycleA"), parent = Some("cycleA"),
engineVersion = Some(SemVerEnsoVersion(SemVer(1, 2, 3))), engineVersion = Some(SemVer(1, 2, 3)),
repositories = Map(), repositories = Map(),
libraries = Map() libraries = Map()
) )
@ -59,12 +63,14 @@ class EditionResolverSpec
val repo = Repository.make("foo", "http://example.com").get val repo = Repository.make("foo", "http://example.com").get
val edition = Editions.Raw.Edition( val edition = Editions.Raw.Edition(
parent = None, parent = None,
engineVersion = Some(SemVerEnsoVersion(SemVer(1, 2, 3))), engineVersion = Some(SemVer(1, 2, 3)),
repositories = Map("foo" -> repo), repositories = Map("foo" -> repo),
libraries = Map( libraries = Map(
"bar.baz" -> Editions.Raw.LocalLibrary("bar.baz"), LibraryName("bar", "baz") -> Editions.Raw.LocalLibrary(
"foo.bar" -> Editions.Raw LibraryName("bar", "baz")
.PublishedLibrary("foo.bar", SemVer(1, 2, 3), "foo") ),
LibraryName("foo", "bar") -> Editions.Raw
.PublishedLibrary(LibraryName("foo", "bar"), SemVer(1, 2, 3), "foo")
) )
) )
@ -73,33 +79,47 @@ class EditionResolverSpec
resolved.parent should be(empty) resolved.parent should be(empty)
resolved.repositories shouldEqual edition.repositories resolved.repositories shouldEqual edition.repositories
resolved.libraries should have size 2 resolved.libraries should have size 2
resolved.libraries("bar.baz") shouldEqual Editions.Resolved resolved.libraries(
.LocalLibrary("bar.baz") LibraryName("bar", "baz")
resolved.libraries("foo.bar") shouldEqual Editions.Resolved ) shouldEqual Editions.Resolved
.PublishedLibrary("foo.bar", SemVer(1, 2, 3), repo) .LocalLibrary(LibraryName("bar", "baz"))
resolved.libraries(
LibraryName("foo", "bar")
) shouldEqual Editions.Resolved
.PublishedLibrary(LibraryName("foo", "bar"), SemVer(1, 2, 3), repo)
} }
} }
"resolve a nested edition" in { "resolve a nested edition" in {
val edition = Editions.Raw.Edition( val edition = Editions.Raw.Edition(
parent = Some("2021.0"), parent = Some("2021.0"),
engineVersion = Some(SemVerEnsoVersion(SemVer(1, 2, 3))), engineVersion = Some(SemVer(1, 2, 3)),
repositories = Map(), repositories = Map(),
libraries = Map( libraries = Map(
"bar.baz" -> Editions.Raw.LocalLibrary("bar.baz"), LibraryName("bar", "baz") -> Editions.Raw.LocalLibrary(
"foo.bar" -> Editions.Raw LibraryName("bar", "baz")
.PublishedLibrary("foo.bar", SemVer(1, 2, 3), "main") ),
LibraryName("foo", "bar") -> Editions.Raw
.PublishedLibrary(
LibraryName("foo", "bar"),
SemVer(1, 2, 3),
"main"
)
) )
) )
inside(resolver.resolve(edition)) { case Right(resolved) => inside(resolver.resolve(edition)) { case Right(resolved) =>
resolved.parent should be(defined) resolved.parent should be(defined)
resolved.libraries should have size 2 resolved.libraries should have size 2
resolved.libraries("bar.baz") shouldEqual Editions.Resolved resolved.libraries(
.LocalLibrary("bar.baz") LibraryName("bar", "baz")
resolved.libraries("foo.bar") shouldEqual Editions.Resolved ) shouldEqual Editions.Resolved
.LocalLibrary(LibraryName("bar", "baz"))
resolved.libraries(
LibraryName("foo", "bar")
) shouldEqual Editions.Resolved
.PublishedLibrary( .PublishedLibrary(
"foo.bar", LibraryName("foo", "bar"),
SemVer(1, 2, 3), SemVer(1, 2, 3),
FakeEditionProvider.mainRepo FakeEditionProvider.mainRepo
) )
@ -116,24 +136,30 @@ class EditionResolverSpec
engineVersion = None, engineVersion = None,
repositories = Map("main" -> localRepo), repositories = Map("main" -> localRepo),
libraries = Map( libraries = Map(
"foo.bar" -> Editions.Raw LibraryName("foo", "bar") -> Editions.Raw
.PublishedLibrary("foo.bar", SemVer(1, 2, 3), "main") .PublishedLibrary(
LibraryName("foo", "bar"),
SemVer(1, 2, 3),
"main"
)
) )
) )
inside(resolver.resolve(edition)) { case Right(resolved) => inside(resolver.resolve(edition)) { case Right(resolved) =>
resolved.parent should be(defined) resolved.parent should be(defined)
resolved.libraries should have size 1 resolved.libraries should have size 1
resolved.libraries("foo.bar") shouldEqual resolved.libraries(LibraryName("foo", "bar")) shouldEqual
Editions.Resolved.PublishedLibrary( Editions.Resolved.PublishedLibrary(
"foo.bar", LibraryName("foo", "bar"),
SemVer(1, 2, 3), SemVer(1, 2, 3),
localRepo localRepo
) )
resolved.parent.value.libraries("Standard.Base") shouldEqual resolved.parent.value.libraries(
LibraryName("Standard", "Base")
) shouldEqual
Editions.Resolved.PublishedLibrary( Editions.Resolved.PublishedLibrary(
"Standard.Base", LibraryName("Standard", "Base"),
SemVer(1, 2, 3), SemVer(1, 2, 3),
FakeEditionProvider.mainRepo FakeEditionProvider.mainRepo
) )
@ -143,7 +169,7 @@ class EditionResolverSpec
"avoid cycles in the resolution" in { "avoid cycles in the resolution" in {
val edition = Editions.Raw.Edition( val edition = Editions.Raw.Edition(
parent = Some("cycleA"), parent = Some("cycleA"),
engineVersion = Some(SemVerEnsoVersion(SemVer(1, 2, 3))), engineVersion = Some(SemVer(1, 2, 3)),
repositories = Map(), repositories = Map(),
libraries = Map() libraries = Map()
) )

View File

@ -54,13 +54,16 @@ class EditionSerializationSpec extends AnyWordSpec with Matchers with Inside {
.url shouldEqual "http://127.0.0.1:8080/root" .url shouldEqual "http://127.0.0.1:8080/root"
edition.libraries.values should contain theSameElementsAs Seq( edition.libraries.values should contain theSameElementsAs Seq(
Editions.Raw.LocalLibrary("Foo.Local"), Editions.Raw.LocalLibrary(LibraryName("Foo", "Local")),
Editions.Raw.PublishedLibrary("Bar.Baz", SemVer(0, 0, 0), "example"), Editions.Raw.PublishedLibrary(
Editions.Raw.PublishedLibrary("A.B", SemVer(1, 0, 1), "bar") LibraryName("Bar", "Baz"),
) SemVer(0, 0, 0),
edition.engineVersion should contain( "example"
SemVerEnsoVersion(SemVer(1, 2, 3, Some("SNAPSHOT"))) ),
Editions.Raw
.PublishedLibrary(LibraryName("A", "B"), SemVer(1, 0, 1), "bar")
) )
edition.engineVersion should contain(SemVer(1, 2, 3, Some("SNAPSHOT")))
} }
} }
@ -82,7 +85,7 @@ class EditionSerializationSpec extends AnyWordSpec with Matchers with Inside {
val parsed = EditionSerialization.parseYamlString( val parsed = EditionSerialization.parseYamlString(
"""extends: foo """extends: foo
|libraries: |libraries:
|- name: bar |- name: bar.baz
| repository: local | repository: local
| version: 1.2.3-SHOULD-NOT-BE-HERE | version: 1.2.3-SHOULD-NOT-BE-HERE
|""".stripMargin |""".stripMargin
@ -94,7 +97,7 @@ class EditionSerializationSpec extends AnyWordSpec with Matchers with Inside {
val parsed2 = EditionSerialization.parseYamlString( val parsed2 = EditionSerialization.parseYamlString(
"""extends: foo """extends: foo
|libraries: |libraries:
|- name: bar |- name: bar.baz
| repository: something | repository: something
|""".stripMargin |""".stripMargin
) )

View File

@ -118,6 +118,11 @@ abstract class JsonRpcServerTestKit
parsed shouldEqual Right(json) parsed shouldEqual Right(json)
} }
def expectSomeJson(timeout: FiniteDuration = 5.seconds.dilated): Json = {
val parsed = parse(expectMessage(timeout))
inside(parsed) { case Right(json) => json }
}
def expectNoMessage(): Unit = outActor.expectNoMessage() def expectNoMessage(): Unit = outActor.expectNoMessage()
} }
} }

View File

@ -54,7 +54,7 @@ case class LibraryResolver(
): Either[LibraryResolutionError, LibraryVersion] = { ): Either[LibraryResolutionError, LibraryVersion] = {
import Editions.Resolved._ import Editions.Resolved._
val immediateResult = val immediateResult =
edition.libraries.get(libraryName.qualifiedName).map { edition.libraries.get(libraryName).map {
case LocalLibrary(_) => case LocalLibrary(_) =>
Right(LibraryVersion.Local) Right(LibraryVersion.Local)
case PublishedLibrary(_, version, repository) => case PublishedLibrary(_, version, repository) =>

View File

@ -1,7 +1,6 @@
package org.enso.librarymanager.local package org.enso.librarymanager.local
import com.typesafe.scalalogging.Logger import com.typesafe.scalalogging.Logger
import org.enso.distribution.FileSystem.PathSyntax
import org.enso.editions.LibraryName import org.enso.editions.LibraryName
import org.enso.logger.masking.MaskedPath import org.enso.logger.masking.MaskedPath
@ -31,7 +30,8 @@ class DefaultLocalLibraryProvider(searchPaths: List[Path])
searchPaths: List[Path] searchPaths: List[Path]
): Option[Path] = searchPaths match { ): Option[Path] = searchPaths match {
case head :: tail => case head :: tail =>
val potentialPath = head / libraryName.namespace / libraryName.name val potentialPath =
LocalLibraryProvider.resolveLibraryPath(head, libraryName)
if (Files.exists(potentialPath) && Files.isDirectory(potentialPath)) { if (Files.exists(potentialPath) && Files.isDirectory(potentialPath)) {
logger.trace( logger.trace(
s"Found a local $libraryName at " + s"Found a local $libraryName at " +

View File

@ -1,5 +1,6 @@
package org.enso.librarymanager.local package org.enso.librarymanager.local
import org.enso.distribution.FileSystem.PathSyntax
import org.enso.editions.LibraryName import org.enso.editions.LibraryName
import java.nio.file.Path import java.nio.file.Path
@ -12,3 +13,12 @@ trait LocalLibraryProvider {
*/ */
def findLibrary(libraryName: LibraryName): Option[Path] def findLibrary(libraryName: LibraryName): Option[Path]
} }
object LocalLibraryProvider {
/** Resolve a path to the package root of a particular library located in one
* of the local library roots.
*/
def resolveLibraryPath(root: Path, libraryName: LibraryName): Path =
root / libraryName.namespace / libraryName.name
}

View File

@ -2,12 +2,7 @@ package org.enso.librarymanager
import nl.gn0s1s.bump.SemVer import nl.gn0s1s.bump.SemVer
import org.enso.editions.Editions.Repository import org.enso.editions.Editions.Repository
import org.enso.editions.{ import org.enso.editions.{Editions, LibraryName, LibraryVersion}
DefaultEnsoVersion,
Editions,
LibraryName,
LibraryVersion
}
import org.enso.librarymanager.local.LocalLibraryProvider import org.enso.librarymanager.local.LocalLibraryProvider
import org.enso.testkit.EitherValue import org.enso.testkit.EitherValue
import org.scalatest.Inside import org.scalatest.Inside
@ -25,11 +20,15 @@ class LibraryResolverSpec
val mainRepo = Repository.make("main", "https://example.com/main").get val mainRepo = Repository.make("main", "https://example.com/main").get
val parentEdition = Editions.Resolved.Edition( val parentEdition = Editions.Resolved.Edition(
parent = None, parent = None,
engineVersion = Some(DefaultEnsoVersion), engineVersion = Some(SemVer(0, 0, 0)),
repositories = Map("main" -> mainRepo), repositories = Map("main" -> mainRepo),
libraries = Map( libraries = Map(
"Standard.Base" -> Editions.Resolved LibraryName("Standard", "Base") -> Editions.Resolved
.PublishedLibrary("Standard.Base", SemVer(4, 5, 6), mainRepo) .PublishedLibrary(
LibraryName("Standard", "Base"),
SemVer(4, 5, 6),
mainRepo
)
) )
) )
val customRepo = Repository.make("custom", "https://example.com/custom").get val customRepo = Repository.make("custom", "https://example.com/custom").get
@ -38,24 +37,34 @@ class LibraryResolverSpec
engineVersion = None, engineVersion = None,
repositories = Map("custom" -> customRepo), repositories = Map("custom" -> customRepo),
libraries = Map( libraries = Map(
"Foo.Main" -> Editions.Resolved LibraryName("Foo", "Main") -> Editions.Resolved
.PublishedLibrary("Foo.Main", SemVer(1, 0, 0), mainRepo), .PublishedLibrary(
"Foo.My" -> Editions.Resolved LibraryName("Foo", "Main"),
.PublishedLibrary("Foo.My", SemVer(2, 0, 0), customRepo), SemVer(1, 0, 0),
"Foo.Local" -> Editions.Resolved.LocalLibrary("Foo.Local") mainRepo
),
LibraryName("Foo", "My") -> Editions.Resolved
.PublishedLibrary(
LibraryName("Foo", "My"),
SemVer(2, 0, 0),
customRepo
),
LibraryName("Foo", "Local") -> Editions.Resolved.LocalLibrary(
LibraryName("Foo", "Local")
)
) )
) )
case class FakeLocalLibraryProvider(fixtures: Map[String, Path]) case class FakeLocalLibraryProvider(fixtures: Map[LibraryName, Path])
extends LocalLibraryProvider { extends LocalLibraryProvider {
override def findLibrary(libraryName: LibraryName): Option[Path] = override def findLibrary(libraryName: LibraryName): Option[Path] =
fixtures.get(libraryName.qualifiedName) fixtures.get(libraryName)
} }
val localLibraries = Map( val localLibraries = Map(
"Foo.My" -> Path.of("./Foo/My"), LibraryName("Foo", "My") -> Path.of("./Foo/My"),
"Foo.Local" -> Path.of("./Foo/Local"), LibraryName("Foo", "Local") -> Path.of("./Foo/Local"),
"Standard.Base" -> Path.of("./Standard/Base") LibraryName("Standard", "Base") -> Path.of("./Standard/Base")
) )
val resolver = LibraryResolver(FakeLocalLibraryProvider(localLibraries)) val resolver = LibraryResolver(FakeLocalLibraryProvider(localLibraries))

View File

@ -205,7 +205,26 @@ object Config {
val overridesObject = JsonObject( val overridesObject = JsonObject(
overrides ++ preferLocalOverride: _* overrides ++ preferLocalOverride: _*
) )
originals.remove(JsonFields.ensoVersion).deepMerge(overridesObject).asJson
/** Fields that should not be inherited from the original set of fields.
*
* `ensoVersion` is dropped, because due to migration it is overridden by
* `edition` and we don't want to have both to avoid inconsistency.
*
* `prefer-local-libraries` cannot be inherited, because if it was set to
* `true` and we have changed it to `false`, overrides will not include it,
* because, as `false` is its default value, we just ignore the field. But
* if we inherit it from original fields, we would get `true` back. If the
* setting is still set to true, it will be included in the overrides, so
* it does not have to be inherited either.
*/
val fieldsToRemoveFromOriginals =
Seq(JsonFields.ensoVersion, JsonFields.preferLocalLibraries)
val removed = fieldsToRemoveFromOriginals.foldLeft(originals) {
case (obj, key) => obj.remove(key)
}
removed.deepMerge(overridesObject).asJson
} }
/** Tries to parse the [[Config]] from a YAML string. */ /** Tries to parse the [[Config]] from a YAML string. */
@ -229,7 +248,7 @@ object Config {
ensoVersion: SemVer ensoVersion: SemVer
): Editions.RawEdition = Editions.Raw.Edition( ): Editions.RawEdition = Editions.Raw.Edition(
parent = None, parent = None,
engineVersion = Some(SemVerEnsoVersion(ensoVersion)), engineVersion = Some(ensoVersion),
repositories = Map(), repositories = Map(),
libraries = Map() libraries = Map()
) )

View File

@ -48,11 +48,13 @@ case class Package[F](
/** Stores the package metadata on the hard drive. If the package does not exist, /** Stores the package metadata on the hard drive. If the package does not exist,
* creates the required directory structure. * creates the required directory structure.
*/ */
def save(): Unit = { def save(): Try[Unit] = for {
_ <- Try {
if (!root.exists) createDirectories() if (!root.exists) createDirectories()
if (!sourceDir.exists) createSourceDir() if (!sourceDir.exists) createSourceDir()
saveConfig()
} }
_ <- saveConfig()
} yield ()
/** Creates the package directory structure. /** Creates the package directory structure.
*/ */
@ -97,10 +99,9 @@ case class Package[F](
/** Saves the config metadata into the package configuration file. /** Saves the config metadata into the package configuration file.
*/ */
def saveConfig(): Unit = { def saveConfig(): Try[Unit] =
val writer = configFile.newBufferedWriter Using(configFile.newBufferedWriter) { writer =>
Try(writer.write(config.toYaml)) writer.write(config.toYaml)
writer.close()
} }
/** Gets the location of the package's Main file. /** Gets the location of the package's Main file.
@ -208,13 +209,14 @@ class PackageManager[F](implicit val fileSystem: FileSystem[F]) {
version: String = "0.0.1", version: String = "0.0.1",
edition: Option[Editions.RawEdition] = None, edition: Option[Editions.RawEdition] = None,
authors: List[Contact] = List(), authors: List[Contact] = List(),
maintainers: List[Contact] = List() maintainers: List[Contact] = List(),
license: String = ""
): Package[F] = { ): Package[F] = {
val config = Config( val config = Config(
name = NameValidation.normalizeName(name), name = NameValidation.normalizeName(name),
namespace = namespace, namespace = namespace,
version = version, version = version,
license = "", license = license,
authors = authors, authors = authors,
edition = edition, edition = edition,
preferLocalLibraries = true, preferLocalLibraries = true,

View File

@ -2,7 +2,6 @@ package org.enso.pkg
import io.circe.{Json, JsonObject} import io.circe.{Json, JsonObject}
import nl.gn0s1s.bump.SemVer import nl.gn0s1s.bump.SemVer
import org.enso.editions.SemVerEnsoVersion
import org.scalatest.matchers.should.Matchers import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec import org.scalatest.wordspec.AnyWordSpec
import org.scalatest.{Inside, OptionValues} import org.scalatest.{Inside, OptionValues}
@ -64,9 +63,7 @@ class ConfigSpec
|""".stripMargin |""".stripMargin
val parsed = Config.fromYaml(oldFormat).get val parsed = Config.fromYaml(oldFormat).get
parsed.edition.get.engineVersion should contain( parsed.edition.get.engineVersion should contain(SemVer(1, 2, 3))
SemVerEnsoVersion(SemVer(1, 2, 3))
)
val serialized = parsed.toYaml val serialized = parsed.toYaml
val parsedAgain = Config.fromYaml(serialized).get val parsedAgain = Config.fromYaml(serialized).get

View File

@ -1,26 +0,0 @@
package org.enso.projectmanager.data
import enumeratum._
import org.enso.cli.task.{TaskProgress, ProgressUnit => TaskProgressUnit}
/** Represents the unit used by progress updates. */
sealed trait ProgressUnit extends EnumEntry
object ProgressUnit extends Enum[ProgressUnit] with CirceEnum[ProgressUnit] {
/** Indicates that progress is measured by amount of bytes processed. */
case object Bytes extends ProgressUnit
/** Indicates that progress is measured by some other unit or it is not
* measured at all.
*/
case object Other extends ProgressUnit
override val values = findValues
/** Creates a [[ProgressUnit]] from the unit associated with [[TaskProgress]].
*/
def fromTask(task: TaskProgress[_]): ProgressUnit = task.unit match {
case TaskProgressUnit.Bytes => Bytes
case TaskProgressUnit.Unspecified => Other
}
}

View File

@ -3,6 +3,7 @@ package org.enso.projectmanager.protocol
import io.circe.generic.auto._ import io.circe.generic.auto._
import org.enso.jsonrpc.Protocol import org.enso.jsonrpc.Protocol
import org.enso.projectmanager.protocol.ProjectManagementApi._ import org.enso.projectmanager.protocol.ProjectManagementApi._
import org.enso.cli.task.notifications.TaskNotificationApi._
/** Implicits from this module are required for correct serialization. /** Implicits from this module are required for correct serialization.
* *

View File

@ -9,7 +9,6 @@ import org.enso.jsonrpc.{Error, HasParams, HasResult, Method, Unused}
import org.enso.projectmanager.data.{ import org.enso.projectmanager.data.{
EngineVersion, EngineVersion,
MissingComponentAction, MissingComponentAction,
ProgressUnit,
ProjectMetadata, ProjectMetadata,
Socket Socket
} }
@ -117,46 +116,6 @@ object ProjectManagementApi {
} }
} }
case object TaskStarted extends Method("task/started") {
case class Params(
taskId: UUID,
relatedOperation: String,
unit: ProgressUnit,
total: Option[Long]
)
implicit val hasParams = new HasParams[this.type] {
type Params = TaskStarted.Params
}
}
case object TaskProgressUpdate extends Method("task/progress-update") {
case class Params(
taskId: UUID,
message: Option[String],
done: Long
)
implicit val hasParams = new HasParams[this.type] {
type Params = TaskProgressUpdate.Params
}
}
case object TaskFinished extends Method("task/progress-update") {
case class Params(
taskId: UUID,
message: Option[String],
success: Boolean
)
implicit val hasParams = new HasParams[this.type] {
type Params = TaskFinished.Params
}
}
case object EngineListInstalled extends Method("engine/list-installed") { case object EngineListInstalled extends Method("engine/list-installed") {
case class Result(versions: Seq[EngineVersion]) case class Result(versions: Seq[EngineVersion])

View File

@ -3,19 +3,11 @@ package org.enso.projectmanager.requesthandler
import akka.actor.{Actor, ActorRef, Cancellable, Stash, Status} import akka.actor.{Actor, ActorRef, Cancellable, Stash, Status}
import akka.pattern.pipe import akka.pattern.pipe
import com.typesafe.scalalogging.{LazyLogging, Logger} import com.typesafe.scalalogging.{LazyLogging, Logger}
import org.enso.cli.task.ProgressNotification
import org.enso.cli.task.notifications.ActorProgressNotificationForwarder
import org.enso.jsonrpc.Errors.ServiceError import org.enso.jsonrpc.Errors.ServiceError
import org.enso.jsonrpc.{ import org.enso.jsonrpc._
HasParams,
HasResult,
Id,
Method,
Request,
ResponseError,
ResponseResult
}
import org.enso.projectmanager.control.effect.Exec import org.enso.projectmanager.control.effect.Exec
import org.enso.projectmanager.service.versionmanagement.ProgressNotification
import org.enso.projectmanager.service.versionmanagement.ProgressNotification.translateProgressNotification
import org.enso.projectmanager.util.UnhandledLogging import org.enso.projectmanager.util.UnhandledLogging
import scala.annotation.unused import scala.annotation.unused
@ -118,7 +110,8 @@ abstract class RequestHandler[
abandonTimeout(id, replyTo, timeoutCancellable) abandonTimeout(id, replyTo, timeoutCancellable)
case _ => case _ =>
} }
replyTo ! translateProgressNotification(method.name, notification) replyTo ! ActorProgressNotificationForwarder
.translateProgressNotification(method.name, notification)
} }
/** Cancels the timeout operation. /** Cancels the timeout operation.

View File

@ -4,7 +4,7 @@ import akka.actor.ActorRef
import cats.MonadError import cats.MonadError
import com.typesafe.scalalogging.Logger import com.typesafe.scalalogging.Logger
import nl.gn0s1s.bump.SemVer import nl.gn0s1s.bump.SemVer
import org.enso.editions.EnsoVersion import org.enso.editions.DefaultEdition
import org.enso.pkg.Config import org.enso.pkg.Config
import org.enso.projectmanager.control.core.syntax._ import org.enso.projectmanager.control.core.syntax._
import org.enso.projectmanager.control.core.{ import org.enso.projectmanager.control.core.{
@ -44,7 +44,6 @@ import org.enso.projectmanager.service.ValidationFailure.{
NameShouldStartWithCapitalLetter NameShouldStartWithCapitalLetter
} }
import org.enso.projectmanager.service.config.GlobalConfigServiceApi import org.enso.projectmanager.service.config.GlobalConfigServiceApi
import org.enso.projectmanager.service.config.GlobalConfigServiceFailure.ConfigurationFileAccessFailure
import org.enso.projectmanager.service.versionmanagement.RuntimeVersionManagerErrorRecoverySyntax._ import org.enso.projectmanager.service.versionmanagement.RuntimeVersionManagerErrorRecoverySyntax._
import org.enso.projectmanager.service.versionmanagement.RuntimeVersionManagerFactory import org.enso.projectmanager.service.versionmanagement.RuntimeVersionManagerFactory
import org.enso.projectmanager.versionmanagement.DistributionConfiguration import org.enso.projectmanager.versionmanagement.DistributionConfiguration
@ -306,14 +305,6 @@ class ProjectService[
missingComponentAction: MissingComponentAction missingComponentAction: MissingComponentAction
): F[ProjectServiceFailure, RunningLanguageServerInfo] = for { ): F[ProjectServiceFailure, RunningLanguageServerInfo] = for {
version <- resolveProjectVersion(project) version <- resolveProjectVersion(project)
version <- configurationService
.resolveEnsoVersion(version)
.mapError { case ConfigurationFileAccessFailure(message) =>
ProjectOpenFailed(
"Could not deduce the default version to use for the project: " +
message
)
}
_ <- preinstallEngine(progressTracker, version, missingComponentAction) _ <- preinstallEngine(progressTracker, version, missingComponentAction)
sockets <- languageServerGateway sockets <- languageServerGateway
.start(progressTracker, clientId, project, version) .start(progressTracker, clientId, project, version)
@ -371,18 +362,7 @@ class ProjectService[
private def resolveProjectMetadata( private def resolveProjectMetadata(
project: Project project: Project
): F[ProjectServiceFailure, ProjectMetadata] = { ): F[ProjectServiceFailure, ProjectMetadata] = {
val version = for { val version = resolveProjectVersion(project)
version <- resolveProjectVersion(project)
version <- configurationService
.resolveEnsoVersion(version)
.mapError { case ConfigurationFileAccessFailure(message) =>
GlobalConfigurationAccessFailure(
"Could not deduce the default version to use for the project: " +
message
)
}
} yield version
for { for {
version <- version.map(Some(_)).recover { error => version <- version.map(Some(_)).recover { error =>
// TODO [RW] We may consider sending this warning to the IDE once // TODO [RW] We may consider sending this warning to the IDE once
@ -483,11 +463,16 @@ class ProjectService[
private def resolveProjectVersion( private def resolveProjectVersion(
project: Project project: Project
): F[ProjectServiceFailure, EnsoVersion] = ): F[ProjectServiceFailure, SemVer] =
Sync[F] Sync[F]
.blockingOp { .blockingOp {
// TODO [RW] at some point we will need to use the configuration service to get the actual default version, see #1864
val _ = configurationService
val edition =
project.edition.getOrElse(DefaultEdition.getDefaultEdition)
distributionConfiguration.editionManager distributionConfiguration.editionManager
.resolveEngineVersion(project.edition) .resolveEngineVersion(edition)
.get .get
} }
.mapError { error => .mapError { error =>

View File

@ -1,18 +1,20 @@
package org.enso.projectmanager.service.versionmanagement package org.enso.projectmanager.service.versionmanagement
import java.util.UUID
import akka.actor.ActorRef import akka.actor.ActorRef
import com.typesafe.scalalogging.Logger import com.typesafe.scalalogging.Logger
import nl.gn0s1s.bump.SemVer import nl.gn0s1s.bump.SemVer
import org.enso.cli.task.{ProgressListener, TaskProgress} import org.enso.cli.task.{
ProgressNotification,
ProgressNotificationForwarder,
ProgressUnit
}
import org.enso.distribution.locking.Resource import org.enso.distribution.locking.Resource
import org.enso.projectmanager.data.ProgressUnit
import org.enso.runtimeversionmanager.components.{ import org.enso.runtimeversionmanager.components.{
GraalVMVersion, GraalVMVersion,
RuntimeVersionManagementUserInterface RuntimeVersionManagementUserInterface
} }
import scala.util.{Failure, Success, Try} import java.util.UUID
/** A [[RuntimeVersionManagementUserInterface]] that sends /** A [[RuntimeVersionManagementUserInterface]] that sends
* [[ProgressNotification]] to the specified actors (both for usual tasks and * [[ProgressNotification]] to the specified actors (both for usual tasks and
@ -27,54 +29,8 @@ class ControllerInterface(
progressTracker: ActorRef, progressTracker: ActorRef,
allowMissingComponents: Boolean, allowMissingComponents: Boolean,
allowBrokenComponents: Boolean allowBrokenComponents: Boolean
) extends RuntimeVersionManagementUserInterface { ) extends RuntimeVersionManagementUserInterface
with ProgressNotificationForwarder {
/** @inheritdoc */
override def trackProgress(message: String, task: TaskProgress[_]): Unit = {
var uuid: Option[UUID] = None
/** Initializes the task on first invocation and just returns the
* generated UUID on further invocations.
*/
def initializeTask(total: Option[Long]): UUID = uuid match {
case Some(value) => value
case None =>
val generated = UUID.randomUUID()
uuid = Some(generated)
val unit = ProgressUnit.fromTask(task)
progressTracker ! ProgressNotification.TaskStarted(
generated,
total,
unit
)
generated
}
task.addProgressListener(new ProgressListener[Any] {
/** @inheritdoc */
override def progressUpdate(
done: Long,
total: Option[Long]
): Unit = {
val uuid = initializeTask(total)
progressTracker ! ProgressNotification.TaskUpdate(
uuid,
Some(message),
done
)
}
/** @inheritdoc */
override def done(result: Try[Any]): Unit = result match {
case Failure(exception) =>
val uuid = initializeTask(None)
progressTracker ! ProgressNotification.TaskFailure(uuid, exception)
case Success(_) =>
val uuid = initializeTask(None)
progressTracker ! ProgressNotification.TaskSuccess(uuid)
}
})
}
/** @inheritdoc */ /** @inheritdoc */
override def shouldInstallMissingEngine(version: SemVer): Boolean = override def shouldInstallMissingEngine(version: SemVer): Boolean =
@ -101,7 +57,7 @@ class ControllerInterface(
progressTracker ! ProgressNotification.TaskStarted( progressTracker ! ProgressNotification.TaskStarted(
uuid, uuid,
None, None,
ProgressUnit.Other ProgressUnit.Unspecified
) )
progressTracker ! ProgressNotification.TaskUpdate( progressTracker ! ProgressNotification.TaskUpdate(
uuid, uuid,
@ -117,4 +73,10 @@ class ControllerInterface(
progressTracker ! ProgressNotification.TaskSuccess(uuid) progressTracker ! ProgressNotification.TaskSuccess(uuid)
} }
} }
/** @inheritdoc */
override def sendProgressNotification(
notification: ProgressNotification
): Unit =
progressTracker ! notification
} }

View File

@ -1,68 +0,0 @@
package org.enso.projectmanager.service.versionmanagement
import java.util.UUID
import org.enso.jsonrpc.Notification
import org.enso.projectmanager.data.ProgressUnit
import org.enso.projectmanager.protocol.ProjectManagementApi
/** Internal representation of progress notifications that are sent by the
* [[ControllerInterface]].
*
* They are translated by the [[RequestHandler]] into protocol progress
* notifications.
*/
sealed trait ProgressNotification
object ProgressNotification {
/** Singals that a new task with progress has been started. */
case class TaskStarted(
taskId: UUID,
total: Option[Long],
unit: ProgressUnit
) extends ProgressNotification
/** Singals an update to task's progress. */
case class TaskUpdate(taskId: UUID, message: Option[String], done: Long)
extends ProgressNotification
/** Singals that a task has been finished successfully. */
case class TaskSuccess(taskId: UUID) extends ProgressNotification
/** Singals that a task has failed. */
case class TaskFailure(taskId: UUID, throwable: Throwable)
extends ProgressNotification
/** Translates a [[ProgressNotification]] into a protocol message. */
def translateProgressNotification(
relatedOperationName: String,
progressNotification: ProgressNotification
): Notification[_, _] = progressNotification match {
case TaskStarted(taskId, total, unit) =>
Notification(
ProjectManagementApi.TaskStarted,
ProjectManagementApi.TaskStarted.Params(
taskId = taskId,
relatedOperation = relatedOperationName,
unit = unit,
total = total
)
)
case TaskUpdate(taskId, message, done) =>
Notification(
ProjectManagementApi.TaskProgressUpdate,
ProjectManagementApi.TaskProgressUpdate.Params(taskId, message, done)
)
case TaskSuccess(taskId) =>
Notification(
ProjectManagementApi.TaskFinished,
ProjectManagementApi.TaskFinished.Params(taskId, None, success = true)
)
case TaskFailure(taskId, throwable) =>
Notification(
ProjectManagementApi.TaskFinished,
ProjectManagementApi.TaskFinished
.Params(taskId, Some(throwable.getMessage), success = false)
)
}
}

View File

@ -49,9 +49,7 @@ object DefaultDistributionConfiguration
lazy val resourceManager = new ResourceManager(lockManager) lazy val resourceManager = new ResourceManager(lockManager)
/** @inheritdoc */ /** @inheritdoc */
lazy val editionManager = new EditionManager( lazy val editionManager = EditionManager(distributionManager)
distributionManager.paths.editionSearchPaths.toList
)
/** @inheritdoc */ /** @inheritdoc */
lazy val temporaryDirectoryManager = lazy val temporaryDirectoryManager =

View File

@ -64,9 +64,7 @@ class TestDistributionConfiguration(
lazy val resourceManager = new ResourceManager(lockManager) lazy val resourceManager = new ResourceManager(lockManager)
lazy val editionManager: EditionManager = new EditionManager( lazy val editionManager: EditionManager = EditionManager(distributionManager)
distributionManager.paths.editionSearchPaths.toList
)
lazy val temporaryDirectoryManager = lazy val temporaryDirectoryManager =
new TemporaryDirectoryManager(distributionManager, resourceManager) new TemporaryDirectoryManager(distributionManager, resourceManager)

View File

@ -4,7 +4,6 @@ import akka.testkit.TestActors.blackholeProps
import io.circe.Json import io.circe.Json
import io.circe.literal.JsonStringContext import io.circe.literal.JsonStringContext
import nl.gn0s1s.bump.SemVer import nl.gn0s1s.bump.SemVer
import org.enso.editions.SemVerEnsoVersion
import org.enso.projectmanager.data.MissingComponentAction import org.enso.projectmanager.data.MissingComponentAction
import org.enso.projectmanager.{BaseServerSpec, ProjectManagementOps} import org.enso.projectmanager.{BaseServerSpec, ProjectManagementOps}
import org.enso.testkit.RetrySpec import org.enso.testkit.RetrySpec
@ -51,7 +50,7 @@ abstract class ProjectOpenSpecBase
val edition = config.edition.get val edition = config.edition.get
config.copy(edition = config.copy(edition =
Some( Some(
edition.copy(engineVersion = Some(SemVerEnsoVersion(brokenVersion))) edition.copy(engineVersion = Some(brokenVersion))
) )
) )
}) })

View File

@ -44,11 +44,13 @@ trait FakeEnvironment { self: HasTestDirectory =>
val configDir = getTestDirectory.resolve("test_config") val configDir = getTestDirectory.resolve("test_config")
val binDir = getTestDirectory.resolve("test_bin") val binDir = getTestDirectory.resolve("test_bin")
val runDir = getTestDirectory.resolve("test_run") val runDir = getTestDirectory.resolve("test_run")
val homeDir = getTestDirectory.resolve("test_home")
val env = extraOverrides val env = extraOverrides
.updated("ENSO_DATA_DIRECTORY", dataDir.toString) .updated("ENSO_DATA_DIRECTORY", dataDir.toString)
.updated("ENSO_CONFIG_DIRECTORY", configDir.toString) .updated("ENSO_CONFIG_DIRECTORY", configDir.toString)
.updated("ENSO_BIN_DIRECTORY", binDir.toString) .updated("ENSO_BIN_DIRECTORY", binDir.toString)
.updated("ENSO_RUNTIME_DIRECTORY", runDir.toString) .updated("ENSO_RUNTIME_DIRECTORY", runDir.toString)
.updated("ENSO_HOME", homeDir.toString)
val fakeEnvironment = new Environment { val fakeEnvironment = new Environment {
override def getPathToRunningExecutable: Path = executable override def getPathToRunningExecutable: Path = executable

View File

@ -249,8 +249,11 @@ class Runner(
): SemVer = versionOverride.getOrElse { ): SemVer = versionOverride.getOrElse {
project match { project match {
case Some(project) => case Some(project) =>
val edition = project.edition // TODO [RW] properly get the default edition, see #1864
val version = editionManager.resolveEngineVersion(edition).get val version = project.edition
.map(edition => editionManager.resolveEngineVersion(edition).get)
.map(SemVerEnsoVersion)
.getOrElse(DefaultEnsoVersion)
version match { version match {
case DefaultEnsoVersion => case DefaultEnsoVersion =>
globalConfigurationManager.defaultVersion globalConfigurationManager.defaultVersion

View File

@ -0,0 +1,64 @@
package org.enso.cli.task.notifications
import akka.actor.ActorRef
import org.enso.cli.task.{
ProgressNotification,
ProgressNotificationForwarder,
ProgressReporter
}
import org.enso.cli.task.ProgressNotification.{
TaskFailure,
TaskStarted,
TaskSuccess,
TaskUpdate
}
import org.enso.jsonrpc.Notification
object ActorProgressNotificationForwarder {
def translateAndForward(
relatedOperationName: String,
recipient: ActorRef
): ProgressReporter =
new ProgressNotificationForwarder {
override def sendProgressNotification(
notification: ProgressNotification
): Unit = {
val translated: Notification[_, _] =
translateProgressNotification(relatedOperationName, notification)
recipient ! translated
}
}
/** Translates a [[ProgressNotification]] into a protocol message. */
def translateProgressNotification(
relatedOperationName: String,
progressNotification: ProgressNotification
): Notification[_, _] = progressNotification match {
case TaskStarted(taskId, total, unit) =>
Notification(
TaskNotificationApi.TaskStarted,
TaskNotificationApi.TaskStarted.Params(
taskId = taskId,
relatedOperation = relatedOperationName,
unit = unit,
total = total
)
)
case TaskUpdate(taskId, message, done) =>
Notification(
TaskNotificationApi.TaskProgressUpdate,
TaskNotificationApi.TaskProgressUpdate.Params(taskId, message, done)
)
case TaskSuccess(taskId) =>
Notification(
TaskNotificationApi.TaskFinished,
TaskNotificationApi.TaskFinished.Params(taskId, None, success = true)
)
case TaskFailure(taskId, throwable) =>
Notification(
TaskNotificationApi.TaskFinished,
TaskNotificationApi.TaskFinished
.Params(taskId, Option(throwable.getMessage), success = false)
)
}
}

View File

@ -0,0 +1,29 @@
package org.enso.cli.task.notifications
import enumeratum._
import org.enso.cli.task.{ProgressUnit => TaskProgressUnit}
/** Represents the unit used by progress updates. */
sealed trait SerializableProgressUnit extends EnumEntry
object SerializableProgressUnit
extends Enum[SerializableProgressUnit]
with CirceEnum[SerializableProgressUnit] {
/** Indicates that progress is measured by amount of bytes processed. */
case object Bytes extends SerializableProgressUnit
/** Indicates that progress is measured by some other unit or it is not
* measured at all.
*/
case object Other extends SerializableProgressUnit
override val values = findValues
/** Converts a [[TaskProgressUnit]] to [[SerializableProgressUnit]].
*/
implicit def fromUnit(unit: TaskProgressUnit): SerializableProgressUnit =
unit match {
case TaskProgressUnit.Bytes => Bytes
case TaskProgressUnit.Unspecified => Other
}
}

View File

@ -0,0 +1,48 @@
package org.enso.cli.task.notifications
import org.enso.jsonrpc.{HasParams, Method}
import java.util.UUID
object TaskNotificationApi {
case object TaskStarted extends Method("task/started") {
case class Params(
taskId: UUID,
relatedOperation: String,
unit: SerializableProgressUnit,
total: Option[Long]
)
implicit val hasParams = new HasParams[this.type] {
type Params = TaskStarted.Params
}
}
case object TaskProgressUpdate extends Method("task/progress-update") {
case class Params(
taskId: UUID,
message: Option[String],
done: Long
)
implicit val hasParams = new HasParams[this.type] {
type Params = TaskProgressUpdate.Params
}
}
case object TaskFinished extends Method("task/finished") {
case class Params(
taskId: UUID,
message: Option[String],
success: Boolean
)
implicit val hasParams = new HasParams[this.type] {
type Params = TaskFinished.Params
}
}
}

View File

@ -1,3 +1,3 @@
E4A61C4649AD6FB148D4779D9B20B395AEAB73475EE13D20EFB55C163724A77B C03EC922F039EB5CC96F93A489453ADDD4FA6EBBF75713B05FE67B48CFF6ACBF
64FE86A276F737CE2B4D352D8F35F790CA0DA18F329A48A467F34FC9C3CAF07D 64FE86A276F737CE2B4D352D8F35F790CA0DA18F329A48A467F34FC9C3CAF07D
0 0

View File

@ -1,3 +1,3 @@
0BA0D3694722E724BABC3D4D860FA5D11DA541B20632F18175E1F45BF44DE717 0AED341E22D16C5722BF722FD5F92039464E9FE3CCD3288D3643F05E09AF62FB
B7948AB0E996317E7F073260BA28A63D14215436079C09E793F803CF999D607C B7948AB0E996317E7F073260BA28A63D14215436079C09E793F803CF999D607C
0 0