Start work on read-only project access for channel guests
Co-authored-by: Conrad <conrad@zed.dev> Co-authored-by: Mikayla <mikayla@zed.dev>
This commit is contained in:
parent
1bc2e0ee5c
commit
28c39aae17
12 changed files with 160 additions and 57 deletions
|
@ -1099,7 +1099,7 @@ impl Room {
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.joined_projects.retain(|project| {
|
this.joined_projects.retain(|project| {
|
||||||
if let Some(project) = project.upgrade() {
|
if let Some(project) = project.upgrade() {
|
||||||
!project.read(cx).is_read_only()
|
!project.read(cx).is_disconnected()
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
|
@ -129,39 +129,39 @@ impl Database {
|
||||||
.await?,
|
.await?,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
debug_assert!(
|
||||||
|
self.channel_role_for_user(&channel, user_id, &*tx).await? == role
|
||||||
|
);
|
||||||
|
} else if channel.visibility == ChannelVisibility::Public {
|
||||||
|
role = Some(ChannelRole::Guest);
|
||||||
|
let channel_to_join = self
|
||||||
|
.public_ancestors_including_self(&channel, &*tx)
|
||||||
|
.await?
|
||||||
|
.first()
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or(channel.clone());
|
||||||
|
|
||||||
|
channel_member::Entity::insert(channel_member::ActiveModel {
|
||||||
|
id: ActiveValue::NotSet,
|
||||||
|
channel_id: ActiveValue::Set(channel_to_join.id),
|
||||||
|
user_id: ActiveValue::Set(user_id),
|
||||||
|
accepted: ActiveValue::Set(true),
|
||||||
|
role: ActiveValue::Set(ChannelRole::Guest),
|
||||||
|
})
|
||||||
|
.exec(&*tx)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
accept_invite_result = Some(
|
||||||
|
self.calculate_membership_updated(&channel_to_join, user_id, &*tx)
|
||||||
|
.await?,
|
||||||
|
);
|
||||||
|
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
self.channel_role_for_user(&channel, user_id, &*tx).await? == role
|
self.channel_role_for_user(&channel, user_id, &*tx).await? == role
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if channel.visibility == ChannelVisibility::Public {
|
|
||||||
role = Some(ChannelRole::Guest);
|
|
||||||
let channel_to_join = self
|
|
||||||
.public_ancestors_including_self(&channel, &*tx)
|
|
||||||
.await?
|
|
||||||
.first()
|
|
||||||
.cloned()
|
|
||||||
.unwrap_or(channel.clone());
|
|
||||||
|
|
||||||
channel_member::Entity::insert(channel_member::ActiveModel {
|
|
||||||
id: ActiveValue::NotSet,
|
|
||||||
channel_id: ActiveValue::Set(channel_to_join.id),
|
|
||||||
user_id: ActiveValue::Set(user_id),
|
|
||||||
accepted: ActiveValue::Set(true),
|
|
||||||
role: ActiveValue::Set(ChannelRole::Guest),
|
|
||||||
})
|
|
||||||
.exec(&*tx)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
accept_invite_result = Some(
|
|
||||||
self.calculate_membership_updated(&channel_to_join, user_id, &*tx)
|
|
||||||
.await?,
|
|
||||||
);
|
|
||||||
|
|
||||||
debug_assert!(self.channel_role_for_user(&channel, user_id, &*tx).await? == role);
|
|
||||||
}
|
|
||||||
|
|
||||||
if role.is_none() || role == Some(ChannelRole::Banned) {
|
if role.is_none() || role == Some(ChannelRole::Banned) {
|
||||||
Err(anyhow!("not allowed"))?
|
Err(anyhow!("not allowed"))?
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ use call::Room;
|
||||||
use gpui::{Model, TestAppContext};
|
use gpui::{Model, TestAppContext};
|
||||||
|
|
||||||
mod channel_buffer_tests;
|
mod channel_buffer_tests;
|
||||||
|
mod channel_guest_tests;
|
||||||
mod channel_message_tests;
|
mod channel_message_tests;
|
||||||
mod channel_tests;
|
mod channel_tests;
|
||||||
mod editor_tests;
|
mod editor_tests;
|
||||||
|
|
88
crates/collab/src/tests/channel_guest_tests.rs
Normal file
88
crates/collab/src/tests/channel_guest_tests.rs
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
use crate::tests::TestServer;
|
||||||
|
use call::ActiveCall;
|
||||||
|
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
|
||||||
|
use rpc::proto;
|
||||||
|
use workspace::Workspace;
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_channel_guests(
|
||||||
|
executor: BackgroundExecutor,
|
||||||
|
mut cx_a: &mut TestAppContext,
|
||||||
|
mut cx_b: &mut TestAppContext,
|
||||||
|
) {
|
||||||
|
let mut server = TestServer::start(executor.clone()).await;
|
||||||
|
let client_a = server.create_client(cx_a, "user_a").await;
|
||||||
|
let client_b = server.create_client(cx_b, "user_b").await;
|
||||||
|
|
||||||
|
let channel_id = server
|
||||||
|
.make_channel("the-channel", None, (&client_a, cx_a), &mut [])
|
||||||
|
.await;
|
||||||
|
|
||||||
|
client_a
|
||||||
|
.channel_store()
|
||||||
|
.update(cx_a, |channel_store, cx| {
|
||||||
|
channel_store.set_channel_visibility(channel_id, proto::ChannelVisibility::Public, cx)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
client_a
|
||||||
|
.fs()
|
||||||
|
.insert_tree(
|
||||||
|
"/a",
|
||||||
|
serde_json::json!({
|
||||||
|
"a.txt": "a-contents",
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let active_call_a = cx_a.read(ActiveCall::global);
|
||||||
|
let active_call_b = cx_b.read(ActiveCall::global);
|
||||||
|
|
||||||
|
// Client A shares a project in the channel
|
||||||
|
active_call_a
|
||||||
|
.update(cx_a, |call, cx| call.join_channel(channel_id, cx))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
|
||||||
|
let worktree_a = project_a.read_with(cx_a, |project, _| project.worktrees().next().unwrap());
|
||||||
|
let project_id = active_call_a
|
||||||
|
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
cx_a.executor().run_until_parked();
|
||||||
|
|
||||||
|
// Client B joins channel A as a guest
|
||||||
|
cx_b.update(|cx| workspace::join_channel(channel_id, client_b.app_state.clone(), None, cx))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// b should be following a in the shared project.
|
||||||
|
// B is a guest,
|
||||||
|
cx_a.executor().run_until_parked();
|
||||||
|
|
||||||
|
// todo!() the test window does not call activation handlers
|
||||||
|
// correctly yet, so this API does not work.
|
||||||
|
// let project_b = active_call_b.read_with(cx_b, |call, _| {
|
||||||
|
// call.location()
|
||||||
|
// .unwrap()
|
||||||
|
// .upgrade()
|
||||||
|
// .expect("should not be weak")
|
||||||
|
// });
|
||||||
|
|
||||||
|
let window_b = cx_b.update(|cx| cx.active_window().unwrap());
|
||||||
|
let cx_b = &mut VisualTestContext::from_window(window_b, cx_b);
|
||||||
|
|
||||||
|
let workspace_b = window_b
|
||||||
|
.downcast::<Workspace>()
|
||||||
|
.unwrap()
|
||||||
|
.root_view(cx_b)
|
||||||
|
.unwrap();
|
||||||
|
let project_b = workspace_b.update(cx_b, |workspace, _| workspace.project().clone());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
project_b.read_with(cx_b, |project, _| project.remote_id()),
|
||||||
|
Some(project_id),
|
||||||
|
);
|
||||||
|
assert!(project_b.read_with(cx_b, |project, _| project.is_read_only()))
|
||||||
|
}
|
|
@ -1380,7 +1380,7 @@ async fn test_unshare_project(
|
||||||
.unwrap();
|
.unwrap();
|
||||||
executor.run_until_parked();
|
executor.run_until_parked();
|
||||||
|
|
||||||
assert!(project_b.read_with(cx_b, |project, _| project.is_read_only()));
|
assert!(project_b.read_with(cx_b, |project, _| project.is_disconnected()));
|
||||||
|
|
||||||
// Client C opens the project.
|
// Client C opens the project.
|
||||||
let project_c = client_c.build_remote_project(project_id, cx_c).await;
|
let project_c = client_c.build_remote_project(project_id, cx_c).await;
|
||||||
|
@ -1393,7 +1393,7 @@ async fn test_unshare_project(
|
||||||
|
|
||||||
assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared()));
|
assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared()));
|
||||||
|
|
||||||
assert!(project_c.read_with(cx_c, |project, _| project.is_read_only()));
|
assert!(project_c.read_with(cx_c, |project, _| project.is_disconnected()));
|
||||||
|
|
||||||
// Client C can open the project again after client A re-shares.
|
// Client C can open the project again after client A re-shares.
|
||||||
let project_id = active_call_a
|
let project_id = active_call_a
|
||||||
|
@ -1419,7 +1419,7 @@ async fn test_unshare_project(
|
||||||
project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
|
project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
|
||||||
|
|
||||||
project_c2.read_with(cx_c, |project, _| {
|
project_c2.read_with(cx_c, |project, _| {
|
||||||
assert!(project.is_read_only());
|
assert!(project.is_disconnected());
|
||||||
assert!(project.collaborators().is_empty());
|
assert!(project.collaborators().is_empty());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1551,7 +1551,7 @@ async fn test_project_reconnect(
|
||||||
});
|
});
|
||||||
|
|
||||||
project_b1.read_with(cx_b, |project, _| {
|
project_b1.read_with(cx_b, |project, _| {
|
||||||
assert!(!project.is_read_only());
|
assert!(!project.is_disconnected());
|
||||||
assert_eq!(project.collaborators().len(), 1);
|
assert_eq!(project.collaborators().len(), 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1653,7 +1653,7 @@ async fn test_project_reconnect(
|
||||||
});
|
});
|
||||||
|
|
||||||
project_b1.read_with(cx_b, |project, cx| {
|
project_b1.read_with(cx_b, |project, cx| {
|
||||||
assert!(!project.is_read_only());
|
assert!(!project.is_disconnected());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
project
|
project
|
||||||
.worktree_for_id(worktree1_id, cx)
|
.worktree_for_id(worktree1_id, cx)
|
||||||
|
@ -1687,9 +1687,9 @@ async fn test_project_reconnect(
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
project_b2.read_with(cx_b, |project, _| assert!(project.is_read_only()));
|
project_b2.read_with(cx_b, |project, _| assert!(project.is_disconnected()));
|
||||||
|
|
||||||
project_b3.read_with(cx_b, |project, _| assert!(!project.is_read_only()));
|
project_b3.read_with(cx_b, |project, _| assert!(!project.is_disconnected()));
|
||||||
|
|
||||||
buffer_a1.read_with(cx_a, |buffer, _| assert_eq!(buffer.text(), "WaZ"));
|
buffer_a1.read_with(cx_a, |buffer, _| assert_eq!(buffer.text(), "WaZ"));
|
||||||
|
|
||||||
|
@ -1746,7 +1746,7 @@ async fn test_project_reconnect(
|
||||||
executor.run_until_parked();
|
executor.run_until_parked();
|
||||||
|
|
||||||
project_b1.read_with(cx_b, |project, cx| {
|
project_b1.read_with(cx_b, |project, cx| {
|
||||||
assert!(!project.is_read_only());
|
assert!(!project.is_disconnected());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
project
|
project
|
||||||
.worktree_for_id(worktree1_id, cx)
|
.worktree_for_id(worktree1_id, cx)
|
||||||
|
@ -1780,7 +1780,7 @@ async fn test_project_reconnect(
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
project_b3.read_with(cx_b, |project, _| assert!(project.is_read_only()));
|
project_b3.read_with(cx_b, |project, _| assert!(project.is_disconnected()));
|
||||||
|
|
||||||
buffer_a1.read_with(cx_a, |buffer, _| assert_eq!(buffer.text(), "WXaYZ"));
|
buffer_a1.read_with(cx_a, |buffer, _| assert_eq!(buffer.text(), "WXaYZ"));
|
||||||
|
|
||||||
|
@ -3535,7 +3535,7 @@ async fn test_leaving_project(
|
||||||
});
|
});
|
||||||
|
|
||||||
project_b2.read_with(cx_b, |project, _| {
|
project_b2.read_with(cx_b, |project, _| {
|
||||||
assert!(project.is_read_only());
|
assert!(project.is_disconnected());
|
||||||
});
|
});
|
||||||
|
|
||||||
project_c.read_with(cx_c, |project, _| {
|
project_c.read_with(cx_c, |project, _| {
|
||||||
|
@ -3568,11 +3568,11 @@ async fn test_leaving_project(
|
||||||
});
|
});
|
||||||
|
|
||||||
project_b2.read_with(cx_b, |project, _| {
|
project_b2.read_with(cx_b, |project, _| {
|
||||||
assert!(project.is_read_only());
|
assert!(project.is_disconnected());
|
||||||
});
|
});
|
||||||
|
|
||||||
project_c.read_with(cx_c, |project, _| {
|
project_c.read_with(cx_c, |project, _| {
|
||||||
assert!(project.is_read_only());
|
assert!(project.is_disconnected());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1149,7 +1149,7 @@ impl RandomizedTest for ProjectCollaborationTest {
|
||||||
Some((project, cx))
|
Some((project, cx))
|
||||||
});
|
});
|
||||||
|
|
||||||
if !guest_project.is_read_only() {
|
if !guest_project.is_disconnected() {
|
||||||
if let Some((host_project, host_cx)) = host_project {
|
if let Some((host_project, host_cx)) = host_project {
|
||||||
let host_worktree_snapshots =
|
let host_worktree_snapshots =
|
||||||
host_project.read_with(host_cx, |host_project, cx| {
|
host_project.read_with(host_cx, |host_project, cx| {
|
||||||
|
@ -1236,7 +1236,7 @@ impl RandomizedTest for ProjectCollaborationTest {
|
||||||
let buffers = client.buffers().clone();
|
let buffers = client.buffers().clone();
|
||||||
for (guest_project, guest_buffers) in &buffers {
|
for (guest_project, guest_buffers) in &buffers {
|
||||||
let project_id = if guest_project.read_with(client_cx, |project, _| {
|
let project_id = if guest_project.read_with(client_cx, |project, _| {
|
||||||
project.is_local() || project.is_read_only()
|
project.is_local() || project.is_disconnected()
|
||||||
}) {
|
}) {
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -518,7 +518,7 @@ impl<T: RandomizedTest> TestPlan<T> {
|
||||||
for project in client.remote_projects().iter() {
|
for project in client.remote_projects().iter() {
|
||||||
project.read_with(&client_cx, |project, _| {
|
project.read_with(&client_cx, |project, _| {
|
||||||
assert!(
|
assert!(
|
||||||
project.is_read_only(),
|
project.is_disconnected(),
|
||||||
"project {:?} should be read only",
|
"project {:?} should be read only",
|
||||||
project.remote_id()
|
project.remote_id()
|
||||||
)
|
)
|
||||||
|
|
|
@ -44,8 +44,9 @@ pub trait IntoElement: Sized {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert into an element, then draw in the current window at the given origin.
|
/// Convert into an element, then draw in the current window at the given origin.
|
||||||
/// The provided available space is provided to the layout engine to determine the size of the root element.
|
/// The available space argument is provided to the layout engine to determine the size of the
|
||||||
/// Once the element is drawn, its associated element staet is yielded to the given callback.
|
// root element. Once the element is drawn, its associated element state is yielded to the
|
||||||
|
// given callback.
|
||||||
fn draw_and_update_state<T, R>(
|
fn draw_and_update_state<T, R>(
|
||||||
self,
|
self,
|
||||||
origin: Point<Pixels>,
|
origin: Point<Pixels>,
|
||||||
|
|
|
@ -19,7 +19,7 @@ pub struct TestPlatform {
|
||||||
background_executor: BackgroundExecutor,
|
background_executor: BackgroundExecutor,
|
||||||
foreground_executor: ForegroundExecutor,
|
foreground_executor: ForegroundExecutor,
|
||||||
|
|
||||||
active_window: Arc<Mutex<Option<AnyWindowHandle>>>,
|
pub(crate) active_window: Arc<Mutex<Option<AnyWindowHandle>>>,
|
||||||
active_display: Rc<dyn PlatformDisplay>,
|
active_display: Rc<dyn PlatformDisplay>,
|
||||||
active_cursor: Mutex<CursorStyle>,
|
active_cursor: Mutex<CursorStyle>,
|
||||||
current_clipboard_item: Mutex<Option<ClipboardItem>>,
|
current_clipboard_item: Mutex<Option<ClipboardItem>>,
|
||||||
|
@ -106,7 +106,7 @@ impl Platform for TestPlatform {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn activate(&self, _ignoring_other_apps: bool) {
|
fn activate(&self, _ignoring_other_apps: bool) {
|
||||||
unimplemented!()
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hide(&self) {
|
fn hide(&self) {
|
||||||
|
@ -142,6 +142,7 @@ impl Platform for TestPlatform {
|
||||||
*self.active_window.lock() = Some(handle);
|
*self.active_window.lock() = Some(handle);
|
||||||
Box::new(TestWindow::new(
|
Box::new(TestWindow::new(
|
||||||
options,
|
options,
|
||||||
|
handle,
|
||||||
self.weak.clone(),
|
self.weak.clone(),
|
||||||
self.active_display.clone(),
|
self.active_display.clone(),
|
||||||
))
|
))
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
px, AtlasKey, AtlasTextureId, AtlasTile, Pixels, PlatformAtlas, PlatformDisplay,
|
px, AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Pixels, PlatformAtlas,
|
||||||
PlatformInputHandler, PlatformWindow, Point, Size, TestPlatform, TileId, WindowAppearance,
|
PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, Size, TestPlatform, TileId,
|
||||||
WindowBounds, WindowOptions,
|
WindowAppearance, WindowBounds, WindowOptions,
|
||||||
};
|
};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
@ -20,6 +20,7 @@ pub(crate) struct TestWindowHandlers {
|
||||||
|
|
||||||
pub struct TestWindow {
|
pub struct TestWindow {
|
||||||
pub(crate) bounds: WindowBounds,
|
pub(crate) bounds: WindowBounds,
|
||||||
|
pub(crate) handle: AnyWindowHandle,
|
||||||
display: Rc<dyn PlatformDisplay>,
|
display: Rc<dyn PlatformDisplay>,
|
||||||
pub(crate) title: Option<String>,
|
pub(crate) title: Option<String>,
|
||||||
pub(crate) edited: bool,
|
pub(crate) edited: bool,
|
||||||
|
@ -32,6 +33,7 @@ pub struct TestWindow {
|
||||||
impl TestWindow {
|
impl TestWindow {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
options: WindowOptions,
|
options: WindowOptions,
|
||||||
|
handle: AnyWindowHandle,
|
||||||
platform: Weak<TestPlatform>,
|
platform: Weak<TestPlatform>,
|
||||||
display: Rc<dyn PlatformDisplay>,
|
display: Rc<dyn PlatformDisplay>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
@ -39,6 +41,7 @@ impl TestWindow {
|
||||||
bounds: options.bounds,
|
bounds: options.bounds,
|
||||||
display,
|
display,
|
||||||
platform,
|
platform,
|
||||||
|
handle,
|
||||||
input_handler: None,
|
input_handler: None,
|
||||||
sprite_atlas: Arc::new(TestAtlas::new()),
|
sprite_atlas: Arc::new(TestAtlas::new()),
|
||||||
handlers: Default::default(),
|
handlers: Default::default(),
|
||||||
|
@ -107,7 +110,12 @@ impl PlatformWindow for TestWindow {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn activate(&self) {
|
fn activate(&self) {
|
||||||
unimplemented!()
|
*self
|
||||||
|
.platform
|
||||||
|
.upgrade()
|
||||||
|
.expect("platform dropped")
|
||||||
|
.active_window
|
||||||
|
.lock() = Some(self.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_title(&mut self, title: &str) {
|
fn set_title(&mut self, title: &str) {
|
||||||
|
|
|
@ -1659,7 +1659,7 @@ impl Project {
|
||||||
cx.emit(Event::Closed);
|
cx.emit(Event::Closed);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_read_only(&self) -> bool {
|
pub fn is_disconnected(&self) -> bool {
|
||||||
match &self.client_state {
|
match &self.client_state {
|
||||||
Some(ProjectClientState::Remote {
|
Some(ProjectClientState::Remote {
|
||||||
sharing_has_stopped,
|
sharing_has_stopped,
|
||||||
|
@ -1669,6 +1669,10 @@ impl Project {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_read_only(&self) -> bool {
|
||||||
|
self.is_disconnected()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_local(&self) -> bool {
|
pub fn is_local(&self) -> bool {
|
||||||
match &self.client_state {
|
match &self.client_state {
|
||||||
Some(ProjectClientState::Remote { .. }) => false,
|
Some(ProjectClientState::Remote { .. }) => false,
|
||||||
|
@ -6015,7 +6019,7 @@ impl Project {
|
||||||
this.upgrade().context("project dropped")?;
|
this.upgrade().context("project dropped")?;
|
||||||
let response = rpc.request(message).await?;
|
let response = rpc.request(message).await?;
|
||||||
let this = this.upgrade().context("project dropped")?;
|
let this = this.upgrade().context("project dropped")?;
|
||||||
if this.update(&mut cx, |this, _| this.is_read_only())? {
|
if this.update(&mut cx, |this, _| this.is_disconnected())? {
|
||||||
Err(anyhow!("disconnected before completing request"))
|
Err(anyhow!("disconnected before completing request"))
|
||||||
} else {
|
} else {
|
||||||
request
|
request
|
||||||
|
@ -7942,7 +7946,7 @@ impl Project {
|
||||||
|
|
||||||
if let Some(buffer) = buffer {
|
if let Some(buffer) = buffer {
|
||||||
break buffer;
|
break buffer;
|
||||||
} else if this.update(&mut cx, |this, _| this.is_read_only())? {
|
} else if this.update(&mut cx, |this, _| this.is_disconnected())? {
|
||||||
return Err(anyhow!("disconnected before buffer {} could be opened", id));
|
return Err(anyhow!("disconnected before buffer {} could be opened", id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1184,7 +1184,7 @@ impl Workspace {
|
||||||
mut save_intent: SaveIntent,
|
mut save_intent: SaveIntent,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Task<Result<bool>> {
|
) -> Task<Result<bool>> {
|
||||||
if self.project.read(cx).is_read_only() {
|
if self.project.read(cx).is_disconnected() {
|
||||||
return Task::ready(Ok(true));
|
return Task::ready(Ok(true));
|
||||||
}
|
}
|
||||||
let dirty_items = self
|
let dirty_items = self
|
||||||
|
@ -2508,7 +2508,7 @@ impl Workspace {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
|
fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
let is_edited = !self.project.read(cx).is_read_only()
|
let is_edited = !self.project.read(cx).is_disconnected()
|
||||||
&& self
|
&& self
|
||||||
.items(cx)
|
.items(cx)
|
||||||
.any(|item| item.has_conflict(cx) || item.is_dirty(cx));
|
.any(|item| item.has_conflict(cx) || item.is_dirty(cx));
|
||||||
|
@ -3632,7 +3632,7 @@ impl Render for Workspace {
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
.child(self.status_bar.clone())
|
.child(self.status_bar.clone())
|
||||||
.children(if self.project.read(cx).is_read_only() {
|
.children(if self.project.read(cx).is_disconnected() {
|
||||||
Some(DisconnectedOverlay)
|
Some(DisconnectedOverlay)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue