Automatically unfollow when editing, scrolling or changing selections

This commit is contained in:
Antonio Scandurra 2022-03-22 09:16:25 +01:00
parent c550fc3f01
commit 3117554568
14 changed files with 214 additions and 60 deletions

View file

@ -1035,14 +1035,19 @@ impl Editor {
self.scroll_top_anchor = Some(anchor); self.scroll_top_anchor = Some(anchor);
} }
cx.emit(Event::ScrollPositionChanged); cx.emit(Event::ScrollPositionChanged { local: true });
cx.notify(); cx.notify();
} }
fn set_scroll_top_anchor(&mut self, anchor: Option<Anchor>, cx: &mut ViewContext<Self>) { fn set_scroll_top_anchor(
&mut self,
anchor: Option<Anchor>,
local: bool,
cx: &mut ViewContext<Self>,
) {
self.scroll_position = Vector2F::zero(); self.scroll_position = Vector2F::zero();
self.scroll_top_anchor = anchor; self.scroll_top_anchor = anchor;
cx.emit(Event::ScrollPositionChanged); cx.emit(Event::ScrollPositionChanged { local });
cx.notify(); cx.notify();
} }
@ -1267,7 +1272,7 @@ impl Editor {
_ => {} _ => {}
} }
self.set_selections(self.selections.clone(), Some(pending), cx); self.set_selections(self.selections.clone(), Some(pending), true, cx);
} }
fn begin_selection( fn begin_selection(
@ -1347,7 +1352,12 @@ impl Editor {
} else { } else {
selections = Arc::from([]); selections = Arc::from([]);
} }
self.set_selections(selections, Some(PendingSelection { selection, mode }), cx); self.set_selections(
selections,
Some(PendingSelection { selection, mode }),
true,
cx,
);
cx.notify(); cx.notify();
} }
@ -1461,7 +1471,7 @@ impl Editor {
pending.selection.end = buffer.anchor_before(head); pending.selection.end = buffer.anchor_before(head);
pending.selection.reversed = false; pending.selection.reversed = false;
} }
self.set_selections(self.selections.clone(), Some(pending), cx); self.set_selections(self.selections.clone(), Some(pending), true, cx);
} else { } else {
log::error!("update_selection dispatched with no pending selection"); log::error!("update_selection dispatched with no pending selection");
return; return;
@ -1548,7 +1558,7 @@ impl Editor {
if selections.is_empty() { if selections.is_empty() {
selections = Arc::from([pending.selection]); selections = Arc::from([pending.selection]);
} }
self.set_selections(selections, None, cx); self.set_selections(selections, None, true, cx);
self.request_autoscroll(Autoscroll::Fit, cx); self.request_autoscroll(Autoscroll::Fit, cx);
} else { } else {
let mut oldest_selection = self.oldest_selection::<usize>(&cx); let mut oldest_selection = self.oldest_selection::<usize>(&cx);
@ -1895,7 +1905,7 @@ impl Editor {
} }
drop(snapshot); drop(snapshot);
self.set_selections(selections.into(), None, cx); self.set_selections(selections.into(), None, true, cx);
true true
} }
} else { } else {
@ -3294,7 +3304,7 @@ impl Editor {
pub fn undo(&mut self, _: &Undo, cx: &mut ViewContext<Self>) { pub fn undo(&mut self, _: &Undo, cx: &mut ViewContext<Self>) {
if let Some(tx_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) { if let Some(tx_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
if let Some((selections, _)) = self.selection_history.get(&tx_id).cloned() { if let Some((selections, _)) = self.selection_history.get(&tx_id).cloned() {
self.set_selections(selections, None, cx); self.set_selections(selections, None, true, cx);
} }
self.request_autoscroll(Autoscroll::Fit, cx); self.request_autoscroll(Autoscroll::Fit, cx);
} }
@ -3303,7 +3313,7 @@ impl Editor {
pub fn redo(&mut self, _: &Redo, cx: &mut ViewContext<Self>) { pub fn redo(&mut self, _: &Redo, cx: &mut ViewContext<Self>) {
if let Some(tx_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) { if let Some(tx_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
if let Some((_, Some(selections))) = self.selection_history.get(&tx_id).cloned() { if let Some((_, Some(selections))) = self.selection_history.get(&tx_id).cloned() {
self.set_selections(selections, None, cx); self.set_selections(selections, None, true, cx);
} }
self.request_autoscroll(Autoscroll::Fit, cx); self.request_autoscroll(Autoscroll::Fit, cx);
} }
@ -4967,6 +4977,7 @@ impl Editor {
} }
})), })),
None, None,
true,
cx, cx,
); );
} }
@ -5027,6 +5038,7 @@ impl Editor {
&mut self, &mut self,
selections: Arc<[Selection<Anchor>]>, selections: Arc<[Selection<Anchor>]>,
pending_selection: Option<PendingSelection>, pending_selection: Option<PendingSelection>,
local: bool,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
assert!( assert!(
@ -5095,7 +5107,7 @@ impl Editor {
self.refresh_document_highlights(cx); self.refresh_document_highlights(cx);
self.pause_cursor_blinking(cx); self.pause_cursor_blinking(cx);
cx.emit(Event::SelectionsChanged); cx.emit(Event::SelectionsChanged { local });
} }
pub fn request_autoscroll(&mut self, autoscroll: Autoscroll, cx: &mut ViewContext<Self>) { pub fn request_autoscroll(&mut self, autoscroll: Autoscroll, cx: &mut ViewContext<Self>) {
@ -5508,10 +5520,10 @@ impl Editor {
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
match event { match event {
language::Event::Edited => { language::Event::Edited { local } => {
self.refresh_active_diagnostics(cx); self.refresh_active_diagnostics(cx);
self.refresh_code_actions(cx); self.refresh_code_actions(cx);
cx.emit(Event::Edited); cx.emit(Event::Edited { local: *local });
} }
language::Event::Dirtied => cx.emit(Event::Dirtied), language::Event::Dirtied => cx.emit(Event::Dirtied),
language::Event::Saved => cx.emit(Event::Saved), language::Event::Saved => cx.emit(Event::Saved),
@ -5638,13 +5650,13 @@ fn compute_scroll_position(
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub enum Event { pub enum Event {
Activate, Activate,
Edited, Edited { local: bool },
Blurred, Blurred,
Dirtied, Dirtied,
Saved, Saved,
TitleChanged, TitleChanged,
SelectionsChanged, SelectionsChanged { local: bool },
ScrollPositionChanged, ScrollPositionChanged { local: bool },
Closed, Closed,
} }

View file

@ -58,7 +58,7 @@ impl FollowableItem for Editor {
.collect::<Vec<_>>() .collect::<Vec<_>>()
}; };
if !selections.is_empty() { if !selections.is_empty() {
editor.set_selections(selections.into(), None, cx); editor.set_selections(selections.into(), None, false, cx);
} }
editor editor
}) })
@ -104,7 +104,7 @@ impl FollowableItem for Editor {
_: &AppContext, _: &AppContext,
) -> Option<update_view::Variant> { ) -> Option<update_view::Variant> {
match event { match event {
Event::ScrollPositionChanged | Event::SelectionsChanged => { Event::ScrollPositionChanged { .. } | Event::SelectionsChanged { .. } => {
Some(update_view::Variant::Editor(update_view::Editor { Some(update_view::Variant::Editor(update_view::Editor {
scroll_top: self scroll_top: self
.scroll_top_anchor .scroll_top_anchor
@ -138,10 +138,11 @@ impl FollowableItem for Editor {
text_anchor: language::proto::deserialize_anchor(anchor) text_anchor: language::proto::deserialize_anchor(anchor)
.ok_or_else(|| anyhow!("invalid scroll top"))?, .ok_or_else(|| anyhow!("invalid scroll top"))?,
}), }),
false,
cx, cx,
); );
} else { } else {
self.set_scroll_top_anchor(None, cx); self.set_scroll_top_anchor(None, false, cx);
} }
let selections = message let selections = message
@ -152,15 +153,20 @@ impl FollowableItem for Editor {
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if !selections.is_empty() { if !selections.is_empty() {
self.set_selections(selections.into(), None, cx); self.set_selections(selections.into(), None, false, cx);
} }
} }
} }
Ok(()) Ok(())
} }
fn should_unfollow_on_event(event: &Self::Event, cx: &AppContext) -> bool { fn should_unfollow_on_event(event: &Self::Event, _: &AppContext) -> bool {
false match event {
Event::Edited { local } => *local,
Event::SelectionsChanged { local } => *local,
Event::ScrollPositionChanged { local } => *local,
_ => false,
}
} }
} }

