Move Buffer::format to Project::format

This commit is contained in:
Antonio Scandurra 2022-02-11 11:05:25 +01:00
parent 645df73a37
commit 4929b8c525
10 changed files with 290 additions and 177 deletions

View file

@ -572,8 +572,12 @@ impl workspace::ItemView for ProjectDiagnosticsEditor {
true
}
fn save(&mut self, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
self.editor.save(cx)
fn save(
&mut self,
project: ModelHandle<Project>,
cx: &mut ViewContext<Self>,
) -> Task<Result<()>> {
self.editor.save(project, cx)
}
fn can_save_as(&self, _: &AppContext) -> bool {

View file

@ -220,11 +220,15 @@ impl ItemView for Editor {
!self.buffer().read(cx).is_singleton() || self.project_path(cx).is_some()
}
fn save(&mut self, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
fn save(
&mut self,
project: ModelHandle<Project>,
cx: &mut ViewContext<Self>,
) -> Task<Result<()>> {
let buffer = self.buffer().clone();
cx.spawn(|editor, mut cx| async move {
buffer
.update(&mut cx, |buffer, cx| buffer.format(cx).log_err())
.update(&mut cx, |buffer, cx| buffer.format(project, cx).log_err())
.await;
editor.update(&mut cx, |editor, cx| {
editor.request_autoscroll(Autoscroll::Fit, cx)

View file

@ -10,6 +10,7 @@ use language::{
Buffer, BufferChunks, BufferSnapshot, Chunk, DiagnosticEntry, Event, File, Language, Outline,
OutlineItem, Selection, ToOffset as _, ToPoint as _, ToPointUtf16 as _, TransactionId,
};
use project::Project;
use std::{
cell::{Ref, RefCell},
cmp, fmt, io,
@ -930,16 +931,25 @@ impl MultiBuffer {
cx.emit(event.clone());
}
pub fn format(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
let mut format_tasks = Vec::new();
for BufferState { buffer, .. } in self.buffers.borrow().values() {
format_tasks.push(buffer.update(cx, |buffer, cx| buffer.format(cx)));
}
cx.spawn(|_, _| async move {
for format in format_tasks {
format.await?;
}
pub fn format(
&mut self,
project: ModelHandle<Project>,
cx: &mut ModelContext<Self>,
) -> Task<Result<()>> {
let buffers = self
.buffers
.borrow()
.values()
.map(|state| state.buffer.clone())
.collect();
let transaction = project.update(cx, |project, cx| project.format(buffers, true, cx));
cx.spawn(|this, mut cx| async move {
let transaction = transaction.await?;
this.update(&mut cx, |this, _| {
if !this.singleton {
this.push_transaction(&transaction.0);
}
});
Ok(())
})
}

View file

@ -201,9 +201,6 @@ pub trait File {
cx: &mut MutableAppContext,
) -> Task<Result<(clock::Global, SystemTime)>>;
fn format_remote(&self, buffer_id: u64, cx: &mut MutableAppContext)
-> Option<Task<Result<()>>>;
fn buffer_updated(&self, buffer_id: u64, operation: Operation, cx: &mut MutableAppContext);
fn buffer_removed(&self, buffer_id: u64, cx: &mut MutableAppContext);
@ -278,10 +275,6 @@ impl File for FakeFile {
cx.spawn(|_| async move { Ok((Default::default(), SystemTime::UNIX_EPOCH)) })
}
fn format_remote(&self, _: u64, _: &mut MutableAppContext) -> Option<Task<Result<()>>> {
None
}
fn buffer_updated(&self, _: u64, _: Operation, _: &mut MutableAppContext) {}
fn buffer_removed(&self, _: u64, _: &mut MutableAppContext) {}
@ -540,52 +533,6 @@ impl Buffer {
self.file.as_deref()
}
pub fn format(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
let file = if let Some(file) = self.file.as_ref() {
file
} else {
return Task::ready(Err(anyhow!("buffer has no file")));
};
if let Some(LanguageServerState { server, .. }) = self.language_server.as_ref() {
let server = server.clone();
let abs_path = file.as_local().unwrap().abs_path(cx);
let version = self.version();
cx.spawn(|this, mut cx| async move {
let edits = server
.request::<lsp::request::Formatting>(lsp::DocumentFormattingParams {
text_document: lsp::TextDocumentIdentifier::new(
lsp::Url::from_file_path(&abs_path).unwrap(),
),
options: Default::default(),
work_done_progress_params: Default::default(),
})
.await?;
if let Some(edits) = edits {
this.update(&mut cx, |this, cx| {
if this.version == version {
this.apply_lsp_edits(edits, None, cx)?;
Ok(())
} else {
Err(anyhow!("buffer edited since starting to format"))
}
})
} else {
Ok(())
}
})
} else {
let format = file.format_remote(self.remote_id(), cx.as_mut());
cx.spawn(|_, _| async move {
if let Some(format) = format {
format.await?;
}
Ok(())
})
}
}
pub fn save(
&mut self,
cx: &mut ModelContext<Self>,

View file

@ -348,7 +348,7 @@ impl Project {
client.subscribe_to_entity(remote_id, cx, Self::handle_update_buffer),
client.subscribe_to_entity(remote_id, cx, Self::handle_save_buffer),
client.subscribe_to_entity(remote_id, cx, Self::handle_buffer_saved),
client.subscribe_to_entity(remote_id, cx, Self::handle_format_buffer),
client.subscribe_to_entity(remote_id, cx, Self::handle_format_buffers),
client.subscribe_to_entity(remote_id, cx, Self::handle_get_completions),
client.subscribe_to_entity(
remote_id,
@ -613,9 +613,7 @@ impl Project {
})
.await?;
let buffer = response.buffer.ok_or_else(|| anyhow!("missing buffer"))?;
this.update(&mut cx, |this, cx| {
this.deserialize_remote_buffer(buffer, cx)
})
this.update(&mut cx, |this, cx| this.deserialize_buffer(buffer, cx))
})
}
@ -1045,6 +1043,112 @@ impl Project {
Ok(())
}
pub fn format(
&self,
buffers: HashSet<ModelHandle<Buffer>>,
push_to_history: bool,
cx: &mut ModelContext<Project>,
) -> 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);
let worktree;
if let Some(file) = File::from_dyn(buffer.file()) {
worktree = file.worktree.clone();
if let Some(buffer_abs_path) = file.as_local().map(|f| f.abs_path(cx)) {
let lang_server;
if let Some(lang) = buffer.language() {
if let Some(server) = self
.language_servers
.get(&(worktree.read(cx).id(), lang.name().to_string()))
{
lang_server = server.clone();
} else {
return Task::ready(Err(anyhow!(
"buffer {} does not have a language server",
buffer.remote_id()
)));
};
} else {
return Task::ready(Err(anyhow!("buffer does not have a language")));
}
local_buffers.push((buffer_handle, buffer_abs_path, lang_server));
} else {
remote_buffers.get_or_insert(Vec::new()).push(buffer_handle);
}
} else {
return Task::ready(Err(anyhow!(
"buffer {} does not belong to any worktree",
buffer.remote_id()
)));
}
}
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::FormatBuffers {
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, buffer_abs_path, lang_server) in local_buffers {
let lsp_edits = lang_server
.request::<lsp::request::Formatting>(lsp::DocumentFormattingParams {
text_document: lsp::TextDocumentIdentifier::new(
lsp::Url::from_file_path(&buffer_abs_path).unwrap(),
),
options: Default::default(),
work_done_progress_params: Default::default(),
})
.await?;
if let Some(lsp_edits) = lsp_edits {
let edits = buffer
.update(&mut cx, |buffer, cx| {
buffer.edits_from_lsp(lsp_edits, None, cx)
})
.await?;
buffer.update(&mut cx, |buffer, cx| {
buffer.finalize_last_transaction();
buffer.start_transaction();
for (range, text) in edits {
buffer.edit([range], text, cx);
}
if buffer.end_transaction(cx).is_some() {
let transaction = buffer.finalize_last_transaction().unwrap().clone();
if !push_to_history {
buffer.forget_transaction(transaction.id);
}
project_transaction.0.insert(cx.handle(), transaction);
}
});
}
}
Ok(project_transaction)
})
}
pub fn definition<T: ToPointUtf16>(
&self,
source_buffer_handle: &ModelHandle<Buffer>,
@ -1156,7 +1260,7 @@ impl Project {
this.update(&mut cx, |this, cx| {
let mut definitions = Vec::new();
for definition in response.definitions {
let target_buffer = this.deserialize_remote_buffer(
let target_buffer = this.deserialize_buffer(
definition.buffer.ok_or_else(|| anyhow!("missing buffer"))?,
cx,
)?;
@ -1637,29 +1741,10 @@ impl Project {
.await?
.transaction
.ok_or_else(|| anyhow!("missing transaction"))?;
let mut project_transaction = ProjectTransaction::default();
for (buffer, transaction) in response.buffers.into_iter().zip(response.transactions)
{
let buffer = this.update(&mut cx, |this, cx| {
this.deserialize_remote_buffer(buffer, cx)
})?;
let transaction = language::proto::deserialize_transaction(transaction)?;
buffer
.update(&mut cx, |buffer, _| {
buffer.wait_for_edits(transaction.edit_ids.iter().copied())
})
.await;
if push_to_history {
buffer.update(&mut cx, |buffer, _| {
buffer.push_transaction(transaction.clone(), Instant::now());
});
}
project_transaction.0.insert(buffer, transaction);
}
Ok(project_transaction)
this.update(&mut cx, |this, cx| {
this.deserialize_project_transaction(response, push_to_history, cx)
})
.await
})
} else {
Task::ready(Err(anyhow!("project does not have a remote id")))
@ -2163,26 +2248,51 @@ impl Project {
Ok(())
}
pub fn handle_format_buffer(
pub fn handle_format_buffers(
&mut self,
envelope: TypedEnvelope<proto::FormatBuffer>,
envelope: TypedEnvelope<proto::FormatBuffers>,
rpc: Arc<Client>,
cx: &mut ModelContext<Self>,
) -> Result<()> {
let receipt = envelope.receipt();
let sender_id = envelope.original_sender_id()?;
let buffer = self
let shared_buffers = self
.shared_buffers
.get(&sender_id)
.and_then(|shared_buffers| shared_buffers.get(&envelope.payload.buffer_id).cloned())
.ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))?;
cx.spawn(|_, mut cx| async move {
let format = buffer.update(&mut cx, |buffer, cx| buffer.format(cx)).await;
// We spawn here in order to enqueue the sending of `Ack` *after* transmission of edits
// associated with formatting.
.ok_or_else(|| anyhow!("peer has no buffers"))?;
let mut buffers = HashSet::default();
for buffer_id in envelope.payload.buffer_ids {
buffers.insert(
shared_buffers
.get(&buffer_id)
.cloned()
.ok_or_else(|| anyhow!("unknown buffer id {}", buffer_id))?,
);
}
cx.spawn(|this, mut cx| async move {
dbg!("here!");
let project_transaction = this
.update(&mut cx, |this, cx| this.format(buffers, false, cx))
.await
.map(|project_transaction| {
this.update(&mut cx, |this, cx| {
this.serialize_project_transaction_for_peer(
project_transaction,
sender_id,
cx,
)
})
});
// We spawn here in order to enqueue the sending of the response *after* transmission of
// edits associated with formatting.
cx.spawn(|_| async move {
match format {
Ok(()) => rpc.respond(receipt, proto::Ack {})?,
match project_transaction {
Ok(transaction) => rpc.respond(
receipt,
proto::FormatBuffersResponse {
transaction: Some(transaction),
},
)?,
Err(error) => rpc.respond_with_error(
receipt,
proto::Error {
@ -2358,18 +2468,11 @@ impl Project {
cx.spawn(|this, mut cx| async move {
match apply_code_action.await {
Ok(project_transaction) => this.update(&mut cx, |this, cx| {
let mut serialized_transaction = proto::ProjectTransaction {
buffers: Default::default(),
transactions: Default::default(),
};
for (buffer, transaction) in project_transaction.0 {
serialized_transaction
.buffers
.push(this.serialize_buffer_for_peer(&buffer, sender_id, cx));
serialized_transaction
.transactions
.push(language::proto::serialize_transaction(&transaction));
}
let serialized_transaction = this.serialize_project_transaction_for_peer(
project_transaction,
sender_id,
cx,
);
rpc.respond(
receipt,
proto::ApplyCodeActionResponse {
@ -2471,6 +2574,58 @@ impl Project {
Ok(())
}
fn serialize_project_transaction_for_peer(
&mut self,
project_transaction: ProjectTransaction,
peer_id: PeerId,
cx: &AppContext,
) -> proto::ProjectTransaction {
let mut serialized_transaction = proto::ProjectTransaction {
buffers: Default::default(),
transactions: Default::default(),
};
for (buffer, transaction) in project_transaction.0 {
serialized_transaction
.buffers
.push(self.serialize_buffer_for_peer(&buffer, peer_id, cx));
serialized_transaction
.transactions
.push(language::proto::serialize_transaction(&transaction));
}
serialized_transaction
}
fn deserialize_project_transaction(
&self,
message: proto::ProjectTransaction,
push_to_history: bool,
cx: &mut ModelContext<Self>,
) -> Task<Result<ProjectTransaction>> {
cx.spawn(|this, mut cx| async move {
let mut project_transaction = ProjectTransaction::default();
for (buffer, transaction) in message.buffers.into_iter().zip(message.transactions) {
let buffer =
this.update(&mut cx, |this, cx| this.deserialize_buffer(buffer, cx))?;
let transaction = language::proto::deserialize_transaction(transaction)?;
buffer
.update(&mut cx, |buffer, _| {
buffer.wait_for_edits(transaction.edit_ids.iter().copied())
})
.await;
if push_to_history {
buffer.update(&mut cx, |buffer, _| {
buffer.push_transaction(transaction.clone(), Instant::now());
});
}
project_transaction.0.insert(buffer, transaction);
}
Ok(project_transaction)
})
}
fn serialize_buffer_for_peer(
&mut self,
buffer: &ModelHandle<Buffer>,
@ -2492,7 +2647,7 @@ impl Project {
}
}
fn deserialize_remote_buffer(
fn deserialize_buffer(
&mut self,
buffer: proto::Buffer,
cx: &mut ModelContext<Self>,

View file

@ -1385,25 +1385,6 @@ impl language::File for File {
})
}
fn format_remote(
&self,
buffer_id: u64,
cx: &mut MutableAppContext,
) -> Option<Task<Result<()>>> {
let worktree = self.worktree.read(cx);
let worktree = worktree.as_remote()?;
let rpc = worktree.client.clone();
let project_id = worktree.project_id;
Some(cx.foreground().spawn(async move {
rpc.request(proto::FormatBuffer {
project_id,
buffer_id,
})
.await?;
Ok(())
}))
}
fn buffer_updated(&self, buffer_id: u64, operation: Operation, cx: &mut MutableAppContext) {
self.worktree.update(cx, |worktree, cx| {
worktree.send_buffer_update(buffer_id, operation, cx);

View file

@ -39,31 +39,32 @@ message Envelope {
SaveBuffer save_buffer = 31;
BufferSaved buffer_saved = 32;
BufferReloaded buffer_reloaded = 33;
FormatBuffer format_buffer = 34;
GetCompletions get_completions = 35;
GetCompletionsResponse get_completions_response = 36;
ApplyCompletionAdditionalEdits apply_completion_additional_edits = 37;
ApplyCompletionAdditionalEditsResponse apply_completion_additional_edits_response = 38;
GetCodeActions get_code_actions = 39;
GetCodeActionsResponse get_code_actions_response = 40;
ApplyCodeAction apply_code_action = 41;
ApplyCodeActionResponse apply_code_action_response = 42;
FormatBuffers format_buffers = 34;
FormatBuffersResponse format_buffers_response = 35;
GetCompletions get_completions = 36;
GetCompletionsResponse get_completions_response = 37;
ApplyCompletionAdditionalEdits apply_completion_additional_edits = 38;
ApplyCompletionAdditionalEditsResponse apply_completion_additional_edits_response = 39;
GetCodeActions get_code_actions = 40;
GetCodeActionsResponse get_code_actions_response = 41;
ApplyCodeAction apply_code_action = 42;
ApplyCodeActionResponse apply_code_action_response = 43;
GetChannels get_channels = 43;
GetChannelsResponse get_channels_response = 44;
JoinChannel join_channel = 45;
JoinChannelResponse join_channel_response = 46;
LeaveChannel leave_channel = 47;
SendChannelMessage send_channel_message = 48;
SendChannelMessageResponse send_channel_message_response = 49;
ChannelMessageSent channel_message_sent = 50;
GetChannelMessages get_channel_messages = 51;
GetChannelMessagesResponse get_channel_messages_response = 52;
GetChannels get_channels = 44;
GetChannelsResponse get_channels_response = 45;
JoinChannel join_channel = 46;
JoinChannelResponse join_channel_response = 47;
LeaveChannel leave_channel = 48;
SendChannelMessage send_channel_message = 49;
SendChannelMessageResponse send_channel_message_response = 50;
ChannelMessageSent channel_message_sent = 51;
GetChannelMessages get_channel_messages = 52;
GetChannelMessagesResponse get_channel_messages_response = 53;
UpdateContacts update_contacts = 53;
UpdateContacts update_contacts = 54;
GetUsers get_users = 54;
GetUsersResponse get_users_response = 55;
GetUsers get_users = 55;
GetUsersResponse get_users_response = 56;
}
}
@ -206,9 +207,13 @@ message BufferReloaded {
Timestamp mtime = 4;
}
message FormatBuffer {
message FormatBuffers {
uint64 project_id = 1;
uint64 buffer_id = 2;
repeated uint64 buffer_ids = 2;
}
message FormatBuffersResponse {
ProjectTransaction transaction = 1;
}
message GetCompletions {

View file

@ -133,7 +133,8 @@ messages!(
DiskBasedDiagnosticsUpdated,
DiskBasedDiagnosticsUpdating,
Error,
FormatBuffer,
FormatBuffers,
FormatBuffersResponse,
GetChannelMessages,
GetChannelMessagesResponse,
GetChannels,
@ -180,7 +181,7 @@ request_messages!(
ApplyCompletionAdditionalEdits,
ApplyCompletionAdditionalEditsResponse
),
(FormatBuffer, Ack),
(FormatBuffers, FormatBuffersResponse),
(GetChannelMessages, GetChannelMessagesResponse),
(GetChannels, GetChannelsResponse),
(GetCodeActions, GetCodeActionsResponse),
@ -210,7 +211,7 @@ entity_messages!(
CloseBuffer,
DiskBasedDiagnosticsUpdated,
DiskBasedDiagnosticsUpdating,
FormatBuffer,
FormatBuffers,
GetCodeActions,
GetCompletions,
GetDefinition,

View file

@ -82,7 +82,7 @@ impl Server {
.add_handler(Server::buffer_reloaded)
.add_handler(Server::buffer_saved)
.add_handler(Server::save_buffer)
.add_handler(Server::format_buffer)
.add_handler(Server::format_buffers)
.add_handler(Server::get_completions)
.add_handler(Server::apply_additional_edits_for_completion)
.add_handler(Server::get_code_actions)
@ -669,9 +669,9 @@ impl Server {
Ok(())
}
async fn format_buffer(
async fn format_buffers(
self: Arc<Server>,
request: TypedEnvelope<proto::FormatBuffer>,
request: TypedEnvelope<proto::FormatBuffers>,
) -> tide::Result<()> {
let host;
{
@ -2607,7 +2607,9 @@ mod tests {
.await
.unwrap();
let format = buffer_b.update(&mut cx_b, |buffer, cx| buffer.format(cx));
let format = project_b.update(&mut cx_b, |project, cx| {
project.format(HashSet::from_iter([buffer_b.clone()]), true, cx)
});
let (request_id, _) = fake_language_server
.receive_request::<lsp::request::Formatting>()
.await;

View file

@ -168,7 +168,11 @@ pub trait ItemView: View {
false
}
fn can_save(&self, cx: &AppContext) -> bool;
fn save(&mut self, cx: &mut ViewContext<Self>) -> Task<Result<()>>;
fn save(
&mut self,
project: ModelHandle<Project>,
cx: &mut ViewContext<Self>,
) -> Task<Result<()>>;
fn can_save_as(&self, cx: &AppContext) -> bool;
fn save_as(
&mut self,
@ -234,7 +238,7 @@ pub trait ItemViewHandle: 'static {
fn has_conflict(&self, cx: &AppContext) -> bool;
fn can_save(&self, cx: &AppContext) -> bool;
fn can_save_as(&self, cx: &AppContext) -> bool;
fn save(&self, cx: &mut MutableAppContext) -> Task<Result<()>>;
fn save(&self, project: ModelHandle<Project>, cx: &mut MutableAppContext) -> Task<Result<()>>;
fn save_as(
&self,
project: ModelHandle<Project>,
@ -402,8 +406,8 @@ impl<T: ItemView> ItemViewHandle for ViewHandle<T> {
self.update(cx, |this, cx| this.navigate(data, cx));
}
fn save(&self, cx: &mut MutableAppContext) -> Task<Result<()>> {
self.update(cx, |item, cx| item.save(cx))
fn save(&self, project: ModelHandle<Project>, cx: &mut MutableAppContext) -> Task<Result<()>> {
self.update(cx, |item, cx| item.save(project, cx))
}
fn save_as(
@ -820,6 +824,7 @@ impl Workspace {
}
pub fn save_active_item(&mut self, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
let project = self.project.clone();
if let Some(item) = self.active_item(cx) {
if item.can_save(cx) {
if item.has_conflict(cx.as_ref()) {
@ -833,12 +838,12 @@ impl Workspace {
cx.spawn(|_, mut cx| async move {
let answer = answer.recv().await;
if answer == Some(0) {
cx.update(|cx| item.save(cx)).await?;
cx.update(|cx| item.save(project, cx)).await?;
}
Ok(())
})
} else {
item.save(cx)
item.save(project, cx)
}
} else if item.can_save_as(cx) {
let worktree = self.worktrees(cx).next();
@ -847,9 +852,8 @@ impl Workspace {
.map_or(Path::new(""), |w| w.abs_path())
.to_path_buf();
let mut abs_path = cx.prompt_for_new_path(&start_abs_path);
cx.spawn(|this, mut cx| async move {
cx.spawn(|_, mut cx| async move {
if let Some(abs_path) = abs_path.recv().await.flatten() {
let project = this.read_with(&cx, |this, _| this.project().clone());
cx.update(|cx| item.save_as(project, abs_path, cx)).await?;
}
Ok(())