Allow explicit reload of buffers via Project::reload_buffers

This commit is contained in:
Antonio Scandurra 2022-04-01 14:01:56 +02:00
parent bdd95a82d7
commit 65048760b2
7 changed files with 306 additions and 64 deletions

View file

@ -498,6 +498,30 @@ impl Buffer {
cx.notify(); cx.notify();
} }
pub fn reload(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<Option<Transaction>>> {
cx.spawn(|this, mut cx| async move {
if let Some((new_mtime, new_text)) = this.read_with(&cx, |this, cx| {
let file = this.file.as_ref()?.as_local()?;
Some((file.mtime(), file.load(cx)))
}) {
let new_text = new_text.await?;
let diff = this
.read_with(&cx, |this, cx| this.diff(new_text.into(), cx))
.await;
this.update(&mut cx, |this, cx| {
if let Some(transaction) = this.apply_diff(diff, cx).cloned() {
this.did_reload(this.version(), new_mtime, cx);
Ok(Some(transaction))
} else {
Ok(None)
}
})
} else {
Ok(None)
}
})
}
pub fn did_reload( pub fn did_reload(
&mut self, &mut self,
version: clock::Global, version: clock::Global,
@ -543,29 +567,8 @@ impl Buffer {
file_changed = true; file_changed = true;
if !self.is_dirty() { if !self.is_dirty() {
task = cx.spawn(|this, mut cx| { let reload = self.reload(cx).log_err().map(drop);
async move { task = cx.foreground().spawn(reload);
let new_text = this.read_with(&cx, |this, cx| {
this.file
.as_ref()
.and_then(|file| file.as_local().map(|f| f.load(cx)))
});
if let Some(new_text) = new_text {
let new_text = new_text.await?;
let diff = this
.read_with(&cx, |this, cx| this.diff(new_text.into(), cx))
.await;
this.update(&mut cx, |this, cx| {
if this.apply_diff(diff, cx) {
this.did_reload(this.version(), new_mtime, cx);
}
});
}
Ok(())
}
.log_err()
.map(drop)
});
} }
} }
} }
@ -902,8 +905,13 @@ impl Buffer {
}) })
} }
pub(crate) fn apply_diff(&mut self, diff: Diff, cx: &mut ModelContext<Self>) -> bool { pub(crate) fn apply_diff(
&mut self,
diff: Diff,
cx: &mut ModelContext<Self>,
) -> Option<&Transaction> {
if self.version == diff.base_version { if self.version == diff.base_version {
self.finalize_last_transaction();
self.start_transaction(); self.start_transaction();
let mut offset = diff.start_offset; let mut offset = diff.start_offset;
for (tag, len) in diff.changes { for (tag, len) in diff.changes {
@ -924,10 +932,13 @@ impl Buffer {
} }
} }
} }
self.end_transaction(cx); if self.end_transaction(cx).is_some() {
true self.finalize_last_transaction()
} else { } else {
false None
}
} else {
None
} }
} }

View file

@ -136,12 +136,16 @@ async fn test_apply_diff(cx: &mut gpui::TestAppContext) {
let text = "a\nccc\ndddd\nffffff\n"; let text = "a\nccc\ndddd\nffffff\n";
let diff = buffer.read_with(cx, |b, cx| b.diff(text.into(), cx)).await; let diff = buffer.read_with(cx, |b, cx| b.diff(text.into(), cx)).await;
buffer.update(cx, |b, cx| b.apply_diff(diff, cx)); buffer.update(cx, |buffer, cx| {
buffer.apply_diff(diff, cx).unwrap();
});
cx.read(|cx| assert_eq!(buffer.read(cx).text(), text)); cx.read(|cx| assert_eq!(buffer.read(cx).text(), text));
let text = "a\n1\n\nccc\ndd2dd\nffffff\n"; let text = "a\n1\n\nccc\ndd2dd\nffffff\n";
let diff = buffer.read_with(cx, |b, cx| b.diff(text.into(), cx)).await; let diff = buffer.read_with(cx, |b, cx| b.diff(text.into(), cx)).await;
buffer.update(cx, |b, cx| b.apply_diff(diff, cx)); buffer.update(cx, |buffer, cx| {
buffer.apply_diff(diff, cx).unwrap();
});
cx.read(|cx| assert_eq!(buffer.read(cx).text(), text)); cx.read(|cx| assert_eq!(buffer.read(cx).text(), text));
} }

