From e7a0312298e212ee5c3ed58804eec44022515566 Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Thu, 6 Jul 2023 14:27:47 +0100 Subject: [PATCH] Reload externally changed file on a keypress (#7217) part of #7178 Changelog: - add: `cmd+alt+y` keybinding that re-opens the file, applies new content, and re-executes the program This is the first part of the task to support the external edits. The next step will be to reload the module contents by the notification from the language server. # Important Notes https://github.com/enso-org/enso/assets/357683/79917e22-b846-4bd9-b03a-33a48d5f75b9 --- app/gui/src/controller/graph/executed.rs | 15 +++++++++ app/gui/src/model/module.rs | 3 ++ app/gui/src/model/module/plain.rs | 5 +++ app/gui/src/model/module/synchronized.rs | 42 +++++++++++++++++++----- app/gui/src/presenter/project.rs | 10 ++++++ app/gui/view/src/project.rs | 5 +++ 6 files changed, 71 insertions(+), 9 deletions(-) diff --git a/app/gui/src/controller/graph/executed.rs b/app/gui/src/controller/graph/executed.rs index b74950d787f..d922c512c56 100644 --- a/app/gui/src/controller/graph/executed.rs +++ b/app/gui/src/controller/graph/executed.rs @@ -380,6 +380,21 @@ impl Handle { } } + /// Reload the main file and restart the program execution. + /// + /// ### Errors + /// - Fails if the project is in read-only mode. + pub async fn reload_and_restart(&self) -> FallibleResult { + if self.project.read_only() { + Err(ReadOnly.into()) + } else { + let model = self.project.main_module_model().await?; + model.reopen_externally_changed_file().await?; + self.execution_ctx.restart().await?; + Ok(()) + } + } + /// Get the current call stack frames. pub fn call_stack(&self) -> Vec { self.execution_ctx.stack_items().collect() diff --git a/app/gui/src/model/module.rs b/app/gui/src/model/module.rs index d632a699fd4..7d1938a8939 100644 --- a/app/gui/src/model/module.rs +++ b/app/gui/src/model/module.rs @@ -673,6 +673,9 @@ pub trait API: Debug + model::undo_redo::Aware { /// Reopen file in language server. fn reopen_file_in_language_server(&self) -> BoxFuture; + + /// Reopen externally changed file. + fn reopen_externally_changed_file(&self) -> BoxFuture; } /// Trait for methods that cannot be defined in `API` because it is a trait object. diff --git a/app/gui/src/model/module/plain.rs b/app/gui/src/model/module/plain.rs index dc52fb5435d..a385f2069c2 100644 --- a/app/gui/src/model/module/plain.rs +++ b/app/gui/src/model/module/plain.rs @@ -311,6 +311,11 @@ impl model::module::API for Module { info!("Ignoring request for reopening file in the Language Server, because it's not connected"); future::ready_boxed(Ok(())) } + + fn reopen_externally_changed_file(&self) -> BoxFuture { + info!("Ignoring request for reopening externally changed file in the Language Server, because it's not connected"); + future::ready_boxed(Ok(())) + } } impl model::undo_redo::Aware for Module { diff --git a/app/gui/src/model/module/synchronized.rs b/app/gui/src/model/module/synchronized.rs index 81dce4fc3ee..8bfb0fc1d8b 100644 --- a/app/gui/src/model/module/synchronized.rs +++ b/app/gui/src/model/module/synchronized.rs @@ -206,13 +206,8 @@ impl Module { /// Reopen file in the Language Server. /// /// After reopening we update the LS state with the model's current content. - pub async fn reopen_file(&self, new_file: SourceFile) -> FallibleResult { - let file_path = self.path(); - info!("Reopening file {file_path}."); - if let Err(error) = self.language_server.client.close_text_file(file_path).await { - error!("Error while reopening file {file_path}: Closing operation failed: {error} Trying to open the file anyway."); - } - let opened = self.language_server.client.open_text_file(file_path).await?; + pub async fn reopen_file_and_invalidate(&self, new_file: SourceFile) -> FallibleResult { + let opened = self.reopen_file().await?; let content = opened.content.into(); let summary = ContentSummary::new(&content); @@ -220,6 +215,18 @@ impl Module { Ok(()) } + /// Reopen file in the Language Server. + /// + /// After reopening we update the model's current content with the LS state. + pub async fn reopen_file_and_set_content(&self) -> FallibleResult { + let opened = self.reopen_file().await?; + let content = opened.content.into(); + + self.set_module_content_from_ls(content).await?; + + Ok(()) + } + /// Apply text changes received from the language server. pub async fn apply_text_change_from_ls(&self, edits: Vec) -> FallibleResult { let mut content: text::Rope = self.serialized_content()?.content.into(); @@ -260,6 +267,17 @@ impl Module { notify_ls.await; Ok(()) } + + /// Reopen file in the Language Server. + async fn reopen_file(&self) -> FallibleResult { + let file_path = self.path(); + info!("Reopening file {file_path}."); + if let Err(error) = self.language_server.client.close_text_file(file_path).await { + error!("Error while reopening file {file_path}: Closing operation failed: {error} Trying to open the file anyway."); + } + let opened = self.language_server.client.open_text_file(file_path).await?; + Ok(opened) + } } impl API for Module { @@ -357,7 +375,11 @@ impl API for Module { fn reopen_file_in_language_server(&self) -> BoxFuture { let file = self.model.content().borrow().serialize(); - async { self.reopen_file(file?).await }.boxed_local() + async { self.reopen_file_and_invalidate(file?).await }.boxed_local() + } + + fn reopen_externally_changed_file(&self) -> BoxFuture { + async { self.reopen_file_and_set_content().await }.boxed_local() } } @@ -396,7 +418,9 @@ impl Module { debug!("Handling notification when known LS content is {current_ls_content:?}."); match current_ls_content { LanguageServerContent::Unknown => { - if let Err(error) = profiler::await_!(self.reopen_file(new_file), _profiler) { + if let Err(error) = + profiler::await_!(self.reopen_file_and_invalidate(new_file), _profiler) + { error!("Error while reloading module model: {error}"); } } diff --git a/app/gui/src/presenter/project.rs b/app/gui/src/presenter/project.rs index 545e5e7ca7c..36249993a7b 100644 --- a/app/gui/src/presenter/project.rs +++ b/app/gui/src/presenter/project.rs @@ -248,6 +248,15 @@ impl Model { }) } + fn execution_context_reload_and_restart(&self) { + let controller = self.graph_controller.clone_ref(); + executor::global::spawn(async move { + if let Err(err) = controller.reload_and_restart().await { + error!("Error reloading and restarting execution context: {err}"); + } + }) + } + /// Prepare a list of projects to display in the Open Project dialog. fn project_list_opened(&self, project_list_ready: frp::Source<()>) { let controller = self.ide_controller.clone_ref(); @@ -374,6 +383,7 @@ impl Project { eval_ view.execution_context_interrupt(model.execution_context_interrupt()); eval_ view.execution_context_restart(model.execution_context_restart()); + eval_ view.execution_context_reload_and_restart(model.execution_context_reload_and_restart()); view.set_read_only <+ view.toggle_read_only.map(f_!(model.toggle_read_only())); eval graph_view.execution_environment((env) model.execution_environment_changed(*env)); diff --git a/app/gui/view/src/project.rs b/app/gui/view/src/project.rs index 6d7b28d1379..344eea3690b 100644 --- a/app/gui/view/src/project.rs +++ b/app/gui/view/src/project.rs @@ -107,6 +107,8 @@ ensogl::define_endpoints! { execution_context_interrupt(), /// Restart the program execution. execution_context_restart(), + /// Reload the main module and restart the program execution. + execution_context_reload_and_restart(), toggle_read_only(), set_read_only(bool), } @@ -759,6 +761,9 @@ impl application::View for View { (Press, "debug_mode", DEBUG_MODE_SHORTCUT, "disable_debug_mode"), (Press, "", "cmd alt t", "execution_context_interrupt"), (Press, "", "cmd alt r", "execution_context_restart"), + // TODO(#7178): Remove this temporary shortcut when the modified-on-disk notification + // is ready. + (Press, "", "cmd alt y", "execution_context_reload_and_restart"), // TODO(#6179): Remove this temporary shortcut when Play button is ready. (Press, "", "ctrl shift b", "toggle_read_only"), ]