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
This commit is contained in:
Dmitry Bushev 2023-07-06 14:27:47 +01:00 committed by GitHub
parent 70f79b1df4
commit e7a0312298
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 71 additions and 9 deletions

View File

@ -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<LocalCall> {
self.execution_ctx.stack_items().collect()

View File

@ -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<FallibleResult>;
/// Reopen externally changed file.
fn reopen_externally_changed_file(&self) -> BoxFuture<FallibleResult>;
}
/// Trait for methods that cannot be defined in `API` because it is a trait object.

View File

@ -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<FallibleResult> {
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 {

View File

@ -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<TextEdit>) -> 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<language_server::response::OpenTextFile> {
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<FallibleResult> {
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<FallibleResult> {
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}");
}
}

View File

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

View File

@ -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"),
]