mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-10 20:41:59 +00:00
Automatically unfollow when editing, scrolling or changing selections
This commit is contained in:
parent
c550fc3f01
commit
3117554568
14 changed files with 214 additions and 60 deletions
|
@ -1035,14 +1035,19 @@ impl Editor {
|
|||
self.scroll_top_anchor = Some(anchor);
|
||||
}
|
||||
|
||||
cx.emit(Event::ScrollPositionChanged);
|
||||
cx.emit(Event::ScrollPositionChanged { local: true });
|
||||
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_top_anchor = anchor;
|
||||
cx.emit(Event::ScrollPositionChanged);
|
||||
cx.emit(Event::ScrollPositionChanged { local });
|
||||
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(
|
||||
|
@ -1347,7 +1352,12 @@ impl Editor {
|
|||
} else {
|
||||
selections = Arc::from([]);
|
||||
}
|
||||
self.set_selections(selections, Some(PendingSelection { selection, mode }), cx);
|
||||
self.set_selections(
|
||||
selections,
|
||||
Some(PendingSelection { selection, mode }),
|
||||
true,
|
||||
cx,
|
||||
);
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
@ -1461,7 +1471,7 @@ impl Editor {
|
|||
pending.selection.end = buffer.anchor_before(head);
|
||||
pending.selection.reversed = false;
|
||||
}
|
||||
self.set_selections(self.selections.clone(), Some(pending), cx);
|
||||
self.set_selections(self.selections.clone(), Some(pending), true, cx);
|
||||
} else {
|
||||
log::error!("update_selection dispatched with no pending selection");
|
||||
return;
|
||||
|
@ -1548,7 +1558,7 @@ impl Editor {
|
|||
if selections.is_empty() {
|
||||
selections = Arc::from([pending.selection]);
|
||||
}
|
||||
self.set_selections(selections, None, cx);
|
||||
self.set_selections(selections, None, true, cx);
|
||||
self.request_autoscroll(Autoscroll::Fit, cx);
|
||||
} else {
|
||||
let mut oldest_selection = self.oldest_selection::<usize>(&cx);
|
||||
|
@ -1895,7 +1905,7 @@ impl Editor {
|
|||
}
|
||||
drop(snapshot);
|
||||
|
||||
self.set_selections(selections.into(), None, cx);
|
||||
self.set_selections(selections.into(), None, true, cx);
|
||||
true
|
||||
}
|
||||
} else {
|
||||
|
@ -3294,7 +3304,7 @@ impl Editor {
|
|||
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((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);
|
||||
}
|
||||
|
@ -3303,7 +3313,7 @@ impl Editor {
|
|||
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((_, 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);
|
||||
}
|
||||
|
@ -4967,6 +4977,7 @@ impl Editor {
|
|||
}
|
||||
})),
|
||||
None,
|
||||
true,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
@ -5027,6 +5038,7 @@ impl Editor {
|
|||
&mut self,
|
||||
selections: Arc<[Selection<Anchor>]>,
|
||||
pending_selection: Option<PendingSelection>,
|
||||
local: bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
assert!(
|
||||
|
@ -5095,7 +5107,7 @@ impl Editor {
|
|||
self.refresh_document_highlights(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>) {
|
||||
|
@ -5508,10 +5520,10 @@ impl Editor {
|
|||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
match event {
|
||||
language::Event::Edited => {
|
||||
language::Event::Edited { local } => {
|
||||
self.refresh_active_diagnostics(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::Saved => cx.emit(Event::Saved),
|
||||
|
@ -5638,13 +5650,13 @@ fn compute_scroll_position(
|
|||
#[derive(Copy, Clone)]
|
||||
pub enum Event {
|
||||
Activate,
|
||||
Edited,
|
||||
Edited { local: bool },
|
||||
Blurred,
|
||||
Dirtied,
|
||||
Saved,
|
||||
TitleChanged,
|
||||
SelectionsChanged,
|
||||
ScrollPositionChanged,
|
||||
SelectionsChanged { local: bool },
|
||||
ScrollPositionChanged { local: bool },
|
||||
Closed,
|
||||
}
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ impl FollowableItem for Editor {
|
|||
.collect::<Vec<_>>()
|
||||
};
|
||||
if !selections.is_empty() {
|
||||
editor.set_selections(selections.into(), None, cx);
|
||||
editor.set_selections(selections.into(), None, false, cx);
|
||||
}
|
||||
editor
|
||||
})
|
||||
|
@ -104,7 +104,7 @@ impl FollowableItem for Editor {
|
|||
_: &AppContext,
|
||||
) -> Option<update_view::Variant> {
|
||||
match event {
|
||||
Event::ScrollPositionChanged | Event::SelectionsChanged => {
|
||||
Event::ScrollPositionChanged { .. } | Event::SelectionsChanged { .. } => {
|
||||
Some(update_view::Variant::Editor(update_view::Editor {
|
||||
scroll_top: self
|
||||
.scroll_top_anchor
|
||||
|
@ -138,10 +138,11 @@ impl FollowableItem for Editor {
|
|||
text_anchor: language::proto::deserialize_anchor(anchor)
|
||||
.ok_or_else(|| anyhow!("invalid scroll top"))?,
|
||||
}),
|
||||
false,
|
||||
cx,
|
||||
);
|
||||
} else {
|
||||
self.set_scroll_top_anchor(None, cx);
|
||||
self.set_scroll_top_anchor(None, false, cx);
|
||||
}
|
||||
|
||||
let selections = message
|
||||
|
@ -152,15 +153,20 @@ impl FollowableItem for Editor {
|
|||
})
|
||||
.collect::<Vec<_>>();
|
||||
if !selections.is_empty() {
|
||||
self.set_selections(selections.into(), None, cx);
|
||||
self.set_selections(selections.into(), None, false, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn should_unfollow_on_event(event: &Self::Event, cx: &AppContext) -> bool {
|
||||
false
|
||||
fn should_unfollow_on_event(event: &Self::Event, _: &AppContext) -> bool {
|
||||
match event {
|
||||
Event::Edited { local } => *local,
|
||||
Event::SelectionsChanged { local } => *local,
|
||||
Event::ScrollPositionChanged { local } => *local,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -291,7 +291,7 @@ impl FileFinder {
|
|||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
match event {
|
||||
editor::Event::Edited => {
|
||||
editor::Event::Edited { .. } => {
|
||||
let query = self.query_editor.update(cx, |buffer, cx| buffer.text(cx));
|
||||
if query.is_empty() {
|
||||
self.latest_search_id = post_inc(&mut self.search_count);
|
||||
|
|
|
@ -102,7 +102,7 @@ impl GoToLine {
|
|||
) {
|
||||
match event {
|
||||
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 mut components = line_editor.trim().split(&[',', ':'][..]);
|
||||
let row = components.next().and_then(|row| row.parse::<u32>().ok());
|
||||
|
|
|
@ -142,7 +142,7 @@ pub enum Operation {
|
|||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Event {
|
||||
Operation(Operation),
|
||||
Edited,
|
||||
Edited { local: bool },
|
||||
Dirtied,
|
||||
Saved,
|
||||
FileHandleChanged,
|
||||
|
@ -968,7 +968,7 @@ impl Buffer {
|
|||
) -> Option<TransactionId> {
|
||||
if let Some((transaction_id, start_version)) = self.text.end_transaction_at(now) {
|
||||
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)
|
||||
} else {
|
||||
None
|
||||
|
@ -1161,6 +1161,7 @@ impl Buffer {
|
|||
&mut self,
|
||||
old_version: &clock::Global,
|
||||
was_dirty: bool,
|
||||
local: bool,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
if self.edits_since::<usize>(old_version).next().is_none() {
|
||||
|
@ -1169,7 +1170,7 @@ impl Buffer {
|
|||
|
||||
self.reparse(cx);
|
||||
|
||||
cx.emit(Event::Edited);
|
||||
cx.emit(Event::Edited { local });
|
||||
if !was_dirty {
|
||||
cx.emit(Event::Dirtied);
|
||||
}
|
||||
|
@ -1206,7 +1207,7 @@ impl Buffer {
|
|||
self.text.apply_ops(buffer_ops)?;
|
||||
self.deferred_ops.insert(deferred_ops);
|
||||
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
|
||||
// selection update.
|
||||
cx.notify();
|
||||
|
@ -1321,7 +1322,7 @@ impl Buffer {
|
|||
|
||||
if let Some((transaction_id, operation)) = self.text.undo() {
|
||||
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)
|
||||
} else {
|
||||
None
|
||||
|
@ -1342,7 +1343,7 @@ impl Buffer {
|
|||
self.send_operation(Operation::Buffer(operation), cx);
|
||||
}
|
||||
if undone {
|
||||
self.did_edit(&old_version, was_dirty, cx)
|
||||
self.did_edit(&old_version, was_dirty, true, cx)
|
||||
}
|
||||
undone
|
||||
}
|
||||
|
@ -1353,7 +1354,7 @@ impl Buffer {
|
|||
|
||||
if let Some((transaction_id, operation)) = self.text.redo() {
|
||||
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)
|
||||
} else {
|
||||
None
|
||||
|
@ -1374,7 +1375,7 @@ impl Buffer {
|
|||
self.send_operation(Operation::Buffer(operation), cx);
|
||||
}
|
||||
if redone {
|
||||
self.did_edit(&old_version, was_dirty, cx)
|
||||
self.did_edit(&old_version, was_dirty, true, cx)
|
||||
}
|
||||
redone
|
||||
}
|
||||
|
@ -1440,7 +1441,7 @@ impl Buffer {
|
|||
if !ops.is_empty() {
|
||||
for op in ops {
|
||||
self.send_operation(Operation::Buffer(op), cx);
|
||||
self.did_edit(&old_version, was_dirty, cx);
|
||||
self.did_edit(&old_version, was_dirty, true, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -122,11 +122,19 @@ fn test_edit_events(cx: &mut gpui::MutableAppContext) {
|
|||
let buffer_1_events = buffer_1_events.borrow();
|
||||
assert_eq!(
|
||||
*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();
|
||||
assert_eq!(*buffer_2_events, vec![Event::Edited, Event::Dirtied]);
|
||||
assert_eq!(
|
||||
*buffer_2_events,
|
||||
vec![Event::Edited { local: false }, Event::Dirtied]
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
|
|
|
@ -224,7 +224,7 @@ impl OutlineView {
|
|||
) {
|
||||
match event {
|
||||
editor::Event::Blurred => cx.emit(Event::Dismissed),
|
||||
editor::Event::Edited => self.update_matches(cx),
|
||||
editor::Event::Edited { .. } => self.update_matches(cx),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1178,7 +1178,7 @@ impl Project {
|
|||
});
|
||||
cx.background().spawn(request).detach_and_log_err(cx);
|
||||
}
|
||||
BufferEvent::Edited => {
|
||||
BufferEvent::Edited { .. } => {
|
||||
let language_server = self
|
||||
.language_server_for_buffer(buffer.read(cx), cx)?
|
||||
.clone();
|
||||
|
@ -6227,7 +6227,10 @@ mod tests {
|
|||
assert!(buffer.is_dirty());
|
||||
assert_eq!(
|
||||
*events.borrow(),
|
||||
&[language::Event::Edited, language::Event::Dirtied]
|
||||
&[
|
||||
language::Event::Edited { local: true },
|
||||
language::Event::Dirtied
|
||||
]
|
||||
);
|
||||
events.borrow_mut().clear();
|
||||
buffer.did_save(buffer.version(), buffer.file().unwrap().mtime(), None, cx);
|
||||
|
@ -6250,9 +6253,9 @@ mod tests {
|
|||
assert_eq!(
|
||||
*events.borrow(),
|
||||
&[
|
||||
language::Event::Edited,
|
||||
language::Event::Edited { local: true },
|
||||
language::Event::Dirtied,
|
||||
language::Event::Edited,
|
||||
language::Event::Edited { local: true },
|
||||
],
|
||||
);
|
||||
events.borrow_mut().clear();
|
||||
|
@ -6264,7 +6267,7 @@ mod tests {
|
|||
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.
|
||||
let events = Rc::new(RefCell::new(Vec::new()));
|
||||
|
|
|
@ -328,7 +328,7 @@ impl ProjectSymbolsView {
|
|||
) {
|
||||
match event {
|
||||
editor::Event::Blurred => cx.emit(Event::Dismissed),
|
||||
editor::Event::Edited => self.update_matches(cx),
|
||||
editor::Event::Edited { .. } => self.update_matches(cx),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -360,7 +360,7 @@ impl SearchBar {
|
|||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
match event {
|
||||
editor::Event::Edited => {
|
||||
editor::Event::Edited { .. } => {
|
||||
self.query_contains_error = false;
|
||||
self.clear_matches(cx);
|
||||
self.update_matches(true, cx);
|
||||
|
@ -377,8 +377,8 @@ impl SearchBar {
|
|||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
match event {
|
||||
editor::Event::Edited => self.update_matches(false, cx),
|
||||
editor::Event::SelectionsChanged => self.update_match_index(cx),
|
||||
editor::Event::Edited { .. } => self.update_matches(false, cx),
|
||||
editor::Event::SelectionsChanged { .. } => self.update_match_index(cx),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -350,7 +350,7 @@ impl ProjectSearchView {
|
|||
cx.observe(&results_editor, |_, _, cx| cx.emit(ViewEvent::UpdateTab))
|
||||
.detach();
|
||||
cx.subscribe(&results_editor, |this, _, event, cx| {
|
||||
if matches!(event, editor::Event::SelectionsChanged) {
|
||||
if matches!(event, editor::Event::SelectionsChanged { .. }) {
|
||||
this.update_match_index(cx);
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1086,7 +1086,7 @@ mod tests {
|
|||
self, ConfirmCodeAction, ConfirmCompletion, ConfirmRename, Editor, Input, Redo, Rename,
|
||||
ToOffset, ToggleCodeActions, Undo,
|
||||
};
|
||||
use gpui::{executor, ModelHandle, TestAppContext, ViewHandle};
|
||||
use gpui::{executor, geometry::vector::vec2f, ModelHandle, TestAppContext, ViewHandle};
|
||||
use language::{
|
||||
tree_sitter_rust, Diagnostic, DiagnosticEntry, Language, LanguageConfig, LanguageRegistry,
|
||||
LanguageServerConfig, OffsetRangeExt, Point, ToLspPosition,
|
||||
|
@ -4308,11 +4308,6 @@ mod tests {
|
|||
.project_path(cx)),
|
||||
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.
|
||||
workspace_a.update(cx_a, |workspace, cx| {
|
||||
|
@ -4324,7 +4319,7 @@ mod tests {
|
|||
})
|
||||
.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.select_ranges([1..1, 2..2], None, cx);
|
||||
});
|
||||
|
@ -4334,17 +4329,26 @@ mod tests {
|
|||
})
|
||||
.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.
|
||||
workspace_b.update(cx_b, |workspace, cx| {
|
||||
workspace.unfollow(&workspace.active_pane().clone(), cx)
|
||||
});
|
||||
workspace_a.update(cx_a, |workspace, cx| {
|
||||
workspace.activate_item(&editor_a2, cx);
|
||||
editor_a2.update(cx, |editor, cx| editor.set_text("TWO", cx));
|
||||
workspace.activate_item(&editor_a2, cx)
|
||||
});
|
||||
editor_b2
|
||||
.condition(cx_b, |editor, cx| editor.text(cx) == "TWO")
|
||||
.await;
|
||||
cx_a.foreground().run_until_parked();
|
||||
assert_eq!(
|
||||
workspace_b.read_with(cx_b, |workspace, cx| workspace
|
||||
.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)]
|
||||
async fn test_random_collaboration(cx: &mut TestAppContext, rng: StdRng) {
|
||||
cx.foreground().forbid_parking();
|
||||
|
|
|
@ -204,7 +204,7 @@ impl ThemeSelector {
|
|||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
match event {
|
||||
editor::Event::Edited => {
|
||||
editor::Event::Edited { .. } => {
|
||||
self.update_matches(cx);
|
||||
self.select_if_matching(&cx.global::<Settings>().theme.name);
|
||||
self.show_selected_theme(cx);
|
||||
|
|
|
@ -1750,7 +1750,7 @@ impl Workspace {
|
|||
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
|
||||
.iter()
|
||||
.find_map(|(leader_id, state)| {
|
||||
|
|
Loading…
Reference in a new issue