Eliminate GPUI View, ViewContext, and WindowContext types (#22632)

There's still a bit more work to do on this, but this PR is compiling
(with warnings) after eliminating the key types. When the tasks below
are complete, this will be the new narrative for GPUI:

- `Entity<T>` - This replaces `View<T>`/`Model<T>`. It represents a unit
of state, and if `T` implements `Render`, then `Entity<T>` implements
`Element`.
- `&mut App` This replaces `AppContext` and represents the app.
- `&mut Context<T>` This replaces `ModelContext` and derefs to `App`. It
is provided by the framework when updating an entity.
- `&mut Window` Broken out of `&mut WindowContext` which no longer
exists. Every method that once took `&mut WindowContext` now takes `&mut
Window, &mut App` and every method that took `&mut ViewContext<T>` now
takes `&mut Window, &mut Context<T>`

Not pictured here are the two other failed attempts. It's been quite a
month!

Tasks:

- [x] Remove `View`, `ViewContext`, `WindowContext` and thread through
`Window`
- [x] [@cole-miller @mikayla-maki] Redraw window when entities change
- [x] [@cole-miller @mikayla-maki] Get examples and Zed running
- [x] [@cole-miller @mikayla-maki] Fix Zed rendering
- [x] [@mikayla-maki] Fix todo! macros and comments
- [x] Fix a bug where the editor would not be redrawn because of view
caching
- [x] remove publicness window.notify() and replace with
`AppContext::notify`
- [x] remove `observe_new_window_models`, replace with
`observe_new_models` with an optional window
- [x] Fix a bug where the project panel would not be redrawn because of
the wrong refresh() call being used
- [x] Fix the tests
- [x] Fix warnings by eliminating `Window` params or using `_`
- [x] Fix conflicts
- [x] Simplify generic code where possible
- [x] Rename types
- [ ] Update docs

### issues post merge

- [x] Issues switching between normal and insert mode
- [x] Assistant re-rendering failure
- [x] Vim test failures
- [x] Mac build issue



Release Notes:

- N/A

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
Co-authored-by: Cole Miller <cole@zed.dev>
Co-authored-by: Mikayla <mikayla@zed.dev>
Co-authored-by: Joseph <joseph@zed.dev>
Co-authored-by: max <max@zed.dev>
Co-authored-by: Michael Sloan <michael@zed.dev>
Co-authored-by: Mikayla Maki <mikaylamaki@Mikaylas-MacBook-Pro.local>
Co-authored-by: Mikayla <mikayla.c.maki@gmail.com>
Co-authored-by: joão <joao@zed.dev>
This commit is contained in:
Nathan Sobo 2025-01-25 20:02:45 -07:00 committed by GitHub
parent 21b4a0d50e
commit 6fca1d2b0b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
648 changed files with 36248 additions and 28208 deletions

View file

@ -3,7 +3,7 @@ use crate::{
rpc::Principal,
AppState, Error, Result,
};
use anyhow::{anyhow, Context};
use anyhow::{anyhow, Context as _};
use axum::{
http::{self, Request, StatusCode},
middleware::Next,

View file

@ -6,6 +6,7 @@ use axum::{
routing::get,
Extension, Router,
};
use collab::api::billing::sync_llm_usage_with_stripe_periodically;
use collab::api::CloudflareIpCountryHeader;
use collab::llm::{db::LlmDatabase, log_usage_periodically};

View file

@ -1,6 +1,6 @@
use crate::db::{self, ChannelRole, NewUserParams};
use anyhow::Context;
use anyhow::Context as _;
use chrono::{DateTime, Utc};
use db::Database;
use serde::{de::DeserializeOwned, Deserialize};

View file

@ -1,7 +1,7 @@
use std::sync::Arc;
use crate::{llm, Cents, Result};
use anyhow::Context;
use anyhow::Context as _;
use chrono::{Datelike, Utc};
use collections::HashMap;
use serde::{Deserialize, Serialize};

View file

@ -5,7 +5,7 @@ use std::sync::Arc;
use call::Room;
use client::ChannelId;
use gpui::{Model, TestAppContext};
use gpui::{Entity, TestAppContext};
mod channel_buffer_tests;
mod channel_guest_tests;
@ -33,7 +33,7 @@ struct RoomParticipants {
pending: Vec<String>,
}
fn room_participants(room: &Model<Room>, cx: &mut TestAppContext) -> RoomParticipants {
fn room_participants(room: &Entity<Room>, cx: &mut TestAppContext) -> RoomParticipants {
room.read_with(cx, |room, _| {
let mut remote = room
.remote_participants()
@ -51,7 +51,7 @@ fn room_participants(room: &Model<Room>, cx: &mut TestAppContext) -> RoomPartici
})
}
fn channel_id(room: &Model<Room>, cx: &mut TestAppContext) -> Option<ChannelId> {
fn channel_id(room: &Entity<Room>, cx: &mut TestAppContext) -> Option<ChannelId> {
cx.read(|cx| room.read(cx).channel_id())
}

View file

@ -9,7 +9,7 @@ use collab_ui::channel_view::ChannelView;
use collections::HashMap;
use editor::{Anchor, Editor, ToOffset};
use futures::future;
use gpui::{BackgroundExecutor, Model, TestAppContext, ViewContext};
use gpui::{BackgroundExecutor, Context, Entity, TestAppContext, Window};
use rpc::{proto::PeerId, RECEIVE_TIMEOUT};
use serde_json::json;
use std::ops::Range;
@ -161,43 +161,43 @@ async fn test_channel_notes_participant_indices(
// Clients A, B, and C open the channel notes
let channel_view_a = cx_a
.update(|cx| ChannelView::open(channel_id, None, workspace_a.clone(), cx))
.update(|window, cx| ChannelView::open(channel_id, None, workspace_a.clone(), window, cx))
.await
.unwrap();
let channel_view_b = cx_b
.update(|cx| ChannelView::open(channel_id, None, workspace_b.clone(), cx))
.update(|window, cx| ChannelView::open(channel_id, None, workspace_b.clone(), window, cx))
.await
.unwrap();
let channel_view_c = cx_c
.update(|cx| ChannelView::open(channel_id, None, workspace_c.clone(), cx))
.update(|window, cx| ChannelView::open(channel_id, None, workspace_c.clone(), window, cx))
.await
.unwrap();
// Clients A, B, and C all insert and select some text
channel_view_a.update(cx_a, |notes, cx| {
channel_view_a.update_in(cx_a, |notes, window, cx| {
notes.editor.update(cx, |editor, cx| {
editor.insert("a", cx);
editor.change_selections(None, cx, |selections| {
editor.insert("a", window, cx);
editor.change_selections(None, window, cx, |selections| {
selections.select_ranges(vec![0..1]);
});
});
});
executor.run_until_parked();
channel_view_b.update(cx_b, |notes, cx| {
channel_view_b.update_in(cx_b, |notes, window, cx| {
notes.editor.update(cx, |editor, cx| {
editor.move_down(&Default::default(), cx);
editor.insert("b", cx);
editor.change_selections(None, cx, |selections| {
editor.move_down(&Default::default(), window, cx);
editor.insert("b", window, cx);
editor.change_selections(None, window, cx, |selections| {
selections.select_ranges(vec![1..2]);
});
});
});
executor.run_until_parked();
channel_view_c.update(cx_c, |notes, cx| {
channel_view_c.update_in(cx_c, |notes, window, cx| {
notes.editor.update(cx, |editor, cx| {
editor.move_down(&Default::default(), cx);
editor.insert("c", cx);
editor.change_selections(None, cx, |selections| {
editor.move_down(&Default::default(), window, cx);
editor.insert("c", window, cx);
editor.change_selections(None, window, cx, |selections| {
selections.select_ranges(vec![2..3]);
});
});
@ -206,9 +206,9 @@ async fn test_channel_notes_participant_indices(
// Client A sees clients B and C without assigned colors, because they aren't
// in a call together.
executor.run_until_parked();
channel_view_a.update(cx_a, |notes, cx| {
channel_view_a.update_in(cx_a, |notes, window, cx| {
notes.editor.update(cx, |editor, cx| {
assert_remote_selections(editor, &[(None, 1..2), (None, 2..3)], cx);
assert_remote_selections(editor, &[(None, 1..2), (None, 2..3)], window, cx);
});
});
@ -222,20 +222,22 @@ async fn test_channel_notes_participant_indices(
// Clients A and B see each other with two different assigned colors. Client C
// still doesn't have a color.
executor.run_until_parked();
channel_view_a.update(cx_a, |notes, cx| {
channel_view_a.update_in(cx_a, |notes, window, cx| {
notes.editor.update(cx, |editor, cx| {
assert_remote_selections(
editor,
&[(Some(ParticipantIndex(1)), 1..2), (None, 2..3)],
window,
cx,
);
});
});
channel_view_b.update(cx_b, |notes, cx| {
channel_view_b.update_in(cx_b, |notes, window, cx| {
notes.editor.update(cx, |editor, cx| {
assert_remote_selections(
editor,
&[(Some(ParticipantIndex(0)), 0..1), (None, 2..3)],
window,
cx,
);
});
@ -252,8 +254,8 @@ async fn test_channel_notes_participant_indices(
// Clients A and B open the same file.
executor.start_waiting();
let editor_a = workspace_a
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id_a, "file.txt"), None, true, cx)
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id_a, "file.txt"), None, true, window, cx)
})
.await
.unwrap()
@ -261,32 +263,32 @@ async fn test_channel_notes_participant_indices(
.unwrap();
executor.start_waiting();
let editor_b = workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id_a, "file.txt"), None, true, cx)
.update_in(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id_a, "file.txt"), None, true, window, cx)
})
.await
.unwrap()
.downcast::<Editor>()
.unwrap();
editor_a.update(cx_a, |editor, cx| {
editor.change_selections(None, cx, |selections| {
editor_a.update_in(cx_a, |editor, window, cx| {
editor.change_selections(None, window, cx, |selections| {
selections.select_ranges(vec![0..1]);
});
});
editor_b.update(cx_b, |editor, cx| {
editor.change_selections(None, cx, |selections| {
editor_b.update_in(cx_b, |editor, window, cx| {
editor.change_selections(None, window, cx, |selections| {
selections.select_ranges(vec![2..3]);
});
});
executor.run_until_parked();
// Clients A and B see each other with the same colors as in the channel notes.
editor_a.update(cx_a, |editor, cx| {
assert_remote_selections(editor, &[(Some(ParticipantIndex(1)), 2..3)], cx);
editor_a.update_in(cx_a, |editor, window, cx| {
assert_remote_selections(editor, &[(Some(ParticipantIndex(1)), 2..3)], window, cx);
});
editor_b.update(cx_b, |editor, cx| {
assert_remote_selections(editor, &[(Some(ParticipantIndex(0)), 0..1)], cx);
editor_b.update_in(cx_b, |editor, window, cx| {
assert_remote_selections(editor, &[(Some(ParticipantIndex(0)), 0..1)], window, cx);
});
}
@ -294,9 +296,10 @@ async fn test_channel_notes_participant_indices(
fn assert_remote_selections(
editor: &mut Editor,
expected_selections: &[(Option<ParticipantIndex>, Range<usize>)],
cx: &mut ViewContext<Editor>,
window: &mut Window,
cx: &mut Context<Editor>,
) {
let snapshot = editor.snapshot(cx);
let snapshot = editor.snapshot(window, cx);
let range = Anchor::min()..Anchor::max();
let remote_selections = snapshot
.remote_selections_in_range(&range, editor.collaboration_hub().unwrap(), cx)
@ -641,9 +644,9 @@ async fn test_channel_buffer_changes(
});
// Closing the buffer should re-enable change tracking
cx_b.update(|cx| {
cx_b.update(|window, cx| {
workspace_b.update(cx, |workspace, cx| {
workspace.close_all_items_and_panes(&Default::default(), cx)
workspace.close_all_items_and_panes(&Default::default(), window, cx)
});
});
deterministic.run_until_parked();
@ -691,6 +694,6 @@ fn assert_collaborators(collaborators: &HashMap<PeerId, Collaborator>, ids: &[Op
);
}
fn buffer_text(channel_buffer: &Model<language::Buffer>, cx: &mut TestAppContext) -> String {
fn buffer_text(channel_buffer: &Entity<language::Buffer>, cx: &mut TestAppContext) -> String {
channel_buffer.read_with(cx, |buffer, _| buffer.text())
}

View file

@ -107,7 +107,7 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test
});
assert!(project_b.read_with(cx_b, |project, cx| project.is_read_only(cx)));
assert!(editor_b.update(cx_b, |e, cx| e.read_only(cx)));
cx_b.update(|cx_b| {
cx_b.update(|_window, cx_b| {
assert!(room_b.read_with(cx_b, |room, _| !room.can_use_microphone()));
});
assert!(room_b
@ -135,7 +135,7 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test
assert!(editor_b.update(cx_b, |editor, cx| !editor.read_only(cx)));
// B sees themselves as muted, and can unmute.
cx_b.update(|cx_b| {
cx_b.update(|_window, cx_b| {
assert!(room_b.read_with(cx_b, |room, _| room.can_use_microphone()));
});
room_b.read_with(cx_b, |room, _| assert!(room.is_muted()));

View file

@ -1,7 +1,7 @@
use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer};
use channel::{ChannelChat, ChannelMessageId, MessageParams};
use collab_ui::chat_panel::ChatPanel;
use gpui::{BackgroundExecutor, Model, TestAppContext};
use gpui::{BackgroundExecutor, Entity, TestAppContext};
use rpc::Notification;
use workspace::dock::Panel;
@ -295,7 +295,7 @@ async fn test_remove_channel_message(
}
#[track_caller]
fn assert_messages(chat: &Model<ChannelChat>, messages: &[&str], cx: &mut TestAppContext) {
fn assert_messages(chat: &Entity<ChannelChat>, messages: &[&str], cx: &mut TestAppContext) {
assert_eq!(
chat.read_with(cx, |chat, _| {
chat.messages()
@ -356,10 +356,10 @@ async fn test_channel_message_changes(
let project_b = client_b.build_empty_local_project(cx_b);
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
let chat_panel_b = workspace_b.update(cx_b, ChatPanel::new);
let chat_panel_b = workspace_b.update_in(cx_b, ChatPanel::new);
chat_panel_b
.update(cx_b, |chat_panel, cx| {
chat_panel.set_active(true, cx);
.update_in(cx_b, |chat_panel, window, cx| {
chat_panel.set_active(true, window, cx);
chat_panel.select_channel(channel_id, None, cx)
})
.await
@ -367,7 +367,7 @@ async fn test_channel_message_changes(
executor.run_until_parked();
let b_has_messages = cx_b.update(|cx| {
let b_has_messages = cx_b.update(|_, cx| {
client_b
.channel_store()
.read(cx)
@ -384,7 +384,7 @@ async fn test_channel_message_changes(
executor.run_until_parked();
let b_has_messages = cx_b.update(|cx| {
let b_has_messages = cx_b.update(|_, cx| {
client_b
.channel_store()
.read(cx)
@ -394,8 +394,8 @@ async fn test_channel_message_changes(
assert!(!b_has_messages);
// Sending a message while the chat is closed should change the flag.
chat_panel_b.update(cx_b, |chat_panel, cx| {
chat_panel.set_active(false, cx);
chat_panel_b.update_in(cx_b, |chat_panel, window, cx| {
chat_panel.set_active(false, window, cx);
});
// Sending a message while the chat is open should not change the flag.
@ -406,7 +406,7 @@ async fn test_channel_message_changes(
executor.run_until_parked();
let b_has_messages = cx_b.update(|cx| {
let b_has_messages = cx_b.update(|_, cx| {
client_b
.channel_store()
.read(cx)
@ -416,7 +416,7 @@ async fn test_channel_message_changes(
assert!(b_has_messages);
// Closing the chat should re-enable change tracking
cx_b.update(|_| drop(chat_panel_b));
cx_b.update(|_, _| drop(chat_panel_b));
channel_chat_a
.update(cx_a, |c, cx| c.send_message("four".into(), cx).unwrap())
@ -425,7 +425,7 @@ async fn test_channel_message_changes(
executor.run_until_parked();
let b_has_messages = cx_b.update(|cx| {
let b_has_messages = cx_b.update(|_, cx| {
client_b
.channel_store()
.read(cx)

View file

@ -7,7 +7,7 @@ use call::ActiveCall;
use channel::{ChannelMembership, ChannelStore};
use client::{ChannelId, User};
use futures::future::try_join_all;
use gpui::{BackgroundExecutor, Model, SharedString, TestAppContext};
use gpui::{BackgroundExecutor, Entity, SharedString, TestAppContext};
use rpc::{
proto::{self, ChannelRole},
RECEIVE_TIMEOUT,
@ -1401,7 +1401,7 @@ struct ExpectedChannel {
#[track_caller]
fn assert_channel_invitations(
channel_store: &Model<ChannelStore>,
channel_store: &Entity<ChannelStore>,
cx: &TestAppContext,
expected_channels: &[ExpectedChannel],
) {
@ -1423,7 +1423,7 @@ fn assert_channel_invitations(
#[track_caller]
fn assert_channels(
channel_store: &Model<ChannelStore>,
channel_store: &Entity<ChannelStore>,
cx: &TestAppContext,
expected_channels: &[ExpectedChannel],
) {
@ -1444,7 +1444,7 @@ fn assert_channels(
#[track_caller]
fn assert_channels_list_shape(
channel_store: &Model<ChannelStore>,
channel_store: &Entity<ChannelStore>,
cx: &TestAppContext,
expected_channels: &[(ChannelId, usize)],
) {

View file

@ -81,14 +81,21 @@ async fn test_host_disconnect(
assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer()));
let workspace_b = cx_b
.add_window(|cx| Workspace::new(None, project_b.clone(), client_b.app_state.clone(), cx));
let workspace_b = cx_b.add_window(|window, cx| {
Workspace::new(
None,
project_b.clone(),
client_b.app_state.clone(),
window,
cx,
)
});
let cx_b = &mut VisualTestContext::from_window(*workspace_b, cx_b);
let workspace_b_view = workspace_b.root_view(cx_b).unwrap();
let workspace_b_view = workspace_b.root_model(cx_b).unwrap();
let editor_b = workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "b.txt"), None, true, cx)
.update(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id, "b.txt"), None, true, window, cx)
})
.unwrap()
.await
@ -97,10 +104,10 @@ async fn test_host_disconnect(
.unwrap();
//TODO: focus
assert!(cx_b.update_view(&editor_b, |editor, cx| editor.is_focused(cx)));
editor_b.update(cx_b, |editor, cx| editor.insert("X", cx));
assert!(cx_b.update_window_model(&editor_b, |editor, window, _| editor.is_focused(window)));
editor_b.update_in(cx_b, |editor, window, cx| editor.insert("X", window, cx));
cx_b.update(|cx| {
cx_b.update(|_, cx| {
assert!(workspace_b_view.read(cx).is_edited());
});
@ -120,7 +127,7 @@ async fn test_host_disconnect(
// Ensure client B's edited state is reset and that the whole window is blurred.
workspace_b
.update(cx_b, |workspace, cx| {
.update(cx_b, |workspace, _, cx| {
assert!(workspace.active_modal::<DisconnectedOverlay>(cx).is_some());
assert!(!workspace.is_edited());
})
@ -128,8 +135,8 @@ async fn test_host_disconnect(
// Ensure client B is not prompted to save edits when closing window after disconnecting.
let can_close = workspace_b
.update(cx_b, |workspace, cx| {
workspace.prepare_to_close(CloseIntent::Quit, cx)
.update(cx_b, |workspace, window, cx| {
workspace.prepare_to_close(CloseIntent::Quit, window, cx)
})
.unwrap()
.await
@ -200,11 +207,12 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor(
.await
.unwrap();
let cx_a = cx_a.add_empty_window();
let editor_a = cx_a.new_view(|cx| Editor::for_buffer(buffer_a, Some(project_a), cx));
let editor_a = cx_a
.new_window_model(|window, cx| Editor::for_buffer(buffer_a, Some(project_a), window, cx));
let mut editor_cx_a = EditorTestContext {
cx: cx_a.clone(),
window: cx_a.handle(),
window: cx_a.window_handle(),
editor: editor_a,
assertion_cx: AssertionContextManager::new(),
};
@ -215,11 +223,12 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor(
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
.await
.unwrap();
let editor_b = cx_b.new_view(|cx| Editor::for_buffer(buffer_b, Some(project_b), cx));
let editor_b = cx_b
.new_window_model(|window, cx| Editor::for_buffer(buffer_b, Some(project_b), window, cx));
let mut editor_cx_b = EditorTestContext {
cx: cx_b.clone(),
window: cx_b.handle(),
window: cx_b.window_handle(),
editor: editor_b,
assertion_cx: AssertionContextManager::new(),
};
@ -231,8 +240,9 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor(
editor_cx_b.set_selections_state(indoc! {"
Some textˇ
"});
editor_cx_a
.update_editor(|editor, cx| editor.newline_above(&editor::actions::NewlineAbove, cx));
editor_cx_a.update_editor(|editor, window, cx| {
editor.newline_above(&editor::actions::NewlineAbove, window, cx)
});
executor.run_until_parked();
editor_cx_a.assert_editor_state(indoc! {"
ˇ
@ -252,8 +262,9 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor(
Some textˇ
"});
editor_cx_a
.update_editor(|editor, cx| editor.newline_below(&editor::actions::NewlineBelow, cx));
editor_cx_a.update_editor(|editor, window, cx| {
editor.newline_below(&editor::actions::NewlineBelow, window, cx)
});
executor.run_until_parked();
editor_cx_a.assert_editor_state(indoc! {"
@ -316,8 +327,9 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
.await
.unwrap();
let cx_b = cx_b.add_empty_window();
let editor_b =
cx_b.new_view(|cx| Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), cx));
let editor_b = cx_b.new_window_model(|window, cx| {
Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), window, cx)
});
let fake_language_server = fake_language_servers.next().await.unwrap();
cx_a.background_executor.run_until_parked();
@ -327,11 +339,11 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
});
// Type a completion trigger character as the guest.
editor_b.update(cx_b, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
editor.handle_input(".", cx);
editor_b.update_in(cx_b, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
editor.handle_input(".", window, cx);
});
cx_b.focus_view(&editor_b);
cx_b.focus(&editor_b);
// Receive a completion request as the host's language server.
// Return some completions from the host's language server.
@ -393,9 +405,9 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
});
// Confirm a completion on the guest.
editor_b.update(cx_b, |editor, cx| {
editor_b.update_in(cx_b, |editor, window, cx| {
assert!(editor.context_menu_visible());
editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, cx);
editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, window, cx);
assert_eq!(editor.text(cx), "fn main() { a.first_method() }");
});
@ -440,10 +452,10 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
// Now we do a second completion, this time to ensure that documentation/snippets are
// resolved
editor_b.update(cx_b, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([46..46]));
editor.handle_input("; a", cx);
editor.handle_input(".", cx);
editor_b.update_in(cx_b, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| s.select_ranges([46..46]));
editor.handle_input("; a", window, cx);
editor.handle_input(".", window, cx);
});
buffer_b.read_with(cx_b, |buffer, _| {
@ -507,18 +519,18 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
completion_response.next().await.unwrap();
editor_b.update(cx_b, |editor, cx| {
editor_b.update_in(cx_b, |editor, window, cx| {
assert!(editor.context_menu_visible());
editor.context_menu_first(&ContextMenuFirst {}, cx);
editor.context_menu_first(&ContextMenuFirst {}, window, cx);
});
resolve_completion_response.next().await.unwrap();
cx_b.executor().run_until_parked();
// When accepting the completion, the snippet is insert.
editor_b.update(cx_b, |editor, cx| {
editor_b.update_in(cx_b, |editor, window, cx| {
assert!(editor.context_menu_visible());
editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, cx);
editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, window, cx);
assert_eq!(
editor.text(cx),
"use d::SomeTrait;\nfn main() { a.first_method(); a.third_method(, , ) }"
@ -568,8 +580,8 @@ async fn test_collaborating_with_code_actions(
let project_b = client_b.join_remote_project(project_id, cx_b).await;
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
let editor_b = workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, cx)
.update_in(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
})
.await
.unwrap()
@ -592,12 +604,12 @@ async fn test_collaborating_with_code_actions(
requests.next().await;
// Move cursor to a location that contains code actions.
editor_b.update(cx_b, |editor, cx| {
editor.change_selections(None, cx, |s| {
editor_b.update_in(cx_b, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| {
s.select_ranges([Point::new(1, 31)..Point::new(1, 31)])
});
});
cx_b.focus_view(&editor_b);
cx_b.focus(&editor_b);
let mut requests = fake_language_server
.handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
@ -657,11 +669,12 @@ async fn test_collaborating_with_code_actions(
requests.next().await;
// Toggle code actions and wait for them to display.
editor_b.update(cx_b, |editor, cx| {
editor_b.update_in(cx_b, |editor, window, cx| {
editor.toggle_code_actions(
&ToggleCodeActions {
deployed_from_indicator: None,
},
window,
cx,
);
});
@ -673,8 +686,8 @@ async fn test_collaborating_with_code_actions(
// Confirming the code action will trigger a resolve request.
let confirm_action = editor_b
.update(cx_b, |editor, cx| {
Editor::confirm_code_action(editor, &ConfirmCodeAction { item_ix: Some(0) }, cx)
.update_in(cx_b, |editor, window, cx| {
Editor::confirm_code_action(editor, &ConfirmCodeAction { item_ix: Some(0) }, window, cx)
})
.unwrap();
fake_language_server.handle_request::<lsp::request::CodeActionResolveRequest, _, _>(
@ -725,14 +738,14 @@ async fn test_collaborating_with_code_actions(
.downcast::<Editor>()
.unwrap()
});
code_action_editor.update(cx_b, |editor, cx| {
code_action_editor.update_in(cx_b, |editor, window, cx| {
assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
editor.undo(&Undo, cx);
editor.undo(&Undo, window, cx);
assert_eq!(
editor.text(cx),
"mod other;\nfn main() { let foo = other::foo(); }\npub fn foo() -> usize { 4 }"
);
editor.redo(&Redo, cx);
editor.redo(&Redo, window, cx);
assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
});
}
@ -784,8 +797,8 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
let editor_b = workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "one.rs"), None, true, cx)
.update_in(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id, "one.rs"), None, true, window, cx)
})
.await
.unwrap()
@ -794,9 +807,9 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
let fake_language_server = fake_language_servers.next().await.unwrap();
// Move cursor to a location that can be renamed.
let prepare_rename = editor_b.update(cx_b, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([7..7]));
editor.rename(&Rename, cx).unwrap()
let prepare_rename = editor_b.update_in(cx_b, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| s.select_ranges([7..7]));
editor.rename(&Rename, window, cx).unwrap()
});
fake_language_server
@ -834,12 +847,12 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
});
// Cancel the rename, and repeat the same, but use selections instead of cursor movement
editor_b.update(cx_b, |editor, cx| {
editor.cancel(&editor::actions::Cancel, cx);
editor_b.update_in(cx_b, |editor, window, cx| {
editor.cancel(&editor::actions::Cancel, window, cx);
});
let prepare_rename = editor_b.update(cx_b, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([7..8]));
editor.rename(&Rename, cx).unwrap()
let prepare_rename = editor_b.update_in(cx_b, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| s.select_ranges([7..8]));
editor.rename(&Rename, window, cx).unwrap()
});
fake_language_server
@ -875,8 +888,8 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
});
});
let confirm_rename = editor_b.update(cx_b, |editor, cx| {
Editor::confirm_rename(editor, &ConfirmRename, cx).unwrap()
let confirm_rename = editor_b.update_in(cx_b, |editor, window, cx| {
Editor::confirm_rename(editor, &ConfirmRename, window, cx).unwrap()
});
fake_language_server
.handle_request::<lsp::request::Rename, _, _>(|params, _| async move {
@ -934,17 +947,17 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
workspace.active_item_as::<Editor>(cx).unwrap()
});
rename_editor.update(cx_b, |editor, cx| {
rename_editor.update_in(cx_b, |editor, window, cx| {
assert_eq!(
editor.text(cx),
"const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
);
editor.undo(&Undo, cx);
editor.undo(&Undo, window, cx);
assert_eq!(
editor.text(cx),
"const ONE: usize = 1;\nconst TWO: usize = one::ONE + one::ONE;"
);
editor.redo(&Redo, cx);
editor.redo(&Redo, window, cx);
assert_eq!(
editor.text(cx),
"const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
@ -952,12 +965,12 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
});
// Ensure temporary rename edits cannot be undone/redone.
editor_b.update(cx_b, |editor, cx| {
editor.undo(&Undo, cx);
editor_b.update_in(cx_b, |editor, window, cx| {
editor.undo(&Undo, window, cx);
assert_eq!(editor.text(cx), "const ONE: usize = 1;");
editor.undo(&Undo, cx);
editor.undo(&Undo, window, cx);
assert_eq!(editor.text(cx), "const ONE: usize = 1;");
editor.redo(&Redo, cx);
editor.redo(&Redo, window, cx);
assert_eq!(editor.text(cx), "const THREE: usize = 1;");
})
}
@ -1192,7 +1205,8 @@ async fn test_share_project(
.await
.unwrap();
let editor_b = cx_b.new_view(|cx| Editor::for_buffer(buffer_b, None, cx));
let editor_b =
cx_b.new_window_model(|window, cx| Editor::for_buffer(buffer_b, None, window, cx));
// Client A sees client B's selection
executor.run_until_parked();
@ -1206,7 +1220,9 @@ async fn test_share_project(
});
// Edit the buffer as client B and see that edit as client A.
editor_b.update(cx_b, |editor, cx| editor.handle_input("ok, ", cx));
editor_b.update_in(cx_b, |editor, window, cx| {
editor.handle_input("ok, ", window, cx)
});
executor.run_until_parked();
buffer_a.read_with(cx_a, |buffer, _| {
@ -1233,7 +1249,7 @@ async fn test_share_project(
let _project_c = client_c.join_remote_project(initial_project.id, cx_c).await;
// Client B closes the editor, and client A sees client B's selections removed.
cx_b.update(move |_| drop(editor_b));
cx_b.update(move |_, _| drop(editor_b));
executor.run_until_parked();
buffer_a.read_with(cx_a, |buffer, _| {
@ -1297,7 +1313,9 @@ async fn test_on_input_format_from_host_to_guest(
.await
.unwrap();
let cx_a = cx_a.add_empty_window();
let editor_a = cx_a.new_view(|cx| Editor::for_buffer(buffer_a, Some(project_a.clone()), cx));
let editor_a = cx_a.new_window_model(|window, cx| {
Editor::for_buffer(buffer_a, Some(project_a.clone()), window, cx)
});
let fake_language_server = fake_language_servers.next().await.unwrap();
executor.run_until_parked();
@ -1329,10 +1347,10 @@ async fn test_on_input_format_from_host_to_guest(
.unwrap();
// Type a on type formatting trigger character as the guest.
cx_a.focus_view(&editor_a);
editor_a.update(cx_a, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
editor.handle_input(">", cx);
cx_a.focus(&editor_a);
editor_a.update_in(cx_a, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
editor.handle_input(">", window, cx);
});
executor.run_until_parked();
@ -1342,9 +1360,9 @@ async fn test_on_input_format_from_host_to_guest(
});
// Undo should remove LSP edits first
editor_a.update(cx_a, |editor, cx| {
editor_a.update_in(cx_a, |editor, window, cx| {
assert_eq!(editor.text(cx), "fn main() { a>~< }");
editor.undo(&Undo, cx);
editor.undo(&Undo, window, cx);
assert_eq!(editor.text(cx), "fn main() { a> }");
});
executor.run_until_parked();
@ -1353,9 +1371,9 @@ async fn test_on_input_format_from_host_to_guest(
assert_eq!(buffer.text(), "fn main() { a> }")
});
editor_a.update(cx_a, |editor, cx| {
editor_a.update_in(cx_a, |editor, window, cx| {
assert_eq!(editor.text(cx), "fn main() { a> }");
editor.undo(&Undo, cx);
editor.undo(&Undo, window, cx);
assert_eq!(editor.text(cx), "fn main() { a }");
});
executor.run_until_parked();
@ -1417,16 +1435,18 @@ async fn test_on_input_format_from_guest_to_host(
.await
.unwrap();
let cx_b = cx_b.add_empty_window();
let editor_b = cx_b.new_view(|cx| Editor::for_buffer(buffer_b, Some(project_b.clone()), cx));
let editor_b = cx_b.new_window_model(|window, cx| {
Editor::for_buffer(buffer_b, Some(project_b.clone()), window, cx)
});
let fake_language_server = fake_language_servers.next().await.unwrap();
executor.run_until_parked();
// Type a on type formatting trigger character as the guest.
cx_b.focus_view(&editor_b);
editor_b.update(cx_b, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
editor.handle_input(":", cx);
cx_b.focus(&editor_b);
editor_b.update_in(cx_b, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
editor.handle_input(":", window, cx);
});
// Receive an OnTypeFormatting request as the host's language server.
@ -1465,9 +1485,9 @@ async fn test_on_input_format_from_guest_to_host(
});
// Undo should remove LSP edits first
editor_b.update(cx_b, |editor, cx| {
editor_b.update_in(cx_b, |editor, window, cx| {
assert_eq!(editor.text(cx), "fn main() { a:~: }");
editor.undo(&Undo, cx);
editor.undo(&Undo, window, cx);
assert_eq!(editor.text(cx), "fn main() { a: }");
});
executor.run_until_parked();
@ -1476,9 +1496,9 @@ async fn test_on_input_format_from_guest_to_host(
assert_eq!(buffer.text(), "fn main() { a: }")
});
editor_b.update(cx_b, |editor, cx| {
editor_b.update_in(cx_b, |editor, window, cx| {
assert_eq!(editor.text(cx), "fn main() { a: }");
editor.undo(&Undo, cx);
editor.undo(&Undo, window, cx);
assert_eq!(editor.text(cx), "fn main() { a }");
});
executor.run_until_parked();
@ -1589,8 +1609,8 @@ async fn test_mutual_editor_inlay_hint_cache_update(
.await
.unwrap();
let editor_a = workspace_a
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, cx)
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
})
.await
.unwrap()
@ -1639,8 +1659,8 @@ async fn test_mutual_editor_inlay_hint_cache_update(
});
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
let editor_b = workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, cx)
.update_in(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
})
.await
.unwrap()
@ -1657,11 +1677,11 @@ async fn test_mutual_editor_inlay_hint_cache_update(
});
let after_client_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
editor_b.update(cx_b, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([13..13].clone()));
editor.handle_input(":", cx);
editor_b.update_in(cx_b, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| s.select_ranges([13..13].clone()));
editor.handle_input(":", window, cx);
});
cx_b.focus_view(&editor_b);
cx_b.focus(&editor_b);
executor.run_until_parked();
editor_a.update(cx_a, |editor, _| {
@ -1678,11 +1698,11 @@ async fn test_mutual_editor_inlay_hint_cache_update(
});
let after_host_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
editor_a.update(cx_a, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
editor.handle_input("a change to increment both buffers' versions", cx);
editor_a.update_in(cx_a, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| s.select_ranges([13..13]));
editor.handle_input("a change to increment both buffers' versions", window, cx);
});
cx_a.focus_view(&editor_a);
cx_a.focus(&editor_a);
executor.run_until_parked();
editor_a.update(cx_a, |editor, _| {
@ -1815,8 +1835,8 @@ async fn test_inlay_hint_refresh_is_forwarded(
cx_a.background_executor.start_waiting();
let editor_a = workspace_a
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, cx)
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
})
.await
.unwrap()
@ -1824,8 +1844,8 @@ async fn test_inlay_hint_refresh_is_forwarded(
.unwrap();
let editor_b = workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, cx)
.update_in(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
})
.await
.unwrap()
@ -1985,8 +2005,8 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
// Create editor_a
let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
let editor_a = workspace_a
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, "file.txt"), None, true, cx)
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, "file.txt"), None, true, window, cx)
})
.await
.unwrap()
@ -1997,8 +2017,8 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
let project_b = client_b.join_remote_project(project_id, cx_b).await;
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
let editor_b = workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "file.txt"), None, true, cx)
.update_in(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id, "file.txt"), None, true, window, cx)
})
.await
.unwrap()
@ -2006,9 +2026,9 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
.unwrap();
// client_b now requests git blame for the open buffer
editor_b.update(cx_b, |editor_b, cx| {
editor_b.update_in(cx_b, |editor_b, window, cx| {
assert!(editor_b.blame().is_none());
editor_b.toggle_git_blame(&editor::actions::ToggleGitBlame {}, cx);
editor_b.toggle_git_blame(&editor::actions::ToggleGitBlame {}, window, cx);
});
cx_a.executor().run_until_parked();
@ -2054,7 +2074,7 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
// editor_b updates the file, which gets sent to client_a, which updates git blame,
// which gets back to client_b.
editor_b.update(cx_b, |editor_b, cx| {
editor_b.update_in(cx_b, |editor_b, _, cx| {
editor_b.edit([(Point::new(0, 3)..Point::new(0, 3), "FOO")], cx);
});
@ -2089,7 +2109,7 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
});
// Now editor_a also updates the file
editor_a.update(cx_a, |editor_a, cx| {
editor_a.update_in(cx_a, |editor_a, _, cx| {
editor_a.edit([(Point::new(1, 3)..Point::new(1, 3), "FOO")], cx);
});
@ -2175,19 +2195,21 @@ async fn test_collaborating_with_editorconfig(
.await
.unwrap();
let cx_a = cx_a.add_empty_window();
let main_editor_a =
cx_a.new_view(|cx| Editor::for_buffer(main_buffer_a, Some(project_a.clone()), cx));
let other_editor_a =
cx_a.new_view(|cx| Editor::for_buffer(other_buffer_a, Some(project_a), cx));
let main_editor_a = cx_a.new_window_model(|window, cx| {
Editor::for_buffer(main_buffer_a, Some(project_a.clone()), window, cx)
});
let other_editor_a = cx_a.new_window_model(|window, cx| {
Editor::for_buffer(other_buffer_a, Some(project_a), window, cx)
});
let mut main_editor_cx_a = EditorTestContext {
cx: cx_a.clone(),
window: cx_a.handle(),
window: cx_a.window_handle(),
editor: main_editor_a,
assertion_cx: AssertionContextManager::new(),
};
let mut other_editor_cx_a = EditorTestContext {
cx: cx_a.clone(),
window: cx_a.handle(),
window: cx_a.window_handle(),
editor: other_editor_a,
assertion_cx: AssertionContextManager::new(),
};
@ -2207,19 +2229,21 @@ async fn test_collaborating_with_editorconfig(
.await
.unwrap();
let cx_b = cx_b.add_empty_window();
let main_editor_b =
cx_b.new_view(|cx| Editor::for_buffer(main_buffer_b, Some(project_b.clone()), cx));
let other_editor_b =
cx_b.new_view(|cx| Editor::for_buffer(other_buffer_b, Some(project_b.clone()), cx));
let main_editor_b = cx_b.new_window_model(|window, cx| {
Editor::for_buffer(main_buffer_b, Some(project_b.clone()), window, cx)
});
let other_editor_b = cx_b.new_window_model(|window, cx| {
Editor::for_buffer(other_buffer_b, Some(project_b.clone()), window, cx)
});
let mut main_editor_cx_b = EditorTestContext {
cx: cx_b.clone(),
window: cx_b.handle(),
window: cx_b.window_handle(),
editor: main_editor_b,
assertion_cx: AssertionContextManager::new(),
};
let mut other_editor_cx_b = EditorTestContext {
cx: cx_b.clone(),
window: cx_b.handle(),
window: cx_b.window_handle(),
editor: other_editor_b,
assertion_cx: AssertionContextManager::new(),
};
@ -2383,12 +2407,12 @@ fn tab_undo_assert(
cx_b.assert_editor_state(expected_initial);
if a_tabs {
cx_a.update_editor(|editor, cx| {
editor.tab(&editor::actions::Tab, cx);
cx_a.update_editor(|editor, window, cx| {
editor.tab(&editor::actions::Tab, window, cx);
});
} else {
cx_b.update_editor(|editor, cx| {
editor.tab(&editor::actions::Tab, cx);
cx_b.update_editor(|editor, window, cx| {
editor.tab(&editor::actions::Tab, window, cx);
});
}
@ -2399,12 +2423,12 @@ fn tab_undo_assert(
cx_b.assert_editor_state(expected_tabbed);
if a_tabs {
cx_a.update_editor(|editor, cx| {
editor.undo(&editor::actions::Undo, cx);
cx_a.update_editor(|editor, window, cx| {
editor.undo(&editor::actions::Undo, window, cx);
});
} else {
cx_b.update_editor(|editor, cx| {
editor.undo(&editor::actions::Undo, cx);
cx_b.update_editor(|editor, window, cx| {
editor.undo(&editor::actions::Undo, window, cx);
});
}
cx_a.run_until_parked();

View file

@ -8,8 +8,8 @@ use collab_ui::{
};
use editor::{Editor, ExcerptRange, MultiBuffer};
use gpui::{
point, BackgroundExecutor, BorrowAppContext, Context, Entity, SharedString, TestAppContext,
View, VisualContext, VisualTestContext,
point, AppContext as _, BackgroundExecutor, BorrowAppContext, Entity, SharedString,
TestAppContext, VisualTestContext,
};
use language::Capability;
use project::WorktreeSettings;
@ -77,23 +77,23 @@ async fn test_basic_following(
let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
cx_b.update(|cx| {
assert!(cx.is_window_active());
cx_b.update(|window, _| {
assert!(window.is_window_active());
});
// Client A opens some editors.
let pane_a = workspace_a.update(cx_a, |workspace, _| workspace.active_pane().clone());
let editor_a1 = workspace_a
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, cx)
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, window, cx)
})
.await
.unwrap()
.downcast::<Editor>()
.unwrap();
let editor_a2 = workspace_a
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, "2.txt"), None, true, cx)
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, "2.txt"), None, true, window, cx)
})
.await
.unwrap()
@ -102,8 +102,8 @@ async fn test_basic_following(
// Client B opens an editor.
let editor_b1 = workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, cx)
.update_in(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, window, cx)
})
.await
.unwrap()
@ -116,22 +116,24 @@ async fn test_basic_following(
let peer_id_d = client_d.peer_id().unwrap();
// Client A updates their selections in those editors
editor_a1.update(cx_a, |editor, cx| {
editor.handle_input("a", cx);
editor.handle_input("b", cx);
editor.handle_input("c", cx);
editor.select_left(&Default::default(), cx);
editor_a1.update_in(cx_a, |editor, window, cx| {
editor.handle_input("a", window, cx);
editor.handle_input("b", window, cx);
editor.handle_input("c", window, cx);
editor.select_left(&Default::default(), window, cx);
assert_eq!(editor.selections.ranges(cx), vec![3..2]);
});
editor_a2.update(cx_a, |editor, cx| {
editor.handle_input("d", cx);
editor.handle_input("e", cx);
editor.select_left(&Default::default(), cx);
editor_a2.update_in(cx_a, |editor, window, cx| {
editor.handle_input("d", window, cx);
editor.handle_input("e", window, cx);
editor.select_left(&Default::default(), window, cx);
assert_eq!(editor.selections.ranges(cx), vec![2..1]);
});
// When client B starts following client A, only the active view state is replicated to client B.
workspace_b.update(cx_b, |workspace, cx| workspace.follow(peer_id_a, cx));
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.follow(peer_id_a, window, cx)
});
cx_c.executor().run_until_parked();
let editor_b2 = workspace_b.update(cx_b, |workspace, cx| {
@ -165,7 +167,9 @@ async fn test_basic_following(
drop(project_c);
// Client C also follows client A.
workspace_c.update(cx_c, |workspace, cx| workspace.follow(peer_id_a, cx));
workspace_c.update_in(cx_c, |workspace, window, cx| {
workspace.follow(peer_id_a, window, cx)
});
cx_d.executor().run_until_parked();
let active_call_d = cx_d.read(ActiveCall::global);
@ -188,8 +192,8 @@ async fn test_basic_following(
}
// Client C unfollows client A.
workspace_c.update(cx_c, |workspace, cx| {
workspace.unfollow(peer_id_a, cx).unwrap();
workspace_c.update_in(cx_c, |workspace, window, cx| {
workspace.unfollow(peer_id_a, window, cx).unwrap();
});
// All clients see that clients B is following client A.
@ -203,7 +207,9 @@ async fn test_basic_following(
}
// Client C re-follows client A.
workspace_c.update(cx_c, |workspace, cx| workspace.follow(peer_id_a, cx));
workspace_c.update_in(cx_c, |workspace, window, cx| {
workspace.follow(peer_id_a, window, cx)
});
// All clients see that clients B and C are following client A.
cx_c.executor().run_until_parked();
@ -216,9 +222,13 @@ async fn test_basic_following(
}
// Client D follows client B, then switches to following client C.
workspace_d.update(cx_d, |workspace, cx| workspace.follow(peer_id_b, cx));
workspace_d.update_in(cx_d, |workspace, window, cx| {
workspace.follow(peer_id_b, window, cx)
});
cx_a.executor().run_until_parked();
workspace_d.update(cx_d, |workspace, cx| workspace.follow(peer_id_c, cx));
workspace_d.update_in(cx_d, |workspace, window, cx| {
workspace.follow(peer_id_c, window, cx)
});
// All clients see that D is following C
cx_a.executor().run_until_parked();
@ -235,8 +245,8 @@ async fn test_basic_following(
// Client C closes the project.
let weak_workspace_c = workspace_c.downgrade();
workspace_c.update(cx_c, |workspace, cx| {
workspace.close_window(&Default::default(), cx);
workspace_c.update_in(cx_c, |workspace, window, cx| {
workspace.close_window(&Default::default(), window, cx);
});
executor.run_until_parked();
// are you sure you want to leave the call?
@ -260,8 +270,8 @@ async fn test_basic_following(
}
// When client A activates a different editor, client B does so as well.
workspace_a.update(cx_a, |workspace, cx| {
workspace.activate_item(&editor_a1, true, true, cx)
workspace_a.update_in(cx_a, |workspace, window, cx| {
workspace.activate_item(&editor_a1, true, true, window, cx)
});
executor.run_until_parked();
workspace_b.update(cx_b, |workspace, cx| {
@ -272,7 +282,7 @@ async fn test_basic_following(
});
// When client A opens a multibuffer, client B does so as well.
let multibuffer_a = cx_a.new_model(|cx| {
let multibuffer_a = cx_a.new(|cx| {
let buffer_a1 = project_a.update(cx, |project, cx| {
project
.get_open_buffer(&(worktree_id, "1.txt").into(), cx)
@ -302,11 +312,11 @@ async fn test_basic_following(
);
result
});
let multibuffer_editor_a = workspace_a.update(cx_a, |workspace, cx| {
let editor = cx.new_view(|cx| {
Editor::for_multibuffer(multibuffer_a, Some(project_a.clone()), true, cx)
let multibuffer_editor_a = workspace_a.update_in(cx_a, |workspace, window, cx| {
let editor = cx.new(|cx| {
Editor::for_multibuffer(multibuffer_a, Some(project_a.clone()), true, window, cx)
});
workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, cx);
workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
editor
});
executor.run_until_parked();
@ -324,8 +334,8 @@ async fn test_basic_following(
// When client A navigates back and forth, client B does so as well.
workspace_a
.update(cx_a, |workspace, cx| {
workspace.go_back(workspace.active_pane().downgrade(), cx)
.update_in(cx_a, |workspace, window, cx| {
workspace.go_back(workspace.active_pane().downgrade(), window, cx)
})
.await
.unwrap();
@ -338,8 +348,8 @@ async fn test_basic_following(
});
workspace_a
.update(cx_a, |workspace, cx| {
workspace.go_back(workspace.active_pane().downgrade(), cx)
.update_in(cx_a, |workspace, window, cx| {
workspace.go_back(workspace.active_pane().downgrade(), window, cx)
})
.await
.unwrap();
@ -352,8 +362,8 @@ async fn test_basic_following(
});
workspace_a
.update(cx_a, |workspace, cx| {
workspace.go_forward(workspace.active_pane().downgrade(), cx)
.update_in(cx_a, |workspace, window, cx| {
workspace.go_forward(workspace.active_pane().downgrade(), window, cx)
})
.await
.unwrap();
@ -366,8 +376,8 @@ async fn test_basic_following(
});
// Changes to client A's editor are reflected on client B.
editor_a1.update(cx_a, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2]));
editor_a1.update_in(cx_a, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2]));
});
executor.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
executor.run_until_parked();
@ -377,13 +387,15 @@ async fn test_basic_following(
assert_eq!(editor.selections.ranges(cx), &[1..1, 2..2]);
});
editor_a1.update(cx_a, |editor, cx| editor.set_text("TWO", cx));
editor_a1.update_in(cx_a, |editor, window, cx| {
editor.set_text("TWO", window, cx)
});
executor.run_until_parked();
editor_b1.update(cx_b, |editor, cx| assert_eq!(editor.text(cx), "TWO"));
editor_a1.update(cx_a, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
editor.set_scroll_position(point(0., 100.), cx);
editor_a1.update_in(cx_a, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| s.select_ranges([3..3]));
editor.set_scroll_position(point(0., 100.), window, cx);
});
executor.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
executor.run_until_parked();
@ -392,11 +404,11 @@ async fn test_basic_following(
});
// After unfollowing, client B stops receiving updates from client A.
workspace_b.update(cx_b, |workspace, cx| {
workspace.unfollow(peer_id_a, cx).unwrap()
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.unfollow(peer_id_a, window, cx).unwrap()
});
workspace_a.update(cx_a, |workspace, cx| {
workspace.activate_item(&editor_a2, true, true, cx)
workspace_a.update_in(cx_a, |workspace, window, cx| {
workspace.activate_item(&editor_a2, true, true, window, cx)
});
executor.run_until_parked();
assert_eq!(
@ -408,14 +420,16 @@ async fn test_basic_following(
);
// Client A starts following client B.
workspace_a.update(cx_a, |workspace, cx| workspace.follow(peer_id_b, cx));
workspace_a.update_in(cx_a, |workspace, window, cx| {
workspace.follow(peer_id_b, window, cx)
});
executor.run_until_parked();
assert_eq!(
workspace_a.update(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)),
Some(peer_id_b)
);
assert_eq!(
workspace_a.update(cx_a, |workspace, cx| workspace
workspace_a.update_in(cx_a, |workspace, _, cx| workspace
.active_item(cx)
.unwrap()
.item_id()),
@ -471,8 +485,8 @@ async fn test_basic_following(
});
// Client B activates a multibuffer that was created by following client A. Client A returns to that multibuffer.
workspace_b.update(cx_b, |workspace, cx| {
workspace.activate_item(&multibuffer_editor_b, true, true, cx)
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.activate_item(&multibuffer_editor_b, true, true, window, cx)
});
executor.run_until_parked();
workspace_a.update(cx_a, |workspace, cx| {
@ -483,10 +497,10 @@ async fn test_basic_following(
});
// Client B activates a panel, and the previously-opened screen-sharing item gets activated.
let panel = cx_b.new_view(|cx| TestPanel::new(DockPosition::Left, cx));
workspace_b.update(cx_b, |workspace, cx| {
workspace.add_panel(panel, cx);
workspace.toggle_panel_focus::<TestPanel>(cx);
let panel = cx_b.new(|cx| TestPanel::new(DockPosition::Left, cx));
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.add_panel(panel, window, cx);
workspace.toggle_panel_focus::<TestPanel>(window, cx);
});
executor.run_until_parked();
assert_eq!(
@ -498,8 +512,8 @@ async fn test_basic_following(
);
// Toggling the focus back to the pane causes client A to return to the multibuffer.
workspace_b.update(cx_b, |workspace, cx| {
workspace.toggle_panel_focus::<TestPanel>(cx);
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.toggle_panel_focus::<TestPanel>(window, cx);
});
executor.run_until_parked();
workspace_a.update(cx_a, |workspace, cx| {
@ -511,10 +525,10 @@ async fn test_basic_following(
// Client B activates an item that doesn't implement following,
// so the previously-opened screen-sharing item gets activated.
let unfollowable_item = cx_b.new_view(TestItem::new);
workspace_b.update(cx_b, |workspace, cx| {
let unfollowable_item = cx_b.new(TestItem::new);
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.active_pane().update(cx, |pane, cx| {
pane.add_item(Box::new(unfollowable_item), true, true, None, cx)
pane.add_item(Box::new(unfollowable_item), true, true, None, window, cx)
})
});
executor.run_until_parked();
@ -593,19 +607,19 @@ async fn test_following_tab_order(
//Open 1, 3 in that order on client A
workspace_a
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, cx)
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, window, cx)
})
.await
.unwrap();
workspace_a
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, "3.txt"), None, true, cx)
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, "3.txt"), None, true, window, cx)
})
.await
.unwrap();
let pane_paths = |pane: &View<workspace::Pane>, cx: &mut VisualTestContext| {
let pane_paths = |pane: &Entity<workspace::Pane>, cx: &mut VisualTestContext| {
pane.update(cx, |pane, cx| {
pane.items()
.map(|item| {
@ -624,13 +638,15 @@ async fn test_following_tab_order(
assert_eq!(&pane_paths(&pane_a, cx_a), &["1.txt", "3.txt"]);
//Follow client B as client A
workspace_a.update(cx_a, |workspace, cx| workspace.follow(client_b_id, cx));
workspace_a.update_in(cx_a, |workspace, window, cx| {
workspace.follow(client_b_id, window, cx)
});
executor.run_until_parked();
//Open just 2 on client B
workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "2.txt"), None, true, cx)
.update_in(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id, "2.txt"), None, true, window, cx)
})
.await
.unwrap();
@ -641,8 +657,8 @@ async fn test_following_tab_order(
//Open just 1 on client B
workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, cx)
.update_in(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, window, cx)
})
.await
.unwrap();
@ -701,8 +717,8 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
// Client A opens a file.
let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
workspace_a
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, cx)
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, window, cx)
})
.await
.unwrap()
@ -712,8 +728,8 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
// Client B opens a different file.
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "2.txt"), None, true, cx)
.update_in(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id, "2.txt"), None, true, window, cx)
})
.await
.unwrap()
@ -721,24 +737,38 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
.unwrap();
// Clients A and B follow each other in split panes
workspace_a.update(cx_a, |workspace, cx| {
workspace.split_and_clone(workspace.active_pane().clone(), SplitDirection::Right, cx);
workspace_a.update_in(cx_a, |workspace, window, cx| {
workspace.split_and_clone(
workspace.active_pane().clone(),
SplitDirection::Right,
window,
cx,
);
});
workspace_a.update(cx_a, |workspace, cx| {
workspace.follow(client_b.peer_id().unwrap(), cx)
workspace_a.update_in(cx_a, |workspace, window, cx| {
workspace.follow(client_b.peer_id().unwrap(), window, cx)
});
executor.run_until_parked();
workspace_b.update(cx_b, |workspace, cx| {
workspace.split_and_clone(workspace.active_pane().clone(), SplitDirection::Right, cx);
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.split_and_clone(
workspace.active_pane().clone(),
SplitDirection::Right,
window,
cx,
);
});
workspace_b.update(cx_b, |workspace, cx| {
workspace.follow(client_a.peer_id().unwrap(), cx)
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.follow(client_a.peer_id().unwrap(), window, cx)
});
executor.run_until_parked();
// Clients A and B return focus to the original files they had open
workspace_a.update(cx_a, |workspace, cx| workspace.activate_next_pane(cx));
workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
workspace_a.update_in(cx_a, |workspace, window, cx| {
workspace.activate_next_pane(window, cx)
});
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.activate_next_pane(window, cx)
});
executor.run_until_parked();
// Both clients see the other client's focused file in their right pane.
@ -775,15 +805,15 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
// Clients A and B each open a new file.
workspace_a
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, "3.txt"), None, true, cx)
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, "3.txt"), None, true, window, cx)
})
.await
.unwrap();
workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "4.txt"), None, true, cx)
.update_in(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id, "4.txt"), None, true, window, cx)
})
.await
.unwrap();
@ -831,7 +861,9 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
);
// Client A focuses their right pane, in which they're following client B.
workspace_a.update(cx_a, |workspace, cx| workspace.activate_next_pane(cx));
workspace_a.update_in(cx_a, |workspace, window, cx| {
workspace.activate_next_pane(window, cx)
});
executor.run_until_parked();
// Client B sees that client A is now looking at the same file as them.
@ -877,7 +909,9 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
// Client B focuses their right pane, in which they're following client A,
// who is following them.
workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.activate_next_pane(window, cx)
});
executor.run_until_parked();
// Client A sees that client B is now looking at the same file as them.
@ -923,9 +957,9 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
// Client B focuses a file that they previously followed A to, breaking
// the follow.
workspace_b.update(cx_b, |workspace, cx| {
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.active_pane().update(cx, |pane, cx| {
pane.activate_prev_item(true, cx);
pane.activate_prev_item(true, window, cx);
});
});
executor.run_until_parked();
@ -974,9 +1008,9 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
// Client B closes tabs, some of which were originally opened by client A,
// and some of which were originally opened by client B.
workspace_b.update(cx_b, |workspace, cx| {
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.active_pane().update(cx, |pane, cx| {
pane.close_inactive_items(&Default::default(), cx)
pane.close_inactive_items(&Default::default(), window, cx)
.unwrap()
.detach();
});
@ -1022,14 +1056,14 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
);
// Client B follows client A again.
workspace_b.update(cx_b, |workspace, cx| {
workspace.follow(client_a.peer_id().unwrap(), cx)
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.follow(client_a.peer_id().unwrap(), window, cx)
});
executor.run_until_parked();
// Client A cycles through some tabs.
workspace_a.update(cx_a, |workspace, cx| {
workspace_a.update_in(cx_a, |workspace, window, cx| {
workspace.active_pane().update(cx, |pane, cx| {
pane.activate_prev_item(true, cx);
pane.activate_prev_item(true, window, cx);
});
});
executor.run_until_parked();
@ -1071,9 +1105,9 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
]
);
workspace_a.update(cx_a, |workspace, cx| {
workspace_a.update_in(cx_a, |workspace, window, cx| {
workspace.active_pane().update(cx, |pane, cx| {
pane.activate_prev_item(true, cx);
pane.activate_prev_item(true, window, cx);
});
});
executor.run_until_parked();
@ -1118,9 +1152,9 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
]
);
workspace_a.update(cx_a, |workspace, cx| {
workspace_a.update_in(cx_a, |workspace, window, cx| {
workspace.active_pane().update(cx, |pane, cx| {
pane.activate_prev_item(true, cx);
pane.activate_prev_item(true, window, cx);
});
});
executor.run_until_parked();
@ -1215,8 +1249,8 @@ async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppCont
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
let _editor_a1 = workspace_a
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, cx)
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, window, cx)
})
.await
.unwrap()
@ -1228,7 +1262,9 @@ async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppCont
let leader_id = project_b.update(cx_b, |project, _| {
project.collaborators().values().next().unwrap().peer_id
});
workspace_b.update(cx_b, |workspace, cx| workspace.follow(leader_id, cx));
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.follow(leader_id, window, cx)
});
executor.run_until_parked();
assert_eq!(
workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
@ -1243,15 +1279,17 @@ async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppCont
});
// When client B moves, it automatically stops following client A.
editor_b2.update(cx_b, |editor, cx| {
editor.move_right(&editor::actions::MoveRight, cx)
editor_b2.update_in(cx_b, |editor, window, cx| {
editor.move_right(&editor::actions::MoveRight, window, cx)
});
assert_eq!(
workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
None
);
workspace_b.update(cx_b, |workspace, cx| workspace.follow(leader_id, cx));
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.follow(leader_id, window, cx)
});
executor.run_until_parked();
assert_eq!(
workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
@ -1259,13 +1297,15 @@ async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppCont
);
// When client B edits, it automatically stops following client A.
editor_b2.update(cx_b, |editor, cx| editor.insert("X", cx));
editor_b2.update_in(cx_b, |editor, window, cx| editor.insert("X", window, cx));
assert_eq!(
workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
workspace_b.update_in(cx_b, |workspace, _, _| workspace.leader_for_pane(&pane_b)),
None
);
workspace_b.update(cx_b, |workspace, cx| workspace.follow(leader_id, cx));
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.follow(leader_id, window, cx)
});
executor.run_until_parked();
assert_eq!(
workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
@ -1273,15 +1313,17 @@ async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppCont
);
// When client B scrolls, it automatically stops following client A.
editor_b2.update(cx_b, |editor, cx| {
editor.set_scroll_position(point(0., 3.), cx)
editor_b2.update_in(cx_b, |editor, window, cx| {
editor.set_scroll_position(point(0., 3.), window, cx)
});
assert_eq!(
workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
None
);
workspace_b.update(cx_b, |workspace, cx| workspace.follow(leader_id, cx));
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.follow(leader_id, window, cx)
});
executor.run_until_parked();
assert_eq!(
workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
@ -1289,15 +1331,17 @@ async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppCont
);
// When client B activates a different pane, it continues following client A in the original pane.
workspace_b.update(cx_b, |workspace, cx| {
workspace.split_and_clone(pane_b.clone(), SplitDirection::Right, cx)
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.split_and_clone(pane_b.clone(), SplitDirection::Right, window, cx)
});
assert_eq!(
workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
Some(leader_id)
);
workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.activate_next_pane(window, cx)
});
assert_eq!(
workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
Some(leader_id)
@ -1305,8 +1349,8 @@ async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppCont
// When client B activates a different item in the original pane, it automatically stops following client A.
workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "2.txt"), None, true, cx)
.update_in(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id, "2.txt"), None, true, window, cx)
})
.await
.unwrap();
@ -1352,8 +1396,12 @@ async fn test_peers_simultaneously_following_each_other(
project.collaborators().values().next().unwrap().peer_id
});
workspace_a.update(cx_a, |workspace, cx| workspace.follow(client_b_id, cx));
workspace_b.update(cx_b, |workspace, cx| workspace.follow(client_a_id, cx));
workspace_a.update_in(cx_a, |workspace, window, cx| {
workspace.follow(client_b_id, window, cx)
});
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.follow(client_a_id, window, cx)
});
executor.run_until_parked();
workspace_a.update(cx_a, |workspace, _| {
@ -1434,8 +1482,8 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut
.unwrap();
workspace_a
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id_a, "w.rs"), None, true, cx)
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id_a, "w.rs"), None, true, window, cx)
})
.await
.unwrap();
@ -1443,8 +1491,8 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut
executor.run_until_parked();
assert_eq!(visible_push_notifications(cx_b).len(), 1);
workspace_b.update(cx_b, |workspace, cx| {
workspace.follow(client_a.peer_id().unwrap(), cx)
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.follow(client_a.peer_id().unwrap(), window, cx)
});
executor.run_until_parked();
@ -1490,8 +1538,8 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut
// b moves to x.rs in a's project, and a follows
workspace_b_project_a
.update(&mut cx_b2, |workspace, cx| {
workspace.open_path((worktree_id_a, "x.rs"), None, true, cx)
.update_in(&mut cx_b2, |workspace, window, cx| {
workspace.open_path((worktree_id_a, "x.rs"), None, true, window, cx)
})
.await
.unwrap();
@ -1505,8 +1553,8 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut
);
});
workspace_a.update(cx_a, |workspace, cx| {
workspace.follow(client_b.peer_id().unwrap(), cx)
workspace_a.update_in(cx_a, |workspace, window, cx| {
workspace.follow(client_b.peer_id().unwrap(), window, cx)
});
executor.run_until_parked();
@ -1522,8 +1570,8 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut
// b moves to y.rs in b's project, a is still following but can't yet see
workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id_b, "y.rs"), None, true, cx)
.update_in(cx_b, |workspace, window, cx| {
workspace.open_path((worktree_id_b, "y.rs"), None, true, window, cx)
})
.await
.unwrap();
@ -1544,7 +1592,7 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut
executor.run_until_parked();
assert_eq!(visible_push_notifications(cx_a).len(), 1);
cx_a.update(|cx| {
cx_a.update(|_, cx| {
workspace::join_in_room_project(
project_b_id,
client_b.user_id().unwrap(),
@ -1607,8 +1655,8 @@ async fn test_following_stops_on_unshare(cx_a: &mut TestAppContext, cx_b: &mut T
});
// b should follow a to position 1
editor_a.update(cx_a, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([1..1]))
editor_a.update_in(cx_a, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| s.select_ranges([1..1]))
});
cx_a.executor()
.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
@ -1618,7 +1666,7 @@ async fn test_following_stops_on_unshare(cx_a: &mut TestAppContext, cx_b: &mut T
});
// a unshares the project
cx_a.update(|cx| {
cx_a.update(|_, cx| {
let project = workspace_a.read(cx).project().clone();
ActiveCall::global(cx).update(cx, |call, cx| {
call.unshare_project(project, cx).unwrap();
@ -1627,8 +1675,8 @@ async fn test_following_stops_on_unshare(cx_a: &mut TestAppContext, cx_b: &mut T
cx_a.run_until_parked();
// b should not follow a to position 2
editor_a.update(cx_a, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([2..2]))
editor_a.update_in(cx_a, |editor, window, cx| {
editor.change_selections(None, window, cx, |s| s.select_ranges([2..2]))
});
cx_a.executor()
.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
@ -1636,7 +1684,7 @@ async fn test_following_stops_on_unshare(cx_a: &mut TestAppContext, cx_b: &mut T
editor_b.update(cx_b, |editor, cx| {
assert_eq!(editor.selections.ranges(cx), vec![1..1])
});
cx_b.update(|cx| {
cx_b.update(|_, cx| {
let room = ActiveCall::global(cx).read(cx).room().unwrap().read(cx);
let participant = room.remote_participants().get(&client_a.id()).unwrap();
assert_eq!(participant.location, ParticipantLocation::UnsharedProject)
@ -1703,16 +1751,16 @@ async fn test_following_into_excluded_file(
// Client A opens editors for a regular file and an excluded file.
let editor_for_regular = workspace_a
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, cx)
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, window, cx)
})
.await
.unwrap()
.downcast::<Editor>()
.unwrap();
let editor_for_excluded_a = workspace_a
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, ".git/COMMIT_EDITMSG"), None, true, cx)
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, ".git/COMMIT_EDITMSG"), None, true, window, cx)
})
.await
.unwrap()
@ -1720,22 +1768,24 @@ async fn test_following_into_excluded_file(
.unwrap();
// Client A updates their selections in those editors
editor_for_regular.update(cx_a, |editor, cx| {
editor.handle_input("a", cx);
editor.handle_input("b", cx);
editor.handle_input("c", cx);
editor.select_left(&Default::default(), cx);
editor_for_regular.update_in(cx_a, |editor, window, cx| {
editor.handle_input("a", window, cx);
editor.handle_input("b", window, cx);
editor.handle_input("c", window, cx);
editor.select_left(&Default::default(), window, cx);
assert_eq!(editor.selections.ranges(cx), vec![3..2]);
});
editor_for_excluded_a.update(cx_a, |editor, cx| {
editor.select_all(&Default::default(), cx);
editor.handle_input("new commit message", cx);
editor.select_left(&Default::default(), cx);
editor_for_excluded_a.update_in(cx_a, |editor, window, cx| {
editor.select_all(&Default::default(), window, cx);
editor.handle_input("new commit message", window, cx);
editor.select_left(&Default::default(), window, cx);
assert_eq!(editor.selections.ranges(cx), vec![18..17]);
});
// When client B starts following client A, currently visible file is replicated
workspace_b.update(cx_b, |workspace, cx| workspace.follow(peer_id_a, cx));
workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.follow(peer_id_a, window, cx)
});
executor.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
executor.run_until_parked();
@ -1755,15 +1805,15 @@ async fn test_following_into_excluded_file(
vec![18..17]
);
editor_for_excluded_a.update(cx_a, |editor, cx| {
editor.select_right(&Default::default(), cx);
editor_for_excluded_a.update_in(cx_a, |editor, window, cx| {
editor.select_right(&Default::default(), window, cx);
});
executor.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);
executor.run_until_parked();
// Changes from B to the excluded file are replicated in A's editor
editor_for_excluded_b.update(cx_b, |editor, cx| {
editor.handle_input("\nCo-Authored-By: B <b@b.b>", cx);
editor_for_excluded_b.update_in(cx_b, |editor, window, cx| {
editor.handle_input("\nCo-Authored-By: B <b@b.b>", window, cx);
});
executor.run_until_parked();
editor_for_excluded_a.update(cx_a, |editor, cx| {
@ -1774,13 +1824,11 @@ async fn test_following_into_excluded_file(
});
}
fn visible_push_notifications(
cx: &mut TestAppContext,
) -> Vec<gpui::View<ProjectSharedNotification>> {
fn visible_push_notifications(cx: &mut TestAppContext) -> Vec<Entity<ProjectSharedNotification>> {
let mut ret = Vec::new();
for window in cx.windows() {
window
.update(cx, |window, _| {
.update(cx, |window, _, _| {
if let Ok(handle) = window.downcast::<ProjectSharedNotification>() {
ret.push(handle)
}
@ -1821,7 +1869,7 @@ fn followers_by_leader(project_id: u64, cx: &TestAppContext) -> Vec<(PeerId, Vec
})
}
fn pane_summaries(workspace: &View<Workspace>, cx: &mut VisualTestContext) -> Vec<PaneSummary> {
fn pane_summaries(workspace: &Entity<Workspace>, cx: &mut VisualTestContext) -> Vec<PaneSummary> {
workspace.update(cx, |workspace, cx| {
let active_pane = workspace.active_pane();
workspace
@ -1924,14 +1972,14 @@ async fn test_following_to_channel_notes_without_a_shared_project(
// Client A opens the notes for channel 1.
let channel_notes_1_a = cx_a
.update(|cx| ChannelView::open(channel_1_id, None, workspace_a.clone(), cx))
.update(|window, cx| ChannelView::open(channel_1_id, None, workspace_a.clone(), window, cx))
.await
.unwrap();
channel_notes_1_a.update(cx_a, |notes, cx| {
channel_notes_1_a.update_in(cx_a, |notes, window, cx| {
assert_eq!(notes.channel(cx).unwrap().name, "channel-1");
notes.editor.update(cx, |editor, cx| {
editor.insert("Hello from A.", cx);
editor.change_selections(None, cx, |selections| {
editor.insert("Hello from A.", window, cx);
editor.change_selections(None, window, cx, |selections| {
selections.select_ranges(vec![3..4]);
});
});
@ -1939,9 +1987,9 @@ async fn test_following_to_channel_notes_without_a_shared_project(
// Client B follows client A.
workspace_b
.update(cx_b, |workspace, cx| {
.update_in(cx_b, |workspace, window, cx| {
workspace
.start_following(client_a.peer_id().unwrap(), cx)
.start_following(client_a.peer_id().unwrap(), window, cx)
.unwrap()
})
.await
@ -1971,7 +2019,7 @@ async fn test_following_to_channel_notes_without_a_shared_project(
// Client A opens the notes for channel 2.
let channel_notes_2_a = cx_a
.update(|cx| ChannelView::open(channel_2_id, None, workspace_a.clone(), cx))
.update(|window, cx| ChannelView::open(channel_2_id, None, workspace_a.clone(), window, cx))
.await
.unwrap();
channel_notes_2_a.update(cx_a, |notes, cx| {
@ -1997,8 +2045,8 @@ async fn test_following_to_channel_notes_without_a_shared_project(
// Client A opens a local buffer in their unshared project.
let _unshared_editor_a1 = workspace_a
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, cx)
.update_in(cx_a, |workspace, window, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, window, cx)
})
.await
.unwrap()
@ -2027,7 +2075,7 @@ pub(crate) async fn join_channel(
}
async fn share_workspace(
workspace: &View<Workspace>,
workspace: &Entity<Workspace>,
cx: &mut VisualTestContext,
) -> anyhow::Result<u64> {
let project = workspace.update(cx, |workspace, _| workspace.project().clone());
@ -2069,9 +2117,9 @@ async fn test_following_to_channel_notes_other_workspace(
// a opens a second workspace and the channel notes
let (workspace_a2, cx_a2) = client_a.build_test_workspace(&mut cx_a2).await;
cx_a2.update(|cx| cx.activate_window());
cx_a2.update(|window, _| window.activate_window());
cx_a2
.update(|cx| ChannelView::open(channel, None, workspace_a2, cx))
.update(|window, cx| ChannelView::open(channel, None, workspace_a2, window, cx))
.await
.unwrap();
cx_a2.run_until_parked();
@ -2083,7 +2131,7 @@ async fn test_following_to_channel_notes_other_workspace(
});
// a returns to the shared project
cx_a.update(|cx| cx.activate_window());
cx_a.update(|window, _| window.activate_window());
cx_a.run_until_parked();
workspace_a.update(cx_a, |workspace, cx| {
@ -2141,7 +2189,7 @@ async fn test_following_while_deactivated(cx_a: &mut TestAppContext, cx_b: &mut
// a opens a file in a new window
let (_, cx_a2) = client_a.build_test_workspace(&mut cx_a2).await;
cx_a2.update(|cx| cx.activate_window());
cx_a2.update(|window, _| window.activate_window());
cx_a2.simulate_keystrokes("cmd-p");
cx_a2.run_until_parked();
cx_a2.simulate_keystrokes("3 enter");
@ -2152,7 +2200,7 @@ async fn test_following_while_deactivated(cx_a: &mut TestAppContext, cx_b: &mut
cx_a.run_until_parked();
// a returns to the shared project
cx_a.update(|cx| cx.activate_window());
cx_a.update(|window, _| window.activate_window());
cx_a.run_until_parked();
workspace_a.update(cx_a, |workspace, cx| {

View file

@ -18,7 +18,7 @@ use prompt_library::PromptBuilder;
use git::status::{FileStatus, StatusCode, TrackedStatus, UnmergedStatus, UnmergedStatusCode};
use gpui::{
px, size, AppContext, BackgroundExecutor, Model, Modifiers, MouseButton, MouseDownEvent,
px, size, App, BackgroundExecutor, Entity, Modifiers, MouseButton, MouseDownEvent,
TestAppContext, UpdateGlobal,
};
use language::{
@ -2073,7 +2073,7 @@ async fn test_mute_deafen(
}
fn participant_audio_state(
room: &Model<Room>,
room: &Entity<Room>,
cx: &TestAppContext,
) -> Vec<ParticipantAudioState> {
room.read_with(cx, |room, _| {
@ -2252,7 +2252,7 @@ async fn test_room_location(
);
fn participant_locations(
room: &Model<Room>,
room: &Entity<Room>,
cx: &TestAppContext,
) -> Vec<(String, ParticipantLocation)> {
room.read_with(cx, |room, _| {
@ -2821,7 +2821,7 @@ async fn test_git_branch_name(
executor.run_until_parked();
#[track_caller]
fn assert_branch(branch_name: Option<impl Into<String>>, project: &Project, cx: &AppContext) {
fn assert_branch(branch_name: Option<impl Into<String>>, project: &Project, cx: &App) {
let branch_name = branch_name.map(Into::into);
let worktrees = project.visible_worktrees(cx).collect::<Vec<_>>();
assert_eq!(worktrees.len(), 1);
@ -2931,7 +2931,7 @@ async fn test_git_status_sync(
file: &impl AsRef<Path>,
status: Option<FileStatus>,
project: &Project,
cx: &AppContext,
cx: &App,
) {
let file = file.as_ref();
let worktrees = project.visible_worktrees(cx).collect::<Vec<_>>();
@ -6167,7 +6167,7 @@ async fn test_right_click_menu_behind_collab_panel(cx: &mut TestAppContext) {
cx.simulate_resize(size(px(300.), px(300.)));
cx.simulate_keystrokes("cmd-n cmd-n cmd-n");
cx.update(|cx| cx.refresh());
cx.update(|window, _cx| window.refresh());
let tab_bounds = cx.debug_bounds("TAB-2").unwrap();
let new_tab_button_bounds = cx.debug_bounds("ICON-Plus").unwrap();
@ -6260,14 +6260,14 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
let get_path = |pane: &Pane, idx: usize, cx: &AppContext| {
let get_path = |pane: &Pane, idx: usize, cx: &App| {
pane.item_for_index(idx).unwrap().project_path(cx).unwrap()
};
// Opening item 3 as a "permanent" tab
workspace
.update(cx, |workspace, cx| {
workspace.open_path(path_3.clone(), None, false, cx)
.update_in(cx, |workspace, window, cx| {
workspace.open_path(path_3.clone(), None, false, window, cx)
})
.await
.unwrap();
@ -6283,8 +6283,8 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
// Open item 1 as preview
workspace
.update(cx, |workspace, cx| {
workspace.open_path_preview(path_1.clone(), None, true, true, cx)
.update_in(cx, |workspace, window, cx| {
workspace.open_path_preview(path_1.clone(), None, true, true, window, cx)
})
.await
.unwrap();
@ -6304,8 +6304,8 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
// Open item 2 as preview
workspace
.update(cx, |workspace, cx| {
workspace.open_path_preview(path_2.clone(), None, true, true, cx)
.update_in(cx, |workspace, window, cx| {
workspace.open_path_preview(path_2.clone(), None, true, true, window, cx)
})
.await
.unwrap();
@ -6325,7 +6325,9 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
// Going back should show item 1 as preview
workspace
.update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
.update_in(cx, |workspace, window, cx| {
workspace.go_back(pane.downgrade(), window, cx)
})
.await
.unwrap();
@ -6343,10 +6345,11 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
});
// Closing item 1
pane.update(cx, |pane, cx| {
pane.update_in(cx, |pane, window, cx| {
pane.close_item_by_id(
pane.active_item().unwrap().item_id(),
workspace::SaveIntent::Skip,
window,
cx,
)
})
@ -6364,7 +6367,9 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
// Going back should show item 1 as preview
workspace
.update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
.update_in(cx, |workspace, window, cx| {
workspace.go_back(pane.downgrade(), window, cx)
})
.await
.unwrap();
@ -6382,9 +6387,9 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
});
// Close permanent tab
pane.update(cx, |pane, cx| {
pane.update_in(cx, |pane, window, cx| {
let id = pane.items().next().unwrap().item_id();
pane.close_item_by_id(id, workspace::SaveIntent::Skip, cx)
pane.close_item_by_id(id, workspace::SaveIntent::Skip, window, cx)
})
.await
.unwrap();
@ -6431,8 +6436,8 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
// Open item 2 as preview in right pane
workspace
.update(cx, |workspace, cx| {
workspace.open_path_preview(path_2.clone(), None, true, true, cx)
.update_in(cx, |workspace, window, cx| {
workspace.open_path_preview(path_2.clone(), None, true, true, window, cx)
})
.await
.unwrap();
@ -6463,14 +6468,14 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
});
// Focus left pane
workspace.update(cx, |workspace, cx| {
workspace.activate_pane_in_direction(workspace::SplitDirection::Left, cx)
workspace.update_in(cx, |workspace, window, cx| {
workspace.activate_pane_in_direction(workspace::SplitDirection::Left, window, cx)
});
// Open item 2 as preview in left pane
workspace
.update(cx, |workspace, cx| {
workspace.open_path_preview(path_2.clone(), None, true, true, cx)
.update_in(cx, |workspace, window, cx| {
workspace.open_path_preview(path_2.clone(), None, true, true, window, cx)
})
.await
.unwrap();

View file

@ -21,14 +21,14 @@ async fn test_notifications(
let notification_events_b = Arc::new(Mutex::new(Vec::new()));
client_a.notification_store().update(cx_a, |_, cx| {
let events = notification_events_a.clone();
cx.subscribe(&cx.handle(), move |_, _, event, _| {
cx.subscribe(&cx.model(), move |_, _, event, _| {
events.lock().push(event.clone());
})
.detach()
});
client_b.notification_store().update(cx_b, |_, cx| {
let events = notification_events_b.clone();
cx.subscribe(&cx.handle(), move |_, _, event, _| {
cx.subscribe(&cx.model(), move |_, _, event, _| {
events.lock().push(event.clone());
})
.detach()

View file

@ -7,7 +7,7 @@ use collections::{BTreeMap, HashMap};
use editor::Bias;
use fs::{FakeFs, Fs as _};
use git::status::{FileStatus, StatusCode, TrackedStatus, UnmergedStatus, UnmergedStatusCode};
use gpui::{BackgroundExecutor, Model, TestAppContext};
use gpui::{BackgroundExecutor, Entity, TestAppContext};
use language::{
range_to_lsp, FakeLspAdapter, Language, LanguageConfig, LanguageMatcher, PointUtf16,
};
@ -1475,10 +1475,10 @@ fn generate_git_operation(rng: &mut StdRng, client: &TestClient) -> GitOperation
fn buffer_for_full_path(
client: &TestClient,
project: &Model<Project>,
project: &Entity<Project>,
full_path: &PathBuf,
cx: &TestAppContext,
) -> Option<Model<language::Buffer>> {
) -> Option<Entity<language::Buffer>> {
client
.buffers_for_project(project)
.iter()
@ -1494,7 +1494,7 @@ fn project_for_root_name(
client: &TestClient,
root_name: &str,
cx: &TestAppContext,
) -> Option<Model<Project>> {
) -> Option<Entity<Project>> {
if let Some(ix) = project_ix_for_root_name(client.local_projects().deref(), root_name, cx) {
return Some(client.local_projects()[ix].clone());
}
@ -1506,7 +1506,7 @@ fn project_for_root_name(
}
fn project_ix_for_root_name(
projects: &[Model<Project>],
projects: &[Entity<Project>],
root_name: &str,
cx: &TestAppContext,
) -> Option<usize> {
@ -1518,7 +1518,7 @@ fn project_ix_for_root_name(
})
}
fn root_name_for_project(project: &Model<Project>, cx: &TestAppContext) -> String {
fn root_name_for_project(project: &Entity<Project>, cx: &TestAppContext) -> String {
project.read_with(cx, |project, cx| {
project
.visible_worktrees(cx)
@ -1531,7 +1531,7 @@ fn root_name_for_project(project: &Model<Project>, cx: &TestAppContext) -> Strin
}
fn project_path_for_full_path(
project: &Model<Project>,
project: &Entity<Project>,
full_path: &Path,
cx: &TestAppContext,
) -> Option<ProjectPath> {
@ -1552,7 +1552,7 @@ fn project_path_for_full_path(
}
async fn ensure_project_shared(
project: &Model<Project>,
project: &Entity<Project>,
client: &TestClient,
cx: &mut TestAppContext,
) {
@ -1585,7 +1585,7 @@ async fn ensure_project_shared(
}
}
fn choose_random_project(client: &TestClient, rng: &mut StdRng) -> Option<Model<Project>> {
fn choose_random_project(client: &TestClient, rng: &mut StdRng) -> Option<Entity<Project>> {
client
.local_projects()
.deref()

View file

@ -4,7 +4,9 @@ use collections::HashSet;
use extension::ExtensionHostProxy;
use fs::{FakeFs, Fs as _};
use futures::StreamExt as _;
use gpui::{BackgroundExecutor, Context as _, SemanticVersion, TestAppContext, UpdateGlobal as _};
use gpui::{
AppContext as _, BackgroundExecutor, SemanticVersion, TestAppContext, UpdateGlobal as _,
};
use http_client::BlockedHttpClient;
use language::{
language_settings::{
@ -73,7 +75,7 @@ async fn test_sharing_an_ssh_remote_project(
let remote_http_client = Arc::new(BlockedHttpClient);
let node = NodeRuntime::unavailable();
let languages = Arc::new(LanguageRegistry::new(server_cx.executor()));
let _headless_project = server_cx.new_model(|cx| {
let _headless_project = server_cx.new(|cx| {
client::init_settings(cx);
HeadlessProject::new(
HeadlessAppState {
@ -240,7 +242,7 @@ async fn test_ssh_collaboration_git_branches(
let remote_http_client = Arc::new(BlockedHttpClient);
let node = NodeRuntime::unavailable();
let languages = Arc::new(LanguageRegistry::new(server_cx.executor()));
let headless_project = server_cx.new_model(|cx| {
let headless_project = server_cx.new(|cx| {
client::init_settings(cx);
HeadlessProject::new(
HeadlessAppState {
@ -398,7 +400,7 @@ async fn test_ssh_collaboration_formatting_with_prettier(
// User A connects to the remote project via SSH.
server_cx.update(HeadlessProject::init);
let remote_http_client = Arc::new(BlockedHttpClient);
let _headless_project = server_cx.new_model(|cx| {
let _headless_project = server_cx.new(|cx| {
client::init_settings(cx);
HeadlessProject::new(
HeadlessAppState {

View file

@ -17,7 +17,7 @@ use collections::{HashMap, HashSet};
use fs::FakeFs;
use futures::{channel::oneshot, StreamExt as _};
use git::GitHostingProviderRegistry;
use gpui::{BackgroundExecutor, Context, Model, Task, TestAppContext, View, VisualTestContext};
use gpui::{AppContext as _, BackgroundExecutor, Entity, Task, TestAppContext, VisualTestContext};
use http_client::FakeHttpClient;
use language::LanguageRegistry;
use node_runtime::NodeRuntime;
@ -64,17 +64,17 @@ pub struct TestServer {
pub struct TestClient {
pub username: String,
pub app_state: Arc<workspace::AppState>,
channel_store: Model<ChannelStore>,
notification_store: Model<NotificationStore>,
channel_store: Entity<ChannelStore>,
notification_store: Entity<NotificationStore>,
state: RefCell<TestClientState>,
}
#[derive(Default)]
struct TestClientState {
local_projects: Vec<Model<Project>>,
dev_server_projects: Vec<Model<Project>>,
buffers: HashMap<Model<Project>, HashSet<Model<language::Buffer>>>,
channel_buffers: HashSet<Model<ChannelBuffer>>,
local_projects: Vec<Entity<Project>>,
dev_server_projects: Vec<Entity<Project>>,
buffers: HashMap<Entity<Project>, HashSet<Entity<language::Buffer>>>,
channel_buffers: HashSet<Entity<ChannelBuffer>>,
}
pub struct ContactsSummary {
@ -274,10 +274,10 @@ impl TestServer {
git_hosting_provider_registry
.register_hosting_provider(Arc::new(git_hosting_providers::Github));
let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
let workspace_store = cx.new(|cx| WorkspaceStore::new(client.clone(), cx));
let language_registry = Arc::new(LanguageRegistry::test(cx.executor()));
let session = cx.new_model(|cx| AppSession::new(Session::test(), cx));
let session = cx.new(|cx| AppSession::new(Session::test(), cx));
let app_state = Arc::new(workspace::AppState {
client: client.clone(),
user_store: user_store.clone(),
@ -596,15 +596,15 @@ impl TestClient {
self.app_state.fs.as_fake()
}
pub fn channel_store(&self) -> &Model<ChannelStore> {
pub fn channel_store(&self) -> &Entity<ChannelStore> {
&self.channel_store
}
pub fn notification_store(&self) -> &Model<NotificationStore> {
pub fn notification_store(&self) -> &Entity<NotificationStore> {
&self.notification_store
}
pub fn user_store(&self) -> &Model<UserStore> {
pub fn user_store(&self) -> &Entity<UserStore> {
&self.app_state.user_store
}
@ -639,19 +639,19 @@ impl TestClient {
.await;
}
pub fn local_projects(&self) -> impl Deref<Target = Vec<Model<Project>>> + '_ {
pub fn local_projects(&self) -> impl Deref<Target = Vec<Entity<Project>>> + '_ {
Ref::map(self.state.borrow(), |state| &state.local_projects)
}
pub fn dev_server_projects(&self) -> impl Deref<Target = Vec<Model<Project>>> + '_ {
pub fn dev_server_projects(&self) -> impl Deref<Target = Vec<Entity<Project>>> + '_ {
Ref::map(self.state.borrow(), |state| &state.dev_server_projects)
}
pub fn local_projects_mut(&self) -> impl DerefMut<Target = Vec<Model<Project>>> + '_ {
pub fn local_projects_mut(&self) -> impl DerefMut<Target = Vec<Entity<Project>>> + '_ {
RefMut::map(self.state.borrow_mut(), |state| &mut state.local_projects)
}
pub fn dev_server_projects_mut(&self) -> impl DerefMut<Target = Vec<Model<Project>>> + '_ {
pub fn dev_server_projects_mut(&self) -> impl DerefMut<Target = Vec<Entity<Project>>> + '_ {
RefMut::map(self.state.borrow_mut(), |state| {
&mut state.dev_server_projects
})
@ -659,8 +659,8 @@ impl TestClient {
pub fn buffers_for_project<'a>(
&'a self,
project: &Model<Project>,
) -> impl DerefMut<Target = HashSet<Model<language::Buffer>>> + 'a {
project: &Entity<Project>,
) -> impl DerefMut<Target = HashSet<Entity<language::Buffer>>> + 'a {
RefMut::map(self.state.borrow_mut(), |state| {
state.buffers.entry(project.clone()).or_default()
})
@ -668,12 +668,12 @@ impl TestClient {
pub fn buffers(
&self,
) -> impl DerefMut<Target = HashMap<Model<Project>, HashSet<Model<language::Buffer>>>> + '_
) -> impl DerefMut<Target = HashMap<Entity<Project>, HashSet<Entity<language::Buffer>>>> + '_
{
RefMut::map(self.state.borrow_mut(), |state| &mut state.buffers)
}
pub fn channel_buffers(&self) -> impl DerefMut<Target = HashSet<Model<ChannelBuffer>>> + '_ {
pub fn channel_buffers(&self) -> impl DerefMut<Target = HashSet<Entity<ChannelBuffer>>> + '_ {
RefMut::map(self.state.borrow_mut(), |state| &mut state.channel_buffers)
}
@ -703,7 +703,7 @@ impl TestClient {
&self,
root_path: impl AsRef<Path>,
cx: &mut TestAppContext,
) -> (Model<Project>, WorktreeId) {
) -> (Entity<Project>, WorktreeId) {
let project = self.build_empty_local_project(cx);
let (worktree, _) = project
.update(cx, |p, cx| p.find_or_create_worktree(root_path, true, cx))
@ -718,9 +718,9 @@ impl TestClient {
pub async fn build_ssh_project(
&self,
root_path: impl AsRef<Path>,
ssh: Model<SshRemoteClient>,
ssh: Entity<SshRemoteClient>,
cx: &mut TestAppContext,
) -> (Model<Project>, WorktreeId) {
) -> (Entity<Project>, WorktreeId) {
let project = cx.update(|cx| {
Project::ssh(
ssh,
@ -739,7 +739,7 @@ impl TestClient {
(project, worktree.read_with(cx, |tree, _| tree.id()))
}
pub async fn build_test_project(&self, cx: &mut TestAppContext) -> Model<Project> {
pub async fn build_test_project(&self, cx: &mut TestAppContext) -> Entity<Project> {
self.fs()
.insert_tree(
"/a",
@ -755,17 +755,17 @@ impl TestClient {
pub async fn host_workspace(
&self,
workspace: &View<Workspace>,
workspace: &Entity<Workspace>,
channel_id: ChannelId,
cx: &mut VisualTestContext,
) {
cx.update(|cx| {
cx.update(|_, cx| {
let active_call = ActiveCall::global(cx);
active_call.update(cx, |call, cx| call.join_channel(channel_id, cx))
})
.await
.unwrap();
cx.update(|cx| {
cx.update(|_, cx| {
let active_call = ActiveCall::global(cx);
let project = workspace.read(cx).project().clone();
active_call.update(cx, |call, cx| call.share_project(project, cx))
@ -779,7 +779,7 @@ impl TestClient {
&'a self,
channel_id: ChannelId,
cx: &'a mut TestAppContext,
) -> (View<Workspace>, &'a mut VisualTestContext) {
) -> (Entity<Workspace>, &'a mut VisualTestContext) {
cx.update(|cx| workspace::join_channel(channel_id, self.app_state.clone(), None, cx))
.await
.unwrap();
@ -788,7 +788,7 @@ impl TestClient {
self.active_workspace(cx)
}
pub fn build_empty_local_project(&self, cx: &mut TestAppContext) -> Model<Project> {
pub fn build_empty_local_project(&self, cx: &mut TestAppContext) -> Entity<Project> {
cx.update(|cx| {
Project::local(
self.client().clone(),
@ -806,7 +806,7 @@ impl TestClient {
&self,
host_project_id: u64,
guest_cx: &mut TestAppContext,
) -> Model<Project> {
) -> Entity<Project> {
let active_call = guest_cx.read(ActiveCall::global);
let room = active_call.read_with(guest_cx, |call, _| call.room().unwrap().clone());
room.update(guest_cx, |room, cx| {
@ -823,47 +823,47 @@ impl TestClient {
pub fn build_workspace<'a>(
&'a self,
project: &Model<Project>,
project: &Entity<Project>,
cx: &'a mut TestAppContext,
) -> (View<Workspace>, &'a mut VisualTestContext) {
cx.add_window_view(|cx| {
cx.activate_window();
Workspace::new(None, project.clone(), self.app_state.clone(), cx)
) -> (Entity<Workspace>, &'a mut VisualTestContext) {
cx.add_window_view(|window, cx| {
window.activate_window();
Workspace::new(None, project.clone(), self.app_state.clone(), window, cx)
})
}
pub async fn build_test_workspace<'a>(
&'a self,
cx: &'a mut TestAppContext,
) -> (View<Workspace>, &'a mut VisualTestContext) {
) -> (Entity<Workspace>, &'a mut VisualTestContext) {
let project = self.build_test_project(cx).await;
cx.add_window_view(|cx| {
cx.activate_window();
Workspace::new(None, project.clone(), self.app_state.clone(), cx)
cx.add_window_view(|window, cx| {
window.activate_window();
Workspace::new(None, project.clone(), self.app_state.clone(), window, cx)
})
}
pub fn active_workspace<'a>(
&'a self,
cx: &'a mut TestAppContext,
) -> (View<Workspace>, &'a mut VisualTestContext) {
) -> (Entity<Workspace>, &'a mut VisualTestContext) {
let window = cx.update(|cx| cx.active_window().unwrap().downcast::<Workspace>().unwrap());
let view = window.root_view(cx).unwrap();
let model = window.root_model(cx).unwrap();
let cx = VisualTestContext::from_window(*window.deref(), cx).as_mut();
// it might be nice to try and cleanup these at the end of each test.
(view, cx)
(model, cx)
}
}
pub fn open_channel_notes(
channel_id: ChannelId,
cx: &mut VisualTestContext,
) -> Task<anyhow::Result<View<ChannelView>>> {
let window = cx.update(|cx| cx.active_window().unwrap().downcast::<Workspace>().unwrap());
let view = window.root_view(cx).unwrap();
) -> Task<anyhow::Result<Entity<ChannelView>>> {
let window = cx.update(|_, cx| cx.active_window().unwrap().downcast::<Workspace>().unwrap());
let model = window.root_model(cx).unwrap();
cx.update(|cx| ChannelView::open(channel_id, None, view.clone(), cx))
cx.update(|window, cx| ChannelView::open(channel_id, None, model.clone(), window, cx))
}
impl Drop for TestClient {

View file

@ -1,6 +1,6 @@
use std::sync::Arc;
use anyhow::{anyhow, Context, Result};
use anyhow::{anyhow, Context as _, Result};
use chrono::{DateTime, Utc};
use util::ResultExt;