Detailed changes
@@ -2078,7 +2078,7 @@ impl Editor {
})?;
let apply_code_actions = workspace.project().update(cx, |project, cx| {
- project.apply_code_action(buffer, action, cx)
+ project.apply_code_action(buffer, action, true, cx)
});
Some(cx.spawn(|workspace, mut cx| async move {
let buffers = apply_code_actions.await?;
@@ -216,6 +216,13 @@ pub trait File {
cx: &mut MutableAppContext,
) -> Task<Result<Vec<clock::Local>>>;
+ fn code_actions(
+ &self,
+ buffer_id: u64,
+ position: Anchor,
+ cx: &mut MutableAppContext,
+ ) -> Task<Result<Vec<CodeAction<Anchor>>>>;
+
fn buffer_updated(&self, buffer_id: u64, operation: Operation, cx: &mut MutableAppContext);
fn buffer_removed(&self, buffer_id: u64, cx: &mut MutableAppContext);
@@ -304,6 +311,15 @@ impl File for FakeFile {
Task::ready(Ok(Default::default()))
}
+ fn code_actions(
+ &self,
+ _: u64,
+ _: Anchor,
+ _: &mut MutableAppContext,
+ ) -> Task<Result<Vec<CodeAction<Anchor>>>> {
+ Task::ready(Ok(Default::default()))
+ }
+
fn buffer_updated(&self, _: u64, _: Operation, _: &mut MutableAppContext) {}
fn buffer_removed(&self, _: u64, _: &mut MutableAppContext) {}
@@ -1350,10 +1366,29 @@ impl Buffer {
}
}
+ pub fn push_transaction(
+ &mut self,
+ edit_ids: impl IntoIterator<Item = clock::Local>,
+ now: Instant,
+ ) {
+ self.text.push_transaction(edit_ids, now);
+ }
+
pub fn avoid_grouping_next_transaction(&mut self) {
self.text.avoid_grouping_next_transaction();
}
+ pub fn forget_transaction(&mut self, transaction_id: TransactionId) {
+ self.text.forget_transaction(transaction_id);
+ }
+
+ pub fn wait_for_edits(
+ &mut self,
+ edit_ids: impl IntoIterator<Item = clock::Local>,
+ ) -> impl Future<Output = ()> {
+ self.text.wait_for_edits(edit_ids)
+ }
+
pub fn set_active_selections(
&mut self,
selections: Arc<[Selection<Anchor>]>,
@@ -1873,6 +1908,8 @@ impl Buffer {
} else {
return Task::ready(Ok(Default::default()));
};
+ let position = position.to_point_utf16(self);
+ let anchor = self.anchor_after(position);
if let Some(file) = file.as_local() {
let server = if let Some(language_server) = self.language_server.as_ref() {
@@ -1881,8 +1918,6 @@ impl Buffer {
return Task::ready(Ok(Default::default()));
};
let abs_path = file.abs_path(cx);
- let position = position.to_point_utf16(self);
- let anchor = self.anchor_after(position);
cx.foreground().spawn(async move {
let actions = server
@@ -1922,8 +1957,7 @@ impl Buffer {
Ok(actions)
})
} else {
- log::info!("code actions are not implemented for guests");
- Task::ready(Ok(Default::default()))
+ file.code_actions(self.remote_id(), anchor, cx.as_mut())
}
}
@@ -1959,7 +1993,7 @@ impl Buffer {
let edits = this.apply_lsp_edits(additional_edits, None, cx);
if let Some(transaction_id) = this.end_transaction(cx) {
if !push_to_history {
- this.text.forget_transaction(transaction_id);
+ this.forget_transaction(transaction_id);
}
}
Ok(edits?.into_iter().map(|(_, edit_id)| edit_id).collect())
@@ -1976,12 +2010,13 @@ impl Buffer {
);
cx.spawn(|this, mut cx| async move {
let edit_ids = apply_edits.await?;
- this.update(&mut cx, |this, _| this.text.wait_for_edits(&edit_ids))
- .await;
+ this.update(&mut cx, |this, _| {
+ this.wait_for_edits(edit_ids.iter().copied())
+ })
+ .await;
if push_to_history {
this.update(&mut cx, |this, _| {
- this.text
- .push_transaction(edit_ids.iter().copied(), Instant::now());
+ this.push_transaction(edit_ids.iter().copied(), Instant::now());
});
}
Ok(edit_ids)
@@ -1,12 +1,13 @@
use crate::{
- diagnostic_set::DiagnosticEntry, Completion, CompletionLabel, Diagnostic, Language, Operation,
+ diagnostic_set::DiagnosticEntry, CodeAction, Completion, CompletionLabel, Diagnostic, Language,
+ Operation,
};
use anyhow::{anyhow, Result};
use clock::ReplicaId;
use collections::HashSet;
use lsp::DiagnosticSeverity;
use rpc::proto;
-use std::sync::Arc;
+use std::{ops::Range, sync::Arc};
use text::*;
pub use proto::{Buffer, BufferState, SelectionSet};
@@ -411,3 +412,62 @@ pub fn deserialize_completion(
lsp_completion,
})
}
+
+pub fn serialize_code_action(action: &CodeAction<Anchor>) -> proto::CodeAction {
+ proto::CodeAction {
+ position: Some(serialize_anchor(&action.position)),
+ lsp_action: serde_json::to_vec(&action.lsp_action).unwrap(),
+ }
+}
+
+pub fn deserialize_code_action(action: proto::CodeAction) -> Result<CodeAction<Anchor>> {
+ let position = action
+ .position
+ .and_then(deserialize_anchor)
+ .ok_or_else(|| anyhow!("invalid position"))?;
+ let lsp_action = serde_json::from_slice(&action.lsp_action)?;
+ Ok(CodeAction {
+ position,
+ lsp_action,
+ })
+}
+
+pub fn serialize_code_action_edit(
+ edit_id: clock::Local,
+ old_range: &Range<Anchor>,
+) -> proto::CodeActionEdit {
+ proto::CodeActionEdit {
+ id: Some(serialize_edit_id(edit_id)),
+ old_start: Some(serialize_anchor(&old_range.start)),
+ old_end: Some(serialize_anchor(&old_range.end)),
+ }
+}
+
+pub fn deserialize_code_action_edit(
+ edit: proto::CodeActionEdit,
+) -> Result<(Range<Anchor>, clock::Local)> {
+ let old_start = edit
+ .old_start
+ .and_then(deserialize_anchor)
+ .ok_or_else(|| anyhow!("invalid old_start"))?;
+ let old_end = edit
+ .old_end
+ .and_then(deserialize_anchor)
+ .ok_or_else(|| anyhow!("invalid old_end"))?;
+ let edit_id = deserialize_edit_id(edit.id.ok_or_else(|| anyhow!("invalid edit_id"))?);
+ Ok((old_start..old_end, edit_id))
+}
+
+pub fn serialize_edit_id(edit_id: clock::Local) -> proto::EditId {
+ proto::EditId {
+ replica_id: edit_id.replica_id as u32,
+ local_timestamp: edit_id.value,
+ }
+}
+
+pub fn deserialize_edit_id(edit_id: proto::EditId) -> clock::Local {
+ clock::Local {
+ replica_id: edit_id.replica_id as ReplicaId,
+ value: edit_id.local_timestamp,
+ }
+}
@@ -26,6 +26,7 @@ use std::{
ops::Range,
path::{Path, PathBuf},
sync::{atomic::AtomicBool, Arc},
+ time::Instant,
};
use util::{post_inc, ResultExt, TryFutureExt as _};
@@ -341,6 +342,8 @@ impl Project {
cx,
Self::handle_apply_additional_edits_for_completion,
),
+ client.subscribe_to_entity(remote_id, cx, Self::handle_get_code_actions),
+ client.subscribe_to_entity(remote_id, cx, Self::handle_apply_code_action),
client.subscribe_to_entity(remote_id, cx, Self::handle_get_definition),
]);
}
@@ -1169,6 +1172,7 @@ impl Project {
&self,
buffer_handle: ModelHandle<Buffer>,
mut action: CodeAction<language::Anchor>,
+ push_to_history: bool,
cx: &mut ModelContext<Self>,
) -> Task<Result<HashMap<ModelHandle<Buffer>, Vec<(Range<language::Anchor>, clock::Local)>>>>
{
@@ -1299,7 +1303,19 @@ impl Project {
lsp::OneOf::Left(edit) => edit,
lsp::OneOf::Right(edit) => edit.text_edit,
});
- buffer.apply_lsp_edits(edits, op.text_document.version, cx)
+ if !push_to_history {
+ buffer.avoid_grouping_next_transaction();
+ }
+ buffer.start_transaction();
+ let edits =
+ buffer.apply_lsp_edits(edits, op.text_document.version, cx);
+ if let Some(transaction_id) = buffer.end_transaction(cx) {
+ if !push_to_history {
+ buffer.forget_transaction(transaction_id);
+ }
+ }
+
+ edits
})?;
edited_buffers
.entry(buffer_to_edit)
@@ -1309,51 +1325,49 @@ impl Project {
}
}
+ Ok(edited_buffers)
+ })
+ } else if let Some(project_id) = self.remote_id() {
+ let client = self.client.clone();
+ let request = proto::ApplyCodeAction {
+ project_id,
+ buffer_id: buffer_handle.read(cx).remote_id(),
+ action: Some(language::proto::serialize_code_action(&action)),
+ };
+ cx.spawn(|this, mut cx| async move {
+ let response = client.request(request).await?;
+ let mut edited_buffers = HashMap::default();
+ for buffer_edit in response.buffer_edits {
+ let buffer = buffer_edit
+ .buffer
+ .ok_or_else(|| anyhow!("invalid buffer"))?;
+ let buffer = this.update(&mut cx, |this, cx| {
+ this.deserialize_remote_buffer(buffer, cx)
+ })?;
+
+ let buffer_edits = edited_buffers.entry(buffer.clone()).or_insert(Vec::new());
+ for edit in buffer_edit.edits {
+ buffer_edits.push(language::proto::deserialize_code_action_edit(edit)?);
+ }
+
+ buffer
+ .update(&mut cx, |buffer, _| {
+ buffer.wait_for_edits(buffer_edits.iter().map(|e| e.1))
+ })
+ .await;
+
+ if push_to_history {
+ buffer.update(&mut cx, |buffer, _| {
+ buffer
+ .push_transaction(buffer_edits.iter().map(|e| e.1), Instant::now());
+ });
+ }
+ }
Ok(edited_buffers)
})
} else {
- log::info!("applying code actions is not implemented for guests");
- Task::ready(Ok(Default::default()))
+ Task::ready(Err(anyhow!("project does not have a remote id")))
}
- // let file = if let Some(file) = self.file.as_ref() {
- // file
- // } else {
- // return Task::ready(Ok(Default::default()));
- // };
-
- // if file.is_local() {
- // let server = if let Some(language_server) = self.language_server.as_ref() {
- // language_server.server.clone()
- // } else {
- // return Task::ready(Ok(Default::default()));
- // };
- // let position = action.position.to_point_utf16(self).to_lsp_position();
-
- // cx.spawn(|this, mut cx| async move {
- // let range = action
- // .lsp_action
- // .data
- // .as_mut()
- // .and_then(|d| d.get_mut("codeActionParams"))
- // .and_then(|d| d.get_mut("range"))
- // .ok_or_else(|| anyhow!("code action has no range"))?;
- // *range = serde_json::to_value(&lsp::Range::new(position, position)).unwrap();
- // let action = server
- // .request::<lsp::request::CodeActionResolveRequest>(action.lsp_action)
- // .await?;
- // let edit = action
- // .edit
- // .ok_or_else(|| anyhow!("code action has no edit"));
- // match edit {
- // Ok(edit) => edit.,
- // Err(_) => todo!(),
- // }
- // Ok(Default::default())
- // })
- // } else {
- // log::info!("applying code actions is not implemented for guests");
- // Task::ready(Ok(Default::default()))
- // }
}
pub fn find_or_create_local_worktree(
@@ -1951,7 +1965,7 @@ impl Project {
envelope
.payload
.completion
- .ok_or_else(|| anyhow!("invalid position"))?,
+ .ok_or_else(|| anyhow!("invalid completion"))?,
language,
)?;
cx.spawn(|_, mut cx| async move {
@@ -1966,10 +1980,51 @@ impl Project {
proto::ApplyCompletionAdditionalEditsResponse {
additional_edits: edit_ids
.into_iter()
- .map(|edit_id| proto::AdditionalEdit {
- replica_id: edit_id.replica_id as u32,
- local_timestamp: edit_id.value,
- })
+ .map(language::proto::serialize_edit_id)
+ .collect(),
+ },
+ ),
+ Err(error) => rpc.respond_with_error(
+ receipt,
+ proto::Error {
+ message: error.to_string(),
+ },
+ ),
+ }
+ })
+ .detach_and_log_err(cx);
+ Ok(())
+ }
+
+ fn handle_get_code_actions(
+ &mut self,
+ envelope: TypedEnvelope<proto::GetCodeActions>,
+ rpc: Arc<Client>,
+ cx: &mut ModelContext<Self>,
+ ) -> Result<()> {
+ let receipt = envelope.receipt();
+ let sender_id = envelope.original_sender_id()?;
+ let buffer = 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))?;
+ let position = envelope
+ .payload
+ .position
+ .and_then(language::proto::deserialize_anchor)
+ .ok_or_else(|| anyhow!("invalid position"))?;
+ cx.spawn(|_, mut cx| async move {
+ match buffer
+ .update(&mut cx, |buffer, cx| buffer.code_actions(position, cx))
+ .await
+ {
+ Ok(completions) => rpc.respond(
+ receipt,
+ proto::GetCodeActionsResponse {
+ actions: completions
+ .iter()
+ .map(language::proto::serialize_code_action)
.collect(),
},
),
@@ -1985,6 +2040,55 @@ impl Project {
Ok(())
}
+ fn handle_apply_code_action(
+ &mut self,
+ envelope: TypedEnvelope<proto::ApplyCodeAction>,
+ rpc: Arc<Client>,
+ cx: &mut ModelContext<Self>,
+ ) -> Result<()> {
+ let receipt = envelope.receipt();
+ let sender_id = envelope.original_sender_id()?;
+ let buffer = 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))?;
+ let action = language::proto::deserialize_code_action(
+ envelope
+ .payload
+ .action
+ .ok_or_else(|| anyhow!("invalid action"))?,
+ )?;
+ let apply_code_action = self.apply_code_action(buffer, action, false, cx);
+ cx.spawn(|this, mut cx| async move {
+ match apply_code_action.await {
+ Ok(edited_buffers) => this.update(&mut cx, |this, cx| {
+ let buffer_edits = edited_buffers
+ .into_iter()
+ .map(|(buffer, edits)| proto::CodeActionBufferEdits {
+ buffer: Some(this.serialize_buffer_for_peer(&buffer, sender_id, cx)),
+ edits: edits
+ .into_iter()
+ .map(|(range, edit_id)| {
+ language::proto::serialize_code_action_edit(edit_id, &range)
+ })
+ .collect(),
+ })
+ .collect();
+ rpc.respond(receipt, proto::ApplyCodeActionResponse { buffer_edits })
+ }),
+ Err(error) => rpc.respond_with_error(
+ receipt,
+ proto::Error {
+ message: error.to_string(),
+ },
+ ),
+ }
+ })
+ .detach_and_log_err(cx);
+ Ok(())
+ }
+
pub fn handle_get_definition(
&mut self,
envelope: TypedEnvelope<proto::GetDefinition>,
@@ -1469,14 +1469,43 @@ impl language::File for File {
Ok(response
.additional_edits
.into_iter()
- .map(|edit| clock::Local {
- replica_id: edit.replica_id as ReplicaId,
- value: edit.local_timestamp,
- })
+ .map(language::proto::deserialize_edit_id)
.collect())
})
}
+ fn code_actions(
+ &self,
+ buffer_id: u64,
+ position: Anchor,
+ cx: &mut MutableAppContext,
+ ) -> Task<Result<Vec<language::CodeAction<Anchor>>>> {
+ let worktree = self.worktree.read(cx);
+ let worktree = if let Some(worktree) = worktree.as_remote() {
+ worktree
+ } else {
+ return Task::ready(Err(anyhow!(
+ "remote code actions requested on a local worktree"
+ )));
+ };
+ let rpc = worktree.client.clone();
+ let project_id = worktree.project_id;
+ cx.foreground().spawn(async move {
+ let response = rpc
+ .request(proto::GetCodeActions {
+ project_id,
+ buffer_id,
+ position: Some(language::proto::serialize_anchor(&position)),
+ })
+ .await?;
+ response
+ .actions
+ .into_iter()
+ .map(language::proto::deserialize_code_action)
+ .collect()
+ })
+ }
+
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);
@@ -44,22 +44,26 @@ message Envelope {
GetCompletionsResponse get_completions_response = 36;
ApplyCompletionAdditionalEdits apply_completion_additional_edits = 37;
ApplyCompletionAdditionalEditsResponse apply_completion_additional_edits_response = 38;
-
- GetChannels get_channels = 39;
- GetChannelsResponse get_channels_response = 40;
- JoinChannel join_channel = 41;
- JoinChannelResponse join_channel_response = 42;
- LeaveChannel leave_channel = 43;
- SendChannelMessage send_channel_message = 44;
- SendChannelMessageResponse send_channel_message_response = 45;
- ChannelMessageSent channel_message_sent = 46;
- GetChannelMessages get_channel_messages = 47;
- GetChannelMessagesResponse get_channel_messages_response = 48;
-
- UpdateContacts update_contacts = 49;
-
- GetUsers get_users = 50;
- GetUsersResponse get_users_response = 51;
+ GetCodeActions get_code_actions = 39;
+ GetCodeActionsResponse get_code_actions_response = 40;
+ ApplyCodeAction apply_code_action = 41;
+ ApplyCodeActionResponse apply_code_action_response = 42;
+
+ 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;
+
+ UpdateContacts update_contacts = 53;
+
+ GetUsers get_users = 54;
+ GetUsersResponse get_users_response = 55;
}
}
@@ -224,12 +228,7 @@ message ApplyCompletionAdditionalEdits {
}
message ApplyCompletionAdditionalEditsResponse {
- repeated AdditionalEdit additional_edits = 1;
-}
-
-message AdditionalEdit {
- uint32 replica_id = 1;
- uint32 local_timestamp = 2;
+ repeated EditId additional_edits = 1;
}
message Completion {
@@ -239,6 +238,47 @@ message Completion {
bytes lsp_completion = 4;
}
+message GetCodeActions {
+ uint64 project_id = 1;
+ uint64 buffer_id = 2;
+ Anchor position = 3;
+}
+
+message GetCodeActionsResponse {
+ repeated CodeAction actions = 1;
+}
+
+message ApplyCodeAction {
+ uint64 project_id = 1;
+ uint64 buffer_id = 2;
+ CodeAction action = 3;
+}
+
+message ApplyCodeActionResponse {
+ repeated CodeActionBufferEdits buffer_edits = 1;
+}
+
+message CodeAction {
+ Anchor position = 1;
+ bytes lsp_action = 2;
+}
+
+message CodeActionBufferEdits {
+ Buffer buffer = 1;
+ repeated CodeActionEdit edits = 2;
+}
+
+message CodeActionEdit {
+ EditId id = 1;
+ Anchor old_start = 2;
+ Anchor old_end = 3;
+}
+
+message EditId {
+ uint32 replica_id = 1;
+ uint32 local_timestamp = 2;
+}
+
message UpdateDiagnosticSummary {
uint64 project_id = 1;
uint64 worktree_id = 2;
@@ -122,6 +122,8 @@ macro_rules! entity_messages {
messages!(
Ack,
AddProjectCollaborator,
+ ApplyCodeAction,
+ ApplyCodeActionResponse,
ApplyCompletionAdditionalEdits,
ApplyCompletionAdditionalEditsResponse,
BufferReloaded,
@@ -136,6 +138,8 @@ messages!(
GetChannelMessagesResponse,
GetChannels,
GetChannelsResponse,
+ GetCodeActions,
+ GetCodeActionsResponse,
GetCompletions,
GetCompletionsResponse,
GetDefinition,
@@ -171,6 +175,7 @@ messages!(
);
request_messages!(
+ (ApplyCodeAction, ApplyCodeActionResponse),
(
ApplyCompletionAdditionalEdits,
ApplyCompletionAdditionalEditsResponse
@@ -178,6 +183,7 @@ request_messages!(
(FormatBuffer, Ack),
(GetChannelMessages, GetChannelMessagesResponse),
(GetChannels, GetChannelsResponse),
+ (GetCodeActions, GetCodeActionsResponse),
(GetCompletions, GetCompletionsResponse),
(GetDefinition, GetDefinitionResponse),
(GetUsers, GetUsersResponse),
@@ -197,6 +203,7 @@ request_messages!(
entity_messages!(
project_id,
AddProjectCollaborator,
+ ApplyCodeAction,
ApplyCompletionAdditionalEdits,
BufferReloaded,
BufferSaved,
@@ -204,6 +211,7 @@ entity_messages!(
DiskBasedDiagnosticsUpdated,
DiskBasedDiagnosticsUpdating,
FormatBuffer,
+ GetCodeActions,
GetCompletions,
GetDefinition,
JoinProject,
@@ -85,6 +85,8 @@ impl Server {
.add_handler(Server::format_buffer)
.add_handler(Server::get_completions)
.add_handler(Server::apply_additional_edits_for_completion)
+ .add_handler(Server::get_code_actions)
+ .add_handler(Server::apply_code_action)
.add_handler(Server::get_channels)
.add_handler(Server::get_users)
.add_handler(Server::join_channel)
@@ -737,6 +739,52 @@ impl Server {
Ok(())
}
+ async fn get_code_actions(
+ self: Arc<Server>,
+ request: TypedEnvelope<proto::GetCodeActions>,
+ ) -> tide::Result<()> {
+ let host;
+ {
+ let state = self.state();
+ let project = state
+ .read_project(request.payload.project_id, request.sender_id)
+ .ok_or_else(|| anyhow!(NO_SUCH_PROJECT))?;
+ host = project.host_connection_id;
+ }
+
+ let sender = request.sender_id;
+ let receipt = request.receipt();
+ let response = self
+ .peer
+ .forward_request(sender, host, request.payload.clone())
+ .await?;
+ self.peer.respond(receipt, response)?;
+ Ok(())
+ }
+
+ async fn apply_code_action(
+ self: Arc<Server>,
+ request: TypedEnvelope<proto::ApplyCodeAction>,
+ ) -> tide::Result<()> {
+ let host;
+ {
+ let state = self.state();
+ let project = state
+ .read_project(request.payload.project_id, request.sender_id)
+ .ok_or_else(|| anyhow!(NO_SUCH_PROJECT))?;
+ host = project.host_connection_id;
+ }
+
+ let sender = request.sender_id;
+ let receipt = request.receipt();
+ let response = self
+ .peer
+ .forward_request(sender, host, request.payload.clone())
+ .await?;
+ self.peer.respond(receipt, response)?;
+ Ok(())
+ }
+
async fn update_buffer(
self: Arc<Server>,
request: TypedEnvelope<proto::UpdateBuffer>,
@@ -570,7 +570,6 @@ impl Buffer {
Self {
remote_id,
replica_id,
-
history: History::new("".into()),
deferred_ops: OperationQueue::new(),
deferred_replicas: Default::default(),
@@ -1294,13 +1293,13 @@ impl Buffer {
pub fn wait_for_edits(
&mut self,
- edit_ids: &[clock::Local],
+ edit_ids: impl IntoIterator<Item = clock::Local>,
) -> impl 'static + Future<Output = ()> {
let mut futures = Vec::new();
for edit_id in edit_ids {
- if !self.version.observed(*edit_id) {
+ if !self.version.observed(edit_id) {
let (tx, rx) = oneshot::channel();
- self.edit_id_resolvers.entry(*edit_id).or_default().push(tx);
+ self.edit_id_resolvers.entry(edit_id).or_default().push(tx);
futures.push(rx);
}
}