View file

@ -270,6 +270,7 @@ impl Project {
client.add_model_message_handler(Self::handle_update_worktree); client.add_model_message_handler(Self::handle_update_worktree);
client.add_model_request_handler(Self::handle_apply_additional_edits_for_completion); client.add_model_request_handler(Self::handle_apply_additional_edits_for_completion);
client.add_model_request_handler(Self::handle_apply_code_action); client.add_model_request_handler(Self::handle_apply_code_action);
client.add_model_request_handler(Self::handle_reload_buffers);
client.add_model_request_handler(Self::handle_format_buffers); client.add_model_request_handler(Self::handle_format_buffers);
client.add_model_request_handler(Self::handle_get_code_actions); client.add_model_request_handler(Self::handle_get_code_actions);
client.add_model_request_handler(Self::handle_get_completions); client.add_model_request_handler(Self::handle_get_completions);
@ -1973,6 +1974,70 @@ impl Project {
Ok(()) Ok(())
} }
pub fn reload_buffers(
&self,
buffers: HashSet<ModelHandle<Buffer>>,
push_to_history: bool,
cx: &mut ModelContext<Self>,
) -> Task<Result<ProjectTransaction>> {
let mut local_buffers = Vec::new();
let mut remote_buffers = None;
for buffer_handle in buffers {
let buffer = buffer_handle.read(cx);
if buffer.is_dirty() {
if let Some(file) = File::from_dyn(buffer.file()) {
if file.is_local() {
local_buffers.push(buffer_handle);
} else {
remote_buffers.get_or_insert(Vec::new()).push(buffer_handle);
}
}
}
}
let remote_buffers = self.remote_id().zip(remote_buffers);
let client = self.client.clone();
cx.spawn(|this, mut cx| async move {
let mut project_transaction = ProjectTransaction::default();
if let Some((project_id, remote_buffers)) = remote_buffers {
let response = client
.request(proto::ReloadBuffers {
project_id,
buffer_ids: remote_buffers
.iter()
.map(|buffer| buffer.read_with(&cx, |buffer, _| buffer.remote_id()))
.collect(),
})
.await?
.transaction
.ok_or_else(|| anyhow!("missing transaction"))?;
project_transaction = this
.update(&mut cx, |this, cx| {
this.deserialize_project_transaction(response, push_to_history, cx)
})
.await?;
}
for buffer in local_buffers {
let transaction = buffer
.update(&mut cx, |buffer, cx| buffer.reload(cx))
.await?;
buffer.update(&mut cx, |buffer, cx| {
if let Some(transaction) = transaction {
if !push_to_history {
buffer.forget_transaction(transaction.id);
}
project_transaction.0.insert(cx.handle(), transaction);
}
});
}
Ok(project_transaction)
})
}
pub fn format( pub fn format(
&self, &self,
buffers: HashSet<ModelHandle<Buffer>>, buffers: HashSet<ModelHandle<Buffer>>,
@ -3667,6 +3732,35 @@ impl Project {
}) })
} }
async fn handle_reload_buffers(
this: ModelHandle<Self>,
envelope: TypedEnvelope<proto::ReloadBuffers>,
_: Arc<Client>,
mut cx: AsyncAppContext,
) -> Result<proto::ReloadBuffersResponse> {
let sender_id = envelope.original_sender_id()?;
let reload = this.update(&mut cx, |this, cx| {
let mut buffers = HashSet::default();
for buffer_id in &envelope.payload.buffer_ids {
buffers.insert(
this.opened_buffers
.get(buffer_id)
.map(|buffer| buffer.upgrade(cx).unwrap())
.ok_or_else(|| anyhow!("unknown buffer id {}", buffer_id))?,
);
}
Ok::<_, anyhow::Error>(this.reload_buffers(buffers, false, cx))
})?;
let project_transaction = reload.await?;
let project_transaction = this.update(&mut cx, |this, cx| {
this.serialize_project_transaction_for_peer(project_transaction, sender_id, cx)
});
Ok(proto::ReloadBuffersResponse {
transaction: Some(project_transaction),
})
}
async fn handle_format_buffers( async fn handle_format_buffers(
this: ModelHandle<Self>, this: ModelHandle<Self>,
envelope: TypedEnvelope<proto::FormatBuffers>, envelope: TypedEnvelope<proto::FormatBuffers>,

View file

@ -48,43 +48,45 @@ message Envelope {
SaveBuffer save_buffer = 40; SaveBuffer save_buffer = 40;
BufferSaved buffer_saved = 41; BufferSaved buffer_saved = 41;
BufferReloaded buffer_reloaded = 42; BufferReloaded buffer_reloaded = 42;
FormatBuffers format_buffers = 43; ReloadBuffers reload_buffers = 43;
FormatBuffersResponse format_buffers_response = 44; ReloadBuffersResponse reload_buffers_response = 44;
GetCompletions get_completions = 45; FormatBuffers format_buffers = 45;
GetCompletionsResponse get_completions_response = 46; FormatBuffersResponse format_buffers_response = 46;
ApplyCompletionAdditionalEdits apply_completion_additional_edits = 47; GetCompletions get_completions = 47;
ApplyCompletionAdditionalEditsResponse apply_completion_additional_edits_response = 48; GetCompletionsResponse get_completions_response = 48;
GetCodeActions get_code_actions = 49; ApplyCompletionAdditionalEdits apply_completion_additional_edits = 49;
GetCodeActionsResponse get_code_actions_response = 50; ApplyCompletionAdditionalEditsResponse apply_completion_additional_edits_response = 50;
ApplyCodeAction apply_code_action = 51; GetCodeActions get_code_actions = 51;
ApplyCodeActionResponse apply_code_action_response = 52; GetCodeActionsResponse get_code_actions_response = 52;
PrepareRename prepare_rename = 53; ApplyCodeAction apply_code_action = 53;
PrepareRenameResponse prepare_rename_response = 54; ApplyCodeActionResponse apply_code_action_response = 54;
PerformRename perform_rename = 55; PrepareRename prepare_rename = 55;
PerformRenameResponse perform_rename_response = 56; PrepareRenameResponse prepare_rename_response = 56;
SearchProject search_project = 57; PerformRename perform_rename = 57;
SearchProjectResponse search_project_response = 58; PerformRenameResponse perform_rename_response = 58;
SearchProject search_project = 59;
SearchProjectResponse search_project_response = 60;
GetChannels get_channels = 59; GetChannels get_channels = 61;
GetChannelsResponse get_channels_response = 60; GetChannelsResponse get_channels_response = 62;
JoinChannel join_channel = 61; JoinChannel join_channel = 63;
JoinChannelResponse join_channel_response = 62; JoinChannelResponse join_channel_response = 64;
LeaveChannel leave_channel = 63; LeaveChannel leave_channel = 65;
SendChannelMessage send_channel_message = 64; SendChannelMessage send_channel_message = 66;
SendChannelMessageResponse send_channel_message_response = 65; SendChannelMessageResponse send_channel_message_response = 67;
ChannelMessageSent channel_message_sent = 66; ChannelMessageSent channel_message_sent = 68;
GetChannelMessages get_channel_messages = 67; GetChannelMessages get_channel_messages = 69;
GetChannelMessagesResponse get_channel_messages_response = 68; GetChannelMessagesResponse get_channel_messages_response = 70;
UpdateContacts update_contacts = 69; UpdateContacts update_contacts = 71;
GetUsers get_users = 70; GetUsers get_users = 72;
GetUsersResponse get_users_response = 71; GetUsersResponse get_users_response = 73;
Follow follow = 72; Follow follow = 74;
FollowResponse follow_response = 73; FollowResponse follow_response = 75;
UpdateFollowers update_followers = 74; UpdateFollowers update_followers = 76;
Unfollow unfollow = 75; Unfollow unfollow = 77;
} }
} }
@ -299,6 +301,15 @@ message BufferReloaded {
Timestamp mtime = 4; Timestamp mtime = 4;
} }
message ReloadBuffers {
uint64 project_id = 1;
repeated uint64 buffer_ids = 2;
}
message ReloadBuffersResponse {
ProjectTransaction transaction = 1;
}
message FormatBuffers { message FormatBuffers {
uint64 project_id = 1; uint64 project_id = 1;
repeated uint64 buffer_ids = 2; repeated uint64 buffer_ids = 2;

View file

@ -190,6 +190,8 @@ messages!(
(Ping, Foreground), (Ping, Foreground),
(RegisterProject, Foreground), (RegisterProject, Foreground),
(RegisterWorktree, Foreground), (RegisterWorktree, Foreground),
(ReloadBuffers, Foreground),
(ReloadBuffersResponse, Foreground),
(RemoveProjectCollaborator, Foreground), (RemoveProjectCollaborator, Foreground),
(SaveBuffer, Foreground), (SaveBuffer, Foreground),
(SearchProject, Background), (SearchProject, Background),
@ -237,6 +239,7 @@ request_messages!(
(PrepareRename, PrepareRenameResponse), (PrepareRename, PrepareRenameResponse),
(RegisterProject, RegisterProjectResponse), (RegisterProject, RegisterProjectResponse),
(RegisterWorktree, Ack), (RegisterWorktree, Ack),
(ReloadBuffers, ReloadBuffersResponse),
(SaveBuffer, BufferSaved), (SaveBuffer, BufferSaved),
(SearchProject, SearchProjectResponse), (SearchProject, SearchProjectResponse),
(SendChannelMessage, SendChannelMessageResponse), (SendChannelMessage, SendChannelMessageResponse),
@ -268,6 +271,7 @@ entity_messages!(
OpenBufferForSymbol, OpenBufferForSymbol,
PerformRename, PerformRename,
PrepareRename, PrepareRename,
ReloadBuffers,
RemoveProjectCollaborator, RemoveProjectCollaborator,
SaveBuffer, SaveBuffer,
SearchProject, SearchProject,

View file

@ -5,4 +5,4 @@ pub mod proto;
pub use conn::Connection; pub use conn::Connection;
pub use peer::*; pub use peer::*;
pub const PROTOCOL_VERSION: u32 = 12; pub const PROTOCOL_VERSION: u32 = 13;

View file

@ -102,6 +102,7 @@ impl Server {
.add_request_handler(Server::forward_project_request::<proto::ApplyCodeAction>) .add_request_handler(Server::forward_project_request::<proto::ApplyCodeAction>)
.add_request_handler(Server::forward_project_request::<proto::PrepareRename>) .add_request_handler(Server::forward_project_request::<proto::PrepareRename>)
.add_request_handler(Server::forward_project_request::<proto::PerformRename>) .add_request_handler(Server::forward_project_request::<proto::PerformRename>)
.add_request_handler(Server::forward_project_request::<proto::ReloadBuffers>)
.add_request_handler(Server::forward_project_request::<proto::FormatBuffers>) .add_request_handler(Server::forward_project_request::<proto::FormatBuffers>)
.add_request_handler(Server::update_buffer) .add_request_handler(Server::update_buffer)
.add_message_handler(Server::update_buffer_file) .add_message_handler(Server::update_buffer_file)
@ -1089,7 +1090,7 @@ mod tests {
use gpui::{executor, geometry::vector::vec2f, ModelHandle, TestAppContext, ViewHandle}; use gpui::{executor, geometry::vector::vec2f, ModelHandle, TestAppContext, ViewHandle};
use language::{ use language::{
tree_sitter_rust, Diagnostic, DiagnosticEntry, Language, LanguageConfig, LanguageRegistry, tree_sitter_rust, Diagnostic, DiagnosticEntry, Language, LanguageConfig, LanguageRegistry,
LanguageServerConfig, OffsetRangeExt, Point, ToLspPosition, LanguageServerConfig, OffsetRangeExt, Point, Rope, ToLspPosition,
}; };
use lsp; use lsp;
use parking_lot::Mutex; use parking_lot::Mutex;
@ -2460,6 +2461,123 @@ mod tests {
.await; .await;
} }
#[gpui::test(iterations = 10)]
async fn test_reloading_buffer_manually(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
cx_a.foreground().forbid_parking();
let lang_registry = Arc::new(LanguageRegistry::test());
let fs = FakeFs::new(cx_a.background());
// Connect to a server as 2 clients.
let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
let client_a = server.create_client(cx_a, "user_a").await;
let client_b = server.create_client(cx_b, "user_b").await;
// Share a project as client A
fs.insert_tree(
"/a",
json!({
".zed.toml": r#"collaborators = ["user_b"]"#,
"a.rs": "let one = 1;",
}),
)
.await;
let project_a = cx_a.update(|cx| {
Project::local(
client_a.clone(),
client_a.user_store.clone(),
lang_registry.clone(),
fs.clone(),
cx,
)
});
let (worktree_a, _) = project_a
.update(cx_a, |p, cx| {
p.find_or_create_local_worktree("/a", true, cx)
})
.await
.unwrap();
worktree_a
.read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
.await;
let project_id = project_a.update(cx_a, |p, _| p.next_remote_id()).await;
let worktree_id = worktree_a.read_with(cx_a, |tree, _| tree.id());
project_a.update(cx_a, |p, cx| p.share(cx)).await.unwrap();
let buffer_a = project_a
.update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))
.await
.unwrap();
// Join the worktree as client B.
let project_b = Project::remote(
project_id,
client_b.clone(),
client_b.user_store.clone(),
lang_registry.clone(),
fs.clone(),
&mut cx_b.to_async(),
)
.await
.unwrap();
let buffer_b = cx_b
.background()
.spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)))
.await
.unwrap();
buffer_b.update(cx_b, |buffer, cx| {
buffer.edit([4..7], "six", cx);
buffer.edit([10..11], "6", cx);
assert_eq!(buffer.text(), "let six = 6;");
assert!(buffer.is_dirty());
assert!(!buffer.has_conflict());
});
buffer_a
.condition(cx_a, |buffer, _| buffer.text() == "let six = 6;")
.await;
fs.save(Path::new("/a/a.rs"), &Rope::from("let seven = 7;"))
.await
.unwrap();
buffer_a
.condition(cx_a, |buffer, _| buffer.has_conflict())
.await;
buffer_b
.condition(cx_b, |buffer, _| buffer.has_conflict())
.await;
project_b
.update(cx_b, |project, cx| {
project.reload_buffers(HashSet::from_iter([buffer_b.clone()]), true, cx)
})
.await
.unwrap();
buffer_a.read_with(cx_a, |buffer, _| {
assert_eq!(buffer.text(), "let seven = 7;");
assert!(!buffer.is_dirty());
assert!(!buffer.has_conflict());
});
buffer_b.read_with(cx_b, |buffer, _| {
assert_eq!(buffer.text(), "let seven = 7;");
assert!(!buffer.is_dirty());
assert!(!buffer.has_conflict());
});
buffer_a.update(cx_a, |buffer, cx| {
// Undoing on the host is a no-op when the reload was initiated by the guest.
buffer.undo(cx);
assert_eq!(buffer.text(), "let seven = 7;");
assert!(!buffer.is_dirty());
assert!(!buffer.has_conflict());
});
buffer_b.update(cx_b, |buffer, cx| {
// Undoing on the guest rolls back the buffer to before it was reloaded but the conflict gets cleared.
buffer.undo(cx);
assert_eq!(buffer.text(), "let six = 6;");
assert!(buffer.is_dirty());
assert!(!buffer.has_conflict());
});
}
#[gpui::test(iterations = 10)] #[gpui::test(iterations = 10)]
async fn test_formatting_buffer(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { async fn test_formatting_buffer(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
cx_a.foreground().forbid_parking(); cx_a.foreground().forbid_parking();