From 5dc2c4c5fd4a2da97434ea83983361d3f8ef493e Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Tue, 22 Aug 2023 12:32:46 +0100 Subject: [PATCH] Notification about the project rename action (#7613) close #7604 After moving the rename action to the dashboard, IDE is unaware of the new project name. PR implements a new `refactoring/projectRenamed` notification that is sent from the server to clients and informs them about the changed project name. # Important Notes https://github.com/enso-org/enso/assets/357683/7c62726d-217e-4e69-8e48-568e0b7b8c34 --- CHANGELOG.md | 2 + .../src/language_server/types.rs | 23 +++++ app/gui/src/model/project.rs | 6 +- app/gui/src/model/project/synchronized.rs | 53 +++++----- app/gui/src/presenter/project.rs | 8 ++ .../src/component/node/input/widget.rs | 2 - .../protocol-language-server.md | 36 +++++++ .../json/JsonConnectionController.scala | 26 +++++ .../protocol/json/JsonRpc.scala | 1 + .../refactoring/RefactoringApi.scala | 17 ++++ .../refactoring/RefactoringProtocol.scala | 16 +++ .../refactoring/RenameProjectHandler.scala | 27 ++++- .../runtime/VisualizationConfiguration.scala | 5 +- .../websocket/json/LibrariesTest.scala | 2 +- .../websocket/json/RefactoringTest.scala | 99 +++++++++++++++++++ .../org/enso/polyglot/runtime/Runtime.scala | 22 ++++- .../instrument/command/RenameProjectCmd.scala | 63 ++++++++---- .../test/instrument/RuntimeServerTest.scala | 4 +- .../RuntimeSuggestionUpdatesTest.scala | 2 +- .../org/enso/compiler/ImportExportCache.java | 2 +- .../java/org/enso/compiler/ModuleCache.java | 2 +- .../org/enso/compiler/SuggestionsCache.java | 3 +- .../scala/org/enso/compiler/Compiler.scala | 10 +- .../enso/compiler/pass/desugar/Imports.scala | 2 +- .../runtime/DefaultPackageRepository.scala | 2 +- .../repository/LibraryDownloadTest.scala | 2 +- .../libraryupload/LibraryUploadTest.scala | 4 +- .../src/main/scala/org/enso/pkg/Package.scala | 13 ++- .../repository/ProjectFileRepository.scala | 2 +- .../runner/Project.scala | 2 +- 30 files changed, 374 insertions(+), 84 deletions(-) create mode 100644 engine/language-server/src/main/scala/org/enso/languageserver/refactoring/RefactoringProtocol.scala diff --git a/CHANGELOG.md b/CHANGELOG.md index a7a47dc29e1..87311e26e32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -919,6 +919,7 @@ - [Support renaming variable or function][7515] - [Only use types as State keys][7585] - [Allow Java Enums in case of branches][7607] +- [Notification about the project rename action][7613] [3227]: https://github.com/enso-org/enso/pull/3227 [3248]: https://github.com/enso-org/enso/pull/3248 @@ -1052,6 +1053,7 @@ [7515]: https://github.com/enso-org/enso/pull/7515 [7585]: https://github.com/enso-org/enso/pull/7585 [7607]: https://github.com/enso-org/enso/pull/7607 +[7613]: https://github.com/enso-org/enso/pull/7613 # Enso 2.0.0-alpha.18 (2021-10-12) diff --git a/app/gui/controller/engine-protocol/src/language_server/types.rs b/app/gui/controller/engine-protocol/src/language_server/types.rs index 1a4b872e0ae..4710ba9ebfa 100644 --- a/app/gui/controller/engine-protocol/src/language_server/types.rs +++ b/app/gui/controller/engine-protocol/src/language_server/types.rs @@ -174,6 +174,10 @@ pub enum Notification { /// visualization. #[serde(rename = "executionContext/visualizationEvaluationFailed")] VisualizationEvaluationFailed(VisualizationEvaluationFailed), + + /// Sent from server to the client to inform that the project has been renamed. + #[serde(rename = "refactoring/projectRenamed")] + ProjectRenamed(ProjectRenamed), } /// Sent from the server to the client to inform about a failure during execution of an execution @@ -382,6 +386,25 @@ pub struct TextFileModifiedOnDisk { +// ================================= +// === Refactoring Notifications === +// ================================= + +/// The `refactoring/projectRenamed` notification parameters. +#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ProjectRenamed { + /// The old normalized project name. + pub old_normalized_name: String, + /// The new normalized project name. + pub new_normalized_name: String, + /// The new display project name. + pub new_name: String, +} + + + // ====================== // === FileAttributes === // ====================== diff --git a/app/gui/src/model/project.rs b/app/gui/src/model/project.rs index 17859aefd9a..efa691e6673 100644 --- a/app/gui/src/model/project.rs +++ b/app/gui/src/model/project.rs @@ -88,10 +88,6 @@ pub trait API: Debug { context_id: model::execution_context::Id, ) -> BoxFuture<'a, FallibleResult>; - /// Set a new project name. - #[allow(clippy::needless_lifetimes)] // Note: Needless lifetimes - fn rename_project<'a>(&'a self, name: String) -> BoxFuture<'a, FallibleResult<()>>; - /// Returns the primary content root id for this project. fn project_content_root_id(&self) -> Uuid { self.json_rpc().project_root().id() @@ -184,6 +180,8 @@ pub enum Notification { ExecutionComplete, /// Indicates failure of the project execution. ExecutionFailed, + /// Project has been renamed. + Renamed, } /// Denotes one of backend connections used by a project. diff --git a/app/gui/src/model/project/synchronized.rs b/app/gui/src/model/project/synchronized.rs index 916dee58a74..120a2acd03b 100644 --- a/app/gui/src/model/project/synchronized.rs +++ b/app/gui/src/model/project/synchronized.rs @@ -225,6 +225,22 @@ async fn reload_module_on_file_change( Ok(()) } +/// Update internal state when the `refactoring/projectRenamed` notification received. +#[profile(Detail)] +fn rename_project_on_notification( + project_renamed: language_server::types::ProjectRenamed, + properties: Rc>, + execution_contexts: Rc, +) { + { + let mut properties = properties.borrow_mut(); + properties.displayed_name = project_renamed.new_name.into(); + properties.project_name.project = project_renamed.new_normalized_name.clone().into(); + } + execution_contexts + .rename_project(project_renamed.old_normalized_name, project_renamed.new_normalized_name); +} + // ============= @@ -522,10 +538,13 @@ impl Project { let publisher = self.notifications.clone_ref(); let project_root_id = self.project_content_root_id(); let language_server = self.json_rpc().clone_ref(); + let properties = self.properties.clone_ref(); + let execution_contexts = self.execution_contexts.clone_ref(); let weak_suggestion_db = Rc::downgrade(&self.suggestion_db); let weak_content_roots = Rc::downgrade(&self.content_roots); let weak_module_registry = Rc::downgrade(&self.module_registry); let execution_update_handler = self.execution_update_handler(); + move |event| { debug!("Received an event from the json-rpc protocol: {event:?}"); use engine_protocol::language_server::Event; @@ -618,6 +637,13 @@ impl Project { update.message ); } + Event::Notification(Notification::ProjectRenamed(project_renamed)) => { + let properties = properties.clone_ref(); + let execution_contexts = execution_contexts.clone_ref(); + rename_project_on_notification(project_renamed, properties, execution_contexts); + let notification = model::project::Notification::Renamed; + publisher.notify(notification); + } Event::Closed => { error!("Lost JSON-RPC connection with the Language Server!"); let which = model::project::BackendConnection::LanguageServerJson; @@ -750,33 +776,6 @@ impl model::project::API for Project { .boxed_local() } - fn rename_project(&self, name: String) -> BoxFuture { - if self.read_only() { - std::future::ready(Err(RenameInReadOnly.into())).boxed_local() - } else { - async move { - let old_name = self.properties.borrow_mut().project_name.project.clone_ref(); - let referent_name = name.to_im_string(); - let project_manager = - self.project_manager.as_ref().ok_or(ProjectManagerUnavailable)?; - let project_id = self.properties.borrow().id; - let project_name = ProjectName::new_unchecked(name); - project_manager.rename_project(&project_id, &project_name).await.map_err( - |error| match error { - RpcError::RemoteError(cause) - if cause.code == code::PROJECT_NAME_INVALID => - failure::Error::from(ProjectNameInvalid { cause: cause.message }), - error => error.into(), - }, - )?; - self.properties.borrow_mut().project_name.project = referent_name.clone_ref(); - self.execution_contexts.rename_project(old_name, referent_name); - Ok(()) - } - .boxed_local() - } - } - fn project_content_root_id(&self) -> Uuid { self.language_server_rpc.project_root().id() } diff --git a/app/gui/src/presenter/project.rs b/app/gui/src/presenter/project.rs index b04759a6b9b..bfc9d54995e 100644 --- a/app/gui/src/presenter/project.rs +++ b/app/gui/src/presenter/project.rs @@ -224,6 +224,11 @@ impl Model { self.view.top_bar().project_name().set_project_changed(changed); } + fn project_renamed(&self) { + let actual_name = self.controller.model.name(); + self.view.top_bar().project_name().set_name(actual_name); + } + fn execution_complete(&self) { self.view.graph().frp.set_read_only(false); self.view.graph().frp.execution_complete.emit(()); @@ -502,6 +507,9 @@ impl Project { Notification::ExecutionFailed => { model.execution_failed(); } + Notification::Renamed => { + model.project_renamed(); + } }; std::future::ready(()) }); diff --git a/app/gui/view/graph-editor/src/component/node/input/widget.rs b/app/gui/view/graph-editor/src/component/node/input/widget.rs index 86cdc7eff48..a3de4cfe982 100644 --- a/app/gui/view/graph-editor/src/component/node/input/widget.rs +++ b/app/gui/view/graph-editor/src/component/node/input/widget.rs @@ -50,8 +50,6 @@ //! [`SpanWidget::PRIORITY_OVER_OVERRIDE`] as `true` in their implementation. In that case, the //! override will be applied again on their children that use the same span-tree node. - - use crate::prelude::*; use crate::component::node::input::area::NODE_HEIGHT; diff --git a/docs/language-server/protocol-language-server.md b/docs/language-server/protocol-language-server.md index 074337e3b81..8d97bce7044 100644 --- a/docs/language-server/protocol-language-server.md +++ b/docs/language-server/protocol-language-server.md @@ -126,6 +126,7 @@ transport formats, please look [here](./protocol-architecture). - [Refactoring](#refactoring) - [`refactoring/renameProject`](#refactoringrenameproject) - [`refactoring/renameSymbol`](#refactoringrenamesymbol) + - [`refactoring/projectRenamed`](#refactoringprojectrenamed) - [Execution Management Operations](#execution-management-operations) - [Execution Management Example](#execution-management-example) - [Create Execution Context](#create-execution-context) @@ -3412,6 +3413,41 @@ Current limitations of the method renaming are: - [`RefactoringNotSupported`](#refactoringnotsupported) to signal that the refactoring of the given expression is not supported. +### `refactoring/projectRenamed` + +This is a notification sent from the server to the clients to inform them about +the new project name. + +- **Type:** Notification +- **Direction:** Server -> Client +- **Connection:** Protocol +- **Visibility:** Public + +#### Parameters + +```typescript +{ + /** + * Old normalized name of the project. + */ + oldNormalizedName: string; + + /** + * New normalized name of the prject. + */ + newNormalizedName: string; + + /** + * New display name of the project. + */ + newName: string; +} +``` + +#### Errors + +None + ## Execution Management Operations The execution management portion of the language server API deals with exposing 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 64e890f2660..e50100ca0b9 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 @@ -36,6 +36,7 @@ import org.enso.languageserver.refactoring.RefactoringApi.{ RenameProject, RenameSymbol } +import org.enso.languageserver.refactoring.{RefactoringApi, RefactoringProtocol} import org.enso.languageserver.requesthandler._ import org.enso.languageserver.requesthandler.capability._ import org.enso.languageserver.requesthandler.io._ @@ -132,6 +133,13 @@ class JsonConnectionController( implicit val timeout: Timeout = Timeout(requestTimeout) + override def preStart(): Unit = { + super.preStart() + + context.system.eventStream + .subscribe(self, classOf[RefactoringProtocol.ProjectRenamedNotification]) + } + override def receive: Receive = { case JsonRpcServer.WebConnect(webActor) => unstashAll() @@ -416,6 +424,20 @@ class JsonConnectionController( ) } + case RefactoringProtocol.ProjectRenamedNotification( + oldNormalizedName, + newNormalizedName, + newName + ) => + webActor ! Notification( + RefactoringApi.ProjectRenamed, + RefactoringApi.ProjectRenamed.Params( + oldNormalizedName, + newNormalizedName, + newName + ) + ) + case Api.ProgressNotification(payload) => val translated: Notification[_, _] = translateProgressNotification(payload) @@ -582,6 +604,10 @@ class JsonConnectionController( libraryConfig.localLibraryManager, libraryConfig.publishedLibraryCache ), + RenameProject -> RenameProjectHandler.props( + requestTimeout, + runtimeConnector + ), RenameSymbol -> RenameSymbolHandler.props( requestTimeout, runtimeConnector 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 0e834f26572..b935a862e42 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 @@ -122,5 +122,6 @@ object JsonRpc { .registerNotification(WaitingForStandardInput) .registerNotification(SuggestionsDatabaseUpdates) .registerNotification(VisualizationEvaluationFailed) + .registerNotification(ProjectRenamed) .finalized() } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/refactoring/RefactoringApi.scala b/engine/language-server/src/main/scala/org/enso/languageserver/refactoring/RefactoringApi.scala index fab2844f130..f31f5d7fd6b 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/refactoring/RefactoringApi.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/refactoring/RefactoringApi.scala @@ -27,6 +27,20 @@ object RefactoringApi { } } + case object ProjectRenamed extends Method("refactoring/projectRenamed") { + + case class Params( + oldNormalizedName: String, + newNormalizedName: String, + newName: String + ) + + implicit val hasParams: HasParams.Aux[this.type, ProjectRenamed.Params] = + new HasParams[this.type] { + type Params = ProjectRenamed.Params + } + } + case object RenameSymbol extends Method("refactoring/renameSymbol") { case class Params( @@ -60,4 +74,7 @@ object RefactoringApi { s"Refactoring not supported for expression [$expressionId]" ) + case class ProjectRenameFailed(oldName: String, newName: String) + extends Error(9004, s"Project rename failed [$oldName, $newName]") + } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/refactoring/RefactoringProtocol.scala b/engine/language-server/src/main/scala/org/enso/languageserver/refactoring/RefactoringProtocol.scala new file mode 100644 index 00000000000..3320347d27b --- /dev/null +++ b/engine/language-server/src/main/scala/org/enso/languageserver/refactoring/RefactoringProtocol.scala @@ -0,0 +1,16 @@ +package org.enso.languageserver.refactoring + +object RefactoringProtocol { + + /** Notification signaling about the project being renamed. + * + * @param oldNormalizedName old normalized name of the project + * @param newNormalizedName new normalized name of the project + * @param newName new display name of the project + */ + case class ProjectRenamedNotification( + oldNormalizedName: String, + newNormalizedName: String, + newName: String + ) +} diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/requesthandler/refactoring/RenameProjectHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/requesthandler/refactoring/RenameProjectHandler.scala index 005b3f06429..bdfa06d1a79 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/requesthandler/refactoring/RenameProjectHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/requesthandler/refactoring/RenameProjectHandler.scala @@ -1,15 +1,19 @@ package org.enso.languageserver.requesthandler.refactoring -import java.util.UUID - import akka.actor.{Actor, ActorRef, Cancellable, Props} import com.typesafe.scalalogging.LazyLogging import org.enso.jsonrpc._ -import org.enso.languageserver.refactoring.RefactoringApi.RenameProject +import org.enso.languageserver.refactoring.RefactoringApi.{ + ProjectRenameFailed, + RenameProject +} +import org.enso.languageserver.refactoring.RefactoringProtocol import org.enso.languageserver.requesthandler.RequestTimeout import org.enso.languageserver.util.UnhandledLogging import org.enso.polyglot.runtime.Runtime.Api +import java.util.UUID + import scala.concurrent.duration.FiniteDuration /** A request handler for `refactoring/renameProject` commands. @@ -52,10 +56,25 @@ class RenameProjectHandler(timeout: FiniteDuration, runtimeConnector: ActorRef) replyTo ! ResponseError(Some(id), Errors.RequestTimeout) context.stop(self) - case Api.Response(_, Api.ProjectRenamed(_, _)) => + case Api.Response( + _, + Api.ProjectRenamed(oldNormalizedName, newNormalizedName, newName) + ) => + context.system.eventStream.publish( + RefactoringProtocol.ProjectRenamedNotification( + oldNormalizedName, + newNormalizedName, + newName + ) + ) replyTo ! ResponseResult(RenameProject, id, Unused) cancellable.cancel() context.stop(self) + + case Api.Response(_, Api.ProjectRenameFailed(oldName, newName)) => + replyTo ! ResponseError(Some(id), ProjectRenameFailed(oldName, newName)) + cancellable.cancel() + context.stop(self) } } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/VisualizationConfiguration.scala b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/VisualizationConfiguration.scala index 2e780f9a622..47ce09ac5c7 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/VisualizationConfiguration.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/VisualizationConfiguration.scala @@ -13,8 +13,6 @@ import java.util.UUID * @param executionContextId an execution context of the visualization * @param expression an expression that creates a visualization * @param visualizationModule the name of a module to execute expression at - * @param executionContextId an execution context of the visualization - * @param expression an expression that creates a visualization */ case class VisualizationConfiguration( executionContextId: UUID, @@ -26,7 +24,8 @@ case class VisualizationConfiguration( override def toLogString(shouldMask: Boolean): String = s"VisualizationConfiguration(" + s"executionContextId=$executionContextId," + - s"expression=${expression.toLogString(shouldMask)})" + s"expression=${expression.toLogString(shouldMask)}," + + s"visualizationModule=$visualizationModule)" /** Convert to corresponding [[Api]] message. */ def toApi: Api.VisualizationConfiguration = 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 fa30cb2482a..489f91d1945 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 @@ -608,7 +608,7 @@ class LibrariesTest extends BaseServerTest { PackageManager.Default .loadPackage(cachedLibraryRoot.location.toFile) .get - pkg.module shouldEqual "Bar" + pkg.normalizedName shouldEqual "Bar" pkg .listSources() .map( diff --git a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/RefactoringTest.scala b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/RefactoringTest.scala index 3b7f5129b31..1ac8660a6c1 100644 --- a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/RefactoringTest.scala +++ b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/RefactoringTest.scala @@ -7,6 +7,105 @@ import java.util.UUID class RefactoringTest extends BaseServerTest { + "refactoring/renameProject" should { + + "return ok response after a successful renaming" in { + val client = getInitialisedWsClient() + + val namespace = "local" + val oldName = "Unnamed" + val newName = "Project1" + + client.send(json""" + { "jsonrpc": "2.0", + "method": "refactoring/renameProject", + "id": 1, + "params": { + "namespace": $namespace, + "oldName": $oldName, + "newName": $newName + } + } + """) + + val requestId = runtimeConnectorProbe.receiveN(1).head match { + case Api.Request(requestId, payload: Api.RenameProject) => + payload.namespace shouldEqual namespace + payload.oldName shouldEqual oldName + payload.newName shouldEqual newName + requestId + case msg => + fail(s"Runtime connector received unexpected message: $msg") + } + runtimeConnectorProbe.lastSender ! Api.Response( + requestId, + Api.ProjectRenamed(oldName, newName, newName) + ) + + client.expectJson(json""" + { "jsonrpc": "2.0", + "id": 1, + "result": null + } + """) + client.expectJson(json""" + { "jsonrpc": "2.0", + "method": "refactoring/projectRenamed", + "params": { + "oldNormalizedName": $oldName, + "newNormalizedName": $newName, + "newName": $newName + } + } + """) + } + + "reply with ProjectRenameFailed in case of failures" in { + val client = getInitialisedWsClient() + + val namespace = "local" + val oldName = "Unnamed" + val newName = "Project1" + + client.send(json""" + { "jsonrpc": "2.0", + "method": "refactoring/renameProject", + "id": 1, + "params": { + "namespace": $namespace, + "oldName": $oldName, + "newName": $newName + } + } + """) + + val requestId = runtimeConnectorProbe.receiveN(1).head match { + case Api.Request(requestId, payload: Api.RenameProject) => + payload.namespace shouldEqual namespace + payload.oldName shouldEqual oldName + payload.newName shouldEqual newName + requestId + case msg => + fail(s"Runtime connector received unexpected message: $msg") + } + runtimeConnectorProbe.lastSender ! Api.Response( + requestId, + Api.ProjectRenameFailed(oldName, newName) + ) + + client.expectJson(json""" + { "jsonrpc": "2.0", + "id": 1, + "error": { + "code": 9004, + "message": ${s"Project rename failed [$oldName, $newName]"} + } + } + """) + } + + } + "refactoring/renameSymbol" should { "return ok response after a successful renaming" in { 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 f9c87430707..281867ec4fd 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 @@ -156,6 +156,10 @@ object Runtime { value = classOf[Api.ProjectRenamed], name = "projectRenamed" ), + new JsonSubTypes.Type( + value = classOf[Api.ProjectRenameFailed], + name = "projectRenameFailed" + ), new JsonSubTypes.Type( value = classOf[Api.RenameSymbol], name = "renameSymbol" @@ -1638,10 +1642,22 @@ object Runtime { /** Signals that project has been renamed. * - * @param namespace the namespace of the project - * @param newName the new project name + * @param oldNormalizedName old normalized name of the project + * @param newNormalizedName new normalized name of the project + * @param newName new display name of the project */ - final case class ProjectRenamed(namespace: String, newName: String) + final case class ProjectRenamed( + oldNormalizedName: String, + newNormalizedName: String, + newName: String + ) extends ApiResponse + + /** Signals that project has been renamed. + * + * @param oldName the old name of the project + * @param newName the new name of the project + */ + final case class ProjectRenameFailed(oldName: String, newName: String) extends ApiResponse /** A request for symbol renaming. diff --git a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/command/RenameProjectCmd.scala b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/command/RenameProjectCmd.scala index fef935623f3..3f5784e523c 100644 --- a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/command/RenameProjectCmd.scala +++ b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/command/RenameProjectCmd.scala @@ -4,7 +4,6 @@ import java.util.logging.Level import org.enso.interpreter.instrument.{CacheInvalidation, InstrumentFrame} import org.enso.interpreter.instrument.execution.RuntimeContext import org.enso.interpreter.instrument.job.{EnsureCompiledJob, ExecuteJob} -import org.enso.interpreter.runtime.Module import org.enso.pkg.QualifiedName import org.enso.polyglot.data.Tree import org.enso.polyglot.runtime.Runtime.Api @@ -40,16 +39,23 @@ class RenameProjectCmd( Level.FINE, s"Renaming project [old:${request.namespace}.${request.oldName},new:${request.namespace}.${request.newName}]..." ) - val projectModules = getProjectModules + val packageRepository = + ctx.executionService.getContext.getPackageRepository + val mainPackage = packageRepository.getMainProjectPackage + .getOrElse(throw new RenameProjectCmd.MainProjectPackageNotFound) + val projectModules = + packageRepository.getModulesForLibrary(mainPackage.libraryName) + + val oldConfig = mainPackage.getConfig() + val newConfig = mainPackage + .reloadConfig() + .fold( + cause => throw new RenameProjectCmd.FailedToReloadConfig(cause), + identity + ) + projectModules.foreach { module => module.setIndexed(false) - val newConfig = module.getPackage.reloadConfig() - if (newConfig.isFailure) { - logger.log( - Level.WARNING, - s"Failed to reload package's config: ${newConfig.failed.get.getMessage}" - ) - } ctx.endpoint.sendToClient( Api.Response( Api.SuggestionsDatabaseModuleUpdateNotification( @@ -76,11 +82,32 @@ class RenameProjectCmd( clearCache(stack) } - reply(Api.ProjectRenamed(request.namespace, request.newName)) + reply( + Api.ProjectRenamed( + oldConfig.moduleName, + newConfig.moduleName, + newConfig.name + ) + ) logger.log( Level.INFO, s"Project renamed to ${request.namespace}.${request.newName}" ) + } catch { + case ex: RenameProjectCmd.MainProjectPackageNotFound => + logger.log( + Level.SEVERE, + "Main project package is not found.", + ex + ) + reply(Api.ProjectRenameFailed(request.oldName, request.newName)) + case ex: RenameProjectCmd.FailedToReloadConfig => + logger.log( + Level.SEVERE, + "Failed to reload package config.", + ex + ) + reply(Api.ProjectRenameFailed(request.oldName, request.newName)) } finally { ctx.locking.releaseWriteCompilationLock() logger.log( @@ -133,13 +160,6 @@ class RenameProjectCmd( } } - private def getProjectModules(implicit ctx: RuntimeContext): Seq[Module] = { - val packageRepository = ctx.executionService.getContext.getPackageRepository - packageRepository.getMainProjectPackage - .map { pkg => packageRepository.getModulesForLibrary(pkg.libraryName) } - .getOrElse(List()) - } - private def clearCache(stack: Iterable[InstrumentFrame]): Unit = { stack.foreach(_.syncState.clearMethodPointersState()) CacheInvalidation.run( @@ -152,3 +172,12 @@ class RenameProjectCmd( } } + +object RenameProjectCmd { + + final private class MainProjectPackageNotFound + extends Exception("Main project package is not found.") + + final private class FailedToReloadConfig(cause: Throwable) + extends Exception("Failed to reload config", cause) +} diff --git a/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala b/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala index 4f59ca086ab..344dca947da 100644 --- a/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala +++ b/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala @@ -5604,7 +5604,7 @@ class RuntimeServerTest ) val renameProjectResponses = context.receiveN(6) renameProjectResponses should contain allOf ( - Api.Response(requestId, Api.ProjectRenamed("Enso_Test", "Foo")), + Api.Response(requestId, Api.ProjectRenamed("Test", "Foo", "Foo")), context.Main.Update.mainX(contextId, typeChanged = false), TestMessages.update( contextId, @@ -5720,7 +5720,7 @@ class RuntimeServerTest ) val renameProjectResponses = context.receiveN(6) renameProjectResponses should contain allOf ( - Api.Response(requestId, Api.ProjectRenamed("Enso_Test", "Foo")), + Api.Response(requestId, Api.ProjectRenamed("Test", "Foo", "Foo")), context.Main.Update.mainX(contextId, typeChanged = false), TestMessages.update( contextId, diff --git a/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeSuggestionUpdatesTest.scala b/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeSuggestionUpdatesTest.scala index 3c9a0704165..a5b0080b798 100644 --- a/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeSuggestionUpdatesTest.scala +++ b/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeSuggestionUpdatesTest.scala @@ -728,7 +728,7 @@ class RuntimeSuggestionUpdatesTest Api.Request(requestId, Api.RenameProject("Enso_Test", "Test", "Foo")) ) context.receiveN(4) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.ProjectRenamed("Enso_Test", "Foo")), + Api.Response(requestId, Api.ProjectRenamed("Test", "Foo", "Foo")), Api.Response( Api.SuggestionsDatabaseModuleUpdateNotification( module = moduleName, diff --git a/engine/runtime/src/main/java/org/enso/compiler/ImportExportCache.java b/engine/runtime/src/main/java/org/enso/compiler/ImportExportCache.java index 69cb7105690..b70951e0e27 100644 --- a/engine/runtime/src/main/java/org/enso/compiler/ImportExportCache.java +++ b/engine/runtime/src/main/java/org/enso/compiler/ImportExportCache.java @@ -93,7 +93,7 @@ public final class ImportExportCache extends Cache(); pathSegmentsJava.addAll(Arrays.asList( pkg.namespace(), - pkg.module(), + pkg.normalizedName(), pkg.getConfig().version(), Info.ensoVersion() )); diff --git a/engine/runtime/src/main/java/org/enso/compiler/SuggestionsCache.java b/engine/runtime/src/main/java/org/enso/compiler/SuggestionsCache.java index 7c6d1e589cb..4bc8455af50 100644 --- a/engine/runtime/src/main/java/org/enso/compiler/SuggestionsCache.java +++ b/engine/runtime/src/main/java/org/enso/compiler/SuggestionsCache.java @@ -85,7 +85,6 @@ public final class SuggestionsCache .map(pkg -> computeDigestOfLibrarySources(pkg.listSourcesJava(), logger)); } - @Override protected Optional getCacheRoots(EnsoContext context) { return context.getPackageRepository().getPackageForLibraryJava(libraryName).map(pkg -> { @@ -94,7 +93,7 @@ public final class SuggestionsCache var distribution = context.getDistributionManager(); var pathSegments = new String[]{ pkg.namespace(), - pkg.module(), + pkg.normalizedName(), pkg.getConfig().version(), Info.ensoVersion(), libraryName.namespace() diff --git a/engine/runtime/src/main/scala/org/enso/compiler/Compiler.scala b/engine/runtime/src/main/scala/org/enso/compiler/Compiler.scala index dea4d0edf22..e2297072095 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/Compiler.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/Compiler.scala @@ -193,26 +193,28 @@ class Compiler( CompletableFuture.completedFuture(false) case Some(pkg) => val packageModule = packageRepository.getModuleMap.get( - s"${pkg.namespace}.${pkg.module}.Main" + s"${pkg.namespace}.${pkg.normalizedName}.Main" ) packageModule match { case None => context.log( Level.SEVERE, "Could not find entry point for compilation in package [{0}.{1}]", - Array(pkg.namespace, pkg.module) + Array(pkg.namespace, pkg.normalizedName) ) CompletableFuture.completedFuture(false) case Some(m) => context.log( Compiler.defaultLogLevel, - s"Compiling the package [${pkg.namespace}.${pkg.module}] " + + s"Compiling the package [${pkg.namespace}.${pkg.normalizedName}] " + s"starting at the root [${m.getName}]." ) val packageModules = packageRepository.freezeModuleMap.collect { case (name, mod) - if name.startsWith(s"${pkg.namespace}.${pkg.module}") => + if name.startsWith( + s"${pkg.namespace}.${pkg.normalizedName}" + ) => mod }.toList diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/desugar/Imports.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/desugar/Imports.scala index 3466bf3042e..76a2214157e 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/desugar/Imports.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/desugar/Imports.scala @@ -129,7 +129,7 @@ case object Imports extends IRPass { ) val pkgName = IR.Name.Literal( - pkg.module, + pkg.normalizedName, isMethod = false, location = None ) diff --git a/engine/runtime/src/main/scala/org/enso/interpreter/runtime/DefaultPackageRepository.scala b/engine/runtime/src/main/scala/org/enso/interpreter/runtime/DefaultPackageRepository.scala index e6e486d2f21..44031d4bdfd 100644 --- a/engine/runtime/src/main/scala/org/enso/interpreter/runtime/DefaultPackageRepository.scala +++ b/engine/runtime/src/main/scala/org/enso/interpreter/runtime/DefaultPackageRepository.scala @@ -327,7 +327,7 @@ private class DefaultPackageRepository( Left(PackageRepository.Error.PackageLoadingError(err.getMessage())) case Right(componentGroups) => logger.debug( - s"Resolving component groups of package [${pkg.module}]." + s"Resolving component groups of package [${pkg.normalizedName}]." ) registerComponentGroups(pkg.libraryName, componentGroups.newGroups) diff --git a/lib/scala/library-manager-test/src/test/scala/org/enso/librarymanager/published/repository/LibraryDownloadTest.scala b/lib/scala/library-manager-test/src/test/scala/org/enso/librarymanager/published/repository/LibraryDownloadTest.scala index c9726bdf2fb..86c82bca92d 100644 --- a/lib/scala/library-manager-test/src/test/scala/org/enso/librarymanager/published/repository/LibraryDownloadTest.scala +++ b/lib/scala/library-manager-test/src/test/scala/org/enso/librarymanager/published/repository/LibraryDownloadTest.scala @@ -43,7 +43,7 @@ class LibraryDownloadTest } val pkg = PackageManager.Default.loadPackage(libPath.location.toFile).get - pkg.module shouldEqual "Bar" + pkg.normalizedName shouldEqual "Bar" val sources = pkg.listSources() sources should have size 1 sources.head.file.getName shouldEqual "Main.enso" diff --git a/lib/scala/library-manager-test/src/test/scala/org/enso/libraryupload/LibraryUploadTest.scala b/lib/scala/library-manager-test/src/test/scala/org/enso/libraryupload/LibraryUploadTest.scala index ba6f00ee28f..5dba0172971 100644 --- a/lib/scala/library-manager-test/src/test/scala/org/enso/libraryupload/LibraryUploadTest.scala +++ b/lib/scala/library-manager-test/src/test/scala/org/enso/libraryupload/LibraryUploadTest.scala @@ -67,7 +67,7 @@ class LibraryUploadTest PackageManager.Default .loadPackage(libRoot.toFile) .get - .module shouldEqual libraryName.name + .normalizedName shouldEqual libraryName.name assert(Files.exists(libRoot.resolve("manifest.yaml"))) assert(Files.exists(libRoot.resolve("main.tgz"))) @@ -83,7 +83,7 @@ class LibraryUploadTest val pkg = PackageManager.Default .loadPackage(installedRoot.location.toFile) .get - pkg.module shouldEqual libraryName.name + pkg.normalizedName shouldEqual libraryName.name val sources = pkg.listSources() sources should have size 1 sources.head.file.getName shouldEqual "Main.enso" 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 e98316c1f48..49e0f454d87 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 @@ -181,16 +181,16 @@ class Package[F]( */ def name: String = config.name - /** Returns the module of this package. - * @return the module of this package. + /** Returns the normalized name of this package. + * @return the normalized name of this package. */ - def module: String = + def normalizedName: String = config.normalizedName.getOrElse(NameValidation.normalizeName(name)) def namespace: String = config.namespace /** A [[LibraryName]] associated with the package. */ - def libraryName: LibraryName = LibraryName(config.namespace, module) + def libraryName: LibraryName = LibraryName(config.namespace, normalizedName) /** Parses a file path into a qualified module name belonging to this * package. @@ -202,7 +202,10 @@ class Package[F]( val segments = sourceDir.relativize(file).getSegments.asScala.toList val dirSegments = segments.take(segments.length - 1) val fileNameWithoutExtension = file.getName.takeWhile(_ != '.') - QualifiedName(namespace :: module :: dirSegments, fileNameWithoutExtension) + QualifiedName( + namespace :: normalizedName :: dirSegments, + fileNameWithoutExtension + ) } /** Lists the source files in this package. diff --git a/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/infrastructure/repository/ProjectFileRepository.scala b/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/infrastructure/repository/ProjectFileRepository.scala index 0452c7a1822..c5b11b4a801 100644 --- a/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/infrastructure/repository/ProjectFileRepository.scala +++ b/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/infrastructure/repository/ProjectFileRepository.scala @@ -106,7 +106,7 @@ class ProjectFileRepository[ Project( id = meta.id, name = pkg.name, - module = pkg.module, + module = pkg.normalizedName, namespace = pkg.namespace, kind = meta.kind, created = meta.created, diff --git a/lib/scala/runtime-version-manager/src/main/scala/org/enso/runtimeversionmanager/runner/Project.scala b/lib/scala/runtime-version-manager/src/main/scala/org/enso/runtimeversionmanager/runner/Project.scala index 40682cdabbb..949520d3090 100644 --- a/lib/scala/runtime-version-manager/src/main/scala/org/enso/runtimeversionmanager/runner/Project.scala +++ b/lib/scala/runtime-version-manager/src/main/scala/org/enso/runtimeversionmanager/runner/Project.scala @@ -18,7 +18,7 @@ class Project( def edition: Option[Editions.RawEdition] = pkg.getConfig().edition /** The package name of the project. */ - def name: String = pkg.module + def name: String = pkg.normalizedName /** The path to the content root of the project. */ def path: Path = pkg.root.toPath