View file

@ -291,7 +291,7 @@ impl FileFinder {
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
match event { match event {
editor::Event::Edited => { editor::Event::Edited { .. } => {
let query = self.query_editor.update(cx, |buffer, cx| buffer.text(cx)); let query = self.query_editor.update(cx, |buffer, cx| buffer.text(cx));
if query.is_empty() { if query.is_empty() {
self.latest_search_id = post_inc(&mut self.search_count); self.latest_search_id = post_inc(&mut self.search_count);

View file

@ -102,7 +102,7 @@ impl GoToLine {
) { ) {
match event { match event {
editor::Event::Blurred => cx.emit(Event::Dismissed), editor::Event::Blurred => cx.emit(Event::Dismissed),
editor::Event::Edited => { editor::Event::Edited { .. } => {
let line_editor = self.line_editor.read(cx).buffer().read(cx).read(cx).text(); let line_editor = self.line_editor.read(cx).buffer().read(cx).read(cx).text();
let mut components = line_editor.trim().split(&[',', ':'][..]); let mut components = line_editor.trim().split(&[',', ':'][..]);
let row = components.next().and_then(|row| row.parse::<u32>().ok()); let row = components.next().and_then(|row| row.parse::<u32>().ok());

View file

@ -142,7 +142,7 @@ pub enum Operation {
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub enum Event { pub enum Event {
Operation(Operation), Operation(Operation),
Edited, Edited { local: bool },
Dirtied, Dirtied,
Saved, Saved,
FileHandleChanged, FileHandleChanged,
@ -968,7 +968,7 @@ impl Buffer {
) -> Option<TransactionId> { ) -> Option<TransactionId> {
if let Some((transaction_id, start_version)) = self.text.end_transaction_at(now) { if let Some((transaction_id, start_version)) = self.text.end_transaction_at(now) {
let was_dirty = start_version != self.saved_version; let was_dirty = start_version != self.saved_version;
self.did_edit(&start_version, was_dirty, cx); self.did_edit(&start_version, was_dirty, true, cx);
Some(transaction_id) Some(transaction_id)
} else { } else {
None None
@ -1161,6 +1161,7 @@ impl Buffer {
&mut self, &mut self,
old_version: &clock::Global, old_version: &clock::Global,
was_dirty: bool, was_dirty: bool,
local: bool,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) { ) {
if self.edits_since::<usize>(old_version).next().is_none() { if self.edits_since::<usize>(old_version).next().is_none() {
@ -1169,7 +1170,7 @@ impl Buffer {
self.reparse(cx); self.reparse(cx);
cx.emit(Event::Edited); cx.emit(Event::Edited { local });
if !was_dirty { if !was_dirty {
cx.emit(Event::Dirtied); cx.emit(Event::Dirtied);
} }
@ -1206,7 +1207,7 @@ impl Buffer {
self.text.apply_ops(buffer_ops)?; self.text.apply_ops(buffer_ops)?;
self.deferred_ops.insert(deferred_ops); self.deferred_ops.insert(deferred_ops);
self.flush_deferred_ops(cx); self.flush_deferred_ops(cx);
self.did_edit(&old_version, was_dirty, cx); self.did_edit(&old_version, was_dirty, false, cx);
// Notify independently of whether the buffer was edited as the operations could include a // Notify independently of whether the buffer was edited as the operations could include a
// selection update. // selection update.
cx.notify(); cx.notify();
@ -1321,7 +1322,7 @@ impl Buffer {
if let Some((transaction_id, operation)) = self.text.undo() { if let Some((transaction_id, operation)) = self.text.undo() {
self.send_operation(Operation::Buffer(operation), cx); self.send_operation(Operation::Buffer(operation), cx);
self.did_edit(&old_version, was_dirty, cx); self.did_edit(&old_version, was_dirty, true, cx);
Some(transaction_id) Some(transaction_id)
} else { } else {
None None
@ -1342,7 +1343,7 @@ impl Buffer {
self.send_operation(Operation::Buffer(operation), cx); self.send_operation(Operation::Buffer(operation), cx);
} }
if undone { if undone {
self.did_edit(&old_version, was_dirty, cx) self.did_edit(&old_version, was_dirty, true, cx)
} }
undone undone
} }
@ -1353,7 +1354,7 @@ impl Buffer {
if let Some((transaction_id, operation)) = self.text.redo() { if let Some((transaction_id, operation)) = self.text.redo() {
self.send_operation(Operation::Buffer(operation), cx); self.send_operation(Operation::Buffer(operation), cx);
self.did_edit(&old_version, was_dirty, cx); self.did_edit(&old_version, was_dirty, true, cx);
Some(transaction_id) Some(transaction_id)
} else { } else {
None None
@ -1374,7 +1375,7 @@ impl Buffer {
self.send_operation(Operation::Buffer(operation), cx); self.send_operation(Operation::Buffer(operation), cx);
} }
if redone { if redone {
self.did_edit(&old_version, was_dirty, cx) self.did_edit(&old_version, was_dirty, true, cx)
} }
redone redone
} }
@ -1440,7 +1441,7 @@ impl Buffer {
if !ops.is_empty() { if !ops.is_empty() {
for op in ops { for op in ops {
self.send_operation(Operation::Buffer(op), cx); self.send_operation(Operation::Buffer(op), cx);
self.did_edit(&old_version, was_dirty, cx); self.did_edit(&old_version, was_dirty, true, cx);
} }
} }
} }

View file

@ -122,11 +122,19 @@ fn test_edit_events(cx: &mut gpui::MutableAppContext) {
let buffer_1_events = buffer_1_events.borrow(); let buffer_1_events = buffer_1_events.borrow();
assert_eq!( assert_eq!(
*buffer_1_events, *buffer_1_events,
vec![Event::Edited, Event::Dirtied, Event::Edited, Event::Edited] vec![
Event::Edited { local: true },
Event::Dirtied,
Event::Edited { local: true },
Event::Edited { local: true }
]
); );
let buffer_2_events = buffer_2_events.borrow(); let buffer_2_events = buffer_2_events.borrow();
assert_eq!(*buffer_2_events, vec![Event::Edited, Event::Dirtied]); assert_eq!(
*buffer_2_events,
vec![Event::Edited { local: false }, Event::Dirtied]
);
} }
#[gpui::test] #[gpui::test]

View file

@ -224,7 +224,7 @@ impl OutlineView {
) { ) {
match event { match event {
editor::Event::Blurred => cx.emit(Event::Dismissed), editor::Event::Blurred => cx.emit(Event::Dismissed),
editor::Event::Edited => self.update_matches(cx), editor::Event::Edited { .. } => self.update_matches(cx),
_ => {} _ => {}
} }
} }

View file

@ -1178,7 +1178,7 @@ impl Project {
}); });
cx.background().spawn(request).detach_and_log_err(cx); cx.background().spawn(request).detach_and_log_err(cx);
} }
BufferEvent::Edited => { BufferEvent::Edited { .. } => {
let language_server = self let language_server = self
.language_server_for_buffer(buffer.read(cx), cx)? .language_server_for_buffer(buffer.read(cx), cx)?
.clone(); .clone();
@ -6227,7 +6227,10 @@ mod tests {
assert!(buffer.is_dirty()); assert!(buffer.is_dirty());
assert_eq!( assert_eq!(
*events.borrow(), *events.borrow(),
&[language::Event::Edited, language::Event::Dirtied] &[
language::Event::Edited { local: true },
language::Event::Dirtied
]
); );
events.borrow_mut().clear(); events.borrow_mut().clear();
buffer.did_save(buffer.version(), buffer.file().unwrap().mtime(), None, cx); buffer.did_save(buffer.version(), buffer.file().unwrap().mtime(), None, cx);
@ -6250,9 +6253,9 @@ mod tests {
assert_eq!( assert_eq!(
*events.borrow(), *events.borrow(),
&[ &[
language::Event::Edited, language::Event::Edited { local: true },
language::Event::Dirtied, language::Event::Dirtied,
language::Event::Edited, language::Event::Edited { local: true },
], ],
); );
events.borrow_mut().clear(); events.borrow_mut().clear();
@ -6264,7 +6267,7 @@ mod tests {
assert!(buffer.is_dirty()); assert!(buffer.is_dirty());
}); });
assert_eq!(*events.borrow(), &[language::Event::Edited]); assert_eq!(*events.borrow(), &[language::Event::Edited { local: true }]);
// When a file is deleted, the buffer is considered dirty. // When a file is deleted, the buffer is considered dirty.
let events = Rc::new(RefCell::new(Vec::new())); let events = Rc::new(RefCell::new(Vec::new()));

View file

@ -328,7 +328,7 @@ impl ProjectSymbolsView {
) { ) {
match event { match event {
editor::Event::Blurred => cx.emit(Event::Dismissed), editor::Event::Blurred => cx.emit(Event::Dismissed),
editor::Event::Edited => self.update_matches(cx), editor::Event::Edited { .. } => self.update_matches(cx),
_ => {} _ => {}
} }
} }

View file

@ -360,7 +360,7 @@ impl SearchBar {
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
match event { match event {
editor::Event::Edited => { editor::Event::Edited { .. } => {
self.query_contains_error = false; self.query_contains_error = false;
self.clear_matches(cx); self.clear_matches(cx);
self.update_matches(true, cx); self.update_matches(true, cx);
@ -377,8 +377,8 @@ impl SearchBar {
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
match event { match event {
editor::Event::Edited => self.update_matches(false, cx), editor::Event::Edited { .. } => self.update_matches(false, cx),
editor::Event::SelectionsChanged => self.update_match_index(cx), editor::Event::SelectionsChanged { .. } => self.update_match_index(cx),
_ => {} _ => {}
} }
} }

View file

@ -350,7 +350,7 @@ impl ProjectSearchView {
cx.observe(&results_editor, |_, _, cx| cx.emit(ViewEvent::UpdateTab)) cx.observe(&results_editor, |_, _, cx| cx.emit(ViewEvent::UpdateTab))
.detach(); .detach();
cx.subscribe(&results_editor, |this, _, event, cx| { cx.subscribe(&results_editor, |this, _, event, cx| {
if matches!(event, editor::Event::SelectionsChanged) { if matches!(event, editor::Event::SelectionsChanged { .. }) {
this.update_match_index(cx); this.update_match_index(cx);
} }
}) })

View file

@ -1086,7 +1086,7 @@ mod tests {
self, ConfirmCodeAction, ConfirmCompletion, ConfirmRename, Editor, Input, Redo, Rename, self, ConfirmCodeAction, ConfirmCompletion, ConfirmRename, Editor, Input, Redo, Rename,
ToOffset, ToggleCodeActions, Undo, ToOffset, ToggleCodeActions, Undo,
}; };
use gpui::{executor, 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, ToLspPosition,
@ -4308,11 +4308,6 @@ mod tests {
.project_path(cx)), .project_path(cx)),
Some((worktree_id, "2.txt").into()) Some((worktree_id, "2.txt").into())
); );
let editor_b2 = workspace_b
.read_with(cx_b, |workspace, cx| workspace.active_item(cx))
.unwrap()
.downcast::<Editor>()
.unwrap();
// When client A activates a different editor, client B does so as well. // When client A activates a different editor, client B does so as well.
workspace_a.update(cx_a, |workspace, cx| { workspace_a.update(cx_a, |workspace, cx| {
@ -4324,7 +4319,7 @@ mod tests {
}) })
.await; .await;
// When client A selects something, client B does as well. // Changes to client A's editor are reflected on client B.
editor_a1.update(cx_a, |editor, cx| { editor_a1.update(cx_a, |editor, cx| {
editor.select_ranges([1..1, 2..2], None, cx); editor.select_ranges([1..1, 2..2], None, cx);
}); });
@ -4334,17 +4329,26 @@ mod tests {
}) })
.await; .await;
editor_a1.update(cx_a, |editor, cx| editor.set_text("TWO", cx));
editor_b1
.condition(cx_b, |editor, cx| editor.text(cx) == "TWO")
.await;
editor_a1.update(cx_a, |editor, cx| {
editor.select_ranges([3..3], None, cx);
});
editor_b1
.condition(cx_b, |editor, cx| editor.selected_ranges(cx) == vec![3..3])
.await;
// After unfollowing, client B stops receiving updates from client A. // After unfollowing, client B stops receiving updates from client A.
workspace_b.update(cx_b, |workspace, cx| { workspace_b.update(cx_b, |workspace, cx| {
workspace.unfollow(&workspace.active_pane().clone(), cx) workspace.unfollow(&workspace.active_pane().clone(), cx)
}); });
workspace_a.update(cx_a, |workspace, cx| { workspace_a.update(cx_a, |workspace, cx| {
workspace.activate_item(&editor_a2, cx); workspace.activate_item(&editor_a2, cx)
editor_a2.update(cx, |editor, cx| editor.set_text("TWO", cx));
}); });
editor_b2 cx_a.foreground().run_until_parked();
.condition(cx_b, |editor, cx| editor.text(cx) == "TWO")
.await;
assert_eq!( assert_eq!(
workspace_b.read_with(cx_b, |workspace, cx| workspace workspace_b.read_with(cx_b, |workspace, cx| workspace
.active_item(cx) .active_item(cx)
@ -4456,6 +4460,126 @@ mod tests {
); );
} }
#[gpui::test(iterations = 10)]
async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
cx_a.foreground().forbid_parking();
let fs = FakeFs::new(cx_a.background());
// 2 clients connect to a server.
let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
let mut client_a = server.create_client(cx_a, "user_a").await;
let mut client_b = server.create_client(cx_b, "user_b").await;
cx_a.update(editor::init);
cx_b.update(editor::init);
// Client A shares a project.
fs.insert_tree(
"/a",
json!({
".zed.toml": r#"collaborators = ["user_b"]"#,
"1.txt": "one",
"2.txt": "two",
"3.txt": "three",
}),
)
.await;
let (project_a, worktree_id) = client_a.build_local_project(fs.clone(), "/a", cx_a).await;
project_a
.update(cx_a, |project, cx| project.share(cx))
.await
.unwrap();
// Client B joins the project.
let project_b = client_b
.build_remote_project(
project_a
.read_with(cx_a, |project, _| project.remote_id())
.unwrap(),
cx_b,
)
.await;
// Client A opens some editors.
let workspace_a = client_a.build_workspace(&project_a, cx_a);
let _editor_a1 = workspace_a
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, "1.txt"), cx)
})
.await
.unwrap()
.downcast::<Editor>()
.unwrap();
// Client B starts following client A.
let workspace_b = client_b.build_workspace(&project_b, cx_b);
let pane_b = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone());
let leader_id = project_b.read_with(cx_b, |project, _| {
project.collaborators().values().next().unwrap().peer_id
});
workspace_b
.update(cx_b, |workspace, cx| {
workspace.toggle_follow(&leader_id.into(), cx).unwrap()
})
.await
.unwrap();
assert_eq!(
workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
Some(leader_id)
);
let editor_b2 = workspace_b.read_with(cx_b, |workspace, cx| {
workspace
.active_item(cx)
.unwrap()
.downcast::<Editor>()
.unwrap()
});
// When client B moves, it automatically stops following client A.
editor_b2.update(cx_b, |editor, cx| editor.move_right(&editor::MoveRight, cx));
assert_eq!(
workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
None
);
workspace_b
.update(cx_b, |workspace, cx| {
workspace.toggle_follow(&leader_id.into(), cx).unwrap()
})
.await
.unwrap();
assert_eq!(
workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
Some(leader_id)
);
// When client B edits, it automatically stops following client A.
editor_b2.update(cx_b, |editor, cx| editor.insert("X", cx));
assert_eq!(
workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
None
);
workspace_b
.update(cx_b, |workspace, cx| {
workspace.toggle_follow(&leader_id.into(), cx).unwrap()
})
.await
.unwrap();
assert_eq!(
workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
Some(leader_id)
);
// When client B scrolls, it automatically stops following client A.
editor_b2.update(cx_b, |editor, cx| {
editor.set_scroll_position(vec2f(0., 3.), cx)
});
assert_eq!(
workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
None
);
}
#[gpui::test(iterations = 100)] #[gpui::test(iterations = 100)]
async fn test_random_collaboration(cx: &mut TestAppContext, rng: StdRng) { async fn test_random_collaboration(cx: &mut TestAppContext, rng: StdRng) {
cx.foreground().forbid_parking(); cx.foreground().forbid_parking();

View file

@ -204,7 +204,7 @@ impl ThemeSelector {
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
match event { match event {
editor::Event::Edited => { editor::Event::Edited { .. } => {
self.update_matches(cx); self.update_matches(cx);
self.select_if_matching(&cx.global::<Settings>().theme.name); self.select_if_matching(&cx.global::<Settings>().theme.name);
self.show_selected_theme(cx); self.show_selected_theme(cx);

View file

@ -1750,7 +1750,7 @@ impl Workspace {
None None
} }
fn leader_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<PeerId> { pub fn leader_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<PeerId> {
self.follower_states_by_leader self.follower_states_by_leader
.iter() .iter()
.find_map(|(leader_id, state)| { .find_map(|(leader_id, state)| {