From b0146e43f6be6bb40baadc5fcdb5eab8fd3d13d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Tue, 13 Jul 2021 00:08:03 +0200 Subject: [PATCH 01/26] WIP adding protocol message types --- .../languageserver/libraries/LibraryApi.scala | 181 ++++++++++++++++++ .../libraries/LibraryEntry.scala | 14 ++ .../protocol/json/JsonRpc.scala | 11 ++ .../protocol/ProjectManagementApi.scala | 1 + 4 files changed, 207 insertions(+) create mode 100644 engine/language-server/src/main/scala/org/enso/languageserver/libraries/LibraryApi.scala create mode 100644 engine/language-server/src/main/scala/org/enso/languageserver/libraries/LibraryEntry.scala diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LibraryApi.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LibraryApi.scala new file mode 100644 index 000000000000..4e09e80f518a --- /dev/null +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LibraryApi.scala @@ -0,0 +1,181 @@ +package org.enso.languageserver.libraries + +import io.circe.Json +import io.circe.literal.JsonStringContext +import org.enso.editions.LibraryName +import org.enso.jsonrpc.{Error, HasParams, HasResult, Method, Unused} + +object LibraryApi { + case object EditionsListAvailable extends Method("editions/listAvailable") { + self => + + // TODO [RW] Option or not? + case class Params(update: Option[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(editionName: Option[String]) + + 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(editionName: Option[String]) + + 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 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(reason: String) + extends Error(8006, s"Could not download the library: $reason.") +} diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LibraryEntry.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LibraryEntry.scala new file mode 100644 index 000000000000..eefb4476b177 --- /dev/null +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LibraryEntry.scala @@ -0,0 +1,14 @@ +package org.enso.languageserver.libraries + +case class LibraryEntry( + namespace: String, + name: String, + version: LibraryEntry.LibraryVersion +) + +object LibraryEntry { + // TODO [RW] proper case serialization + sealed trait LibraryVersion + case object LocalLibraryVersion extends LibraryVersion + case class PublishedLibraryVersion(version: String, repositoryUrl: String) +} diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonRpc.scala b/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonRpc.scala index a1c20f105b8d..ee0248586c5d 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonRpc.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonRpc.scala @@ -17,6 +17,7 @@ import org.enso.languageserver.search.SearchApi._ import org.enso.languageserver.runtime.VisualisationApi._ import org.enso.languageserver.session.SessionApi.InitProtocolConnection import org.enso.languageserver.text.TextApi._ +import org.enso.languageserver.libraries.LibraryApi._ import org.enso.languageserver.workspace.WorkspaceApi.ProjectInfo object JsonRpc { @@ -65,6 +66,16 @@ object JsonRpc { .registerRequest(Import) .registerRequest(RenameProject) .registerRequest(ProjectInfo) + .registerRequest(EditionsListAvailable) + .registerRequest(EditionsResolve) + .registerRequest(EditionsGetProjectSettings) + .registerRequest(EditionsSetParentEdition) + .registerRequest(EditionsSetLocalLibrariesPreference) + .registerRequest(EditionsListDefinedLibraries) + .registerRequest(LibraryListLocal) + .registerRequest(LibraryCreate) + .registerRequest(LibraryPublish) + .registerRequest(LibraryPreinstall) .registerNotification(ForceReleaseCapability) .registerNotification(GrantCapability) .registerNotification(TextDidChange) diff --git a/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/protocol/ProjectManagementApi.scala b/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/protocol/ProjectManagementApi.scala index a8794f8fd074..e0435dbdf037 100644 --- a/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/protocol/ProjectManagementApi.scala +++ b/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/protocol/ProjectManagementApi.scala @@ -117,6 +117,7 @@ object ProjectManagementApi { } } + // TODO [RW] move these to a shared library so that both LS and PM can use them case object TaskStarted extends Method("task/started") { case class Params( From 305673341a1f474ac501f6b2602dd62e07c95291 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Tue, 13 Jul 2021 14:16:00 +0200 Subject: [PATCH 02/26] Extract progres notifications to a common lib --- build.sbt | 17 +++++++ .../projectmanager/data/ProgressUnit.scala | 26 ---------- .../projectmanager/protocol/JsonRpc.scala | 1 + .../protocol/ProjectManagementApi.scala | 42 ---------------- .../requesthandler/RequestHandler.scala | 14 ++---- .../ControllerInterface.scala | 11 +++-- .../notifications}/ProgressNotification.scala | 26 +++++----- .../SerializableProgressUnit.scala | 29 +++++++++++ .../notifications/TaskNotificationApi.scala | 48 +++++++++++++++++++ 9 files changed, 117 insertions(+), 97 deletions(-) delete mode 100644 lib/scala/project-manager/src/main/scala/org/enso/projectmanager/data/ProgressUnit.scala rename lib/scala/{project-manager/src/main/scala/org/enso/projectmanager/service/versionmanagement => task-progress-notifications/src/main/scala/org/enso/cli/task/notifications}/ProgressNotification.scala (73%) create mode 100644 lib/scala/task-progress-notifications/src/main/scala/org/enso/cli/task/notifications/SerializableProgressUnit.scala create mode 100644 lib/scala/task-progress-notifications/src/main/scala/org/enso/cli/task/notifications/TaskNotificationApi.scala diff --git a/build.sbt b/build.sbt index 2d8e3a95ae32..5197c2a98fea 100644 --- a/build.sbt +++ b/build.sbt @@ -716,6 +716,20 @@ lazy val cli = project 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")) .settings( version := "0.1" @@ -798,6 +812,7 @@ lazy val `project-manager` = (project in file("lib/scala/project-manager")) .dependsOn(`version-output`) .dependsOn(editions) .dependsOn(cli) + .dependsOn(`task-progress-notifications`) .dependsOn(`polyglot-api`) .dependsOn(`runtime-version-manager`) .dependsOn(`library-manager`) @@ -991,6 +1006,8 @@ lazy val `language-server` = (project in file("engine/language-server")) ) .dependsOn(`json-rpc-server-test` % Test) .dependsOn(`json-rpc-server`) + .dependsOn(`task-progress-notifications`) + .dependsOn(`library-manager`) .dependsOn(`logging-service`) .dependsOn(`polyglot-api`) .dependsOn(`searcher`) diff --git a/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/data/ProgressUnit.scala b/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/data/ProgressUnit.scala deleted file mode 100644 index bb2537976034..000000000000 --- a/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/data/ProgressUnit.scala +++ /dev/null @@ -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 - } -} diff --git a/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/protocol/JsonRpc.scala b/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/protocol/JsonRpc.scala index b70917405760..f2aa8e90a9ad 100644 --- a/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/protocol/JsonRpc.scala +++ b/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/protocol/JsonRpc.scala @@ -3,6 +3,7 @@ package org.enso.projectmanager.protocol import io.circe.generic.auto._ import org.enso.jsonrpc.Protocol import org.enso.projectmanager.protocol.ProjectManagementApi._ +import org.enso.cli.task.notifications.TaskNotificationApi._ /** Implicits from this module are required for correct serialization. * diff --git a/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/protocol/ProjectManagementApi.scala b/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/protocol/ProjectManagementApi.scala index e0435dbdf037..a96e8f4df785 100644 --- a/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/protocol/ProjectManagementApi.scala +++ b/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/protocol/ProjectManagementApi.scala @@ -9,7 +9,6 @@ import org.enso.jsonrpc.{Error, HasParams, HasResult, Method, Unused} import org.enso.projectmanager.data.{ EngineVersion, MissingComponentAction, - ProgressUnit, ProjectMetadata, Socket } @@ -117,47 +116,6 @@ object ProjectManagementApi { } } - // TODO [RW] move these to a shared library so that both LS and PM can use them - 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 class Result(versions: Seq[EngineVersion]) diff --git a/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/requesthandler/RequestHandler.scala b/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/requesthandler/RequestHandler.scala index 6b4748887467..5e1e81a177bf 100644 --- a/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/requesthandler/RequestHandler.scala +++ b/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/requesthandler/RequestHandler.scala @@ -3,19 +3,11 @@ package org.enso.projectmanager.requesthandler import akka.actor.{Actor, ActorRef, Cancellable, Stash, Status} import akka.pattern.pipe import com.typesafe.scalalogging.{LazyLogging, Logger} +import org.enso.cli.task.notifications.ProgressNotification +import org.enso.cli.task.notifications.ProgressNotification.translateProgressNotification import org.enso.jsonrpc.Errors.ServiceError -import org.enso.jsonrpc.{ - HasParams, - HasResult, - Id, - Method, - Request, - ResponseError, - ResponseResult -} +import org.enso.jsonrpc._ 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 scala.annotation.unused diff --git a/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/service/versionmanagement/ControllerInterface.scala b/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/service/versionmanagement/ControllerInterface.scala index c4da9ea71315..7852ec326e8c 100644 --- a/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/service/versionmanagement/ControllerInterface.scala +++ b/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/service/versionmanagement/ControllerInterface.scala @@ -1,17 +1,20 @@ package org.enso.projectmanager.service.versionmanagement -import java.util.UUID import akka.actor.ActorRef import com.typesafe.scalalogging.Logger import nl.gn0s1s.bump.SemVer +import org.enso.cli.task.notifications.{ + ProgressNotification, + SerializableProgressUnit +} import org.enso.cli.task.{ProgressListener, TaskProgress} import org.enso.distribution.locking.Resource -import org.enso.projectmanager.data.ProgressUnit import org.enso.runtimeversionmanager.components.{ GraalVMVersion, RuntimeVersionManagementUserInterface } +import java.util.UUID import scala.util.{Failure, Success, Try} /** A [[RuntimeVersionManagementUserInterface]] that sends @@ -41,7 +44,7 @@ class ControllerInterface( case None => val generated = UUID.randomUUID() uuid = Some(generated) - val unit = ProgressUnit.fromTask(task) + val unit = SerializableProgressUnit.fromTask(task) progressTracker ! ProgressNotification.TaskStarted( generated, total, @@ -101,7 +104,7 @@ class ControllerInterface( progressTracker ! ProgressNotification.TaskStarted( uuid, None, - ProgressUnit.Other + SerializableProgressUnit.Other ) progressTracker ! ProgressNotification.TaskUpdate( uuid, diff --git a/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/service/versionmanagement/ProgressNotification.scala b/lib/scala/task-progress-notifications/src/main/scala/org/enso/cli/task/notifications/ProgressNotification.scala similarity index 73% rename from lib/scala/project-manager/src/main/scala/org/enso/projectmanager/service/versionmanagement/ProgressNotification.scala rename to lib/scala/task-progress-notifications/src/main/scala/org/enso/cli/task/notifications/ProgressNotification.scala index 1a5a7a42bb9b..9b8436b0dd37 100644 --- a/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/service/versionmanagement/ProgressNotification.scala +++ b/lib/scala/task-progress-notifications/src/main/scala/org/enso/cli/task/notifications/ProgressNotification.scala @@ -1,10 +1,8 @@ -package org.enso.projectmanager.service.versionmanagement - -import java.util.UUID +package org.enso.cli.task.notifications import org.enso.jsonrpc.Notification -import org.enso.projectmanager.data.ProgressUnit -import org.enso.projectmanager.protocol.ProjectManagementApi + +import java.util.UUID /** Internal representation of progress notifications that are sent by the * [[ControllerInterface]]. @@ -19,7 +17,7 @@ object ProgressNotification { case class TaskStarted( taskId: UUID, total: Option[Long], - unit: ProgressUnit + unit: SerializableProgressUnit ) extends ProgressNotification /** Singals an update to task's progress. */ @@ -40,8 +38,8 @@ object ProgressNotification { ): Notification[_, _] = progressNotification match { case TaskStarted(taskId, total, unit) => Notification( - ProjectManagementApi.TaskStarted, - ProjectManagementApi.TaskStarted.Params( + TaskNotificationApi.TaskStarted, + TaskNotificationApi.TaskStarted.Params( taskId = taskId, relatedOperation = relatedOperationName, unit = unit, @@ -50,18 +48,18 @@ object ProgressNotification { ) case TaskUpdate(taskId, message, done) => Notification( - ProjectManagementApi.TaskProgressUpdate, - ProjectManagementApi.TaskProgressUpdate.Params(taskId, message, done) + TaskNotificationApi.TaskProgressUpdate, + TaskNotificationApi.TaskProgressUpdate.Params(taskId, message, done) ) case TaskSuccess(taskId) => Notification( - ProjectManagementApi.TaskFinished, - ProjectManagementApi.TaskFinished.Params(taskId, None, success = true) + TaskNotificationApi.TaskFinished, + TaskNotificationApi.TaskFinished.Params(taskId, None, success = true) ) case TaskFailure(taskId, throwable) => Notification( - ProjectManagementApi.TaskFinished, - ProjectManagementApi.TaskFinished + TaskNotificationApi.TaskFinished, + TaskNotificationApi.TaskFinished .Params(taskId, Some(throwable.getMessage), success = false) ) } diff --git a/lib/scala/task-progress-notifications/src/main/scala/org/enso/cli/task/notifications/SerializableProgressUnit.scala b/lib/scala/task-progress-notifications/src/main/scala/org/enso/cli/task/notifications/SerializableProgressUnit.scala new file mode 100644 index 000000000000..10077f3ef693 --- /dev/null +++ b/lib/scala/task-progress-notifications/src/main/scala/org/enso/cli/task/notifications/SerializableProgressUnit.scala @@ -0,0 +1,29 @@ +package org.enso.cli.task.notifications + +import enumeratum._ +import org.enso.cli.task.{TaskProgress, 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 + + /** Creates a [[ProgressUnit]] from the unit associated with [[TaskProgress]]. + */ + def fromTask(task: TaskProgress[_]): SerializableProgressUnit = + task.unit match { + case TaskProgressUnit.Bytes => Bytes + case TaskProgressUnit.Unspecified => Other + } +} diff --git a/lib/scala/task-progress-notifications/src/main/scala/org/enso/cli/task/notifications/TaskNotificationApi.scala b/lib/scala/task-progress-notifications/src/main/scala/org/enso/cli/task/notifications/TaskNotificationApi.scala new file mode 100644 index 000000000000..cae29ff25a5c --- /dev/null +++ b/lib/scala/task-progress-notifications/src/main/scala/org/enso/cli/task/notifications/TaskNotificationApi.scala @@ -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/progress-update") { + + case class Params( + taskId: UUID, + message: Option[String], + success: Boolean + ) + + implicit val hasParams = new HasParams[this.type] { + type Params = TaskFinished.Params + } + } +} From f43ea2913e089eb6e0fad77ecf71d9c548f59f36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Tue, 13 Jul 2021 15:05:47 +0200 Subject: [PATCH 03/26] Update APIs --- .../libraries/EditionReference.scala | 7 ++ .../languageserver/libraries/LibraryApi.scala | 66 +++++++++++++++++-- .../protocol/json/JsonRpc.scala | 8 +++ 3 files changed, 75 insertions(+), 6 deletions(-) create mode 100644 engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionReference.scala diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionReference.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionReference.scala new file mode 100644 index 000000000000..d219192d67e7 --- /dev/null +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionReference.scala @@ -0,0 +1,7 @@ +package org.enso.languageserver.libraries + +sealed trait EditionReference +object EditionReference { + case class NamedEdition(editionName: String) extends EditionReference + case object CurrentProjectEdition extends EditionReference +} diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LibraryApi.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LibraryApi.scala index 4e09e80f518a..6841796daa23 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LibraryApi.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LibraryApi.scala @@ -2,14 +2,13 @@ package org.enso.languageserver.libraries import io.circe.Json import io.circe.literal.JsonStringContext -import org.enso.editions.LibraryName +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 => - // TODO [RW] Option or not? case class Params(update: Option[Boolean]) case class Result(editionNames: Seq[String]) @@ -25,7 +24,7 @@ object LibraryApi { case object EditionsResolve extends Method("editions/resolve") { self => - case class Params(editionName: Option[String]) + case class Params(edition: EditionReference) case class Result(engineVersion: String) @@ -86,7 +85,7 @@ object LibraryApi { case object EditionsListDefinedLibraries extends Method("editions/listDefinedLibraries") { self => - case class Params(editionName: Option[String]) + case class Params(edition: EditionReference) case class Result(availableLibraries: Seq[LibraryEntry]) @@ -128,6 +127,37 @@ object LibraryApi { } } + 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( @@ -176,6 +206,30 @@ object LibraryApi { case class LibraryUploadError(reason: String) extends Error(8005, s"Could not upload the library: $reason.") - case class LibraryDownloadError(reason: String) - extends Error(8006, s"Could not download 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} + } """ + ) + } } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonRpc.scala b/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonRpc.scala index ee0248586c5d..c79665291d41 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonRpc.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonRpc.scala @@ -1,6 +1,11 @@ package org.enso.languageserver.protocol.json import io.circe.generic.auto._ +import org.enso.cli.task.notifications.TaskNotificationApi.{ + TaskFinished, + TaskProgressUpdate, + TaskStarted +} import org.enso.jsonrpc.Protocol import org.enso.languageserver.capability.CapabilityApi.{ AcquireCapability, @@ -76,6 +81,9 @@ object JsonRpc { .registerRequest(LibraryCreate) .registerRequest(LibraryPublish) .registerRequest(LibraryPreinstall) + .registerNotification(TaskStarted) + .registerNotification(TaskProgressUpdate) + .registerNotification(TaskFinished) .registerNotification(ForceReleaseCapability) .registerNotification(GrantCapability) .registerNotification(TextDidChange) From 2fcb625998ace191c712ac4e75d7127af02a3d97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Wed, 14 Jul 2021 13:12:37 +0200 Subject: [PATCH 04/26] Serialization of custom types --- .../libraries/EditionReference.scala | 41 +++++++++++++++ .../libraries/LibraryEntry.scala | 50 +++++++++++++++++++ .../protocol/json/JsonRpc.scala | 2 + .../EditionNameSerializationSpec.scala | 21 ++++++++ .../LibraryEntrySerializationSpec.scala | 21 ++++++++ 5 files changed, 135 insertions(+) create mode 100644 engine/language-server/src/test/scala/org/enso/languageserver/libraries/EditionNameSerializationSpec.scala create mode 100644 engine/language-server/src/test/scala/org/enso/languageserver/libraries/LibraryEntrySerializationSpec.scala diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionReference.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionReference.scala index d219192d67e7..c91309373e2c 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionReference.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionReference.scala @@ -1,7 +1,48 @@ package org.enso.languageserver.libraries +import io.circe.{Decoder, DecodingFailure, Encoder, Json} +import io.circe.syntax._ + sealed trait EditionReference object EditionReference { case class NamedEdition(editionName: String) extends EditionReference 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 => + for { + editionName <- json.get[String](CodecField.EditionName) + } yield NamedEdition(editionName) + case CodecType.CurrentProjectEdition => Right(CurrentProjectEdition) + case unknownType => + Left( + DecodingFailure( + s"Unknown EditionReference type [$unknownType].", + typeCursor.history + ) + ) + } + } } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LibraryEntry.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LibraryEntry.scala index eefb4476b177..b37f43b3945c 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LibraryEntry.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LibraryEntry.scala @@ -1,5 +1,9 @@ package org.enso.languageserver.libraries +import io.circe.{Decoder, DecodingFailure, Encoder, Json} +import io.circe.syntax._ +import io.circe.generic.semiauto._ + case class LibraryEntry( namespace: String, name: String, @@ -11,4 +15,50 @@ object LibraryEntry { sealed trait LibraryVersion case object LocalLibraryVersion extends LibraryVersion case class PublishedLibraryVersion(version: String, repositoryUrl: String) + extends LibraryVersion + + 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 + ) + ) + } + } } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonRpc.scala b/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonRpc.scala index c79665291d41..41955f7a3dc7 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonRpc.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonRpc.scala @@ -79,6 +79,8 @@ object JsonRpc { .registerRequest(EditionsListDefinedLibraries) .registerRequest(LibraryListLocal) .registerRequest(LibraryCreate) + .registerRequest(LibraryGetMetadata) + .registerRequest(LibrarySetMetadata) .registerRequest(LibraryPublish) .registerRequest(LibraryPreinstall) .registerNotification(TaskStarted) diff --git a/engine/language-server/src/test/scala/org/enso/languageserver/libraries/EditionNameSerializationSpec.scala b/engine/language-server/src/test/scala/org/enso/languageserver/libraries/EditionNameSerializationSpec.scala new file mode 100644 index 000000000000..168e6b87e60e --- /dev/null +++ b/engine/language-server/src/test/scala/org/enso/languageserver/libraries/EditionNameSerializationSpec.scala @@ -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) + } + } +} diff --git a/engine/language-server/src/test/scala/org/enso/languageserver/libraries/LibraryEntrySerializationSpec.scala b/engine/language-server/src/test/scala/org/enso/languageserver/libraries/LibraryEntrySerializationSpec.scala new file mode 100644 index 000000000000..7c8cda8273c0 --- /dev/null +++ b/engine/language-server/src/test/scala/org/enso/languageserver/libraries/LibraryEntrySerializationSpec.scala @@ -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) + } + } +} From 4591e734e8709ef6e7be6ed57249dd94b3380f92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Wed, 14 Jul 2021 16:35:09 +0200 Subject: [PATCH 05/26] WIP on requests --- .../libraries/EditionManager.scala | 13 +++++++++ .../libraries/EditionManagerProtocol.scala | 13 +++++++++ .../libraries/LocalLibraryManager.scala | 9 ++++++ .../LocalLibraryManagerProtocol.scala | 29 +++++++++++++++++++ .../handlers/LibraryGetMetadataHandler.scala | 16 ++++++++++ 5 files changed, 80 insertions(+) create mode 100644 engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionManager.scala create mode 100644 engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionManagerProtocol.scala create mode 100644 engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManager.scala create mode 100644 engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManagerProtocol.scala create mode 100644 engine/language-server/src/main/scala/org/enso/languageserver/libraries/handlers/LibraryGetMetadataHandler.scala diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionManager.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionManager.scala new file mode 100644 index 000000000000..d91d8e7f8c7a --- /dev/null +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionManager.scala @@ -0,0 +1,13 @@ +package org.enso.languageserver.libraries + +import akka.actor.Actor +import org.enso.distribution.DistributionManager + +import java.nio.file.Path + +class EditionManager( + projectRoot: Path, + distributionManager: DistributionManager +) extends Actor { + override def receive: Receive = ??? +} diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionManagerProtocol.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionManagerProtocol.scala new file mode 100644 index 000000000000..2ba8235554ee --- /dev/null +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionManagerProtocol.scala @@ -0,0 +1,13 @@ +package org.enso.languageserver.libraries + +import nl.gn0s1s.bump.SemVer + +object EditionManagerProtocol { + sealed trait Request + + case class ListAvailable(update: Boolean) extends Request + case class ListAvailableResponse(editions: Seq[String]) + + case class Resolve(editionReference: EditionReference) + case class ResolveResponse(engineVersion: SemVer) +} diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManager.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManager.scala new file mode 100644 index 000000000000..fc221a3cfdbe --- /dev/null +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManager.scala @@ -0,0 +1,9 @@ +package org.enso.languageserver.libraries + +import akka.actor.Actor +import org.enso.distribution.DistributionManager + +class LocalLibraryManager(distributionManager: DistributionManager) + extends Actor { + override def receive: Receive = ??? +} diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManagerProtocol.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManagerProtocol.scala new file mode 100644 index 000000000000..76d48a2d183f --- /dev/null +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManagerProtocol.scala @@ -0,0 +1,29 @@ +package org.enso.languageserver.libraries + +import org.enso.editions.LibraryName + +object LocalLibraryManagerProtocol { + sealed trait Request + + case class GetMetadata(libraryName: LibraryName) extends Request + case class GetMetadataResponse( + description: Option[String], + tagLine: Option[String] + ) + + case object Success + case class SetMetadata( + libraryName: LibraryName, + description: Option[String], + tagLine: Option[String] + ) extends Request + + case object List extends Request + case class ListResponse(libraries: Seq[LibraryName]) + + case class Publish( + libraryName: LibraryName, + authToken: String, + bumpVersionAfterPublish: Boolean + ) extends Request +} diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handlers/LibraryGetMetadataHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handlers/LibraryGetMetadataHandler.scala new file mode 100644 index 000000000000..0e0684e99735 --- /dev/null +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handlers/LibraryGetMetadataHandler.scala @@ -0,0 +1,16 @@ +package org.enso.languageserver.libraries.handlers + +import akka.actor.{Actor, ActorRef} +import com.typesafe.scalalogging.LazyLogging +import org.enso.languageserver.util.UnhandledLogging + +import scala.concurrent.duration.FiniteDuration + +class LibraryGetMetadataHandler( + requestTimeout: FiniteDuration, + libraryManager: LibraryManager +) extends Actor + with LazyLogging + with UnhandledLogging { + override def receive: Receive = ??? +} From 945b29b5f72bf8e548a29d9e823a71da3568d60e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Wed, 14 Jul 2021 17:41:27 +0200 Subject: [PATCH 06/26] Update edition parser, removing the default --- .../libraries/EditionManager.scala | 5 +-- .../libraries/LocalLibraryManager.scala | 4 ++- .../handlers/LibraryGetMetadataHandler.scala | 7 ++-- .../enso/distribution/EditionManager.scala | 18 +++------- .../enso/editions/EditionSerialization.scala | 2 +- .../scala/org/enso/editions/Editions.scala | 4 +-- .../enso/editions/EngineVersionResolver.scala | 3 +- .../enso/editions/EditionResolverSpec.scala | 12 +++---- .../editions/EditionSerializationSpec.scala | 4 +-- .../librarymanager/LibraryResolverSpec.scala | 9 ++--- .../src/main/scala/org/enso/pkg/Config.scala | 2 +- .../service/ProjectService.scala | 35 ++++++------------- .../protocol/ProjectOpenSpecBase.scala | 3 +- .../runtimeversionmanager/runner/Runner.scala | 7 ++-- 14 files changed, 46 insertions(+), 69 deletions(-) diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionManager.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionManager.scala index d91d8e7f8c7a..89ef79fa72a8 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionManager.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionManager.scala @@ -4,10 +4,11 @@ import akka.actor.Actor import org.enso.distribution.DistributionManager import java.nio.file.Path +import scala.annotation.unused class EditionManager( - projectRoot: Path, - distributionManager: DistributionManager + @unused projectRoot: Path, + @unused distributionManager: DistributionManager ) extends Actor { override def receive: Receive = ??? } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManager.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManager.scala index fc221a3cfdbe..a9c7a389a1dc 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManager.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManager.scala @@ -3,7 +3,9 @@ package org.enso.languageserver.libraries import akka.actor.Actor import org.enso.distribution.DistributionManager -class LocalLibraryManager(distributionManager: DistributionManager) +import scala.annotation.unused + +class LocalLibraryManager(@unused distributionManager: DistributionManager) extends Actor { override def receive: Receive = ??? } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handlers/LibraryGetMetadataHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handlers/LibraryGetMetadataHandler.scala index 0e0684e99735..743aa72cc3f0 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handlers/LibraryGetMetadataHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handlers/LibraryGetMetadataHandler.scala @@ -1,14 +1,15 @@ package org.enso.languageserver.libraries.handlers -import akka.actor.{Actor, ActorRef} +import akka.actor.Actor import com.typesafe.scalalogging.LazyLogging import org.enso.languageserver.util.UnhandledLogging +import scala.annotation.unused import scala.concurrent.duration.FiniteDuration class LibraryGetMetadataHandler( - requestTimeout: FiniteDuration, - libraryManager: LibraryManager + @unused requestTimeout: FiniteDuration, + @unused libraryManager: Any ) extends Actor with LazyLogging with UnhandledLogging { diff --git a/lib/scala/distribution-manager/src/main/scala/org/enso/distribution/EditionManager.scala b/lib/scala/distribution-manager/src/main/scala/org/enso/distribution/EditionManager.scala index d9f625547dba..0a2729f52ca8 100644 --- a/lib/scala/distribution-manager/src/main/scala/org/enso/distribution/EditionManager.scala +++ b/lib/scala/distribution-manager/src/main/scala/org/enso/distribution/EditionManager.scala @@ -1,16 +1,12 @@ package org.enso.distribution +import nl.gn0s1s.bump.SemVer import org.enso.editions import org.enso.editions.provider.FileSystemEditionProvider -import org.enso.editions.{ - DefaultEnsoVersion, - EditionResolver, - Editions, - EnsoVersion -} +import org.enso.editions.{EditionResolver, Editions} import java.nio.file.Path -import scala.util.{Success, Try} +import scala.util.Try /** A helper class for resolving editions. */ class EditionManager(searchPaths: List[Path]) { @@ -38,10 +34,6 @@ class EditionManager(searchPaths: List[Path]) { * engine version * @return the resolved engine version */ - def resolveEngineVersion( - edition: Option[Editions.RawEdition] - ): Try[EnsoVersion] = - edition - .map(engineVersionResolver.resolveEnsoVersion(_).toTry) - .getOrElse(Success(DefaultEnsoVersion)) + def resolveEngineVersion(edition: Editions.RawEdition): Try[SemVer] = + engineVersionResolver.resolveEnsoVersion(edition).toTry } diff --git a/lib/scala/editions/src/main/scala/org/enso/editions/EditionSerialization.scala b/lib/scala/editions/src/main/scala/org/enso/editions/EditionSerialization.scala index 69c90ee5b89e..9629b6d1b4f4 100644 --- a/lib/scala/editions/src/main/scala/org/enso/editions/EditionSerialization.scala +++ b/lib/scala/editions/src/main/scala/org/enso/editions/EditionSerialization.scala @@ -48,7 +48,7 @@ object EditionSerialization { implicit val editionDecoder: Decoder[Raw.Edition] = { json => for { 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) Left( diff --git a/lib/scala/editions/src/main/scala/org/enso/editions/Editions.scala b/lib/scala/editions/src/main/scala/org/enso/editions/Editions.scala index d541b5d39905..e5c2c2aeb2c4 100644 --- a/lib/scala/editions/src/main/scala/org/enso/editions/Editions.scala +++ b/lib/scala/editions/src/main/scala/org/enso/editions/Editions.scala @@ -75,7 +75,7 @@ trait Editions { */ case class Edition( parent: Option[NestedEditionType] = None, - engineVersion: Option[EnsoVersion] = None, + engineVersion: Option[SemVer] = None, repositories: Map[String, Editions.Repository] = Map.empty, libraries: Map[String, Library] = Map.empty ) { @@ -134,7 +134,7 @@ object Editions { * is either the version override directly specified in the edition or the * version implied by its parent. */ - def getEngineVersion: EnsoVersion = edition.engineVersion.getOrElse { + def getEngineVersion: SemVer = edition.engineVersion.getOrElse { val parent = edition.parent.getOrElse { throw new IllegalStateException( "Internal error: Resolved edition does not imply an engine version." diff --git a/lib/scala/editions/src/main/scala/org/enso/editions/EngineVersionResolver.scala b/lib/scala/editions/src/main/scala/org/enso/editions/EngineVersionResolver.scala index 7c29b706ed3d..5d9ace10fa88 100644 --- a/lib/scala/editions/src/main/scala/org/enso/editions/EngineVersionResolver.scala +++ b/lib/scala/editions/src/main/scala/org/enso/editions/EngineVersionResolver.scala @@ -1,5 +1,6 @@ package org.enso.editions +import nl.gn0s1s.bump.SemVer import org.enso.editions.Editions.RawEdition import org.enso.editions.provider.EditionProvider @@ -19,7 +20,7 @@ case class EngineVersionResolver(editionProvider: EditionProvider) { */ def resolveEnsoVersion( edition: RawEdition - ): Either[EditionResolutionError, EnsoVersion] = { + ): Either[EditionResolutionError, SemVer] = { for { edition <- editionResolver.resolve(edition) } yield edition.getEngineVersion diff --git a/lib/scala/editions/src/test/scala/org/enso/editions/EditionResolverSpec.scala b/lib/scala/editions/src/test/scala/org/enso/editions/EditionResolverSpec.scala index 7f11e2ae37fb..072a2221a573 100644 --- a/lib/scala/editions/src/test/scala/org/enso/editions/EditionResolverSpec.scala +++ b/lib/scala/editions/src/test/scala/org/enso/editions/EditionResolverSpec.scala @@ -20,7 +20,7 @@ class EditionResolverSpec val editions: Map[String, Editions.RawEdition] = Map( "2021.0" -> Editions.Raw.Edition( parent = None, - engineVersion = Some(SemVerEnsoVersion(SemVer(1, 2, 3))), + engineVersion = Some(SemVer(1, 2, 3)), repositories = Map( "main" -> mainRepo ), @@ -31,13 +31,13 @@ class EditionResolverSpec ), "cycleA" -> Editions.Raw.Edition( parent = Some("cycleB"), - engineVersion = Some(SemVerEnsoVersion(SemVer(1, 2, 3))), + engineVersion = Some(SemVer(1, 2, 3)), repositories = Map(), libraries = Map() ), "cycleB" -> Editions.Raw.Edition( parent = Some("cycleA"), - engineVersion = Some(SemVerEnsoVersion(SemVer(1, 2, 3))), + engineVersion = Some(SemVer(1, 2, 3)), repositories = Map(), libraries = Map() ) @@ -59,7 +59,7 @@ class EditionResolverSpec val repo = Repository.make("foo", "http://example.com").get val edition = Editions.Raw.Edition( parent = None, - engineVersion = Some(SemVerEnsoVersion(SemVer(1, 2, 3))), + engineVersion = Some(SemVer(1, 2, 3)), repositories = Map("foo" -> repo), libraries = Map( "bar.baz" -> Editions.Raw.LocalLibrary("bar.baz"), @@ -83,7 +83,7 @@ class EditionResolverSpec "resolve a nested edition" in { val edition = Editions.Raw.Edition( parent = Some("2021.0"), - engineVersion = Some(SemVerEnsoVersion(SemVer(1, 2, 3))), + engineVersion = Some(SemVer(1, 2, 3)), repositories = Map(), libraries = Map( "bar.baz" -> Editions.Raw.LocalLibrary("bar.baz"), @@ -143,7 +143,7 @@ class EditionResolverSpec "avoid cycles in the resolution" in { val edition = Editions.Raw.Edition( parent = Some("cycleA"), - engineVersion = Some(SemVerEnsoVersion(SemVer(1, 2, 3))), + engineVersion = Some(SemVer(1, 2, 3)), repositories = Map(), libraries = Map() ) diff --git a/lib/scala/editions/src/test/scala/org/enso/editions/EditionSerializationSpec.scala b/lib/scala/editions/src/test/scala/org/enso/editions/EditionSerializationSpec.scala index 08c1b88642d1..4a2965e4e277 100644 --- a/lib/scala/editions/src/test/scala/org/enso/editions/EditionSerializationSpec.scala +++ b/lib/scala/editions/src/test/scala/org/enso/editions/EditionSerializationSpec.scala @@ -58,9 +58,7 @@ class EditionSerializationSpec extends AnyWordSpec with Matchers with Inside { Editions.Raw.PublishedLibrary("Bar.Baz", SemVer(0, 0, 0), "example"), Editions.Raw.PublishedLibrary("A.B", SemVer(1, 0, 1), "bar") ) - edition.engineVersion should contain( - SemVerEnsoVersion(SemVer(1, 2, 3, Some("SNAPSHOT"))) - ) + edition.engineVersion should contain(SemVer(1, 2, 3, Some("SNAPSHOT"))) } } diff --git a/lib/scala/library-manager/src/test/scala/org/enso/librarymanager/LibraryResolverSpec.scala b/lib/scala/library-manager/src/test/scala/org/enso/librarymanager/LibraryResolverSpec.scala index 6319cdbed261..c13793054f20 100644 --- a/lib/scala/library-manager/src/test/scala/org/enso/librarymanager/LibraryResolverSpec.scala +++ b/lib/scala/library-manager/src/test/scala/org/enso/librarymanager/LibraryResolverSpec.scala @@ -2,12 +2,7 @@ package org.enso.librarymanager import nl.gn0s1s.bump.SemVer import org.enso.editions.Editions.Repository -import org.enso.editions.{ - DefaultEnsoVersion, - Editions, - LibraryName, - LibraryVersion -} +import org.enso.editions.{Editions, LibraryName, LibraryVersion} import org.enso.librarymanager.local.LocalLibraryProvider import org.enso.testkit.EitherValue import org.scalatest.Inside @@ -25,7 +20,7 @@ class LibraryResolverSpec val mainRepo = Repository.make("main", "https://example.com/main").get val parentEdition = Editions.Resolved.Edition( parent = None, - engineVersion = Some(DefaultEnsoVersion), + engineVersion = Some(SemVer(0, 0, 0)), repositories = Map("main" -> mainRepo), libraries = Map( "Standard.Base" -> Editions.Resolved diff --git a/lib/scala/pkg/src/main/scala/org/enso/pkg/Config.scala b/lib/scala/pkg/src/main/scala/org/enso/pkg/Config.scala index b08842e2216c..f0d82673dcf8 100644 --- a/lib/scala/pkg/src/main/scala/org/enso/pkg/Config.scala +++ b/lib/scala/pkg/src/main/scala/org/enso/pkg/Config.scala @@ -229,7 +229,7 @@ object Config { ensoVersion: SemVer ): Editions.RawEdition = Editions.Raw.Edition( parent = None, - engineVersion = Some(SemVerEnsoVersion(ensoVersion)), + engineVersion = Some(ensoVersion), repositories = Map(), libraries = Map() ) diff --git a/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/service/ProjectService.scala b/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/service/ProjectService.scala index 9918b21e28bb..5b2610d573b0 100644 --- a/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/service/ProjectService.scala +++ b/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/service/ProjectService.scala @@ -4,7 +4,7 @@ import akka.actor.ActorRef import cats.MonadError import com.typesafe.scalalogging.Logger import nl.gn0s1s.bump.SemVer -import org.enso.editions.EnsoVersion +import org.enso.editions.DefaultEdition import org.enso.pkg.Config import org.enso.projectmanager.control.core.syntax._ import org.enso.projectmanager.control.core.{ @@ -44,7 +44,6 @@ import org.enso.projectmanager.service.ValidationFailure.{ NameShouldStartWithCapitalLetter } 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.RuntimeVersionManagerFactory import org.enso.projectmanager.versionmanagement.DistributionConfiguration @@ -306,15 +305,7 @@ class ProjectService[ missingComponentAction: MissingComponentAction ): F[ProjectServiceFailure, RunningLanguageServerInfo] = for { 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 .start(progressTracker, clientId, project, version) .mapError { @@ -371,18 +362,7 @@ class ProjectService[ private def resolveProjectMetadata( project: Project ): F[ProjectServiceFailure, ProjectMetadata] = { - val version = for { - 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 - + val version = resolveProjectVersion(project) for { version <- version.map(Some(_)).recover { error => // TODO [RW] We may consider sending this warning to the IDE once @@ -483,11 +463,16 @@ class ProjectService[ private def resolveProjectVersion( project: Project - ): F[ProjectServiceFailure, EnsoVersion] = + ): F[ProjectServiceFailure, SemVer] = Sync[F] .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 - .resolveEngineVersion(project.edition) + .resolveEngineVersion(edition) .get } .mapError { error => diff --git a/lib/scala/project-manager/src/test/scala/org/enso/projectmanager/protocol/ProjectOpenSpecBase.scala b/lib/scala/project-manager/src/test/scala/org/enso/projectmanager/protocol/ProjectOpenSpecBase.scala index 7aba4275185b..08ef588dab02 100644 --- a/lib/scala/project-manager/src/test/scala/org/enso/projectmanager/protocol/ProjectOpenSpecBase.scala +++ b/lib/scala/project-manager/src/test/scala/org/enso/projectmanager/protocol/ProjectOpenSpecBase.scala @@ -4,7 +4,6 @@ import akka.testkit.TestActors.blackholeProps import io.circe.Json import io.circe.literal.JsonStringContext import nl.gn0s1s.bump.SemVer -import org.enso.editions.SemVerEnsoVersion import org.enso.projectmanager.data.MissingComponentAction import org.enso.projectmanager.{BaseServerSpec, ProjectManagementOps} import org.enso.testkit.RetrySpec @@ -51,7 +50,7 @@ abstract class ProjectOpenSpecBase val edition = config.edition.get config.copy(edition = Some( - edition.copy(engineVersion = Some(SemVerEnsoVersion(brokenVersion))) + edition.copy(engineVersion = Some(brokenVersion)) ) ) }) diff --git a/lib/scala/runtime-version-manager/src/main/scala/org/enso/runtimeversionmanager/runner/Runner.scala b/lib/scala/runtime-version-manager/src/main/scala/org/enso/runtimeversionmanager/runner/Runner.scala index e421f3c34fb5..569bcafde56a 100644 --- a/lib/scala/runtime-version-manager/src/main/scala/org/enso/runtimeversionmanager/runner/Runner.scala +++ b/lib/scala/runtime-version-manager/src/main/scala/org/enso/runtimeversionmanager/runner/Runner.scala @@ -249,8 +249,11 @@ class Runner( ): SemVer = versionOverride.getOrElse { project match { case Some(project) => - val edition = project.edition - val version = editionManager.resolveEngineVersion(edition).get + // TODO [RW] properly get the default edition, see #1864 + val version = project.edition + .map(edition => editionManager.resolveEngineVersion(edition).get) + .map(SemVerEnsoVersion) + .getOrElse(DefaultEnsoVersion) version match { case DefaultEnsoVersion => globalConfigurationManager.defaultVersion From 55a7ecfbdde530dd49fd348c78ecc7743e91e937 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Wed, 14 Jul 2021 19:03:35 +0200 Subject: [PATCH 07/26] Create 'empty' handlers for all endpoints --- .../EditionsGetProjectSettingsHandler.scala | 26 +++++++++++++++ .../EditionsListAvailableHandler.scala | 26 +++++++++++++++ .../EditionsListDefinedLibrariesHandler.scala | 30 +++++++++++++++++ .../handler/EditionsResolveHandler.scala | 26 +++++++++++++++ .../EditionsSetParentEditionHandler.scala | 30 +++++++++++++++++ ...ojectLocalLibrariesPreferenceHandler.scala | 32 +++++++++++++++++++ .../handler/LibraryCreateHandler.scala | 26 +++++++++++++++ .../handler/LibraryGetMetadataHandler.scala | 26 +++++++++++++++ .../handler/LibraryPreinstallHandler.scala | 26 +++++++++++++++ .../handler/LibraryPublishHandler.scala | 26 +++++++++++++++ .../handler/LibrarySetMetadataHandler.scala | 26 +++++++++++++++ .../handlers/LibraryGetMetadataHandler.scala | 17 ---------- .../json/JsonConnectionController.scala | 20 ++++++++++-- 13 files changed, 317 insertions(+), 20 deletions(-) create mode 100644 engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsGetProjectSettingsHandler.scala create mode 100644 engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsListAvailableHandler.scala create mode 100644 engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsListDefinedLibrariesHandler.scala create mode 100644 engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsResolveHandler.scala create mode 100644 engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsSetParentEditionHandler.scala create mode 100644 engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsSetProjectLocalLibrariesPreferenceHandler.scala create mode 100644 engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryCreateHandler.scala create mode 100644 engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryGetMetadataHandler.scala create mode 100644 engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryPreinstallHandler.scala create mode 100644 engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryPublishHandler.scala create mode 100644 engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibrarySetMetadataHandler.scala delete mode 100644 engine/language-server/src/main/scala/org/enso/languageserver/libraries/handlers/LibraryGetMetadataHandler.scala diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsGetProjectSettingsHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsGetProjectSettingsHandler.scala new file mode 100644 index 000000000000..6aa77bb596e3 --- /dev/null +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsGetProjectSettingsHandler.scala @@ -0,0 +1,26 @@ +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 + +class EditionsGetProjectSettingsHandler + extends Actor + with LazyLogging + with UnhandledLogging { + override def receive: Receive = { + case Request(EditionsGetProjectSettings, id, _) => + // TODO [RW] actual implementation + sender() ! ResponseError( + Some(id), + FileSystemError("Feature not implemented") + ) + } +} + +object EditionsGetProjectSettingsHandler { + def props(): Props = Props(new EditionsGetProjectSettingsHandler) +} diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsListAvailableHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsListAvailableHandler.scala new file mode 100644 index 000000000000..4134a2c48b5d --- /dev/null +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsListAvailableHandler.scala @@ -0,0 +1,26 @@ +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 + +class EditionsListAvailableHandler + extends Actor + with LazyLogging + with UnhandledLogging { + override def receive: Receive = { + case Request(EditionsListAvailable, id, _: EditionsListAvailable.Params) => + // TODO [RW] actual implementation + sender() ! ResponseError( + Some(id), + FileSystemError("Feature not implemented") + ) + } +} + +object EditionsListAvailableHandler { + def props(): Props = Props(new EditionsListAvailableHandler) +} diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsListDefinedLibrariesHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsListDefinedLibrariesHandler.scala new file mode 100644 index 000000000000..b7d2a9878726 --- /dev/null +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsListDefinedLibrariesHandler.scala @@ -0,0 +1,30 @@ +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 + +class EditionsListDefinedLibrariesHandler + extends Actor + with LazyLogging + with UnhandledLogging { + override def receive: Receive = { + case Request( + EditionsListDefinedLibraries, + id, + _: EditionsListDefinedLibraries.Params + ) => + // TODO [RW] actual implementation + sender() ! ResponseError( + Some(id), + FileSystemError("Feature not implemented") + ) + } +} + +object EditionsListDefinedLibrariesHandler { + def props(): Props = Props(new EditionsListDefinedLibrariesHandler) +} diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsResolveHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsResolveHandler.scala new file mode 100644 index 000000000000..4f96ce337b5f --- /dev/null +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsResolveHandler.scala @@ -0,0 +1,26 @@ +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 + +class EditionsResolveHandler + extends Actor + with LazyLogging + with UnhandledLogging { + override def receive: Receive = { + case Request(EditionsResolve, id, _: EditionsResolve.Params) => + // TODO [RW] actual implementation + sender() ! ResponseError( + Some(id), + FileSystemError("Feature not implemented") + ) + } +} + +object EditionsResolveHandler { + def props(): Props = Props(new EditionsResolveHandler) +} diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsSetParentEditionHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsSetParentEditionHandler.scala new file mode 100644 index 000000000000..bfe9566b4e1c --- /dev/null +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsSetParentEditionHandler.scala @@ -0,0 +1,30 @@ +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 + +class EditionsSetParentEditionHandler + extends Actor + with LazyLogging + with UnhandledLogging { + override def receive: Receive = { + case Request( + EditionsSetParentEdition, + id, + _: EditionsSetParentEdition.Params + ) => + // TODO [RW] actual implementation + sender() ! ResponseError( + Some(id), + FileSystemError("Feature not implemented") + ) + } +} + +object EditionsSetParentEditionHandler { + def props(): Props = Props(new EditionsSetParentEditionHandler) +} diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsSetProjectLocalLibrariesPreferenceHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsSetProjectLocalLibrariesPreferenceHandler.scala new file mode 100644 index 000000000000..e02fcd58fedb --- /dev/null +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsSetProjectLocalLibrariesPreferenceHandler.scala @@ -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 + +class EditionsSetProjectLocalLibrariesPreferenceHandler + extends Actor + with LazyLogging + with UnhandledLogging { + override def receive: Receive = { + case Request( + EditionsSetLocalLibrariesPreference, + id, + _: EditionsSetLocalLibrariesPreference.Params + ) => + // TODO [RW] actual implementation + sender() ! ResponseError( + Some(id), + FileSystemError("Feature not implemented") + ) + } +} + +object EditionsSetProjectLocalLibrariesPreferenceHandler { + def props(): Props = Props( + new EditionsSetProjectLocalLibrariesPreferenceHandler + ) +} diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryCreateHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryCreateHandler.scala new file mode 100644 index 000000000000..561cad7b2ee5 --- /dev/null +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryCreateHandler.scala @@ -0,0 +1,26 @@ +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 + +class LibraryCreateHandler + extends Actor + with LazyLogging + with UnhandledLogging { + override def receive: Receive = { + case Request(LibraryCreate, id, _: LibraryCreate.Params) => + // TODO [RW] actual implementation + sender() ! ResponseError( + Some(id), + FileSystemError("Feature not implemented") + ) + } +} + +object LibraryCreateHandler { + def props(): Props = Props(new LibraryCreateHandler) +} diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryGetMetadataHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryGetMetadataHandler.scala new file mode 100644 index 000000000000..3fd78cf7dab8 --- /dev/null +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryGetMetadataHandler.scala @@ -0,0 +1,26 @@ +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 + +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 { + def props(): Props = Props(new LibraryGetMetadataHandler) +} diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryPreinstallHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryPreinstallHandler.scala new file mode 100644 index 000000000000..1e713faa6097 --- /dev/null +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryPreinstallHandler.scala @@ -0,0 +1,26 @@ +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 + +class LibraryPreinstallHandler + extends Actor + with LazyLogging + with UnhandledLogging { + override def receive: Receive = { + case Request(LibraryPreinstall, id, _: LibraryPreinstall.Params) => + // TODO [RW] actual implementation + sender() ! ResponseError( + Some(id), + FileSystemError("Feature not implemented") + ) + } +} + +object LibraryPreinstallHandler { + def props(): Props = Props(new LibraryPreinstallHandler) +} diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryPublishHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryPublishHandler.scala new file mode 100644 index 000000000000..82a6cd3c7d2a --- /dev/null +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryPublishHandler.scala @@ -0,0 +1,26 @@ +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 + +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 { + def props(): Props = Props(new LibraryPublishHandler) +} diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibrarySetMetadataHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibrarySetMetadataHandler.scala new file mode 100644 index 000000000000..fb8982322b7e --- /dev/null +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibrarySetMetadataHandler.scala @@ -0,0 +1,26 @@ +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 + +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 { + def props(): Props = Props(new LibrarySetMetadataHandler) +} diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handlers/LibraryGetMetadataHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handlers/LibraryGetMetadataHandler.scala deleted file mode 100644 index 743aa72cc3f0..000000000000 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handlers/LibraryGetMetadataHandler.scala +++ /dev/null @@ -1,17 +0,0 @@ -package org.enso.languageserver.libraries.handlers - -import akka.actor.Actor -import com.typesafe.scalalogging.LazyLogging -import org.enso.languageserver.util.UnhandledLogging - -import scala.annotation.unused -import scala.concurrent.duration.FiniteDuration - -class LibraryGetMetadataHandler( - @unused requestTimeout: FiniteDuration, - @unused libraryManager: Any -) extends Actor - with LazyLogging - with UnhandledLogging { - override def receive: Receive = ??? -} diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionController.scala b/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionController.scala index 21c905232786..68e25a7d1dc9 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionController.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionController.scala @@ -1,7 +1,6 @@ package org.enso.languageserver.protocol.json import java.util.UUID - import akka.actor.{Actor, ActorRef, Cancellable, Props, Stash, Status} import akka.pattern.pipe import akka.util.Timeout @@ -26,6 +25,8 @@ import org.enso.languageserver.filemanager._ import org.enso.languageserver.io.InputOutputApi._ import org.enso.languageserver.io.OutputKind.{StandardError, StandardOutput} import org.enso.languageserver.io.{InputOutputApi, InputOutputProtocol} +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.MonitoringProtocol import org.enso.languageserver.refactoring.RefactoringApi.RenameProject @@ -457,8 +458,21 @@ class JsonConnectionController( .props(stdErrController, rpcSession.clientId), RedirectStandardError -> RedirectStdErrHandler .props(stdErrController, rpcSession.clientId), - FeedStandardInput -> FeedStandardInputHandler.props(stdInController), - ProjectInfo -> ProjectInfoHandler.props(languageServerConfig) + FeedStandardInput -> FeedStandardInputHandler.props(stdInController), + ProjectInfo -> ProjectInfoHandler.props(languageServerConfig), + EditionsGetProjectSettings -> EditionsGetProjectSettingsHandler.props(), + EditionsListAvailable -> EditionsListAvailableHandler.props(), + EditionsListDefinedLibraries -> EditionsListDefinedLibrariesHandler + .props(), + EditionsResolve -> EditionsResolveHandler.props(), + EditionsSetParentEdition -> EditionsSetParentEditionHandler.props(), + EditionsSetLocalLibrariesPreference -> EditionsSetProjectLocalLibrariesPreferenceHandler + .props(), + LibraryCreate -> LibraryCreateHandler.props(), + LibraryGetMetadata -> LibraryGetMetadataHandler.props(), + LibraryPreinstall -> LibraryPreinstallHandler.props(), + LibraryPublish -> LibraryPublishHandler.props(), + LibrarySetMetadata -> LibrarySetMetadataHandler.props() ) } From 4f4a38a5a3849e92967104256edce3b6f397acf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Thu, 15 Jul 2021 00:43:14 +0200 Subject: [PATCH 08/26] Move editions to use LibraryName instead of String --- .../libraries/EditionManager.scala | 14 ---- .../libraries/EditionManagerActor.scala | 38 ++++++++++ .../libraries/EditionManagerProtocol.scala | 17 ++++- .../libraries/EditionReferenceResolver.scala | 27 +++++++ .../libraries/LocalLibraryManager.scala | 10 ++- .../LocalLibraryManagerProtocol.scala | 5 +- .../handler/LibraryPreinstallHandler.scala | 1 + .../enso/distribution/EditionManager.scala | 20 ++++-- .../org/enso/distribution/LanguageHome.scala | 12 ++++ .../editions/EditionResolutionError.scala | 2 +- .../org/enso/editions/EditionResolver.scala | 8 +-- .../enso/editions/EditionSerialization.scala | 10 ++- .../scala/org/enso/editions/Editions.scala | 27 +++++-- .../scala/org/enso/editions/LibraryName.scala | 7 +- .../provider/FileSystemEditionProvider.scala | 2 +- .../enso/editions/EditionResolverSpec.scala | 70 +++++++++++++------ .../editions/EditionSerializationSpec.scala | 15 ++-- 17 files changed, 221 insertions(+), 64 deletions(-) delete mode 100644 engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionManager.scala create mode 100644 engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionManagerActor.scala create mode 100644 engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionReferenceResolver.scala diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionManager.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionManager.scala deleted file mode 100644 index 89ef79fa72a8..000000000000 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionManager.scala +++ /dev/null @@ -1,14 +0,0 @@ -package org.enso.languageserver.libraries - -import akka.actor.Actor -import org.enso.distribution.DistributionManager - -import java.nio.file.Path -import scala.annotation.unused - -class EditionManager( - @unused projectRoot: Path, - @unused distributionManager: DistributionManager -) extends Actor { - override def receive: Receive = ??? -} diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionManagerActor.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionManagerActor.scala new file mode 100644 index 000000000000..df9cee0819d1 --- /dev/null +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionManagerActor.scala @@ -0,0 +1,38 @@ +package org.enso.languageserver.libraries + +import akka.actor.Actor +import org.enso.distribution.{DistributionManager, EditionManager, LanguageHome} +import org.enso.editions.DefaultEdition +import org.enso.languageserver.libraries.EditionManagerProtocol._ +import org.enso.pkg.PackageManager + +import java.nio.file.Path +import scala.annotation.unused + +class EditionHelper( + @unused projectRoot: Path, + languageHome: LanguageHome, + @unused distributionManager: DistributionManager +) { + private val projectPackage = + PackageManager.Default.loadPackage(projectRoot.toFile).get + + // TODO get language home + private val editionManager = new EditionManager( + languageHome.editions :: distributionManager.paths.editionSearchPaths.toList + ) + + // TODO [RW] get the default edition from config (#1864) + private val rawProjectEdition = + projectPackage.config.edition.getOrElse(DefaultEdition.getDefaultEdition) + + override def receive: Receive = { case request: Request => + request match { + case ListAvailable(update) => + case Resolve(editionReference) => + case SetParentEdition(newEditionName) => + case SetLocalLibrariesPreference(preferLocalLibraries) => + case ListDefinedLibraries(editionReference) => + } + } +} diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionManagerProtocol.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionManagerProtocol.scala index 2ba8235554ee..4c02c79a5ab7 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionManagerProtocol.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionManagerProtocol.scala @@ -8,6 +8,21 @@ object EditionManagerProtocol { case class ListAvailable(update: Boolean) extends Request case class ListAvailableResponse(editions: Seq[String]) - case class Resolve(editionReference: EditionReference) + case class Resolve(editionReference: EditionReference) extends Request case class ResolveResponse(engineVersion: SemVer) + + case object GetProjectSettings + case class ProjectSettingsResponse( + parentEdition: Option[String], + preferLocalLibraries: Boolean + ) + + case class SetParentEdition(newEditionName: String) extends Request + + case class SetLocalLibrariesPreference(preferLocalLibraries: Boolean) + extends Request + + case class ListDefinedLibraries(editionReference: EditionReference) + extends Request + case class ListDefinedLibrariesResult(availableLibraries: Seq[LibraryEntry]) } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionReferenceResolver.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionReferenceResolver.scala new file mode 100644 index 000000000000..b579605226b4 --- /dev/null +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionReferenceResolver.scala @@ -0,0 +1,27 @@ +package org.enso.languageserver.libraries + +import org.enso.editions.{DefaultEdition, Editions} +import org.enso.editions.provider.EditionProvider +import org.enso.pkg.Package + +import java.io.File +import scala.util.Try + +class EditionReferenceResolver( + projectPackage: Package[File], + editionProvider: EditionProvider +) { + 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 + } + } + } +} diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManager.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManager.scala index a9c7a389a1dc..fc119a7d2d0f 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManager.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManager.scala @@ -2,10 +2,18 @@ package org.enso.languageserver.libraries import akka.actor.Actor import org.enso.distribution.DistributionManager +import org.enso.languageserver.libraries.LocalLibraryManagerProtocol._ import scala.annotation.unused class LocalLibraryManager(@unused distributionManager: DistributionManager) extends Actor { - override def receive: Receive = ??? + override def receive: Receive = { case request: Request => + request match { + case GetMetadata(libraryName) => + case SetMetadata(libraryName, description, tagLine) => + case ListLocalLibraries => + case Publish(libraryName, authToken, bumpVersionAfterPublish) => + } + } } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManagerProtocol.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManagerProtocol.scala index 76d48a2d183f..db5620917f82 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManagerProtocol.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManagerProtocol.scala @@ -12,14 +12,15 @@ object LocalLibraryManagerProtocol { ) case object Success + case class SetMetadata( libraryName: LibraryName, description: Option[String], tagLine: Option[String] ) extends Request - case object List extends Request - case class ListResponse(libraries: Seq[LibraryName]) + case object ListLocalLibraries extends Request + case class ListLocalLibrariesResponse(libraries: Seq[LibraryName]) case class Publish( libraryName: LibraryName, diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryPreinstallHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryPreinstallHandler.scala index 1e713faa6097..1cf05e65417c 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryPreinstallHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryPreinstallHandler.scala @@ -13,6 +13,7 @@ class LibraryPreinstallHandler with UnhandledLogging { override def receive: Receive = { case Request(LibraryPreinstall, id, _: LibraryPreinstall.Params) => + // TODO [RW] fake progress bar // TODO [RW] actual implementation sender() ! ResponseError( Some(id), diff --git a/lib/scala/distribution-manager/src/main/scala/org/enso/distribution/EditionManager.scala b/lib/scala/distribution-manager/src/main/scala/org/enso/distribution/EditionManager.scala index 0a2729f52ca8..a6318fedc495 100644 --- a/lib/scala/distribution-manager/src/main/scala/org/enso/distribution/EditionManager.scala +++ b/lib/scala/distribution-manager/src/main/scala/org/enso/distribution/EditionManager.scala @@ -2,15 +2,21 @@ package org.enso.distribution import nl.gn0s1s.bump.SemVer import org.enso.editions -import org.enso.editions.provider.FileSystemEditionProvider -import org.enso.editions.{EditionResolver, Editions} +import org.enso.editions.provider.{EditionProvider, FileSystemEditionProvider} +import org.enso.editions.{ + EditionResolver, + Editions, + LibraryName, + LibraryVersion +} import java.nio.file.Path import scala.util.Try /** A helper class for resolving editions. */ -class EditionManager(searchPaths: List[Path]) { - private val editionProvider = FileSystemEditionProvider(searchPaths) +class EditionManager(editionProvider: EditionProvider) { + def this(searchPaths: List[Path]) = + this(new FileSystemEditionProvider(searchPaths)) private val editionResolver = EditionResolver(editionProvider) private val engineVersionResolver = @@ -36,4 +42,10 @@ class EditionManager(searchPaths: List[Path]) { */ def resolveEngineVersion(edition: Editions.RawEdition): Try[SemVer] = engineVersionResolver.resolveEnsoVersion(edition).toTry + + def findAllDefinedLibraries( + edition: Editions.RawEdition + ): Try[Seq[(LibraryName, LibraryVersion)]] = for { + resolved <- editionResolver.resolve(edition).toTry + } yield resolved.getAllDefinedLibraries.toSeq } diff --git a/lib/scala/distribution-manager/src/main/scala/org/enso/distribution/LanguageHome.scala b/lib/scala/distribution-manager/src/main/scala/org/enso/distribution/LanguageHome.scala index 7c78fcf26ac8..5f64972cbb02 100644 --- a/lib/scala/distribution-manager/src/main/scala/org/enso/distribution/LanguageHome.scala +++ b/lib/scala/distribution-manager/src/main/scala/org/enso/distribution/LanguageHome.scala @@ -20,3 +20,15 @@ case class LanguageHome(languageHome: Path) { def libraries: Path = 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) + } +} diff --git a/lib/scala/editions/src/main/scala/org/enso/editions/EditionResolutionError.scala b/lib/scala/editions/src/main/scala/org/enso/editions/EditionResolutionError.scala index 73c2813a4011..61c919eb37cf 100644 --- a/lib/scala/editions/src/main/scala/org/enso/editions/EditionResolutionError.scala +++ b/lib/scala/editions/src/main/scala/org/enso/editions/EditionResolutionError.scala @@ -29,7 +29,7 @@ object EditionResolutionError { * reference is invalid. */ case class LibraryReferencesUndefinedRepository( - libraryName: String, + libraryName: LibraryName, repositoryName: String ) extends EditionResolutionError( s"A library `$libraryName` references a repository `$repositoryName` " + diff --git a/lib/scala/editions/src/main/scala/org/enso/editions/EditionResolver.scala b/lib/scala/editions/src/main/scala/org/enso/editions/EditionResolver.scala index 217a57ae81d0..f3379a87286d 100644 --- a/lib/scala/editions/src/main/scala/org/enso/editions/EditionResolver.scala +++ b/lib/scala/editions/src/main/scala/org/enso/editions/EditionResolver.scala @@ -62,16 +62,16 @@ case class EditionResolver(provider: EditionProvider) { * a mapping of resolved libraries */ private def resolveLibraries( - libraries: Map[String, Editions.Raw.Library], + libraries: Map[LibraryName, Editions.Raw.Library], currentRepositories: Map[String, Editions.Repository], parent: Option[ResolvedEdition] ): Either[ LibraryReferencesUndefinedRepository, - Map[String, Editions.Resolved.Library] + Map[LibraryName, Editions.Resolved.Library] ] = { val resolvedPairs: Either[ LibraryReferencesUndefinedRepository, - List[(String, Editions.Resolved.Library)] + List[(LibraryName, Editions.Resolved.Library)] ] = libraries.toList.traverse { case (name, library) => val resolved = resolveLibrary(library, currentRepositories, parent) @@ -122,7 +122,7 @@ case class EditionResolver(provider: EditionProvider) { case (None, None) => Left( LibraryReferencesUndefinedRepository( - libraryName = library.qualifiedName, + libraryName = library.name, repositoryName = repositoryName ) ) diff --git a/lib/scala/editions/src/main/scala/org/enso/editions/EditionSerialization.scala b/lib/scala/editions/src/main/scala/org/enso/editions/EditionSerialization.scala index 9629b6d1b4f4..771d75553a69 100644 --- a/lib/scala/editions/src/main/scala/org/enso/editions/EditionSerialization.scala +++ b/lib/scala/editions/src/main/scala/org/enso/editions/EditionSerialization.scala @@ -64,7 +64,7 @@ object EditionSerialization { libraries <- json.getOrElse[Seq[Raw.Library]](Fields.libraries)(Seq()) res <- { 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) Left( DecodingFailure( @@ -157,7 +157,11 @@ object EditionSerialization { } 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 (version.isDefined) Left( @@ -181,7 +185,7 @@ object EditionSerialization { } } for { - name <- json.get[String](Fields.name) + name <- json.get[LibraryName](Fields.name) repository <- json.get[String](Fields.repository) version <- json.get[Option[SemVer]](Fields.version) res <- makeLibrary(name, repository, version) diff --git a/lib/scala/editions/src/main/scala/org/enso/editions/Editions.scala b/lib/scala/editions/src/main/scala/org/enso/editions/Editions.scala index e5c2c2aeb2c4..2f7b979d7f02 100644 --- a/lib/scala/editions/src/main/scala/org/enso/editions/Editions.scala +++ b/lib/scala/editions/src/main/scala/org/enso/editions/Editions.scala @@ -42,22 +42,22 @@ trait Editions { * It should consist of a prefix followed by a dot an the library name, for * example `Prefix.Library_Name`. */ - def qualifiedName: String + def name: LibraryName } /** 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 * 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 repository the recommended repository to download the library from, * if it is not yet cached */ case class PublishedLibrary( - override val qualifiedName: String, + override val name: LibraryName, version: SemVer, repository: LibraryRepositoryType ) extends Library @@ -77,7 +77,7 @@ trait Editions { parent: Option[NestedEditionType] = None, engineVersion: Option[SemVer] = None, repositories: Map[String, Editions.Repository] = Map.empty, - libraries: Map[String, Library] = Map.empty + libraries: Map[LibraryName, Library] = Map.empty ) { if (parent.isEmpty && engineVersion.isEmpty) throw new IllegalArgumentException( @@ -142,6 +142,23 @@ object Editions { } 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. */ diff --git a/lib/scala/editions/src/main/scala/org/enso/editions/LibraryName.scala b/lib/scala/editions/src/main/scala/org/enso/editions/LibraryName.scala index e70b06596d44..cb3580f80921 100644 --- a/lib/scala/editions/src/main/scala/org/enso/editions/LibraryName.scala +++ b/lib/scala/editions/src/main/scala/org/enso/editions/LibraryName.scala @@ -1,6 +1,7 @@ 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. * @@ -31,6 +32,10 @@ object LibraryName { } yield name } + implicit val encoder: Encoder[LibraryName] = { libraryName => + libraryName.toString.asJson + } + private val separator = '.' /** Creates a [[LibraryName]] from its string representation. diff --git a/lib/scala/editions/src/main/scala/org/enso/editions/provider/FileSystemEditionProvider.scala b/lib/scala/editions/src/main/scala/org/enso/editions/provider/FileSystemEditionProvider.scala index 74c210c398f0..15d244ace59b 100644 --- a/lib/scala/editions/src/main/scala/org/enso/editions/provider/FileSystemEditionProvider.scala +++ b/lib/scala/editions/src/main/scala/org/enso/editions/provider/FileSystemEditionProvider.scala @@ -10,7 +10,7 @@ import scala.util.{Failure, Success, Try} /** An implementation of [[EditionProvider]] that looks for the edition files in * a list of filesystem paths. */ -case class FileSystemEditionProvider(searchPaths: List[Path]) +class FileSystemEditionProvider(searchPaths: List[Path]) extends EditionProvider { /** @inheritdoc */ diff --git a/lib/scala/editions/src/test/scala/org/enso/editions/EditionResolverSpec.scala b/lib/scala/editions/src/test/scala/org/enso/editions/EditionResolverSpec.scala index 072a2221a573..7c1e1fa52875 100644 --- a/lib/scala/editions/src/test/scala/org/enso/editions/EditionResolverSpec.scala +++ b/lib/scala/editions/src/test/scala/org/enso/editions/EditionResolverSpec.scala @@ -25,8 +25,12 @@ class EditionResolverSpec "main" -> mainRepo ), libraries = Map( - "Standard.Base" -> Editions.Raw - .PublishedLibrary("Standard.Base", SemVer(1, 2, 3), "main") + LibraryName("Standard", "Base") -> Editions.Raw + .PublishedLibrary( + LibraryName("Standard", "Base"), + SemVer(1, 2, 3), + "main" + ) ) ), "cycleA" -> Editions.Raw.Edition( @@ -62,9 +66,11 @@ class EditionResolverSpec engineVersion = Some(SemVer(1, 2, 3)), repositories = Map("foo" -> repo), libraries = Map( - "bar.baz" -> Editions.Raw.LocalLibrary("bar.baz"), - "foo.bar" -> Editions.Raw - .PublishedLibrary("foo.bar", SemVer(1, 2, 3), "foo") + LibraryName("bar", "baz") -> Editions.Raw.LocalLibrary( + LibraryName("bar", "baz") + ), + LibraryName("foo", "bar") -> Editions.Raw + .PublishedLibrary(LibraryName("foo", "bar"), SemVer(1, 2, 3), "foo") ) ) @@ -73,10 +79,14 @@ class EditionResolverSpec resolved.parent should be(empty) resolved.repositories shouldEqual edition.repositories resolved.libraries should have size 2 - resolved.libraries("bar.baz") shouldEqual Editions.Resolved - .LocalLibrary("bar.baz") - resolved.libraries("foo.bar") shouldEqual Editions.Resolved - .PublishedLibrary("foo.bar", SemVer(1, 2, 3), repo) + resolved.libraries( + LibraryName("bar", "baz") + ) shouldEqual Editions.Resolved + .LocalLibrary(LibraryName("bar", "baz")) + resolved.libraries( + LibraryName("foo", "bar") + ) shouldEqual Editions.Resolved + .PublishedLibrary(LibraryName("foo", "bar"), SemVer(1, 2, 3), repo) } } @@ -86,20 +96,30 @@ class EditionResolverSpec engineVersion = Some(SemVer(1, 2, 3)), repositories = Map(), libraries = Map( - "bar.baz" -> Editions.Raw.LocalLibrary("bar.baz"), - "foo.bar" -> Editions.Raw - .PublishedLibrary("foo.bar", SemVer(1, 2, 3), "main") + LibraryName("bar", "baz") -> Editions.Raw.LocalLibrary( + LibraryName("bar", "baz") + ), + LibraryName("foo", "bar") -> Editions.Raw + .PublishedLibrary( + LibraryName("foo", "bar"), + SemVer(1, 2, 3), + "main" + ) ) ) inside(resolver.resolve(edition)) { case Right(resolved) => resolved.parent should be(defined) resolved.libraries should have size 2 - resolved.libraries("bar.baz") shouldEqual Editions.Resolved - .LocalLibrary("bar.baz") - resolved.libraries("foo.bar") shouldEqual Editions.Resolved + resolved.libraries( + LibraryName("bar", "baz") + ) shouldEqual Editions.Resolved + .LocalLibrary(LibraryName("bar", "baz")) + resolved.libraries( + LibraryName("foo", "bar") + ) shouldEqual Editions.Resolved .PublishedLibrary( - "foo.bar", + LibraryName("foo", "bar"), SemVer(1, 2, 3), FakeEditionProvider.mainRepo ) @@ -116,24 +136,30 @@ class EditionResolverSpec engineVersion = None, repositories = Map("main" -> localRepo), libraries = Map( - "foo.bar" -> Editions.Raw - .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) => resolved.parent should be(defined) resolved.libraries should have size 1 - resolved.libraries("foo.bar") shouldEqual + resolved.libraries(LibraryName("foo", "bar")) shouldEqual Editions.Resolved.PublishedLibrary( - "foo.bar", + LibraryName("foo", "bar"), SemVer(1, 2, 3), localRepo ) - resolved.parent.value.libraries("Standard.Base") shouldEqual + resolved.parent.value.libraries( + LibraryName("Standard", "Base") + ) shouldEqual Editions.Resolved.PublishedLibrary( - "Standard.Base", + LibraryName("Standard", "Base"), SemVer(1, 2, 3), FakeEditionProvider.mainRepo ) diff --git a/lib/scala/editions/src/test/scala/org/enso/editions/EditionSerializationSpec.scala b/lib/scala/editions/src/test/scala/org/enso/editions/EditionSerializationSpec.scala index 4a2965e4e277..a114581392cb 100644 --- a/lib/scala/editions/src/test/scala/org/enso/editions/EditionSerializationSpec.scala +++ b/lib/scala/editions/src/test/scala/org/enso/editions/EditionSerializationSpec.scala @@ -54,9 +54,14 @@ class EditionSerializationSpec extends AnyWordSpec with Matchers with Inside { .url shouldEqual "http://127.0.0.1:8080/root" edition.libraries.values should contain theSameElementsAs Seq( - Editions.Raw.LocalLibrary("Foo.Local"), - Editions.Raw.PublishedLibrary("Bar.Baz", SemVer(0, 0, 0), "example"), - Editions.Raw.PublishedLibrary("A.B", SemVer(1, 0, 1), "bar") + Editions.Raw.LocalLibrary(LibraryName("Foo", "Local")), + Editions.Raw.PublishedLibrary( + LibraryName("Bar", "Baz"), + SemVer(0, 0, 0), + "example" + ), + Editions.Raw + .PublishedLibrary(LibraryName("A", "B"), SemVer(1, 0, 1), "bar") ) edition.engineVersion should contain(SemVer(1, 2, 3, Some("SNAPSHOT"))) } @@ -80,7 +85,7 @@ class EditionSerializationSpec extends AnyWordSpec with Matchers with Inside { val parsed = EditionSerialization.parseYamlString( """extends: foo |libraries: - |- name: bar + |- name: bar.baz | repository: local | version: 1.2.3-SHOULD-NOT-BE-HERE |""".stripMargin @@ -92,7 +97,7 @@ class EditionSerializationSpec extends AnyWordSpec with Matchers with Inside { val parsed2 = EditionSerialization.parseYamlString( """extends: foo |libraries: - |- name: bar + |- name: bar.baz | repository: something |""".stripMargin ) From 40b6763f209e6225d8220663bbd83e80c037772b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Thu, 15 Jul 2021 11:04:28 +0200 Subject: [PATCH 09/26] checkpoint --- .../handler/EditionsResolveHandler.scala | 26 ++++++++++++++++--- .../enso/distribution/EditionManager.scala | 13 +--------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsResolveHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsResolveHandler.scala index 4f96ce337b5f..3ca5553eaf88 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsResolveHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsResolveHandler.scala @@ -2,17 +2,32 @@ package org.enso.languageserver.libraries.handler import akka.actor.{Actor, Props} import com.typesafe.scalalogging.LazyLogging +import org.enso.editions.EditionResolver import org.enso.jsonrpc.{Request, ResponseError} 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 -class EditionsResolveHandler - extends Actor +import scala.util.{Failure, Success} + +class EditionsResolveHandler( + editionReferenceResolver: EditionReferenceResolver, + editionResolver: EditionResolver +) extends Actor with LazyLogging with UnhandledLogging { override def receive: Receive = { - case Request(EditionsResolve, id, _: EditionsResolve.Params) => + case Request(EditionsResolve, id, EditionsResolve.Params(reference)) => + val result = for { + rawEdition <- editionReferenceResolver.resolveReference(reference) + resolvedEdition <- editionResolver.resolve(rawEdition).toTry + } yield resolvedEdition.getEngineVersion + + result match { + case Failure(exception) => + case Success(value) => + } // TODO [RW] actual implementation sender() ! ResponseError( Some(id), @@ -22,5 +37,8 @@ class EditionsResolveHandler } object EditionsResolveHandler { - def props(): Props = Props(new EditionsResolveHandler) + def props( + editionReferenceResolver: EditionReferenceResolver, + editionResolver: EditionResolver + ): Props = Props(new EditionsResolveHandler()) } diff --git a/lib/scala/distribution-manager/src/main/scala/org/enso/distribution/EditionManager.scala b/lib/scala/distribution-manager/src/main/scala/org/enso/distribution/EditionManager.scala index a6318fedc495..839d47da063a 100644 --- a/lib/scala/distribution-manager/src/main/scala/org/enso/distribution/EditionManager.scala +++ b/lib/scala/distribution-manager/src/main/scala/org/enso/distribution/EditionManager.scala @@ -3,12 +3,7 @@ package org.enso.distribution import nl.gn0s1s.bump.SemVer import org.enso.editions import org.enso.editions.provider.{EditionProvider, FileSystemEditionProvider} -import org.enso.editions.{ - EditionResolver, - Editions, - LibraryName, - LibraryVersion -} +import org.enso.editions.{EditionResolver, Editions} import java.nio.file.Path import scala.util.Try @@ -42,10 +37,4 @@ class EditionManager(editionProvider: EditionProvider) { */ def resolveEngineVersion(edition: Editions.RawEdition): Try[SemVer] = engineVersionResolver.resolveEnsoVersion(edition).toTry - - def findAllDefinedLibraries( - edition: Editions.RawEdition - ): Try[Seq[(LibraryName, LibraryVersion)]] = for { - resolved <- editionResolver.resolve(edition).toTry - } yield resolved.getAllDefinedLibraries.toSeq } From f0f4118665e5cb48ce58750bb3971871e949455a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Thu, 15 Jul 2021 15:48:15 +0200 Subject: [PATCH 10/26] checkpoint --- build.sbt | 1 + .../enso/languageserver/boot/MainModule.scala | 74 +++++++++++------- .../libraries/EditionManagerActor.scala | 38 ---------- .../libraries/EditionReferenceResolver.scala | 23 +++++- .../libraries/LocalLibraryManager.scala | 71 +++++++++++++++-- .../LocalLibraryManagerProtocol.scala | 7 ++ .../libraries/ProjectSettingsManager.scala | 71 +++++++++++++++++ .../EditionsGetProjectSettingsHandler.scala | 63 ++++++++++++--- .../handler/EditionsResolveHandler.scala | 38 +++++----- .../EditionsSetParentEditionHandler.scala | 65 +++++++++++++--- ...ojectLocalLibrariesPreferenceHandler.scala | 70 ++++++++++++++--- .../json/JsonConnectionController.scala | 25 ++++-- .../JsonConnectionControllerFactory.scala | 33 ++++---- .../websocket/json/BaseServerTest.scala | 76 ++++++++++++++----- .../org/enso/compiler/PackageRepository.scala | 29 ++++--- .../enso/distribution/EditionManager.scala | 22 +++++- .../enso/librarymanager/LibraryResolver.scala | 2 +- .../local/DefaultLocalLibraryProvider.scala | 4 +- .../local/LocalLibraryProvider.scala | 6 ++ .../src/main/scala/org/enso/pkg/Package.scala | 5 +- 20 files changed, 533 insertions(+), 190 deletions(-) delete mode 100644 engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionManagerActor.scala create mode 100644 engine/language-server/src/main/scala/org/enso/languageserver/libraries/ProjectSettingsManager.scala diff --git a/build.sbt b/build.sbt index 5197c2a98fea..2ed2ebd357db 100644 --- a/build.sbt +++ b/build.sbt @@ -1015,6 +1015,7 @@ lazy val `language-server` = (project in file("engine/language-server")) .dependsOn(`version-output`) .dependsOn(pkg) .dependsOn(testkit % Test) + .dependsOn(`runtime-version-manager-test` % Test) lazy val ast = (project in file("lib/scala/ast")) .settings( diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/boot/MainModule.scala b/engine/language-server/src/main/scala/org/enso/languageserver/boot/MainModule.scala index bd14d27ebb57..7838cf949b6e 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/boot/MainModule.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/boot/MainModule.scala @@ -1,27 +1,25 @@ package org.enso.languageserver.boot -import java.io.File -import java.net.URI -import java.time.Clock - import akka.actor.ActorSystem +import org.enso.distribution.{ + DistributionManager, + EditionManager, + Environment, + LanguageHome +} +import org.enso.editions.EditionResolver import org.enso.jsonrpc.JsonRpcServer import org.enso.languageserver.boot.DeploymentType.{Azure, Desktop} import org.enso.languageserver.capability.CapabilityRouter import org.enso.languageserver.data._ import org.enso.languageserver.effect.ZioExec -import org.enso.languageserver.filemanager.{ - ContentRoot, - ContentRootManager, - ContentRootManagerActor, - ContentRootManagerWrapper, - ContentRootWithFile, - FileManager, - FileSystem, - ReceivesTreeUpdatesHandler -} +import org.enso.languageserver.filemanager._ import org.enso.languageserver.http.server.BinaryWebSocketServer import org.enso.languageserver.io._ +import org.enso.languageserver.libraries.{ + EditionReferenceResolver, + ProjectSettingsManager +} import org.enso.languageserver.monitoring.{ HealthCheckEndpoint, IdlenessEndpoint, @@ -49,6 +47,9 @@ import org.graalvm.polyglot.Context import org.graalvm.polyglot.io.MessageEndpoint import org.slf4j.LoggerFactory +import java.io.File +import java.net.URI +import java.time.Clock import scala.concurrent.duration._ /** A main module containing all components of the server. @@ -270,20 +271,39 @@ class MainModule(serverConfig: LanguageServerConfig, logLevel: LogLevel) { context )(system.dispatcher) + val environment = new Environment {} + val languageHome = LanguageHome.detectFromExecutableLocation(environment) + val distributionManager = new DistributionManager(environment) + + val editionProvider = + EditionManager.makeEditionProvider(distributionManager, Some(languageHome)) + val editionResolver = EditionResolver(editionProvider) + val editionReferenceResolver = new EditionReferenceResolver( + contentRoot.file, + editionProvider, + editionResolver + ) + + val projectSettingsManager = system.actorOf( + ProjectSettingsManager.props(contentRoot.file, editionResolver) + ) + val jsonRpcControllerFactory = new JsonConnectionControllerFactory( - initializationComponent, - bufferRegistry, - capabilityRouter, - fileManager, - contentRootManagerActor, - contextRegistry, - suggestionsHandler, - stdOutController, - stdErrController, - stdInController, - runtimeConnector, - idlenessMonitor, - languageServerConfig + mainComponent = initializationComponent, + bufferRegistry = bufferRegistry, + capabilityRouter = capabilityRouter, + fileManager = fileManager, + contentRootManager = contentRootManagerActor, + contextRegistry = contextRegistry, + suggestionsHandler = suggestionsHandler, + stdOutController = stdOutController, + stdErrController = stdErrController, + stdInController = stdInController, + runtimeConnector = runtimeConnector, + idlenessMonitor = idlenessMonitor, + projectSettingsManager = projectSettingsManager, + editionReferenceResolver = editionReferenceResolver, + config = languageServerConfig ) log.trace( "Created JSON connection controller factory [{}].", diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionManagerActor.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionManagerActor.scala deleted file mode 100644 index df9cee0819d1..000000000000 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionManagerActor.scala +++ /dev/null @@ -1,38 +0,0 @@ -package org.enso.languageserver.libraries - -import akka.actor.Actor -import org.enso.distribution.{DistributionManager, EditionManager, LanguageHome} -import org.enso.editions.DefaultEdition -import org.enso.languageserver.libraries.EditionManagerProtocol._ -import org.enso.pkg.PackageManager - -import java.nio.file.Path -import scala.annotation.unused - -class EditionHelper( - @unused projectRoot: Path, - languageHome: LanguageHome, - @unused distributionManager: DistributionManager -) { - private val projectPackage = - PackageManager.Default.loadPackage(projectRoot.toFile).get - - // TODO get language home - private val editionManager = new EditionManager( - languageHome.editions :: distributionManager.paths.editionSearchPaths.toList - ) - - // TODO [RW] get the default edition from config (#1864) - private val rawProjectEdition = - projectPackage.config.edition.getOrElse(DefaultEdition.getDefaultEdition) - - override def receive: Receive = { case request: Request => - request match { - case ListAvailable(update) => - case Resolve(editionReference) => - case SetParentEdition(newEditionName) => - case SetLocalLibrariesPreference(preferLocalLibraries) => - case ListDefinedLibraries(editionReference) => - } - } -} diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionReferenceResolver.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionReferenceResolver.scala index b579605226b4..cb96688cdc8b 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionReferenceResolver.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionReferenceResolver.scala @@ -1,16 +1,21 @@ package org.enso.languageserver.libraries -import org.enso.editions.{DefaultEdition, Editions} import org.enso.editions.provider.EditionProvider -import org.enso.pkg.Package +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 class EditionReferenceResolver( - projectPackage: Package[File], - editionProvider: EditionProvider + projectRoot: File, + editionProvider: EditionProvider, + editionResolver: EditionResolver ) { + private lazy val projectPackage = + PackageManager.Default.loadPackage(projectRoot).get + def resolveReference( editionReference: EditionReference ): Try[Editions.RawEdition] = editionReference match { @@ -24,4 +29,14 @@ class EditionReferenceResolver( } } } + + def resolveEdition( + editionReference: EditionReference + ): Try[Editions.ResolvedEdition] = for { + raw <- resolveReference(editionReference) + resolved <- editionResolver.resolve(raw).toTry + } yield resolved + + def resolveEdition(name: String): Try[Editions.ResolvedEdition] = + resolveEdition(NamedEdition(name)) } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManager.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManager.scala index fc119a7d2d0f..4f56b0a93b20 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManager.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManager.scala @@ -1,19 +1,76 @@ package org.enso.languageserver.libraries import akka.actor.Actor +import com.typesafe.scalalogging.LazyLogging import org.enso.distribution.DistributionManager +import org.enso.editions.{Editions, LibraryName} import org.enso.languageserver.libraries.LocalLibraryManagerProtocol._ +import org.enso.librarymanager.local.LocalLibraryProvider +import org.enso.pkg.PackageManager -import scala.annotation.unused +import java.io.File +import java.nio.file.Files +import scala.util.{Failure, Success, Try} -class LocalLibraryManager(@unused distributionManager: DistributionManager) - extends Actor { +class LocalLibraryManager( + currentProjectRoot: File, + distributionManager: DistributionManager +) extends Actor + with LazyLogging { override def receive: Receive = { case request: Request => request match { - case GetMetadata(libraryName) => - case SetMetadata(libraryName, description, tagLine) => - case ListLocalLibraries => - case Publish(libraryName, authToken, bumpVersionAfterPublish) => + 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() ! Failure(new NotImplementedError()) + 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()) } } + + 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 librariesRoot = + distributionManager.paths.localLibrariesSearchPaths.headOption.getOrElse { + throw new RuntimeException("Cannot find 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 + ) + } + + private def findCurrentProjectEdition(): Option[Editions.RawEdition] = { + val pkg = PackageManager.Default.loadPackage(currentProjectRoot).get + pkg.config.edition + } } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManagerProtocol.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManagerProtocol.scala index db5620917f82..24fcac5dba05 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManagerProtocol.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManagerProtocol.scala @@ -22,6 +22,13 @@ object LocalLibraryManagerProtocol { case object ListLocalLibraries extends Request case class ListLocalLibrariesResponse(libraries: Seq[LibraryName]) + case class Create( + libraryName: LibraryName, + authors: Seq[String], + maintainers: Seq[String], + license: String + ) extends Request + case class Publish( libraryName: LibraryName, authToken: String, diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/ProjectSettingsManager.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/ProjectSettingsManager.scala new file mode 100644 index 000000000000..807cd220b0e7 --- /dev/null +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/ProjectSettingsManager.scala @@ -0,0 +1,71 @@ +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 + +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)) + } + } yield updated.save() + + private def setPreferLocalLibraries( + preferLocalLibraries: Boolean + ): Try[Unit] = for { + pkg <- PackageManager.Default.loadPackage(projectRoot) + updated = pkg.updateConfig { config => + config.copy(preferLocalLibraries = preferLocalLibraries) + } + } yield updated.save() +} + +object ProjectSettingsManager { + def props(projectRoot: File, editionResolver: EditionResolver): Props = Props( + new ProjectSettingsManager(projectRoot, editionResolver) + ) + + sealed trait Request + + case object GetSettings extends Request + case class SettingsResponse( + parentEdition: Option[String], + preferLocalLibraries: Boolean + ) + + case class SetParentEdition(editionName: String) extends Request + + case class SetPreferLocalLibraries(preferLocalLibraries: Boolean) + extends Request +} diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsGetProjectSettingsHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsGetProjectSettingsHandler.scala index 6aa77bb596e3..c91560c15660 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsGetProjectSettingsHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsGetProjectSettingsHandler.scala @@ -1,26 +1,71 @@ package org.enso.languageserver.libraries.handler -import akka.actor.{Actor, Props} +import akka.actor.{Actor, ActorRef, Cancellable, Props} import com.typesafe.scalalogging.LazyLogging -import org.enso.jsonrpc.{Request, ResponseError} +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 -class EditionsGetProjectSettingsHandler - extends Actor +import scala.concurrent.duration.FiniteDuration +import scala.util.{Failure, Success} + +class EditionsGetProjectSettingsHandler( + timeout: FiniteDuration, + projectSettingsManager: ActorRef +) extends Actor with LazyLogging with UnhandledLogging { - override def receive: Receive = { + + import context.dispatcher + + override def receive: Receive = requestStage + + private def requestStage: Receive = { case Request(EditionsGetProjectSettings, id, _) => - // TODO [RW] actual implementation - sender() ! ResponseError( + 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("Feature not implemented") + FileSystemError(s"Failed to load the settings: ${exception.getMessage}") ) + cancellable.cancel() + context.stop(self) } + } object EditionsGetProjectSettingsHandler { - def props(): Props = Props(new EditionsGetProjectSettingsHandler) + def props(timeout: FiniteDuration, projectSettingsManager: ActorRef): Props = + Props( + new EditionsGetProjectSettingsHandler(timeout, projectSettingsManager) + ) } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsResolveHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsResolveHandler.scala index 3ca5553eaf88..66a59f142e56 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsResolveHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsResolveHandler.scala @@ -2,8 +2,7 @@ package org.enso.languageserver.libraries.handler import akka.actor.{Actor, Props} import com.typesafe.scalalogging.LazyLogging -import org.enso.editions.EditionResolver -import org.enso.jsonrpc.{Request, ResponseError} +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._ @@ -11,34 +10,35 @@ import org.enso.languageserver.util.UnhandledLogging import scala.util.{Failure, Success} -class EditionsResolveHandler( - editionReferenceResolver: EditionReferenceResolver, - editionResolver: EditionResolver -) extends Actor +class EditionsResolveHandler(editionReferenceResolver: EditionReferenceResolver) + extends Actor with LazyLogging with UnhandledLogging { override def receive: Receive = { case Request(EditionsResolve, id, EditionsResolve.Params(reference)) => val result = for { - rawEdition <- editionReferenceResolver.resolveReference(reference) - resolvedEdition <- editionResolver.resolve(rawEdition).toTry - } yield resolvedEdition.getEngineVersion + edition <- editionReferenceResolver.resolveEdition(reference) + } yield edition.getEngineVersion result match { case Failure(exception) => - case Success(value) => + // TODO [RW] more detailed errors + sender() ! ResponseError( + Some(id), + FileSystemError(exception.getMessage) + ) + case Success(engineVersion) => + sender() ! ResponseResult( + EditionsResolve, + id, + EditionsResolve.Result(engineVersion.toString) + ) } - // TODO [RW] actual implementation - sender() ! ResponseError( - Some(id), - FileSystemError("Feature not implemented") - ) } } object EditionsResolveHandler { - def props( - editionReferenceResolver: EditionReferenceResolver, - editionResolver: EditionResolver - ): Props = Props(new EditionsResolveHandler()) + def props(editionReferenceResolver: EditionReferenceResolver): Props = Props( + new EditionsResolveHandler(editionReferenceResolver) + ) } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsSetParentEditionHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsSetParentEditionHandler.scala index bfe9566b4e1c..7f82d263437c 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsSetParentEditionHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsSetParentEditionHandler.scala @@ -1,30 +1,75 @@ package org.enso.languageserver.libraries.handler -import akka.actor.{Actor, Props} +import akka.actor.{Actor, ActorRef, Cancellable, Props} import com.typesafe.scalalogging.LazyLogging -import org.enso.jsonrpc.{Request, ResponseError} +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 -class EditionsSetParentEditionHandler - extends Actor +import scala.concurrent.duration.FiniteDuration +import scala.util.{Failure, Success} + +class EditionsSetParentEditionHandler( + timeout: FiniteDuration, + projectSettingsManager: ActorRef +) extends Actor with LazyLogging with UnhandledLogging { - override def receive: Receive = { + + import context.dispatcher + + override def receive: Receive = requestStage + + private def requestStage: Receive = { case Request( EditionsSetParentEdition, id, - _: EditionsSetParentEdition.Params + EditionsSetParentEdition.Params(newEditionName) ) => - // TODO [RW] actual implementation - sender() ! ResponseError( + 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("Feature not implemented") + FileSystemError( + s"Failed to update the settings: ${exception.getMessage}" + ) ) + cancellable.cancel() + context.stop(self) } } object EditionsSetParentEditionHandler { - def props(): Props = Props(new EditionsSetParentEditionHandler) + def props(timeout: FiniteDuration, projectSettingsManager: ActorRef): Props = + Props( + new EditionsSetParentEditionHandler(timeout, projectSettingsManager) + ) } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsSetProjectLocalLibrariesPreferenceHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsSetProjectLocalLibrariesPreferenceHandler.scala index e02fcd58fedb..f93fdf40ed1b 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsSetProjectLocalLibrariesPreferenceHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsSetProjectLocalLibrariesPreferenceHandler.scala @@ -1,32 +1,80 @@ package org.enso.languageserver.libraries.handler -import akka.actor.{Actor, Props} +import akka.actor.{Actor, ActorRef, Cancellable, Props} import com.typesafe.scalalogging.LazyLogging -import org.enso.jsonrpc.{Request, ResponseError} +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 -class EditionsSetProjectLocalLibrariesPreferenceHandler - extends Actor +import scala.concurrent.duration.FiniteDuration +import scala.util.{Failure, Success} + +class EditionsSetProjectLocalLibrariesPreferenceHandler( + timeout: FiniteDuration, + projectSettingsManager: ActorRef +) extends Actor with LazyLogging with UnhandledLogging { - override def receive: Receive = { + + import context.dispatcher + + override def receive: Receive = requestStage + + private def requestStage: Receive = { case Request( EditionsSetLocalLibrariesPreference, id, - _: EditionsSetLocalLibrariesPreference.Params + EditionsSetLocalLibrariesPreference.Params(preferLocalLibraries) ) => - // TODO [RW] actual implementation - sender() ! ResponseError( + 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("Feature not implemented") + FileSystemError( + s"Failed to update the settings: ${exception.getMessage}" + ) ) + cancellable.cancel() + context.stop(self) } } object EditionsSetProjectLocalLibrariesPreferenceHandler { - def props(): Props = Props( - new EditionsSetProjectLocalLibrariesPreferenceHandler + def props( + timeout: FiniteDuration, + projectSettingsManager: ActorRef + ): Props = Props( + new EditionsSetProjectLocalLibrariesPreferenceHandler( + timeout, + projectSettingsManager + ) ) } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionController.scala b/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionController.scala index 68e25a7d1dc9..23c3c569f99b 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionController.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionController.scala @@ -25,6 +25,7 @@ import org.enso.languageserver.filemanager._ import org.enso.languageserver.io.InputOutputApi._ import org.enso.languageserver.io.OutputKind.{StandardError, StandardOutput} 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} @@ -82,6 +83,7 @@ import scala.concurrent.duration._ * @param contextRegistry a router that dispatches execution context requests * @param suggestionsHandler a reference to the suggestions requests handler * @param idlenessMonitor a reference to the idleness monitor actor + * @param projectSettingsManager a reference to the project settings manager * @param requestTimeout a request timeout */ class JsonConnectionController( @@ -98,6 +100,8 @@ class JsonConnectionController( val stdInController: ActorRef, val runtimeConnector: ActorRef, val idlenessMonitor: ActorRef, + val projectSettingsManager: ActorRef, + val editionReferenceResolver: EditionReferenceResolver, val languageServerConfig: Config, requestTimeout: FiniteDuration = 10.seconds ) extends Actor @@ -458,16 +462,19 @@ class JsonConnectionController( .props(stdErrController, rpcSession.clientId), RedirectStandardError -> RedirectStdErrHandler .props(stdErrController, rpcSession.clientId), - FeedStandardInput -> FeedStandardInputHandler.props(stdInController), - ProjectInfo -> ProjectInfoHandler.props(languageServerConfig), - EditionsGetProjectSettings -> EditionsGetProjectSettingsHandler.props(), - EditionsListAvailable -> EditionsListAvailableHandler.props(), + FeedStandardInput -> FeedStandardInputHandler.props(stdInController), + ProjectInfo -> ProjectInfoHandler.props(languageServerConfig), + EditionsGetProjectSettings -> EditionsGetProjectSettingsHandler + .props(requestTimeout, projectSettingsManager), + EditionsListAvailable -> EditionsListAvailableHandler.props(), EditionsListDefinedLibraries -> EditionsListDefinedLibrariesHandler .props(), - EditionsResolve -> EditionsResolveHandler.props(), - EditionsSetParentEdition -> EditionsSetParentEditionHandler.props(), + EditionsResolve -> EditionsResolveHandler + .props(editionReferenceResolver), + EditionsSetParentEdition -> EditionsSetParentEditionHandler + .props(requestTimeout, projectSettingsManager), EditionsSetLocalLibrariesPreference -> EditionsSetProjectLocalLibrariesPreferenceHandler - .props(), + .props(requestTimeout, projectSettingsManager), LibraryCreate -> LibraryCreateHandler.props(), LibraryGetMetadata -> LibraryGetMetadataHandler.props(), LibraryPreinstall -> LibraryPreinstallHandler.props(), @@ -507,6 +514,8 @@ object JsonConnectionController { stdInController: ActorRef, runtimeConnector: ActorRef, idlenessMonitor: ActorRef, + projectSettingsManager: ActorRef, + editionReferenceResolver: EditionReferenceResolver, languageServerConfig: Config, requestTimeout: FiniteDuration = 10.seconds ): Props = @@ -525,6 +534,8 @@ object JsonConnectionController { stdInController, runtimeConnector, idlenessMonitor, + projectSettingsManager, + editionReferenceResolver, languageServerConfig, requestTimeout ) diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionControllerFactory.scala b/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionControllerFactory.scala index d2bec2be2a62..d41f59086efd 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionControllerFactory.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionControllerFactory.scala @@ -4,6 +4,7 @@ import akka.actor.{ActorRef, ActorSystem} import org.enso.jsonrpc.ClientControllerFactory import org.enso.languageserver.boot.resource.InitializationComponent import org.enso.languageserver.data.Config +import org.enso.languageserver.libraries.EditionReferenceResolver import java.util.UUID @@ -27,6 +28,8 @@ class JsonConnectionControllerFactory( stdInController: ActorRef, runtimeConnector: ActorRef, idlenessMonitor: ActorRef, + projectSettingsManager: ActorRef, + editionReferenceResolver: EditionReferenceResolver, config: Config )(implicit system: ActorSystem) extends ClientControllerFactory { @@ -39,20 +42,22 @@ class JsonConnectionControllerFactory( override def createClientController(clientId: UUID): ActorRef = system.actorOf( JsonConnectionController.props( - clientId, - mainComponent, - bufferRegistry, - capabilityRouter, - fileManager, - contentRootManager, - contextRegistry, - suggestionsHandler, - stdOutController, - stdErrController, - stdInController, - runtimeConnector, - idlenessMonitor, - config + connectionId = clientId, + mainComponent = mainComponent, + bufferRegistry = bufferRegistry, + capabilityRouter = capabilityRouter, + fileManager = fileManager, + contentRootManager = contentRootManager, + contextRegistry = contextRegistry, + suggestionsHandler = suggestionsHandler, + stdOutController = stdOutController, + stdErrController = stdErrController, + stdInController = stdInController, + runtimeConnector = runtimeConnector, + idlenessMonitor = idlenessMonitor, + projectSettingsManager = projectSettingsManager, + editionReferenceResolver = editionReferenceResolver, + languageServerConfig = config ) ) } diff --git a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/BaseServerTest.scala b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/BaseServerTest.scala index 2485e4b2e93a..05ce248bc8d8 100644 --- a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/BaseServerTest.scala +++ b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/BaseServerTest.scala @@ -1,15 +1,15 @@ package org.enso.languageserver.websocket.json -import java.nio.file.Files -import java.util.UUID - import akka.testkit.TestProbe import io.circe.literal._ import io.circe.parser.parse import io.circe.syntax.EncoderOps 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.{ClientControllerFactory, Protocol} +import org.enso.languageserver.TestClock import org.enso.languageserver.boot.resource.{ DirectoriesInitialization, RepoInitialization, @@ -21,6 +21,10 @@ import org.enso.languageserver.effect.ZioExec import org.enso.languageserver.event.InitializedEvent import org.enso.languageserver.filemanager._ import org.enso.languageserver.io._ +import org.enso.languageserver.libraries.{ + EditionReferenceResolver, + ProjectSettingsManager +} import org.enso.languageserver.monitoring.IdlenessMonitor import org.enso.languageserver.protocol.json.{ JsonConnectionControllerFactory, @@ -30,22 +34,27 @@ import org.enso.languageserver.refactoring.ProjectNameChangedEvent import org.enso.languageserver.runtime.{ContextRegistry, RuntimeFailureMapper} import org.enso.languageserver.search.SuggestionsHandler import org.enso.languageserver.session.SessionRouter -import org.enso.languageserver.TestClock import org.enso.languageserver.text.BufferRegistry import org.enso.polyglot.data.TypeGraph 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.testkit.EitherValue import org.enso.text.Sha3_224VersionCalculator import org.scalatest.OptionValues +import java.nio.file +import java.nio.file.Files +import java.util.UUID import scala.concurrent.Await import scala.concurrent.duration._ class BaseServerTest extends JsonRpcServerTestKit with EitherValue - with OptionValues { + with OptionValues + with HasTestDirectory + with FakeEnvironment { import system.dispatcher @@ -68,7 +77,12 @@ class BaseServerTest 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(testDirectory.toFile)) def mkConfig: Config = Config( @@ -205,20 +219,45 @@ class BaseServerTest Api.VerifyModulesIndexResponse(Seq()) ) + 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 projectSettingsManager = system.actorOf( + ProjectSettingsManager.props( + config.projectContentRoot.file, + editionResolver + ) + ) + new JsonConnectionControllerFactory( - initializationComponent, - bufferRegistry, - capabilityRouter, - fileManager, - contentRootManagerActor, - contextRegistry, - suggestionsHandler, - stdOutController, - stdErrController, - stdInController, - runtimeConnectorProbe.ref, - idlenessMonitor, - config + mainComponent = initializationComponent, + bufferRegistry = bufferRegistry, + capabilityRouter = capabilityRouter, + fileManager = fileManager, + contentRootManager = contentRootManagerActor, + contextRegistry = contextRegistry, + suggestionsHandler = suggestionsHandler, + stdOutController = stdOutController, + stdErrController = stdErrController, + stdInController = stdInController, + runtimeConnector = runtimeConnectorProbe.ref, + idlenessMonitor = idlenessMonitor, + projectSettingsManager = projectSettingsManager, + editionReferenceResolver = editionReferenceResolver, + config = config ) } @@ -253,5 +292,4 @@ class BaseServerTest ) clientId } - } diff --git a/engine/runtime/src/main/scala/org/enso/compiler/PackageRepository.scala b/engine/runtime/src/main/scala/org/enso/compiler/PackageRepository.scala index 440438fdcc3c..8a17740b7d85 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/PackageRepository.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/PackageRepository.scala @@ -67,7 +67,7 @@ object PackageRepository { object Error { /** Indicates that a resolution error has happened, for example the package - * was not defined in the selected edition. + * was not defined in the selected edition. */ case class PackageCouldNotBeResolved(cause: Throwable) extends Error { override def toString: String = @@ -95,11 +95,11 @@ object PackageRepository { /** The default [[PackageRepository]] implementation. * - * @param libraryProvider the [[ResolvingLibraryProvider]] which resolves - * which library version should be imported and - * locates them (or downloads if they are missing) - * @param context the language context - * @param builtins the builtins module + * @param libraryProvider the [[ResolvingLibraryProvider]] which resolves + * which library version should be imported and + * locates them (or downloads if they are missing) + * @param context the language context + * @param builtins the builtins module * @param notificationHandler a notification handler */ class Default( @@ -315,12 +315,12 @@ object PackageRepository { * Edition and library search paths are based on the distribution and * language home (if it is provided). * - * @param projectPackage the package of the current project (if ran inside of a project) - * @param languageHome the language home (if set) + * @param projectPackage the package of the current project (if ran inside of a project) + * @param languageHome the language home (if set) * @param distributionManager the distribution manager - * @param context the context reference, needed to add polyglot libraries to - * the classpath - * @param builtins the builtins that are always preloaded + * @param context the context reference, needed to add polyglot libraries to + * the classpath + * @param builtins the builtins that are always preloaded * @param notificationHandler a handler for library addition and progress * notifications * @return an initialized [[PackageRepository]] @@ -337,11 +337,8 @@ object PackageRepository { .flatMap(_.config.edition) .getOrElse(DefaultEdition.getDefaultEdition) - val homeManager = languageHome.map { home => LanguageHome(Path.of(home)) } - val editionSearchPaths = - homeManager.map(_.editions).toList ++ - distributionManager.paths.editionSearchPaths - val editionManager = new EditionManager(editionSearchPaths) + val homeManager = languageHome.map { home => LanguageHome(Path.of(home)) } + val editionManager = EditionManager(distributionManager, homeManager) val edition = editionManager.resolveEdition(rawEdition).get val resolvingLibraryProvider = diff --git a/lib/scala/distribution-manager/src/main/scala/org/enso/distribution/EditionManager.scala b/lib/scala/distribution-manager/src/main/scala/org/enso/distribution/EditionManager.scala index 839d47da063a..a3d058736f68 100644 --- a/lib/scala/distribution-manager/src/main/scala/org/enso/distribution/EditionManager.scala +++ b/lib/scala/distribution-manager/src/main/scala/org/enso/distribution/EditionManager.scala @@ -5,14 +5,10 @@ import org.enso.editions import org.enso.editions.provider.{EditionProvider, FileSystemEditionProvider} import org.enso.editions.{EditionResolver, Editions} -import java.nio.file.Path import scala.util.Try /** A helper class for resolving editions. */ class EditionManager(editionProvider: EditionProvider) { - def this(searchPaths: List[Path]) = - this(new FileSystemEditionProvider(searchPaths)) - private val editionResolver = EditionResolver(editionProvider) private val engineVersionResolver = editions.EngineVersionResolver(editionProvider) @@ -38,3 +34,21 @@ class EditionManager(editionProvider: EditionProvider) { def resolveEngineVersion(edition: Editions.RawEdition): Try[SemVer] = engineVersionResolver.resolveEnsoVersion(edition).toTry } + +object EditionManager { + def makeEditionProvider( + distributionManager: DistributionManager, + languageHome: Option[LanguageHome] + ): EditionProvider = { + val searchPaths = languageHome.map(_.editions).toList ++ + distributionManager.paths.editionSearchPaths + new FileSystemEditionProvider(searchPaths) + } + + def apply( + distributionManager: DistributionManager, + languageHome: Option[LanguageHome] + ): EditionManager = new EditionManager( + makeEditionProvider(distributionManager, languageHome) + ) +} diff --git a/lib/scala/library-manager/src/main/scala/org/enso/librarymanager/LibraryResolver.scala b/lib/scala/library-manager/src/main/scala/org/enso/librarymanager/LibraryResolver.scala index 9ef6e2765041..0ad50cce9dbe 100644 --- a/lib/scala/library-manager/src/main/scala/org/enso/librarymanager/LibraryResolver.scala +++ b/lib/scala/library-manager/src/main/scala/org/enso/librarymanager/LibraryResolver.scala @@ -54,7 +54,7 @@ case class LibraryResolver( ): Either[LibraryResolutionError, LibraryVersion] = { import Editions.Resolved._ val immediateResult = - edition.libraries.get(libraryName.qualifiedName).map { + edition.libraries.get(libraryName).map { case LocalLibrary(_) => Right(LibraryVersion.Local) case PublishedLibrary(_, version, repository) => diff --git a/lib/scala/library-manager/src/main/scala/org/enso/librarymanager/local/DefaultLocalLibraryProvider.scala b/lib/scala/library-manager/src/main/scala/org/enso/librarymanager/local/DefaultLocalLibraryProvider.scala index 3b08605eef99..0e25fdb65b5e 100644 --- a/lib/scala/library-manager/src/main/scala/org/enso/librarymanager/local/DefaultLocalLibraryProvider.scala +++ b/lib/scala/library-manager/src/main/scala/org/enso/librarymanager/local/DefaultLocalLibraryProvider.scala @@ -1,7 +1,6 @@ package org.enso.librarymanager.local import com.typesafe.scalalogging.Logger -import org.enso.distribution.FileSystem.PathSyntax import org.enso.editions.LibraryName import org.enso.logger.masking.MaskedPath @@ -31,7 +30,8 @@ class DefaultLocalLibraryProvider(searchPaths: List[Path]) searchPaths: List[Path] ): Option[Path] = searchPaths match { case head :: tail => - val potentialPath = head / libraryName.namespace / libraryName.name + val potentialPath = + LocalLibraryProvider.resolveLibraryPath(head, libraryName) if (Files.exists(potentialPath) && Files.isDirectory(potentialPath)) { logger.trace( s"Found a local $libraryName at " + diff --git a/lib/scala/library-manager/src/main/scala/org/enso/librarymanager/local/LocalLibraryProvider.scala b/lib/scala/library-manager/src/main/scala/org/enso/librarymanager/local/LocalLibraryProvider.scala index 829cc7dd0a6e..78f5c1bfff12 100644 --- a/lib/scala/library-manager/src/main/scala/org/enso/librarymanager/local/LocalLibraryProvider.scala +++ b/lib/scala/library-manager/src/main/scala/org/enso/librarymanager/local/LocalLibraryProvider.scala @@ -1,5 +1,6 @@ package org.enso.librarymanager.local +import org.enso.distribution.FileSystem.PathSyntax import org.enso.editions.LibraryName import java.nio.file.Path @@ -12,3 +13,8 @@ trait LocalLibraryProvider { */ def findLibrary(libraryName: LibraryName): Option[Path] } + +object LocalLibraryProvider { + def resolveLibraryPath(root: Path, libraryName: LibraryName): Path = + root / libraryName.namespace / libraryName.name +} diff --git a/lib/scala/pkg/src/main/scala/org/enso/pkg/Package.scala b/lib/scala/pkg/src/main/scala/org/enso/pkg/Package.scala index 411e959f4b1f..8183ea28c729 100644 --- a/lib/scala/pkg/src/main/scala/org/enso/pkg/Package.scala +++ b/lib/scala/pkg/src/main/scala/org/enso/pkg/Package.scala @@ -208,13 +208,14 @@ class PackageManager[F](implicit val fileSystem: FileSystem[F]) { version: String = "0.0.1", edition: Option[Editions.RawEdition] = None, authors: List[Contact] = List(), - maintainers: List[Contact] = List() + maintainers: List[Contact] = List(), + license: String = "" ): Package[F] = { val config = Config( name = NameValidation.normalizeName(name), namespace = namespace, version = version, - license = "", + license = license, authors = authors, edition = edition, preferLocalLibraries = true, From 58d197051dbb89f4b43367a2cabd2e2d603eb4e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Thu, 15 Jul 2021 15:51:25 +0200 Subject: [PATCH 11/26] Fixes --- .../scala/org/enso/launcher/Launcher.scala | 4 +- .../components/LauncherRunnerSpec.scala | 4 +- .../enso/distribution/EditionManager.scala | 2 +- .../librarymanager/LibraryResolverSpec.scala | 38 +++++++++++++------ .../DefaultDistributionConfiguration.scala | 4 +- .../TestDistributionConfiguration.scala | 4 +- 6 files changed, 31 insertions(+), 25 deletions(-) diff --git a/engine/launcher/src/main/scala/org/enso/launcher/Launcher.scala b/engine/launcher/src/main/scala/org/enso/launcher/Launcher.scala index e09af43063c3..36d1a4f3c068 100644 --- a/engine/launcher/src/main/scala/org/enso/launcher/Launcher.scala +++ b/engine/launcher/src/main/scala/org/enso/launcher/Launcher.scala @@ -46,9 +46,7 @@ case class Launcher(cliOptions: GlobalCLIOptions) { } private lazy val configurationManager = new GlobalConfigurationManager(componentsManager, distributionManager) - private lazy val editionManager = new EditionManager( - distributionManager.paths.editionSearchPaths.toList - ) + private lazy val editionManager = EditionManager(distributionManager) private lazy val projectManager = new ProjectManager private lazy val runner = new LauncherRunner( diff --git a/engine/launcher/src/test/scala/org/enso/launcher/components/LauncherRunnerSpec.scala b/engine/launcher/src/test/scala/org/enso/launcher/components/LauncherRunnerSpec.scala index 775b5a498197..39c1602175ec 100644 --- a/engine/launcher/src/test/scala/org/enso/launcher/components/LauncherRunnerSpec.scala +++ b/engine/launcher/src/test/scala/org/enso/launcher/components/LauncherRunnerSpec.scala @@ -31,9 +31,7 @@ class LauncherRunnerSpec extends RuntimeVersionManagerTest { new GlobalConfigurationManager(componentsManager, distributionManager) { override def defaultVersion: SemVer = defaultEngineVersion } - val editionManager = new EditionManager( - distributionManager.paths.editionSearchPaths.toList - ) + val editionManager = EditionManager(distributionManager) val projectManager = new ProjectManager() val cwd = cwdOverride.getOrElse(getTestDirectory) val runner = diff --git a/lib/scala/distribution-manager/src/main/scala/org/enso/distribution/EditionManager.scala b/lib/scala/distribution-manager/src/main/scala/org/enso/distribution/EditionManager.scala index a3d058736f68..4e8afb5c478b 100644 --- a/lib/scala/distribution-manager/src/main/scala/org/enso/distribution/EditionManager.scala +++ b/lib/scala/distribution-manager/src/main/scala/org/enso/distribution/EditionManager.scala @@ -47,7 +47,7 @@ object EditionManager { def apply( distributionManager: DistributionManager, - languageHome: Option[LanguageHome] + languageHome: Option[LanguageHome] = None ): EditionManager = new EditionManager( makeEditionProvider(distributionManager, languageHome) ) diff --git a/lib/scala/library-manager/src/test/scala/org/enso/librarymanager/LibraryResolverSpec.scala b/lib/scala/library-manager/src/test/scala/org/enso/librarymanager/LibraryResolverSpec.scala index c13793054f20..2cdc407f1d83 100644 --- a/lib/scala/library-manager/src/test/scala/org/enso/librarymanager/LibraryResolverSpec.scala +++ b/lib/scala/library-manager/src/test/scala/org/enso/librarymanager/LibraryResolverSpec.scala @@ -23,8 +23,12 @@ class LibraryResolverSpec engineVersion = Some(SemVer(0, 0, 0)), repositories = Map("main" -> mainRepo), libraries = Map( - "Standard.Base" -> Editions.Resolved - .PublishedLibrary("Standard.Base", SemVer(4, 5, 6), mainRepo) + LibraryName("Standard", "Base") -> Editions.Resolved + .PublishedLibrary( + LibraryName("Standard", "Base"), + SemVer(4, 5, 6), + mainRepo + ) ) ) val customRepo = Repository.make("custom", "https://example.com/custom").get @@ -33,24 +37,34 @@ class LibraryResolverSpec engineVersion = None, repositories = Map("custom" -> customRepo), libraries = Map( - "Foo.Main" -> Editions.Resolved - .PublishedLibrary("Foo.Main", SemVer(1, 0, 0), mainRepo), - "Foo.My" -> Editions.Resolved - .PublishedLibrary("Foo.My", SemVer(2, 0, 0), customRepo), - "Foo.Local" -> Editions.Resolved.LocalLibrary("Foo.Local") + LibraryName("Foo", "Main") -> Editions.Resolved + .PublishedLibrary( + LibraryName("Foo", "Main"), + SemVer(1, 0, 0), + 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 { override def findLibrary(libraryName: LibraryName): Option[Path] = - fixtures.get(libraryName.qualifiedName) + fixtures.get(libraryName) } val localLibraries = Map( - "Foo.My" -> Path.of("./Foo/My"), - "Foo.Local" -> Path.of("./Foo/Local"), - "Standard.Base" -> Path.of("./Standard/Base") + LibraryName("Foo", "My") -> Path.of("./Foo/My"), + LibraryName("Foo", "Local") -> Path.of("./Foo/Local"), + LibraryName("Standard", "Base") -> Path.of("./Standard/Base") ) val resolver = LibraryResolver(FakeLocalLibraryProvider(localLibraries)) diff --git a/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/versionmanagement/DefaultDistributionConfiguration.scala b/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/versionmanagement/DefaultDistributionConfiguration.scala index 950f8033bd88..832ce6b7e4c9 100644 --- a/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/versionmanagement/DefaultDistributionConfiguration.scala +++ b/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/versionmanagement/DefaultDistributionConfiguration.scala @@ -49,9 +49,7 @@ object DefaultDistributionConfiguration lazy val resourceManager = new ResourceManager(lockManager) /** @inheritdoc */ - lazy val editionManager = new EditionManager( - distributionManager.paths.editionSearchPaths.toList - ) + lazy val editionManager = EditionManager(distributionManager) /** @inheritdoc */ lazy val temporaryDirectoryManager = diff --git a/lib/scala/project-manager/src/test/scala/org/enso/projectmanager/TestDistributionConfiguration.scala b/lib/scala/project-manager/src/test/scala/org/enso/projectmanager/TestDistributionConfiguration.scala index 01ba7ba43de7..24564a78d081 100644 --- a/lib/scala/project-manager/src/test/scala/org/enso/projectmanager/TestDistributionConfiguration.scala +++ b/lib/scala/project-manager/src/test/scala/org/enso/projectmanager/TestDistributionConfiguration.scala @@ -64,9 +64,7 @@ class TestDistributionConfiguration( lazy val resourceManager = new ResourceManager(lockManager) - lazy val editionManager: EditionManager = new EditionManager( - distributionManager.paths.editionSearchPaths.toList - ) + lazy val editionManager: EditionManager = EditionManager(distributionManager) lazy val temporaryDirectoryManager = new TemporaryDirectoryManager(distributionManager, resourceManager) From 548a1751e0856891c04f6812f4b0c8510d626e63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Thu, 15 Jul 2021 17:26:01 +0200 Subject: [PATCH 12/26] listing editions --- .../enso/languageserver/boot/MainModule.scala | 2 + .../libraries/LibraryEntry.scala | 14 ++++-- .../EditionsListAvailableHandler.scala | 30 +++++++++--- .../EditionsListDefinedLibrariesHandler.scala | 49 +++++++++++++++---- .../handler/EditionsResolveHandler.scala | 13 ++--- .../json/JsonConnectionController.scala | 10 +++- .../JsonConnectionControllerFactory.scala | 3 ++ .../websocket/json/BaseServerTest.scala | 2 + .../enso/distribution/EditionManager.scala | 36 +++++++++++--- .../provider/FileSystemEditionProvider.scala | 19 ++++++- 10 files changed, 140 insertions(+), 38 deletions(-) diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/boot/MainModule.scala b/engine/language-server/src/main/scala/org/enso/languageserver/boot/MainModule.scala index 7838cf949b6e..841c38e3e0e8 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/boot/MainModule.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/boot/MainModule.scala @@ -283,6 +283,7 @@ class MainModule(serverConfig: LanguageServerConfig, logLevel: LogLevel) { editionProvider, editionResolver ) + val editionManager = EditionManager(distributionManager, Some(languageHome)) val projectSettingsManager = system.actorOf( ProjectSettingsManager.props(contentRoot.file, editionResolver) @@ -303,6 +304,7 @@ class MainModule(serverConfig: LanguageServerConfig, logLevel: LogLevel) { idlenessMonitor = idlenessMonitor, projectSettingsManager = projectSettingsManager, editionReferenceResolver = editionReferenceResolver, + editionManager = editionManager, config = languageServerConfig ) log.trace( diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LibraryEntry.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LibraryEntry.scala index b37f43b3945c..340668691cdd 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LibraryEntry.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LibraryEntry.scala @@ -1,8 +1,9 @@ package org.enso.languageserver.libraries -import io.circe.{Decoder, DecodingFailure, Encoder, Json} -import io.circe.syntax._ import io.circe.generic.semiauto._ +import io.circe.syntax._ +import io.circe.{Decoder, DecodingFailure, Encoder, Json} +import org.enso.editions case class LibraryEntry( namespace: String, @@ -11,12 +12,19 @@ case class LibraryEntry( ) object LibraryEntry { - // TODO [RW] proper case serialization sealed trait LibraryVersion case object LocalLibraryVersion extends LibraryVersion case class PublishedLibraryVersion(version: String, repositoryUrl: String) extends LibraryVersion + 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] diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsListAvailableHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsListAvailableHandler.scala index 4134a2c48b5d..8a46de9273a7 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsListAvailableHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsListAvailableHandler.scala @@ -2,25 +2,39 @@ 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.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 -class EditionsListAvailableHandler +import scala.util.{Failure, Success, Try} + +class EditionsListAvailableHandler(editionManager: EditionManager) extends Actor with LazyLogging with UnhandledLogging { override def receive: Receive = { case Request(EditionsListAvailable, id, _: EditionsListAvailable.Params) => - // TODO [RW] actual implementation - sender() ! ResponseError( - Some(id), - FileSystemError("Feature not implemented") - ) + // 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.getMessage) + ) + } } } object EditionsListAvailableHandler { - def props(): Props = Props(new EditionsListAvailableHandler) + def props(editionManager: EditionManager): Props = Props( + new EditionsListAvailableHandler(editionManager) + ) } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsListDefinedLibrariesHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsListDefinedLibrariesHandler.scala index b7d2a9878726..0916d4e968f4 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsListDefinedLibrariesHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsListDefinedLibrariesHandler.scala @@ -2,29 +2,58 @@ 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.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 -class EditionsListDefinedLibrariesHandler - extends Actor +import scala.util.{Failure, Success} + +class EditionsListDefinedLibrariesHandler( + editionReferenceResolver: EditionReferenceResolver +) extends Actor with LazyLogging with UnhandledLogging { override def receive: Receive = { case Request( EditionsListDefinedLibraries, id, - _: EditionsListDefinedLibraries.Params + EditionsListDefinedLibraries.Params(reference) ) => - // TODO [RW] actual implementation - sender() ! ResponseError( - Some(id), - FileSystemError("Feature not implemented") - ) + 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 { - def props(): Props = Props(new EditionsListDefinedLibrariesHandler) + def props(editionReferenceResolver: EditionReferenceResolver): Props = Props( + new EditionsListDefinedLibrariesHandler(editionReferenceResolver) + ) } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsResolveHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsResolveHandler.scala index 66a59f142e56..cc50349034fe 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsResolveHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsResolveHandler.scala @@ -21,18 +21,19 @@ class EditionsResolveHandler(editionReferenceResolver: EditionReferenceResolver) } yield edition.getEngineVersion result match { - case Failure(exception) => - // TODO [RW] more detailed errors - sender() ! ResponseError( - Some(id), - FileSystemError(exception.getMessage) - ) 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) + ) } } } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionController.scala b/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionController.scala index 23c3c569f99b..3b46bc0fa700 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionController.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionController.scala @@ -5,6 +5,7 @@ import akka.actor.{Actor, ActorRef, Cancellable, Props, Stash, Status} import akka.pattern.pipe import akka.util.Timeout import com.typesafe.scalalogging.LazyLogging +import org.enso.distribution.EditionManager import org.enso.jsonrpc._ import org.enso.languageserver.boot.resource.InitializationComponent import org.enso.languageserver.capability.CapabilityApi.{ @@ -102,6 +103,7 @@ class JsonConnectionController( val idlenessMonitor: ActorRef, val projectSettingsManager: ActorRef, val editionReferenceResolver: EditionReferenceResolver, + val editionManager: EditionManager, val languageServerConfig: Config, requestTimeout: FiniteDuration = 10.seconds ) extends Actor @@ -466,9 +468,11 @@ class JsonConnectionController( ProjectInfo -> ProjectInfoHandler.props(languageServerConfig), EditionsGetProjectSettings -> EditionsGetProjectSettingsHandler .props(requestTimeout, projectSettingsManager), - EditionsListAvailable -> EditionsListAvailableHandler.props(), + EditionsListAvailable -> EditionsListAvailableHandler.props( + editionManager + ), EditionsListDefinedLibraries -> EditionsListDefinedLibrariesHandler - .props(), + .props(editionReferenceResolver), EditionsResolve -> EditionsResolveHandler .props(editionReferenceResolver), EditionsSetParentEdition -> EditionsSetParentEditionHandler @@ -516,6 +520,7 @@ object JsonConnectionController { idlenessMonitor: ActorRef, projectSettingsManager: ActorRef, editionReferenceResolver: EditionReferenceResolver, + editionManager: EditionManager, languageServerConfig: Config, requestTimeout: FiniteDuration = 10.seconds ): Props = @@ -536,6 +541,7 @@ object JsonConnectionController { idlenessMonitor, projectSettingsManager, editionReferenceResolver, + editionManager, languageServerConfig, requestTimeout ) diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionControllerFactory.scala b/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionControllerFactory.scala index d41f59086efd..07c4f6a1afc4 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionControllerFactory.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionControllerFactory.scala @@ -1,6 +1,7 @@ package org.enso.languageserver.protocol.json import akka.actor.{ActorRef, ActorSystem} +import org.enso.distribution.EditionManager import org.enso.jsonrpc.ClientControllerFactory import org.enso.languageserver.boot.resource.InitializationComponent import org.enso.languageserver.data.Config @@ -30,6 +31,7 @@ class JsonConnectionControllerFactory( idlenessMonitor: ActorRef, projectSettingsManager: ActorRef, editionReferenceResolver: EditionReferenceResolver, + editionManager: EditionManager, config: Config )(implicit system: ActorSystem) extends ClientControllerFactory { @@ -57,6 +59,7 @@ class JsonConnectionControllerFactory( idlenessMonitor = idlenessMonitor, projectSettingsManager = projectSettingsManager, editionReferenceResolver = editionReferenceResolver, + editionManager = editionManager, languageServerConfig = config ) ) diff --git a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/BaseServerTest.scala b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/BaseServerTest.scala index 05ce248bc8d8..67ab5ebff4f7 100644 --- a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/BaseServerTest.scala +++ b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/BaseServerTest.scala @@ -234,6 +234,7 @@ class BaseServerTest editionProvider, editionResolver ) + val editionManager = EditionManager(distributionManager, Some(languageHome)) val projectSettingsManager = system.actorOf( ProjectSettingsManager.props( @@ -257,6 +258,7 @@ class BaseServerTest idlenessMonitor = idlenessMonitor, projectSettingsManager = projectSettingsManager, editionReferenceResolver = editionReferenceResolver, + editionManager = editionManager, config = config ) } diff --git a/lib/scala/distribution-manager/src/main/scala/org/enso/distribution/EditionManager.scala b/lib/scala/distribution-manager/src/main/scala/org/enso/distribution/EditionManager.scala index 4e8afb5c478b..cd5432d69b66 100644 --- a/lib/scala/distribution-manager/src/main/scala/org/enso/distribution/EditionManager.scala +++ b/lib/scala/distribution-manager/src/main/scala/org/enso/distribution/EditionManager.scala @@ -5,10 +5,20 @@ import org.enso.editions import org.enso.editions.provider.{EditionProvider, FileSystemEditionProvider} import org.enso.editions.{EditionResolver, Editions} +import java.nio.file.Path +import scala.annotation.unused import scala.util.Try -/** A helper class for resolving editions. */ -class EditionManager(editionProvider: EditionProvider) { +/** 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 engineVersionResolver = editions.EngineVersionResolver(editionProvider) @@ -33,22 +43,32 @@ class EditionManager(editionProvider: EditionProvider) { */ def resolveEngineVersion(edition: Editions.RawEdition): Try[SemVer] = engineVersionResolver.resolveEnsoVersion(edition).toTry + + // TODO [RW] download edition updates + + def findAllAvailableEditions(): Seq[String] = + editionProvider.findAvailableEditions() } object EditionManager { def makeEditionProvider( distributionManager: DistributionManager, languageHome: Option[LanguageHome] - ): EditionProvider = { - val searchPaths = languageHome.map(_.editions).toList ++ - distributionManager.paths.editionSearchPaths - new FileSystemEditionProvider(searchPaths) - } + ): EditionProvider = new FileSystemEditionProvider( + getSearchPaths(distributionManager, languageHome) + ) + + private def getSearchPaths( + distributionManager: DistributionManager, + languageHome: Option[LanguageHome] + ): List[Path] = languageHome.map(_.editions).toList ++ + distributionManager.paths.editionSearchPaths def apply( distributionManager: DistributionManager, languageHome: Option[LanguageHome] = None ): EditionManager = new EditionManager( - makeEditionProvider(distributionManager, languageHome) + distributionManager.paths.cachedEditions, + getSearchPaths(distributionManager, languageHome) ) } diff --git a/lib/scala/editions/src/main/scala/org/enso/editions/provider/FileSystemEditionProvider.scala b/lib/scala/editions/src/main/scala/org/enso/editions/provider/FileSystemEditionProvider.scala index 15d244ace59b..e80d50b15fb8 100644 --- a/lib/scala/editions/src/main/scala/org/enso/editions/provider/FileSystemEditionProvider.scala +++ b/lib/scala/editions/src/main/scala/org/enso/editions/provider/FileSystemEditionProvider.scala @@ -5,7 +5,9 @@ import org.enso.editions.{EditionSerialization, Editions} import java.io.FileNotFoundException import java.nio.file.{Files, Path} 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 * a list of filesystem paths. @@ -60,4 +62,19 @@ class FileSystemEditionProvider(searchPaths: List[Path]) .map(EditionReadError) } else Left(EditionNotFound) } + + 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] = + Using(Files.list(path))(_.toScala(Factory.arrayFactory).toSeq).get + .filter(Files.isRegularFile(_)) + .flatMap(findEditionName) } From e759e93b76c9b656a8bb7db246e09e4395f0b632 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Thu, 15 Jul 2021 18:08:57 +0200 Subject: [PATCH 13/26] Library ops handlers --- .../enso/languageserver/boot/MainModule.scala | 10 ++- .../libraries/LocalLibraryManager.scala | 36 +++++++++- .../LocalLibraryManagerProtocol.scala | 4 +- .../EditionsGetProjectSettingsHandler.scala | 5 +- .../handler/LibraryCreateHandler.scala | 66 +++++++++++++++---- .../handler/LibraryListLocalHandler.scala | 63 ++++++++++++++++++ .../json/JsonConnectionController.scala | 44 +++++++------ .../JsonConnectionControllerFactory.scala | 2 + .../websocket/json/BaseServerTest.scala | 9 +++ 9 files changed, 198 insertions(+), 41 deletions(-) create mode 100644 engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryListLocalHandler.scala diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/boot/MainModule.scala b/engine/language-server/src/main/scala/org/enso/languageserver/boot/MainModule.scala index 841c38e3e0e8..d16aa591df8a 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/boot/MainModule.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/boot/MainModule.scala @@ -18,6 +18,7 @@ import org.enso.languageserver.http.server.BinaryWebSocketServer import org.enso.languageserver.io._ import org.enso.languageserver.libraries.{ EditionReferenceResolver, + LocalLibraryManager, ProjectSettingsManager } import org.enso.languageserver.monitoring.{ @@ -286,7 +287,13 @@ class MainModule(serverConfig: LanguageServerConfig, logLevel: LogLevel) { val editionManager = EditionManager(distributionManager, Some(languageHome)) val projectSettingsManager = system.actorOf( - ProjectSettingsManager.props(contentRoot.file, editionResolver) + ProjectSettingsManager.props(contentRoot.file, editionResolver), + "project-settings-manager" + ) + + val localLibraryManager = system.actorOf( + LocalLibraryManager.props(contentRoot.file, distributionManager), + "local-library-manager" ) val jsonRpcControllerFactory = new JsonConnectionControllerFactory( @@ -303,6 +310,7 @@ class MainModule(serverConfig: LanguageServerConfig, logLevel: LogLevel) { runtimeConnector = runtimeConnector, idlenessMonitor = idlenessMonitor, projectSettingsManager = projectSettingsManager, + localLibraryManager = localLibraryManager, editionReferenceResolver = editionReferenceResolver, editionManager = editionManager, config = languageServerConfig diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManager.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManager.scala index 4f56b0a93b20..3f3d78695aba 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManager.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManager.scala @@ -1,8 +1,8 @@ package org.enso.languageserver.libraries -import akka.actor.Actor +import akka.actor.{Actor, Props} import com.typesafe.scalalogging.LazyLogging -import org.enso.distribution.DistributionManager +import org.enso.distribution.{DistributionManager, FileSystem} import org.enso.editions.{Editions, LibraryName} import org.enso.languageserver.libraries.LocalLibraryManagerProtocol._ import org.enso.librarymanager.local.LocalLibraryProvider @@ -30,7 +30,7 @@ class LocalLibraryManager( ) sender() ! Failure(new NotImplementedError()) case ListLocalLibraries => - sender() ! Failure(new NotImplementedError()) + sender() ! listLocalLibraries() case Create(libraryName, authors, maintainers, license) => sender() ! createLibrary(libraryName, authors, maintainers, license) case Publish(_, _, _) => @@ -69,8 +69,38 @@ class LocalLibraryManager( ) } + 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) + } + 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) + ) +} diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManagerProtocol.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManagerProtocol.scala index 24fcac5dba05..cc652b61b7b2 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManagerProtocol.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManagerProtocol.scala @@ -11,8 +11,6 @@ object LocalLibraryManagerProtocol { tagLine: Option[String] ) - case object Success - case class SetMetadata( libraryName: LibraryName, description: Option[String], @@ -20,7 +18,7 @@ object LocalLibraryManagerProtocol { ) extends Request case object ListLocalLibraries extends Request - case class ListLocalLibrariesResponse(libraries: Seq[LibraryName]) + case class ListLocalLibrariesResponse(libraries: Seq[LibraryEntry]) case class Create( libraryName: LibraryName, diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsGetProjectSettingsHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsGetProjectSettingsHandler.scala index c91560c15660..5853a2cda3f1 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsGetProjectSettingsHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsGetProjectSettingsHandler.scala @@ -53,10 +53,7 @@ class EditionsGetProjectSettingsHandler( context.stop(self) case Failure(exception) => - replyTo ! ResponseError( - Some(id), - FileSystemError(s"Failed to load the settings: ${exception.getMessage}") - ) + replyTo ! ResponseError(Some(id), FileSystemError(exception.getMessage)) cancellable.cancel() context.stop(self) } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryCreateHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryCreateHandler.scala index 561cad7b2ee5..c0f59589698b 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryCreateHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryCreateHandler.scala @@ -1,26 +1,70 @@ package org.enso.languageserver.libraries.handler -import akka.actor.{Actor, Props} +import akka.actor.{Actor, ActorRef, Cancellable, Props} import com.typesafe.scalalogging.LazyLogging -import org.enso.jsonrpc.{Request, ResponseError} +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 -class LibraryCreateHandler - extends Actor +import scala.concurrent.duration.FiniteDuration +import scala.util.{Failure, Success} + +class LibraryCreateHandler( + timeout: FiniteDuration, + localLibraryManager: ActorRef +) extends Actor with LazyLogging with UnhandledLogging { - override def receive: Receive = { - case Request(LibraryCreate, id, _: LibraryCreate.Params) => - // TODO [RW] actual implementation - sender() ! ResponseError( - Some(id), - FileSystemError("Feature not implemented") + 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 { - def props(): Props = Props(new LibraryCreateHandler) + def props(timeout: FiniteDuration, localLibraryManager: ActorRef): Props = + Props( + new LibraryCreateHandler(timeout, localLibraryManager) + ) } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryListLocalHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryListLocalHandler.scala new file mode 100644 index 000000000000..554cb6b8fb27 --- /dev/null +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryListLocalHandler.scala @@ -0,0 +1,63 @@ +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} + +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 { + def props(timeout: FiniteDuration, localLibraryManager: ActorRef): Props = + Props( + new LibraryListLocalHandler(timeout, localLibraryManager) + ) +} diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionController.scala b/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionController.scala index 3b46bc0fa700..49022c0f168d 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionController.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionController.scala @@ -102,6 +102,7 @@ class JsonConnectionController( val runtimeConnector: ActorRef, val idlenessMonitor: ActorRef, val projectSettingsManager: ActorRef, + val localLibraryManager: ActorRef, val editionReferenceResolver: EditionReferenceResolver, val editionManager: EditionManager, val languageServerConfig: Config, @@ -479,7 +480,10 @@ class JsonConnectionController( .props(requestTimeout, projectSettingsManager), EditionsSetLocalLibrariesPreference -> EditionsSetProjectLocalLibrariesPreferenceHandler .props(requestTimeout, projectSettingsManager), - LibraryCreate -> LibraryCreateHandler.props(), + LibraryCreate -> LibraryCreateHandler + .props(requestTimeout, localLibraryManager), + LibraryListLocal -> LibraryListLocalHandler + .props(requestTimeout, localLibraryManager), LibraryGetMetadata -> LibraryGetMetadataHandler.props(), LibraryPreinstall -> LibraryPreinstallHandler.props(), LibraryPublish -> LibraryPublishHandler.props(), @@ -519,6 +523,7 @@ object JsonConnectionController { runtimeConnector: ActorRef, idlenessMonitor: ActorRef, projectSettingsManager: ActorRef, + localLibraryManager: ActorRef, editionReferenceResolver: EditionReferenceResolver, editionManager: EditionManager, languageServerConfig: Config, @@ -526,24 +531,25 @@ object JsonConnectionController { ): Props = Props( new JsonConnectionController( - connectionId, - mainComponent, - bufferRegistry, - capabilityRouter, - fileManager, - contentRootManager, - contextRegistry, - suggestionsHandler, - stdOutController, - stdErrController, - stdInController, - runtimeConnector, - idlenessMonitor, - projectSettingsManager, - editionReferenceResolver, - editionManager, - languageServerConfig, - requestTimeout + connectionId = connectionId, + mainComponent = mainComponent, + bufferRegistry = bufferRegistry, + capabilityRouter = capabilityRouter, + fileManager = fileManager, + contentRootManager = contentRootManager, + contextRegistry = contextRegistry, + suggestionsHandler = suggestionsHandler, + stdOutController = stdOutController, + stdErrController = stdErrController, + stdInController = stdInController, + runtimeConnector = runtimeConnector, + idlenessMonitor = idlenessMonitor, + projectSettingsManager = projectSettingsManager, + localLibraryManager = localLibraryManager, + editionReferenceResolver = editionReferenceResolver, + editionManager = editionManager, + languageServerConfig = languageServerConfig, + requestTimeout = requestTimeout ) ) diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionControllerFactory.scala b/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionControllerFactory.scala index 07c4f6a1afc4..b4aff004efb8 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionControllerFactory.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionControllerFactory.scala @@ -30,6 +30,7 @@ class JsonConnectionControllerFactory( runtimeConnector: ActorRef, idlenessMonitor: ActorRef, projectSettingsManager: ActorRef, + localLibraryManager: ActorRef, editionReferenceResolver: EditionReferenceResolver, editionManager: EditionManager, config: Config @@ -58,6 +59,7 @@ class JsonConnectionControllerFactory( runtimeConnector = runtimeConnector, idlenessMonitor = idlenessMonitor, projectSettingsManager = projectSettingsManager, + localLibraryManager = localLibraryManager, editionReferenceResolver = editionReferenceResolver, editionManager = editionManager, languageServerConfig = config diff --git a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/BaseServerTest.scala b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/BaseServerTest.scala index 67ab5ebff4f7..e64076b5d441 100644 --- a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/BaseServerTest.scala +++ b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/BaseServerTest.scala @@ -23,6 +23,7 @@ import org.enso.languageserver.filemanager._ import org.enso.languageserver.io._ import org.enso.languageserver.libraries.{ EditionReferenceResolver, + LocalLibraryManager, ProjectSettingsManager } import org.enso.languageserver.monitoring.IdlenessMonitor @@ -243,6 +244,13 @@ class BaseServerTest ) ) + val localLibraryManager = system.actorOf( + LocalLibraryManager.props( + config.projectContentRoot.file, + distributionManager + ) + ) + new JsonConnectionControllerFactory( mainComponent = initializationComponent, bufferRegistry = bufferRegistry, @@ -257,6 +265,7 @@ class BaseServerTest runtimeConnector = runtimeConnectorProbe.ref, idlenessMonitor = idlenessMonitor, projectSettingsManager = projectSettingsManager, + localLibraryManager = localLibraryManager, editionReferenceResolver = editionReferenceResolver, editionManager = editionManager, config = config From f2ad813d92458aaa91bc9751ff95e02669a24da0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Thu, 15 Jul 2021 21:41:26 +0200 Subject: [PATCH 14/26] Implement progress notifications --- .../libraries/FakeDownload.scala | 39 +++++++++++ .../handler/LibraryPreinstallHandler.scala | 16 ++++- .../json/JsonConnectionController.scala | 51 ++++++++++++++- .../org/enso/polyglot/runtime/Runtime.scala | 26 ++++++++ .../instrument/NotificationHandler.scala | 26 ++++++-- .../ProgressNotificationTranslator.scala | 37 +++++++++++ .../enso/cli/task/ProgressNotification.scala | 26 ++++++++ .../task/ProgressNotificationForwarder.scala | 60 +++++++++++++++++ .../org/enso/cli/task/ProgressUnit.scala | 13 +++- .../requesthandler/RequestHandler.scala | 2 +- .../ControllerInterface.scala | 64 ++++--------------- ... ActorProgressNotificationForwarder.scala} | 56 ++++++++-------- .../SerializableProgressUnit.scala | 8 +-- 13 files changed, 328 insertions(+), 96 deletions(-) create mode 100644 engine/language-server/src/main/scala/org/enso/languageserver/libraries/FakeDownload.scala create mode 100644 engine/runtime/src/main/scala/org/enso/interpreter/instrument/ProgressNotificationTranslator.scala create mode 100644 lib/scala/cli/src/main/scala/org/enso/cli/task/ProgressNotification.scala create mode 100644 lib/scala/cli/src/main/scala/org/enso/cli/task/ProgressNotificationForwarder.scala rename lib/scala/task-progress-notifications/src/main/scala/org/enso/cli/task/notifications/{ProgressNotification.scala => ActorProgressNotificationForwarder.scala} (54%) diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/FakeDownload.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/FakeDownload.scala new file mode 100644 index 000000000000..a254e472bf36 --- /dev/null +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/FakeDownload.scala @@ -0,0 +1,39 @@ +package org.enso.languageserver.libraries + +import org.enso.cli.task.{ + ProgressReporter, + ProgressUnit, + TaskProgress, + TaskProgressImplementation +} + +import scala.util.Success + +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)) + } + tracker.setComplete(Success(())) + }) + thread.start() + tracker + } + + def simulateDownload( + message: String, + progressReporter: ProgressReporter + ): Unit = { + val download = make() + progressReporter.trackProgress(message, download) + download.force() + } +} diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryPreinstallHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryPreinstallHandler.scala index 1cf05e65417c..6967d31d0c88 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryPreinstallHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryPreinstallHandler.scala @@ -2,8 +2,10 @@ 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 @@ -13,8 +15,20 @@ class LibraryPreinstallHandler with UnhandledLogging { override def receive: Receive = { case Request(LibraryPreinstall, id, _: LibraryPreinstall.Params) => - // TODO [RW] fake progress bar // TODO [RW] actual implementation + val progressReporter = + ActorProgressNotificationForwarder.translateAndForward( + LibraryPreinstall.name, + sender() + ) + FakeDownload.simulateDownload( + "Downloading something...", + progressReporter + ) + FakeDownload.simulateDownload( + "Downloading something else...", + progressReporter + ) sender() ! ResponseError( Some(id), FileSystemError("Feature not implemented") diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionController.scala b/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionController.scala index 49022c0f168d..53b157d36eb1 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionController.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/protocol/json/JsonConnectionController.scala @@ -1,10 +1,11 @@ package org.enso.languageserver.protocol.json -import java.util.UUID import akka.actor.{Actor, ActorRef, Cancellable, Props, Stash, Status} import akka.pattern.pipe import akka.util.Timeout 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.languageserver.boot.resource.InitializationComponent @@ -69,7 +70,10 @@ import org.enso.languageserver.text.TextApi._ import org.enso.languageserver.text.TextProtocol import org.enso.languageserver.util.UnhandledLogging 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._ /** An actor handling communications between a single client and the language @@ -238,8 +242,7 @@ class JsonConnectionController( InitProtocolConnection.Result(allRoots.map(_.toContentRoot).toSet) ) - val requestHandlers = createRequestHandlers(rpcSession) - context.become(initialised(webActor, rpcSession, requestHandlers)) + initialize(webActor, rpcSession) } else { context.become( waitingForContentRoots( @@ -262,6 +265,17 @@ class JsonConnectionController( 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( webActor: ActorRef, rpcSession: JsonSession, @@ -372,6 +386,11 @@ class JsonConnectionController( ) } + case Api.ProgressNotification(payload) => + val translated: Notification[_, _] = + translateProgressNotification(payload) + webActor ! translated + case req @ Request(method, _, _) if requestHandlers.contains(method) => refreshIdleTime(method) val handler = context.actorOf( @@ -491,6 +510,32 @@ class JsonConnectionController( ) } + 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 { diff --git a/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala b/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala index 69e8a86822dd..159eae3b9b72 100644 --- a/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala +++ b/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala @@ -1353,6 +1353,32 @@ object Runtime { location: File ) extends ApiNotification + case class ProgressNotification( + payload: ProgressNotification.NotificationType + ) extends ApiNotification + + object ProgressNotification { + sealed trait NotificationType + case class TaskStarted( + taskId: UUID, + relatedOperation: String, + unit: String, + total: Option[Long] + ) extends NotificationType + + case class TaskProgressUpdate( + taskId: UUID, + message: Option[String], + done: Long + ) extends NotificationType + + case class TaskFinished( + taskId: UUID, + message: Option[String], + success: Boolean + ) extends NotificationType + } + private lazy val mapper = { val factory = new CBORFactory() val mapper = new ObjectMapper(factory) with ScalaObjectMapper diff --git a/engine/runtime/src/main/scala/org/enso/interpreter/instrument/NotificationHandler.scala b/engine/runtime/src/main/scala/org/enso/interpreter/instrument/NotificationHandler.scala index 81dead350ff1..ef420792b902 100644 --- a/engine/runtime/src/main/scala/org/enso/interpreter/instrument/NotificationHandler.scala +++ b/engine/runtime/src/main/scala/org/enso/interpreter/instrument/NotificationHandler.scala @@ -2,7 +2,12 @@ package org.enso.interpreter.instrument import com.typesafe.scalalogging.Logger 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.polyglot.runtime.Runtime.{Api, ApiResponse} @@ -15,9 +20,9 @@ trait NotificationHandler extends ProgressReporter { /** Called when a library has been loaded. * - * @param libraryName name of the added library + * @param libraryName name of the added library * @param libraryVersion selected version - * @param location path to the location from which the library is loaded + * @param location path to the location from which the library is loaded */ def addedLibrary( libraryName: LibraryName, @@ -80,7 +85,9 @@ object NotificationHandler { * notifications to the Language Server, which then should forward them to * the IDE. */ - class InteractiveMode(endpoint: Endpoint) extends NotificationHandler { + class InteractiveMode(endpoint: Endpoint) + extends NotificationHandler + with ProgressNotificationForwarder { private val logger = Logger[InteractiveMode] private def sendMessage(message: ApiResponse): Unit = { @@ -105,7 +112,16 @@ object NotificationHandler { /** @inheritdoc */ override def trackProgress(message: String, task: TaskProgress[_]): Unit = { 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 + ) + ) } } diff --git a/engine/runtime/src/main/scala/org/enso/interpreter/instrument/ProgressNotificationTranslator.scala b/engine/runtime/src/main/scala/org/enso/interpreter/instrument/ProgressNotificationTranslator.scala new file mode 100644 index 000000000000..b700b84d1294 --- /dev/null +++ b/engine/runtime/src/main/scala/org/enso/interpreter/instrument/ProgressNotificationTranslator.scala @@ -0,0 +1,37 @@ +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 + +object ProgressNotificationTranslator { + 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) + } +} diff --git a/lib/scala/cli/src/main/scala/org/enso/cli/task/ProgressNotification.scala b/lib/scala/cli/src/main/scala/org/enso/cli/task/ProgressNotification.scala new file mode 100644 index 000000000000..860f30af98bf --- /dev/null +++ b/lib/scala/cli/src/main/scala/org/enso/cli/task/ProgressNotification.scala @@ -0,0 +1,26 @@ +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. */ + 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 +} diff --git a/lib/scala/cli/src/main/scala/org/enso/cli/task/ProgressNotificationForwarder.scala b/lib/scala/cli/src/main/scala/org/enso/cli/task/ProgressNotificationForwarder.scala new file mode 100644 index 000000000000..c1fc7c0a22b3 --- /dev/null +++ b/lib/scala/cli/src/main/scala/org/enso/cli/task/ProgressNotificationForwarder.scala @@ -0,0 +1,60 @@ +package org.enso.cli.task + +import java.util.UUID +import scala.util.{Failure, Success, Try} + +trait ProgressNotificationForwarder extends ProgressReporter { + def sendProgressNotification(notification: ProgressNotification): Unit + + 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)) + } + }) + } +} diff --git a/lib/scala/cli/src/main/scala/org/enso/cli/task/ProgressUnit.scala b/lib/scala/cli/src/main/scala/org/enso/cli/task/ProgressUnit.scala index d9123527cebe..09188a3eadda 100644 --- a/lib/scala/cli/src/main/scala/org/enso/cli/task/ProgressUnit.scala +++ b/lib/scala/cli/src/main/scala/org/enso/cli/task/ProgressUnit.scala @@ -6,8 +6,17 @@ sealed trait ProgressUnit object ProgressUnit { /** 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. */ - case object Unspecified extends ProgressUnit + case object Unspecified extends ProgressUnit { + override val toString: String = "unspecified" + } + + def toString(unit: ProgressUnit): String = unit.toString + + def fromString(str: String): ProgressUnit = + if (str == Bytes.toString) Bytes else Unspecified } diff --git a/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/requesthandler/RequestHandler.scala b/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/requesthandler/RequestHandler.scala index 5e1e81a177bf..c0edf9e1c262 100644 --- a/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/requesthandler/RequestHandler.scala +++ b/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/requesthandler/RequestHandler.scala @@ -3,7 +3,7 @@ package org.enso.projectmanager.requesthandler import akka.actor.{Actor, ActorRef, Cancellable, Stash, Status} import akka.pattern.pipe import com.typesafe.scalalogging.{LazyLogging, Logger} -import org.enso.cli.task.notifications.ProgressNotification +import org.enso.cli.task.ProgressNotification import org.enso.cli.task.notifications.ProgressNotification.translateProgressNotification import org.enso.jsonrpc.Errors.ServiceError import org.enso.jsonrpc._ diff --git a/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/service/versionmanagement/ControllerInterface.scala b/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/service/versionmanagement/ControllerInterface.scala index 7852ec326e8c..255dfb805904 100644 --- a/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/service/versionmanagement/ControllerInterface.scala +++ b/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/service/versionmanagement/ControllerInterface.scala @@ -3,11 +3,13 @@ package org.enso.projectmanager.service.versionmanagement import akka.actor.ActorRef import com.typesafe.scalalogging.Logger import nl.gn0s1s.bump.SemVer -import org.enso.cli.task.notifications.{ +import org.enso.cli.task.notifications.SerializableProgressUnit +import org.enso.cli.task.{ + ProgressListener, ProgressNotification, - SerializableProgressUnit + ProgressNotificationForwarder, + TaskProgress } -import org.enso.cli.task.{ProgressListener, TaskProgress} import org.enso.distribution.locking.Resource import org.enso.runtimeversionmanager.components.{ GraalVMVersion, @@ -30,54 +32,8 @@ class ControllerInterface( progressTracker: ActorRef, allowMissingComponents: Boolean, allowBrokenComponents: Boolean -) extends RuntimeVersionManagementUserInterface { - - /** @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 = SerializableProgressUnit.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) - } - }) - } +) extends RuntimeVersionManagementUserInterface + with ProgressNotificationForwarder { /** @inheritdoc */ override def shouldInstallMissingEngine(version: SemVer): Boolean = @@ -120,4 +76,10 @@ class ControllerInterface( progressTracker ! ProgressNotification.TaskSuccess(uuid) } } + + /** @inheritdoc */ + override def sendProgressNotification( + notification: ProgressNotification + ): Unit = + progressTracker ! notification } diff --git a/lib/scala/task-progress-notifications/src/main/scala/org/enso/cli/task/notifications/ProgressNotification.scala b/lib/scala/task-progress-notifications/src/main/scala/org/enso/cli/task/notifications/ActorProgressNotificationForwarder.scala similarity index 54% rename from lib/scala/task-progress-notifications/src/main/scala/org/enso/cli/task/notifications/ProgressNotification.scala rename to lib/scala/task-progress-notifications/src/main/scala/org/enso/cli/task/notifications/ActorProgressNotificationForwarder.scala index 9b8436b0dd37..acdac50827db 100644 --- a/lib/scala/task-progress-notifications/src/main/scala/org/enso/cli/task/notifications/ProgressNotification.scala +++ b/lib/scala/task-progress-notifications/src/main/scala/org/enso/cli/task/notifications/ActorProgressNotificationForwarder.scala @@ -1,35 +1,33 @@ 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 -import java.util.UUID - -/** 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: SerializableProgressUnit - ) 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 +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( @@ -60,7 +58,7 @@ object ProgressNotification { Notification( TaskNotificationApi.TaskFinished, TaskNotificationApi.TaskFinished - .Params(taskId, Some(throwable.getMessage), success = false) + .Params(taskId, Option(throwable.getMessage), success = false) ) } } diff --git a/lib/scala/task-progress-notifications/src/main/scala/org/enso/cli/task/notifications/SerializableProgressUnit.scala b/lib/scala/task-progress-notifications/src/main/scala/org/enso/cli/task/notifications/SerializableProgressUnit.scala index 10077f3ef693..3376bf078b72 100644 --- a/lib/scala/task-progress-notifications/src/main/scala/org/enso/cli/task/notifications/SerializableProgressUnit.scala +++ b/lib/scala/task-progress-notifications/src/main/scala/org/enso/cli/task/notifications/SerializableProgressUnit.scala @@ -1,7 +1,7 @@ package org.enso.cli.task.notifications import enumeratum._ -import org.enso.cli.task.{TaskProgress, ProgressUnit => TaskProgressUnit} +import org.enso.cli.task.{ProgressUnit => TaskProgressUnit} /** Represents the unit used by progress updates. */ sealed trait SerializableProgressUnit extends EnumEntry @@ -19,10 +19,10 @@ object SerializableProgressUnit override val values = findValues - /** Creates a [[ProgressUnit]] from the unit associated with [[TaskProgress]]. + /** Converts a [[TaskProgressUnit]] to [[SerializableProgressUnit]]. */ - def fromTask(task: TaskProgress[_]): SerializableProgressUnit = - task.unit match { + implicit def fromUnit(unit: TaskProgressUnit): SerializableProgressUnit = + unit match { case TaskProgressUnit.Bytes => Bytes case TaskProgressUnit.Unspecified => Other } From 7dd29be8067b6d9e87da020691c99752959fc9bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Thu, 15 Jul 2021 21:44:32 +0200 Subject: [PATCH 15/26] Use auto decoder for Named Edition --- .../org/enso/languageserver/libraries/EditionReference.scala | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionReference.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionReference.scala index c91309373e2c..a1ecf4d611cc 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionReference.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionReference.scala @@ -2,6 +2,7 @@ package org.enso.languageserver.libraries import io.circe.{Decoder, DecodingFailure, Encoder, Json} import io.circe.syntax._ +import io.circe.generic.auto._ sealed trait EditionReference object EditionReference { @@ -32,9 +33,7 @@ object EditionReference { val typeCursor = json.downField(CodecField.Type) typeCursor.as[String].flatMap { case CodecType.NamedEdition => - for { - editionName <- json.get[String](CodecField.EditionName) - } yield NamedEdition(editionName) + Decoder[NamedEdition].tryDecode(json) case CodecType.CurrentProjectEdition => Right(CurrentProjectEdition) case unknownType => Left( From f61bb7160b0b1ff4978e86d74716d63390232646 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Thu, 15 Jul 2021 22:28:58 +0200 Subject: [PATCH 16/26] docs --- RELEASES.md | 6 ++++ .../libraries/EditionManagerProtocol.scala | 28 ------------------- .../libraries/EditionReference.scala | 9 +++++- .../libraries/EditionReferenceResolver.scala | 6 ++++ .../libraries/FakeDownload.scala | 7 +++++ .../libraries/LibraryEntry.scala | 19 +++++++++++++ .../libraries/LocalLibraryManager.scala | 22 ++++++++++++--- .../LocalLibraryManagerProtocol.scala | 11 ++++++++ .../libraries/ProjectSettingsManager.scala | 7 +++++ .../org/enso/polyglot/runtime/Runtime.scala | 12 ++++++++ .../ProgressNotificationTranslator.scala | 12 ++++++++ .../enso/cli/task/ProgressNotification.scala | 26 ++++++++++++++--- .../task/ProgressNotificationForwarder.scala | 6 ++++ .../org/enso/cli/task/ProgressUnit.scala | 4 +++ .../enso/distribution/EditionManager.scala | 16 +++++++++-- .../provider/FileSystemEditionProvider.scala | 1 + .../local/LocalLibraryProvider.scala | 4 +++ 17 files changed, 157 insertions(+), 39 deletions(-) delete mode 100644 engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionManagerProtocol.scala diff --git a/RELEASES.md b/RELEASES.md index ad9a7c3177e7..8435a34f237e 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,5 +1,11 @@ # Enso Next +## Tooling + +- Implement parts of the new Language Server API related to library support + ([#1875](https://github.com/enso-org/enso/pull/1875)). Parts of the API are + still mocked, but they are supported too for testing purposes. + # Enso 0.2.14 (2021-07-15) ## Interpreter/Runtime diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionManagerProtocol.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionManagerProtocol.scala deleted file mode 100644 index 4c02c79a5ab7..000000000000 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionManagerProtocol.scala +++ /dev/null @@ -1,28 +0,0 @@ -package org.enso.languageserver.libraries - -import nl.gn0s1s.bump.SemVer - -object EditionManagerProtocol { - sealed trait Request - - case class ListAvailable(update: Boolean) extends Request - case class ListAvailableResponse(editions: Seq[String]) - - case class Resolve(editionReference: EditionReference) extends Request - case class ResolveResponse(engineVersion: SemVer) - - case object GetProjectSettings - case class ProjectSettingsResponse( - parentEdition: Option[String], - preferLocalLibraries: Boolean - ) - - case class SetParentEdition(newEditionName: String) extends Request - - case class SetLocalLibrariesPreference(preferLocalLibraries: Boolean) - extends Request - - case class ListDefinedLibraries(editionReference: EditionReference) - extends Request - case class ListDefinedLibrariesResult(availableLibraries: Seq[LibraryEntry]) -} diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionReference.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionReference.scala index a1ecf4d611cc..918804a16030 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionReference.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionReference.scala @@ -4,10 +4,17 @@ 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 - case object CurrentProjectEdition extends EditionReference + + /** The edition associated with the current project. */ + case object CurrentProjectEdition extends EditionReference object CodecField { val Type = "type" diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionReferenceResolver.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionReferenceResolver.scala index cb96688cdc8b..9060558ad04f 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionReferenceResolver.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/EditionReferenceResolver.scala @@ -8,6 +8,7 @@ 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, @@ -16,6 +17,7 @@ class EditionReferenceResolver( 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 { @@ -30,6 +32,9 @@ class EditionReferenceResolver( } } + /** Resolves all edition dependencies of an edition identified by + * [[EditionReference]]. + */ def resolveEdition( editionReference: EditionReference ): Try[Editions.ResolvedEdition] = for { @@ -37,6 +42,7 @@ class EditionReferenceResolver( 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)) } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/FakeDownload.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/FakeDownload.scala index a254e472bf36..12b30a3c6ddc 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/FakeDownload.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/FakeDownload.scala @@ -9,6 +9,10 @@ import org.enso.cli.task.{ 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. @@ -28,6 +32,9 @@ object FakeDownload { tracker } + /** Simulates a download operation reporting progress updates to the + * [[ProgressReporter]]. + */ def simulateDownload( message: String, progressReporter: ProgressReporter diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LibraryEntry.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LibraryEntry.scala index 340668691cdd..9397c135892a 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LibraryEntry.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LibraryEntry.scala @@ -5,6 +5,12 @@ 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, @@ -12,11 +18,24 @@ case class LibraryEntry( ) 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 { diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManager.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManager.scala index 3f3d78695aba..b3019ca79a10 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManager.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManager.scala @@ -12,6 +12,7 @@ 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 @@ -39,6 +40,11 @@ class LocalLibraryManager( } } + /** 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], @@ -49,10 +55,14 @@ class LocalLibraryManager( val _ = (authors, maintainers) // TODO [RW] make the exceptions more relevant - val librariesRoot = - distributionManager.paths.localLibrariesSearchPaths.headOption.getOrElse { - throw new RuntimeException("Cannot find local library path") - } + val possibleRoots = LazyList + .from(distributionManager.paths.localLibrariesSearchPaths) + .filter(Files.isWritable) + val librariesRoot = possibleRoots.headOption.getOrElse { + throw new RuntimeException( + "Cannot find a writable directory on local library path." + ) + } val libraryPath = LocalLibraryProvider.resolveLibraryPath(librariesRoot, libraryName) @@ -69,6 +79,7 @@ class LocalLibraryManager( ) } + /** Lists all local libraries. */ private def listLocalLibraries(): Try[ListLocalLibrariesResponse] = for { libraryNames <- findLocalLibraries() libraryEntries = libraryNames.distinct.map { name => @@ -90,6 +101,9 @@ class LocalLibraryManager( } 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 diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManagerProtocol.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManagerProtocol.scala index cc652b61b7b2..9f3996509c49 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManagerProtocol.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManagerProtocol.scala @@ -3,23 +3,33 @@ 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], @@ -27,6 +37,7 @@ object LocalLibraryManagerProtocol { license: String ) extends Request + /** A request to publish a library. */ case class Publish( libraryName: LibraryName, authToken: String, diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/ProjectSettingsManager.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/ProjectSettingsManager.scala index 807cd220b0e7..baa045384015 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/ProjectSettingsManager.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/ProjectSettingsManager.scala @@ -7,6 +7,7 @@ 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 @@ -56,16 +57,22 @@ object ProjectSettingsManager { 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 } diff --git a/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala b/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala index 159eae3b9b72..5e4933420f14 100644 --- a/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala +++ b/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala @@ -209,6 +209,10 @@ object Runtime { new JsonSubTypes.Type( value = classOf[Api.LibraryLoaded], name = "libraryLoaded" + ), + new JsonSubTypes.Type( + value = classOf[Api.ProgressNotification], + name = "progressNotification" ) ) ) @@ -1353,12 +1357,18 @@ object Runtime { location: File ) 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, @@ -1366,12 +1376,14 @@ object Runtime { 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], diff --git a/engine/runtime/src/main/scala/org/enso/interpreter/instrument/ProgressNotificationTranslator.scala b/engine/runtime/src/main/scala/org/enso/interpreter/instrument/ProgressNotificationTranslator.scala index b700b84d1294..9de78f557449 100644 --- a/engine/runtime/src/main/scala/org/enso/interpreter/instrument/ProgressNotificationTranslator.scala +++ b/engine/runtime/src/main/scala/org/enso/interpreter/instrument/ProgressNotificationTranslator.scala @@ -8,7 +8,19 @@ 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 diff --git a/lib/scala/cli/src/main/scala/org/enso/cli/task/ProgressNotification.scala b/lib/scala/cli/src/main/scala/org/enso/cli/task/ProgressNotification.scala index 860f30af98bf..866fb5266441 100644 --- a/lib/scala/cli/src/main/scala/org/enso/cli/task/ProgressNotification.scala +++ b/lib/scala/cli/src/main/scala/org/enso/cli/task/ProgressNotification.scala @@ -6,21 +6,39 @@ import java.util.UUID sealed trait ProgressNotification object ProgressNotification { - /** Singals that a new task with progress has been started. */ + /** 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 - /** Singals an update to task's progress. */ + /** 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 - /** Singals that a task has been finished successfully. */ + /** Signals that a task has been finished successfully. + * + * @param taskId the task id + */ case class TaskSuccess(taskId: UUID) extends ProgressNotification - /** Singals that a task has failed. */ + /** 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 } diff --git a/lib/scala/cli/src/main/scala/org/enso/cli/task/ProgressNotificationForwarder.scala b/lib/scala/cli/src/main/scala/org/enso/cli/task/ProgressNotificationForwarder.scala index c1fc7c0a22b3..3f82f1ba3465 100644 --- a/lib/scala/cli/src/main/scala/org/enso/cli/task/ProgressNotificationForwarder.scala +++ b/lib/scala/cli/src/main/scala/org/enso/cli/task/ProgressNotificationForwarder.scala @@ -3,9 +3,15 @@ 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 diff --git a/lib/scala/cli/src/main/scala/org/enso/cli/task/ProgressUnit.scala b/lib/scala/cli/src/main/scala/org/enso/cli/task/ProgressUnit.scala index 09188a3eadda..c4c1263229ae 100644 --- a/lib/scala/cli/src/main/scala/org/enso/cli/task/ProgressUnit.scala +++ b/lib/scala/cli/src/main/scala/org/enso/cli/task/ProgressUnit.scala @@ -15,8 +15,12 @@ object 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 } diff --git a/lib/scala/distribution-manager/src/main/scala/org/enso/distribution/EditionManager.scala b/lib/scala/distribution-manager/src/main/scala/org/enso/distribution/EditionManager.scala index cd5432d69b66..95565cd7520d 100644 --- a/lib/scala/distribution-manager/src/main/scala/org/enso/distribution/EditionManager.scala +++ b/lib/scala/distribution-manager/src/main/scala/org/enso/distribution/EditionManager.scala @@ -46,11 +46,16 @@ class EditionManager(@unused primaryCachePath: Path, searchPaths: List[Path]) { // TODO [RW] download edition updates + /** 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] @@ -58,12 +63,19 @@ object EditionManager { getSearchPaths(distributionManager, languageHome) ) + /** Get search paths associated with the distribution and language home. */ private def getSearchPaths( distributionManager: DistributionManager, languageHome: Option[LanguageHome] - ): List[Path] = languageHome.map(_.editions).toList ++ - distributionManager.paths.editionSearchPaths + ): 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 diff --git a/lib/scala/editions/src/main/scala/org/enso/editions/provider/FileSystemEditionProvider.scala b/lib/scala/editions/src/main/scala/org/enso/editions/provider/FileSystemEditionProvider.scala index e80d50b15fb8..0a4d01288acf 100644 --- a/lib/scala/editions/src/main/scala/org/enso/editions/provider/FileSystemEditionProvider.scala +++ b/lib/scala/editions/src/main/scala/org/enso/editions/provider/FileSystemEditionProvider.scala @@ -63,6 +63,7 @@ class FileSystemEditionProvider(searchPaths: List[Path]) } else Left(EditionNotFound) } + /** Finds all editions available on the [[searchPaths]]. */ def findAvailableEditions(): Seq[String] = searchPaths.flatMap(findEditionsAt).distinct diff --git a/lib/scala/library-manager/src/main/scala/org/enso/librarymanager/local/LocalLibraryProvider.scala b/lib/scala/library-manager/src/main/scala/org/enso/librarymanager/local/LocalLibraryProvider.scala index 78f5c1bfff12..ba376c431161 100644 --- a/lib/scala/library-manager/src/main/scala/org/enso/librarymanager/local/LocalLibraryProvider.scala +++ b/lib/scala/library-manager/src/main/scala/org/enso/librarymanager/local/LocalLibraryProvider.scala @@ -15,6 +15,10 @@ trait LocalLibraryProvider { } 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 } From cc52c09559a1db95932dc79e6d755c275038138f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Thu, 15 Jul 2021 22:36:30 +0200 Subject: [PATCH 17/26] Update legal review --- tools/legal-review/engine/report-state | 2 +- tools/legal-review/project-manager/report-state | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/legal-review/engine/report-state b/tools/legal-review/engine/report-state index 833019d253a5..5ec1cd7af951 100644 --- a/tools/legal-review/engine/report-state +++ b/tools/legal-review/engine/report-state @@ -1,3 +1,3 @@ -E4A61C4649AD6FB148D4779D9B20B395AEAB73475EE13D20EFB55C163724A77B +C03EC922F039EB5CC96F93A489453ADDD4FA6EBBF75713B05FE67B48CFF6ACBF 64FE86A276F737CE2B4D352D8F35F790CA0DA18F329A48A467F34FC9C3CAF07D 0 diff --git a/tools/legal-review/project-manager/report-state b/tools/legal-review/project-manager/report-state index a868585c945c..93606a4a92fb 100644 --- a/tools/legal-review/project-manager/report-state +++ b/tools/legal-review/project-manager/report-state @@ -1,3 +1,3 @@ -0BA0D3694722E724BABC3D4D860FA5D11DA541B20632F18175E1F45BF44DE717 +0AED341E22D16C5722BF722FD5F92039464E9FE3CCD3288D3643F05E09AF62FB B7948AB0E996317E7F073260BA28A63D14215436079C09E793F803CF999D607C 0 From 51a5f572cd83b0799ea821fe9402905f978e182c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Thu, 15 Jul 2021 23:09:44 +0200 Subject: [PATCH 18/26] Add tests skeleton, library/create test --- build.sbt | 1 + .../libraries/LocalLibraryManager.scala | 5 +- .../websocket/json/BaseServerTest.scala | 9 ++ .../websocket/json/LibrariesTest.scala | 94 +++++++++++++++++++ .../test/FakeEnvironment.scala | 2 + 5 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/LibrariesTest.scala diff --git a/build.sbt b/build.sbt index 2ed2ebd357db..3dcbd7dd6f12 100644 --- a/build.sbt +++ b/build.sbt @@ -992,6 +992,7 @@ lazy val `language-server` = (project in file("engine/language-server")) ), Test / testOptions += Tests .Argument(TestFrameworks.ScalaCheck, "-minSuccessfulTests", "1000"), + Test / envVars ++= distributionEnvironmentOverrides, GenerateFlatbuffers.flatcVersion := flatbuffersVersion, Compile / sourceGenerators += GenerateFlatbuffers.task ) diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManager.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManager.scala index b3019ca79a10..cde1650435db 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManager.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LocalLibraryManager.scala @@ -57,7 +57,10 @@ class LocalLibraryManager( // TODO [RW] make the exceptions more relevant val possibleRoots = LazyList .from(distributionManager.paths.localLibrariesSearchPaths) - .filter(Files.isWritable) + .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." diff --git a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/BaseServerTest.scala b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/BaseServerTest.scala index e64076b5d441..314716282fb1 100644 --- a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/BaseServerTest.scala +++ b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/BaseServerTest.scala @@ -36,6 +36,7 @@ import org.enso.languageserver.runtime.{ContextRegistry, RuntimeFailureMapper} import org.enso.languageserver.search.SuggestionsHandler import org.enso.languageserver.session.SessionRouter import org.enso.languageserver.text.BufferRegistry +import org.enso.pkg.PackageManager import org.enso.polyglot.data.TypeGraph import org.enso.polyglot.runtime.Runtime.Api import org.enso.runtimeversionmanager.test.{FakeEnvironment, HasTestDirectory} @@ -272,6 +273,13 @@ class BaseServerTest ) } + lazy val initPackage: Unit = { + PackageManager.Default.create( + config.projectContentRoot.file, + name = "TestProject" + ) + } + def getInitialisedWsClient(): WsTestClient = { val client = new WsTestClient(address) initSession(client) @@ -279,6 +287,7 @@ class BaseServerTest } private def initSession(client: WsTestClient): UUID = { + initPackage val clientId = UUID.randomUUID() client.send(json""" { "jsonrpc": "2.0", diff --git a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/LibrariesTest.scala b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/LibrariesTest.scala new file mode 100644 index 000000000000..8be474f86603 --- /dev/null +++ b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/LibrariesTest.scala @@ -0,0 +1,94 @@ +package org.enso.languageserver.websocket.json + +import io.circe.literal._ + +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" + } + } + ] + } + } + """) + } + } + + "mocked library/preinstall" should { + "send progress notifications" in {} + } + + "editions/listAvailable" should { + "list editions on the search path" in {} + } + + "editions/listDefinedLibraries" should { + "include Standard.Base in the list" in {} + } + + "editions/resolve" should { + "resolve the engine version associated with an edition" in {} + } + + "ProjectSettingsManager" should { + "get default settings" in {} + + "allow to set local libraries preference" in {} + + "allow to override parent edition" in {} + + "fail if the provided parent edition is not resolvable" in {} + } +} diff --git a/lib/scala/runtime-version-manager-test/src/main/scala/org/enso/runtimeversionmanager/test/FakeEnvironment.scala b/lib/scala/runtime-version-manager-test/src/main/scala/org/enso/runtimeversionmanager/test/FakeEnvironment.scala index 17f62d342bfc..f410ca17ab89 100644 --- a/lib/scala/runtime-version-manager-test/src/main/scala/org/enso/runtimeversionmanager/test/FakeEnvironment.scala +++ b/lib/scala/runtime-version-manager-test/src/main/scala/org/enso/runtimeversionmanager/test/FakeEnvironment.scala @@ -44,11 +44,13 @@ trait FakeEnvironment { self: HasTestDirectory => val configDir = getTestDirectory.resolve("test_config") val binDir = getTestDirectory.resolve("test_bin") val runDir = getTestDirectory.resolve("test_run") + val homeDir = getTestDirectory.resolve("test_home") val env = extraOverrides .updated("ENSO_DATA_DIRECTORY", dataDir.toString) .updated("ENSO_CONFIG_DIRECTORY", configDir.toString) .updated("ENSO_BIN_DIRECTORY", binDir.toString) .updated("ENSO_RUNTIME_DIRECTORY", runDir.toString) + .updated("ENSO_HOME", homeDir.toString) val fakeEnvironment = new Environment { override def getPathToRunningExecutable: Path = executable From 020d697ec0319497f9c4c896ea16012eaa38ebb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Thu, 15 Jul 2021 23:15:02 +0200 Subject: [PATCH 19/26] fix compilation --- .../projectmanager/requesthandler/RequestHandler.scala | 5 +++-- .../service/versionmanagement/ControllerInterface.scala | 7 ++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/requesthandler/RequestHandler.scala b/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/requesthandler/RequestHandler.scala index c0edf9e1c262..18b1c4642501 100644 --- a/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/requesthandler/RequestHandler.scala +++ b/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/requesthandler/RequestHandler.scala @@ -4,7 +4,7 @@ import akka.actor.{Actor, ActorRef, Cancellable, Stash, Status} import akka.pattern.pipe import com.typesafe.scalalogging.{LazyLogging, Logger} import org.enso.cli.task.ProgressNotification -import org.enso.cli.task.notifications.ProgressNotification.translateProgressNotification +import org.enso.cli.task.notifications.ActorProgressNotificationForwarder import org.enso.jsonrpc.Errors.ServiceError import org.enso.jsonrpc._ import org.enso.projectmanager.control.effect.Exec @@ -110,7 +110,8 @@ abstract class RequestHandler[ abandonTimeout(id, replyTo, timeoutCancellable) case _ => } - replyTo ! translateProgressNotification(method.name, notification) + replyTo ! ActorProgressNotificationForwarder + .translateProgressNotification(method.name, notification) } /** Cancels the timeout operation. diff --git a/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/service/versionmanagement/ControllerInterface.scala b/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/service/versionmanagement/ControllerInterface.scala index 255dfb805904..808c4c27d97f 100644 --- a/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/service/versionmanagement/ControllerInterface.scala +++ b/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/service/versionmanagement/ControllerInterface.scala @@ -3,12 +3,10 @@ package org.enso.projectmanager.service.versionmanagement import akka.actor.ActorRef import com.typesafe.scalalogging.Logger import nl.gn0s1s.bump.SemVer -import org.enso.cli.task.notifications.SerializableProgressUnit import org.enso.cli.task.{ - ProgressListener, ProgressNotification, ProgressNotificationForwarder, - TaskProgress + ProgressUnit } import org.enso.distribution.locking.Resource import org.enso.runtimeversionmanager.components.{ @@ -17,7 +15,6 @@ import org.enso.runtimeversionmanager.components.{ } import java.util.UUID -import scala.util.{Failure, Success, Try} /** A [[RuntimeVersionManagementUserInterface]] that sends * [[ProgressNotification]] to the specified actors (both for usual tasks and @@ -60,7 +57,7 @@ class ControllerInterface( progressTracker ! ProgressNotification.TaskStarted( uuid, None, - SerializableProgressUnit.Other + ProgressUnit.Unspecified ) progressTracker ! ProgressNotification.TaskUpdate( uuid, From 24208c9fefe94519767b6e048048895536433117 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Thu, 15 Jul 2021 23:38:21 +0200 Subject: [PATCH 20/26] Test edition resolution --- .../websocket/json/BaseServerTest.scala | 15 +++ .../websocket/json/LibrariesTest.scala | 93 ++++++++++++++++++- .../jsonrpc/test/JsonRpcServerTestKit.scala | 5 + 3 files changed, 111 insertions(+), 2 deletions(-) diff --git a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/BaseServerTest.scala b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/BaseServerTest.scala index 314716282fb1..e42b25aca6bc 100644 --- a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/BaseServerTest.scala +++ b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/BaseServerTest.scala @@ -221,6 +221,21 @@ class BaseServerTest 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) diff --git a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/LibrariesTest.scala b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/LibrariesTest.scala index 8be474f86603..79a30b857b43 100644 --- a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/LibrariesTest.scala +++ b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/LibrariesTest.scala @@ -1,6 +1,9 @@ package org.enso.languageserver.websocket.json +import io.circe.Json import io.circe.literal._ +import org.enso.languageserver.libraries.LibraryEntry +import org.enso.languageserver.libraries.LibraryEntry.PublishedLibraryVersion class LibrariesTest extends BaseServerTest { "LocalLibraryManager" should { @@ -75,11 +78,97 @@ class LibrariesTest extends BaseServerTest { } "editions/listDefinedLibraries" should { - "include Standard.Base in the list" in {} + "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 {} + "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": 0, + "params": { + "edition": { + "type": "NamedEdition", + "editionName": ${buildinfo.Info.currentEdition} + } + } + } + """) + client.expectJson(json""" + { "jsonrpc": "2.0", + "id": 0, + "result": { + "engineVersion": $currentVersion + } + } + """) + } } "ProjectSettingsManager" should { diff --git a/lib/scala/json-rpc-server-test/src/main/scala/org/enso/jsonrpc/test/JsonRpcServerTestKit.scala b/lib/scala/json-rpc-server-test/src/main/scala/org/enso/jsonrpc/test/JsonRpcServerTestKit.scala index 5114d5c375ae..f2d62e2e760b 100644 --- a/lib/scala/json-rpc-server-test/src/main/scala/org/enso/jsonrpc/test/JsonRpcServerTestKit.scala +++ b/lib/scala/json-rpc-server-test/src/main/scala/org/enso/jsonrpc/test/JsonRpcServerTestKit.scala @@ -118,6 +118,11 @@ abstract class JsonRpcServerTestKit 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() } } From b4723938acbb0a8a124923ed66f8e4ec2c1e6c6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Thu, 15 Jul 2021 23:44:20 +0200 Subject: [PATCH 21/26] Test listAvailable --- .../EditionsListAvailableHandler.scala | 2 +- .../websocket/json/LibrariesTest.scala | 38 ++++++++++++++++--- .../provider/FileSystemEditionProvider.scala | 9 +++-- 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsListAvailableHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsListAvailableHandler.scala index 8a46de9273a7..2eba8cb7f698 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsListAvailableHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsListAvailableHandler.scala @@ -27,7 +27,7 @@ class EditionsListAvailableHandler(editionManager: EditionManager) case Failure(exception) => sender() ! ResponseError( Some(id), - FileSystemError(exception.getMessage) + FileSystemError(exception.toString) ) } } diff --git a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/LibrariesTest.scala b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/LibrariesTest.scala index 79a30b857b43..872779693259 100644 --- a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/LibrariesTest.scala +++ b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/LibrariesTest.scala @@ -70,11 +70,37 @@ class LibrariesTest extends BaseServerTest { } "mocked library/preinstall" should { - "send progress notifications" in {} + "send progress notifications" ignore {} } "editions/listAvailable" should { - "list editions on the search path" in {} + "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] do we need to support that? + "work if the update field is missing" ignore {} } "editions/listDefinedLibraries" should { @@ -172,12 +198,12 @@ class LibrariesTest extends BaseServerTest { } "ProjectSettingsManager" should { - "get default settings" in {} + "get default settings" ignore {} - "allow to set local libraries preference" in {} + "allow to set local libraries preference" ignore {} - "allow to override parent edition" in {} + "allow to override parent edition" ignore {} - "fail if the provided parent edition is not resolvable" in {} + "fail if the provided parent edition is not resolvable" ignore {} } } diff --git a/lib/scala/editions/src/main/scala/org/enso/editions/provider/FileSystemEditionProvider.scala b/lib/scala/editions/src/main/scala/org/enso/editions/provider/FileSystemEditionProvider.scala index 0a4d01288acf..bfd96b8a1418 100644 --- a/lib/scala/editions/src/main/scala/org/enso/editions/provider/FileSystemEditionProvider.scala +++ b/lib/scala/editions/src/main/scala/org/enso/editions/provider/FileSystemEditionProvider.scala @@ -75,7 +75,10 @@ class FileSystemEditionProvider(searchPaths: List[Path]) } private def findEditionsAt(path: Path): Seq[String] = - Using(Files.list(path))(_.toScala(Factory.arrayFactory).toSeq).get - .filter(Files.isRegularFile(_)) - .flatMap(findEditionName) + 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() } From b85c33731fbd192724561c1c63f14b620bd15f31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Thu, 15 Jul 2021 23:49:19 +0200 Subject: [PATCH 22/26] fix a test --- .../language-server/src/test/resources/package.yaml | 1 - .../websocket/json/BaseServerTest.scala | 13 +++++++++---- .../websocket/json/WorkspaceOperationsTest.scala | 2 ++ 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/engine/language-server/src/test/resources/package.yaml b/engine/language-server/src/test/resources/package.yaml index 2923e3659b2e..a8ecba384183 100644 --- a/engine/language-server/src/test/resources/package.yaml +++ b/engine/language-server/src/test/resources/package.yaml @@ -1,6 +1,5 @@ license: APLv2 name: Standard -enso-version: default version: "0.1.0" author: "Enso Team " maintainer: "Enso Team " diff --git a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/BaseServerTest.scala b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/BaseServerTest.scala index e42b25aca6bc..c5a138b2bc34 100644 --- a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/BaseServerTest.scala +++ b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/BaseServerTest.scala @@ -288,11 +288,16 @@ class BaseServerTest ) } + /** Specifies if the `package.yaml` at project root should be auto-created. */ + protected def initializeProjectPackage: Boolean = true + lazy val initPackage: Unit = { - PackageManager.Default.create( - config.projectContentRoot.file, - name = "TestProject" - ) + if (initializeProjectPackage) { + PackageManager.Default.create( + config.projectContentRoot.file, + name = "TestProject" + ) + } } def getInitialisedWsClient(): WsTestClient = { diff --git a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/WorkspaceOperationsTest.scala b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/WorkspaceOperationsTest.scala index 3ad3affe3eef..4a8d84a316c9 100644 --- a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/WorkspaceOperationsTest.scala +++ b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/WorkspaceOperationsTest.scala @@ -9,6 +9,8 @@ import java.io.{File, FileOutputStream} class WorkspaceOperationsTest extends BaseServerTest with FlakySpec { + override def initializeProjectPackage: Boolean = false + "workspace/projectInfo" must { val packageConfigName = Config.ensoPackageConfigName val testYamlPath = new File(testContentRoot.file, packageConfigName) From 339cd4a387eb0c238cb83b3f2760c0245ccf6276 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Fri, 16 Jul 2021 11:04:12 +0200 Subject: [PATCH 23/26] more docs --- .../EditionsGetProjectSettingsHandler.scala | 13 +++++++++++++ .../handler/EditionsListAvailableHandler.scala | 12 ++++++++++++ .../EditionsListDefinedLibrariesHandler.scala | 10 ++++++++++ .../libraries/handler/EditionsResolveHandler.scala | 9 +++++++++ .../handler/EditionsSetParentEditionHandler.scala | 13 +++++++++++++ ...SetProjectLocalLibrariesPreferenceHandler.scala | 14 ++++++++++++++ .../libraries/handler/LibraryCreateHandler.scala | 11 +++++++++++ .../handler/LibraryGetMetadataHandler.scala | 6 ++++++ .../handler/LibraryListLocalHandler.scala | 11 +++++++++++ .../handler/LibraryPreinstallHandler.scala | 6 ++++++ .../libraries/handler/LibraryPublishHandler.scala | 6 ++++++ .../handler/LibrarySetMetadataHandler.scala | 6 ++++++ 12 files changed, 117 insertions(+) diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsGetProjectSettingsHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsGetProjectSettingsHandler.scala index 5853a2cda3f1..706420d2cf49 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsGetProjectSettingsHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsGetProjectSettingsHandler.scala @@ -12,6 +12,11 @@ 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 @@ -61,6 +66,14 @@ class EditionsGetProjectSettingsHandler( } 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) diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsListAvailableHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsListAvailableHandler.scala index 2eba8cb7f698..847e9fce5050 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsListAvailableHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsListAvailableHandler.scala @@ -10,6 +10,13 @@ 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 @@ -34,6 +41,11 @@ class EditionsListAvailableHandler(editionManager: EditionManager) } object EditionsListAvailableHandler { + + /** Creates a configuration object to create [[EditionsListAvailableHandler]]. + * + * @param editionManager an edition manager instance + */ def props(editionManager: EditionManager): Props = Props( new EditionsListAvailableHandler(editionManager) ) diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsListDefinedLibrariesHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsListDefinedLibrariesHandler.scala index 0916d4e968f4..bb6d7e925f55 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsListDefinedLibrariesHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsListDefinedLibrariesHandler.scala @@ -13,6 +13,10 @@ 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 @@ -53,6 +57,12 @@ class EditionsListDefinedLibrariesHandler( } object EditionsListDefinedLibrariesHandler { + + /** Creates a configuration object to create + * [[EditionsListDefinedLibrariesHandler]]. + * + * @param editionReferenceResolver an [[EditionReferenceResolver]] instance + */ def props(editionReferenceResolver: EditionReferenceResolver): Props = Props( new EditionsListDefinedLibrariesHandler(editionReferenceResolver) ) diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsResolveHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsResolveHandler.scala index cc50349034fe..af8dda4b3c2e 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsResolveHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsResolveHandler.scala @@ -10,6 +10,10 @@ 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 @@ -39,6 +43,11 @@ class EditionsResolveHandler(editionReferenceResolver: EditionReferenceResolver) } object EditionsResolveHandler { + + /** Creates a configuration object to create [[EditionsResolveHandler]]. + * + * @param editionReferenceResolver an [[EditionReferenceResolver]] instance + */ def props(editionReferenceResolver: EditionReferenceResolver): Props = Props( new EditionsResolveHandler(editionReferenceResolver) ) diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsSetParentEditionHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsSetParentEditionHandler.scala index 7f82d263437c..6d0b8c5fbe63 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsSetParentEditionHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsSetParentEditionHandler.scala @@ -12,6 +12,11 @@ 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 @@ -68,6 +73,14 @@ class EditionsSetParentEditionHandler( } 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) diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsSetProjectLocalLibrariesPreferenceHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsSetProjectLocalLibrariesPreferenceHandler.scala index f93fdf40ed1b..e58b90e5c269 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsSetProjectLocalLibrariesPreferenceHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/EditionsSetProjectLocalLibrariesPreferenceHandler.scala @@ -12,6 +12,12 @@ 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 @@ -68,6 +74,14 @@ class EditionsSetProjectLocalLibrariesPreferenceHandler( } 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 diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryCreateHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryCreateHandler.scala index c0f59589698b..ebebc5406c8b 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryCreateHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryCreateHandler.scala @@ -13,6 +13,11 @@ 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 @@ -63,6 +68,12 @@ class LibraryCreateHandler( } 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) diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryGetMetadataHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryGetMetadataHandler.scala index 3fd78cf7dab8..bc88a9906a12 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryGetMetadataHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryGetMetadataHandler.scala @@ -6,6 +6,10 @@ 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 @@ -22,5 +26,7 @@ class LibraryGetMetadataHandler } object LibraryGetMetadataHandler { + + /** Creates a configuration object to create [[LibraryGetMetadataHandler]]. */ def props(): Props = Props(new LibraryGetMetadataHandler) } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryListLocalHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryListLocalHandler.scala index 554cb6b8fb27..c14eebb022cd 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryListLocalHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryListLocalHandler.scala @@ -12,6 +12,11 @@ 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 @@ -56,6 +61,12 @@ class LibraryListLocalHandler( } } 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) diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryPreinstallHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryPreinstallHandler.scala index 6967d31d0c88..f4c86c688f94 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryPreinstallHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryPreinstallHandler.scala @@ -9,6 +9,10 @@ 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 @@ -37,5 +41,7 @@ class LibraryPreinstallHandler } object LibraryPreinstallHandler { + + /** Creates a configuration object to create [[LibraryPreinstallHandler]]. */ def props(): Props = Props(new LibraryPreinstallHandler) } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryPublishHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryPublishHandler.scala index 82a6cd3c7d2a..0b0095b454e8 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryPublishHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryPublishHandler.scala @@ -7,6 +7,10 @@ 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 @@ -22,5 +26,7 @@ class LibraryPublishHandler } object LibraryPublishHandler { + + /** Creates a configuration object to create [[LibraryPublishHandler]]. */ def props(): Props = Props(new LibraryPublishHandler) } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibrarySetMetadataHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibrarySetMetadataHandler.scala index fb8982322b7e..77a237c33f53 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibrarySetMetadataHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibrarySetMetadataHandler.scala @@ -7,6 +7,10 @@ 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 @@ -22,5 +26,7 @@ class LibrarySetMetadataHandler } object LibrarySetMetadataHandler { + + /** Creates a configuration object to create [[LibrarySetMetadataHandler]]. */ def props(): Props = Props(new LibrarySetMetadataHandler) } From 2e28964b94349fb1f4158f0ebb662782c7ebed55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Fri, 16 Jul 2021 13:57:36 +0200 Subject: [PATCH 24/26] test and fix progress --- .../protocol-language-server.md | 4 +- .../libraries/FakeDownload.scala | 6 +- .../languageserver/libraries/LibraryApi.scala | 2 +- .../handler/LibraryPreinstallHandler.scala | 27 ++++--- .../websocket/json/LibrariesTest.scala | 77 +++++++++++++++++-- .../notifications/TaskNotificationApi.scala | 2 +- 6 files changed, 95 insertions(+), 23 deletions(-) diff --git a/docs/language-server/protocol-language-server.md b/docs/language-server/protocol-language-server.md index 8b4b708710af..48cb2a429d1f 100644 --- a/docs/language-server/protocol-language-server.md +++ b/docs/language-server/protocol-language-server.md @@ -4014,13 +4014,11 @@ the repositories and include them in the result as well. > available. In the future it should emit warnings using proper notification > channels. -The `update` field is optional and if it is not provided, it defaults to false. - #### Parameters ```typescript { - update?: Boolean; + update: Boolean; } ``` diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/FakeDownload.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/FakeDownload.scala index 12b30a3c6ddc..49a783845dec 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/FakeDownload.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/FakeDownload.scala @@ -25,6 +25,7 @@ object FakeDownload { val n = (seconds * 10).toLong for (i <- 0L to n) { tracker.reportProgress(i, Some(n)) + Thread.sleep(100) } tracker.setComplete(Success(())) }) @@ -37,9 +38,10 @@ object FakeDownload { */ def simulateDownload( message: String, - progressReporter: ProgressReporter + progressReporter: ProgressReporter, + seconds: Int = 10 ): Unit = { - val download = make() + val download = make(seconds = seconds) progressReporter.trackProgress(message, download) download.force() } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LibraryApi.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LibraryApi.scala index 6841796daa23..e6c04dc883eb 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LibraryApi.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/LibraryApi.scala @@ -9,7 +9,7 @@ object LibraryApi { case object EditionsListAvailable extends Method("editions/listAvailable") { self => - case class Params(update: Option[Boolean]) + case class Params(update: Boolean) case class Result(editionNames: Seq[String]) diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryPreinstallHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryPreinstallHandler.scala index f4c86c688f94..0dd2b4c8daeb 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryPreinstallHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/handler/LibraryPreinstallHandler.scala @@ -18,21 +18,30 @@ class LibraryPreinstallHandler with LazyLogging with UnhandledLogging { override def receive: Receive = { - case Request(LibraryPreinstall, id, _: LibraryPreinstall.Params) => + case Request(LibraryPreinstall, id, LibraryPreinstall.Params(_, name)) => // TODO [RW] actual implementation val progressReporter = ActorProgressNotificationForwarder.translateAndForward( LibraryPreinstall.name, sender() ) - FakeDownload.simulateDownload( - "Downloading something...", - progressReporter - ) - FakeDownload.simulateDownload( - "Downloading something else...", - progressReporter - ) + + 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") diff --git a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/LibrariesTest.scala b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/LibrariesTest.scala index 872779693259..358de1878b98 100644 --- a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/LibrariesTest.scala +++ b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/LibrariesTest.scala @@ -1,7 +1,7 @@ package org.enso.languageserver.websocket.json -import io.circe.Json import io.circe.literal._ +import io.circe.{Json, JsonObject} import org.enso.languageserver.libraries.LibraryEntry import org.enso.languageserver.libraries.LibraryEntry.PublishedLibraryVersion @@ -67,10 +67,57 @@ class LibrariesTest extends BaseServerTest { } """) } + + "fail with LibraryAlreadyExists when creating a library that already " + + "existed" ignore { + // TODO [RW] error handling (#1877) + } } "mocked library/preinstall" should { - "send progress notifications" ignore {} + "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 { @@ -97,10 +144,9 @@ class LibrariesTest extends BaseServerTest { """) } - "update the list of editions if requested" ignore {} - - // TODO [RW] do we need to support that? - "work if the update field is missing" ignore {} + "update the list of editions if requested" ignore { + // TODO [RW] updating editions + } } "editions/listDefinedLibraries" should { @@ -198,7 +244,24 @@ class LibrariesTest extends BaseServerTest { } "ProjectSettingsManager" should { - "get default settings" ignore {} + "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" ignore {} diff --git a/lib/scala/task-progress-notifications/src/main/scala/org/enso/cli/task/notifications/TaskNotificationApi.scala b/lib/scala/task-progress-notifications/src/main/scala/org/enso/cli/task/notifications/TaskNotificationApi.scala index cae29ff25a5c..41377ba933a5 100644 --- a/lib/scala/task-progress-notifications/src/main/scala/org/enso/cli/task/notifications/TaskNotificationApi.scala +++ b/lib/scala/task-progress-notifications/src/main/scala/org/enso/cli/task/notifications/TaskNotificationApi.scala @@ -33,7 +33,7 @@ object TaskNotificationApi { } } - case object TaskFinished extends Method("task/progress-update") { + case object TaskFinished extends Method("task/finished") { case class Params( taskId: UUID, From bae66b7a5ea37a14b3da5437ae2c985ac18719ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Fri, 16 Jul 2021 15:13:45 +0200 Subject: [PATCH 25/26] Fix a pkg serialization bug --- .../libraries/ProjectSettingsManager.scala | 6 +- .../websocket/json/BaseServerTest.scala | 4 +- .../websocket/json/LibrariesTest.scala | 31 +----- .../json/ProjectSettingsManagerTest.scala | 105 ++++++++++++++++++ .../src/main/scala/org/enso/pkg/Config.scala | 21 +++- .../src/main/scala/org/enso/pkg/Package.scala | 21 ++-- .../test/scala/org/enso/pkg/ConfigSpec.scala | 5 +- 7 files changed, 145 insertions(+), 48 deletions(-) create mode 100644 engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/ProjectSettingsManagerTest.scala diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/ProjectSettingsManager.scala b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/ProjectSettingsManager.scala index baa045384015..d950eb7cd68e 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/libraries/ProjectSettingsManager.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/libraries/ProjectSettingsManager.scala @@ -40,7 +40,8 @@ class ProjectSettingsManager( updated = pkg.updateConfig { config => config.copy(edition = Some(newEdition)) } - } yield updated.save() + _ <- updated.save() + } yield () private def setPreferLocalLibraries( preferLocalLibraries: Boolean @@ -49,7 +50,8 @@ class ProjectSettingsManager( updated = pkg.updateConfig { config => config.copy(preferLocalLibraries = preferLocalLibraries) } - } yield updated.save() + _ <- updated.save() + } yield () } object ProjectSettingsManager { diff --git a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/BaseServerTest.scala b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/BaseServerTest.scala index c5a138b2bc34..579100ed8ade 100644 --- a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/BaseServerTest.scala +++ b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/BaseServerTest.scala @@ -300,8 +300,8 @@ class BaseServerTest } } - def getInitialisedWsClient(): WsTestClient = { - val client = new WsTestClient(address) + def getInitialisedWsClient(debug: Boolean = false): WsTestClient = { + val client = new WsTestClient(address, debugMessages = debug) initSession(client) client } diff --git a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/LibrariesTest.scala b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/LibrariesTest.scala index 358de1878b98..1ad058252bda 100644 --- a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/LibrariesTest.scala +++ b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/LibrariesTest.scala @@ -223,7 +223,7 @@ class LibrariesTest extends BaseServerTest { client.send(json""" { "jsonrpc": "2.0", "method": "editions/resolve", - "id": 0, + "id": 1, "params": { "edition": { "type": "NamedEdition", @@ -234,7 +234,7 @@ class LibrariesTest extends BaseServerTest { """) client.expectJson(json""" { "jsonrpc": "2.0", - "id": 0, + "id": 1, "result": { "engineVersion": $currentVersion } @@ -242,31 +242,4 @@ class LibrariesTest extends BaseServerTest { """) } } - - "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" ignore {} - - "allow to override parent edition" ignore {} - - "fail if the provided parent edition is not resolvable" ignore {} - } } diff --git a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/ProjectSettingsManagerTest.scala b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/ProjectSettingsManagerTest.scala new file mode 100644 index 000000000000..61ffcbbcee2f --- /dev/null +++ b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/ProjectSettingsManagerTest.scala @@ -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 {} + } +} diff --git a/lib/scala/pkg/src/main/scala/org/enso/pkg/Config.scala b/lib/scala/pkg/src/main/scala/org/enso/pkg/Config.scala index f0d82673dcf8..120ae5152c69 100644 --- a/lib/scala/pkg/src/main/scala/org/enso/pkg/Config.scala +++ b/lib/scala/pkg/src/main/scala/org/enso/pkg/Config.scala @@ -205,7 +205,26 @@ object Config { val overridesObject = JsonObject( 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. */ diff --git a/lib/scala/pkg/src/main/scala/org/enso/pkg/Package.scala b/lib/scala/pkg/src/main/scala/org/enso/pkg/Package.scala index 8183ea28c729..8b4f2a5e2b71 100644 --- a/lib/scala/pkg/src/main/scala/org/enso/pkg/Package.scala +++ b/lib/scala/pkg/src/main/scala/org/enso/pkg/Package.scala @@ -48,11 +48,13 @@ case class Package[F]( /** Stores the package metadata on the hard drive. If the package does not exist, * creates the required directory structure. */ - def save(): Unit = { - if (!root.exists) createDirectories() - if (!sourceDir.exists) createSourceDir() - saveConfig() - } + def save(): Try[Unit] = for { + _ <- Try { + if (!root.exists) createDirectories() + if (!sourceDir.exists) createSourceDir() + } + _ <- saveConfig() + } yield () /** Creates the package directory structure. */ @@ -97,11 +99,10 @@ case class Package[F]( /** Saves the config metadata into the package configuration file. */ - def saveConfig(): Unit = { - val writer = configFile.newBufferedWriter - Try(writer.write(config.toYaml)) - writer.close() - } + def saveConfig(): Try[Unit] = + Using(configFile.newBufferedWriter) { writer => + writer.write(config.toYaml) + } /** Gets the location of the package's Main file. * diff --git a/lib/scala/pkg/src/test/scala/org/enso/pkg/ConfigSpec.scala b/lib/scala/pkg/src/test/scala/org/enso/pkg/ConfigSpec.scala index 1541aeb9e0b7..94f7658fcddf 100644 --- a/lib/scala/pkg/src/test/scala/org/enso/pkg/ConfigSpec.scala +++ b/lib/scala/pkg/src/test/scala/org/enso/pkg/ConfigSpec.scala @@ -2,7 +2,6 @@ package org.enso.pkg import io.circe.{Json, JsonObject} import nl.gn0s1s.bump.SemVer -import org.enso.editions.SemVerEnsoVersion import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpec import org.scalatest.{Inside, OptionValues} @@ -64,9 +63,7 @@ class ConfigSpec |""".stripMargin val parsed = Config.fromYaml(oldFormat).get - parsed.edition.get.engineVersion should contain( - SemVerEnsoVersion(SemVer(1, 2, 3)) - ) + parsed.edition.get.engineVersion should contain(SemVer(1, 2, 3)) val serialized = parsed.toYaml val parsedAgain = Config.fromYaml(serialized).get From 5cc554a44dedc3bbe654bb82bf1337f4ca8974a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Fri, 16 Jul 2021 17:03:47 +0200 Subject: [PATCH 26/26] CR --- RELEASES.md | 3 ++- build.sbt | 1 + .../src/main/scala/org/enso/distribution/EditionManager.scala | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 8435a34f237e..b26d9dd262f8 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -4,7 +4,8 @@ - Implement parts of the new Language Server API related to library support ([#1875](https://github.com/enso-org/enso/pull/1875)). Parts of the API are - still mocked, but they are supported too for testing purposes. + still mocked internally, but they are supported externally for testing + purposes. # Enso 0.2.14 (2021-07-15) diff --git a/build.sbt b/build.sbt index 3dcbd7dd6f12..c859d99ada58 100644 --- a/build.sbt +++ b/build.sbt @@ -233,6 +233,7 @@ lazy val enso = (project in file(".")) logger.jvm, pkg, cli, + `task-progress-notifications`, `logging-utils`, `logging-service`, `akka-native`, diff --git a/lib/scala/distribution-manager/src/main/scala/org/enso/distribution/EditionManager.scala b/lib/scala/distribution-manager/src/main/scala/org/enso/distribution/EditionManager.scala index 95565cd7520d..2eb9d7605b6d 100644 --- a/lib/scala/distribution-manager/src/main/scala/org/enso/distribution/EditionManager.scala +++ b/lib/scala/distribution-manager/src/main/scala/org/enso/distribution/EditionManager.scala @@ -44,7 +44,7 @@ class EditionManager(@unused primaryCachePath: Path, searchPaths: List[Path]) { def resolveEngineVersion(edition: Editions.RawEdition): Try[SemVer] = engineVersionResolver.resolveEnsoVersion(edition).toTry - // TODO [RW] download edition updates + // TODO [RW] download edition updates, part of #1772 /** Find all editions available in the [[searchPaths]]. */ def findAllAvailableEditions(): Seq[String] =