Merge branch 'zed2' into zed2-workspace

This commit is contained in:
Antonio Scandurra 2023-10-26 15:34:55 +02:00
commit 821fe0f5b5
102 changed files with 5153 additions and 1982 deletions

40
Cargo.lock generated
View File

@ -1507,7 +1507,7 @@ dependencies = [
"parking_lot 0.11.2",
"postage",
"rand 0.8.5",
"rpc",
"rpc2",
"schemars",
"serde",
"serde_derive",
@ -1836,6 +1836,7 @@ dependencies = [
"log",
"lsp2",
"node_runtime",
"parking_lot 0.11.2",
"rpc",
"serde",
"serde_derive",
@ -4285,7 +4286,6 @@ dependencies = [
"collections",
"ctor",
"env_logger 0.9.3",
"fs",
"futures 0.3.28",
"fuzzy2",
"git",
@ -4299,7 +4299,7 @@ dependencies = [
"postage",
"rand 0.8.5",
"regex",
"rpc",
"rpc2",
"schemars",
"serde",
"serde_derive",
@ -5907,6 +5907,7 @@ dependencies = [
"log",
"lsp2",
"node_runtime",
"parking_lot 0.11.2",
"serde",
"serde_derive",
"serde_json",
@ -6087,7 +6088,7 @@ dependencies = [
"pretty_assertions",
"rand 0.8.5",
"regex",
"rpc",
"rpc2",
"schemars",
"serde",
"serde_derive",
@ -6839,6 +6840,35 @@ dependencies = [
"zstd",
]
[[package]]
name = "rpc2"
version = "0.1.0"
dependencies = [
"anyhow",
"async-lock",
"async-tungstenite",
"base64 0.13.1",
"clock",
"collections",
"ctor",
"env_logger 0.9.3",
"futures 0.3.28",
"gpui2",
"parking_lot 0.11.2",
"prost 0.8.0",
"prost-build",
"rand 0.8.5",
"rsa 0.4.0",
"serde",
"serde_derive",
"smol",
"smol-timeout",
"tempdir",
"tracing",
"util",
"zstd",
]
[[package]]
name = "rsa"
version = "0.4.0"
@ -10843,7 +10873,7 @@ dependencies = [
"project2",
"rand 0.8.5",
"regex",
"rpc",
"rpc2",
"rsa 0.4.0",
"rust-embed",
"schemars",

View File

@ -73,6 +73,7 @@ members = [
"crates/recent_projects",
"crates/rope",
"crates/rpc",
"crates/rpc2",
"crates/search",
"crates/settings",
"crates/settings2",

View File

@ -9,14 +9,14 @@ path = "src/client2.rs"
doctest = false
[features]
test-support = ["collections/test-support", "gpui2/test-support", "rpc/test-support"]
test-support = ["collections/test-support", "gpui2/test-support", "rpc2/test-support"]
[dependencies]
collections = { path = "../collections" }
db2 = { path = "../db2" }
gpui2 = { path = "../gpui2" }
util = { path = "../util" }
rpc = { path = "../rpc" }
rpc2 = { path = "../rpc2" }
text = { path = "../text" }
settings2 = { path = "../settings2" }
feature_flags2 = { path = "../feature_flags2" }
@ -47,6 +47,6 @@ url = "2.2"
[dev-dependencies]
collections = { path = "../collections", features = ["test-support"] }
gpui2 = { path = "../gpui2", features = ["test-support"] }
rpc = { path = "../rpc", features = ["test-support"] }
rpc2 = { path = "../rpc2", features = ["test-support"] }
settings = { path = "../settings", features = ["test-support"] }
util = { path = "../util", features = ["test-support"] }

View File

@ -21,7 +21,7 @@ use lazy_static::lazy_static;
use parking_lot::RwLock;
use postage::watch;
use rand::prelude::*;
use rpc::proto::{AnyTypedEnvelope, EntityMessage, EnvelopedMessage, PeerId, RequestMessage};
use rpc2::proto::{AnyTypedEnvelope, EntityMessage, EnvelopedMessage, PeerId, RequestMessage};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings2::Settings;
@ -43,7 +43,7 @@ use util::channel::ReleaseChannel;
use util::http::HttpClient;
use util::{ResultExt, TryFutureExt};
pub use rpc::*;
pub use rpc2::*;
pub use telemetry::ClickhouseEvent;
pub use user::*;
@ -975,7 +975,7 @@ impl Client {
"Authorization",
format!("{} {}", credentials.user_id, credentials.access_token),
)
.header("x-zed-protocol-version", rpc::PROTOCOL_VERSION);
.header("x-zed-protocol-version", rpc2::PROTOCOL_VERSION);
let http = self.http.clone();
cx.executor().spawn(async move {
@ -1025,7 +1025,7 @@ impl Client {
// zed server to encrypt the user's access token, so that it can'be intercepted by
// any other app running on the user's device.
let (public_key, private_key) =
rpc::auth::keypair().expect("failed to generate keypair for auth");
rpc2::auth::keypair().expect("failed to generate keypair for auth");
let public_key_string =
String::try_from(public_key).expect("failed to serialize public key for auth");
@ -1377,290 +1377,275 @@ pub fn decode_worktree_url(url: &str) -> Option<(u64, String)> {
Some((id, access_token.to_string()))
}
// #[cfg(test)]
// mod tests {
// use super::*;
// use crate::test::FakeServer;
// use gpui::{executor::Deterministic, TestAppContext};
// use parking_lot::Mutex;
// use std::future;
// use util::http::FakeHttpClient;
#[cfg(test)]
mod tests {
use super::*;
use crate::test::FakeServer;
// #[gpui::test(iterations = 10)]
// async fn test_reconnection(cx: &mut TestAppContext) {
// cx.foreground().forbid_parking();
use gpui2::{Context, Executor, TestAppContext};
use parking_lot::Mutex;
use std::future;
use util::http::FakeHttpClient;
// let user_id = 5;
// let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
// let server = FakeServer::for_client(user_id, &client, cx).await;
// let mut status = client.status();
// assert!(matches!(
// status.next().await,
// Some(Status::Connected { .. })
// ));
// assert_eq!(server.auth_count(), 1);
#[gpui2::test(iterations = 10)]
async fn test_reconnection(cx: &mut TestAppContext) {
let user_id = 5;
let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
let server = FakeServer::for_client(user_id, &client, cx).await;
let mut status = client.status();
assert!(matches!(
status.next().await,
Some(Status::Connected { .. })
));
assert_eq!(server.auth_count(), 1);
// server.forbid_connections();
// server.disconnect();
// while !matches!(status.next().await, Some(Status::ReconnectionError { .. })) {}
server.forbid_connections();
server.disconnect();
while !matches!(status.next().await, Some(Status::ReconnectionError { .. })) {}
// server.allow_connections();
// cx.foreground().advance_clock(Duration::from_secs(10));
// while !matches!(status.next().await, Some(Status::Connected { .. })) {}
// assert_eq!(server.auth_count(), 1); // Client reused the cached credentials when reconnecting
server.allow_connections();
cx.executor().advance_clock(Duration::from_secs(10));
while !matches!(status.next().await, Some(Status::Connected { .. })) {}
assert_eq!(server.auth_count(), 1); // Client reused the cached credentials when reconnecting
// server.forbid_connections();
// server.disconnect();
// while !matches!(status.next().await, Some(Status::ReconnectionError { .. })) {}
server.forbid_connections();
server.disconnect();
while !matches!(status.next().await, Some(Status::ReconnectionError { .. })) {}
// // Clear cached credentials after authentication fails
// server.roll_access_token();
// server.allow_connections();
// cx.foreground().advance_clock(Duration::from_secs(10));
// while !matches!(status.next().await, Some(Status::Connected { .. })) {}
// assert_eq!(server.auth_count(), 2); // Client re-authenticated due to an invalid token
// }
// Clear cached credentials after authentication fails
server.roll_access_token();
server.allow_connections();
cx.executor().run_until_parked();
cx.executor().advance_clock(Duration::from_secs(10));
while !matches!(status.next().await, Some(Status::Connected { .. })) {}
assert_eq!(server.auth_count(), 2); // Client re-authenticated due to an invalid token
}
// #[gpui::test(iterations = 10)]
// async fn test_connection_timeout(deterministic: Arc<Deterministic>, cx: &mut TestAppContext) {
// deterministic.forbid_parking();
#[gpui2::test(iterations = 10)]
async fn test_connection_timeout(executor: Executor, cx: &mut TestAppContext) {
let user_id = 5;
let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
let mut status = client.status();
// let user_id = 5;
// let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
// let mut status = client.status();
// Time out when client tries to connect.
client.override_authenticate(move |cx| {
cx.executor().spawn(async move {
Ok(Credentials {
user_id,
access_token: "token".into(),
})
})
});
client.override_establish_connection(|_, cx| {
cx.executor().spawn(async move {
future::pending::<()>().await;
unreachable!()
})
});
let auth_and_connect = cx.spawn({
let client = client.clone();
|cx| async move { client.authenticate_and_connect(false, &cx).await }
});
executor.run_until_parked();
assert!(matches!(status.next().await, Some(Status::Connecting)));
// // Time out when client tries to connect.
// client.override_authenticate(move |cx| {
// cx.foreground().spawn(async move {
// Ok(Credentials {
// user_id,
// access_token: "token".into(),
// })
// })
// });
// client.override_establish_connection(|_, cx| {
// cx.foreground().spawn(async move {
// future::pending::<()>().await;
// unreachable!()
// })
// });
// let auth_and_connect = cx.spawn({
// let client = client.clone();
// |cx| async move { client.authenticate_and_connect(false, &cx).await }
// });
// deterministic.run_until_parked();
// assert!(matches!(status.next().await, Some(Status::Connecting)));
executor.advance_clock(CONNECTION_TIMEOUT);
assert!(matches!(
status.next().await,
Some(Status::ConnectionError { .. })
));
auth_and_connect.await.unwrap_err();
// deterministic.advance_clock(CONNECTION_TIMEOUT);
// assert!(matches!(
// status.next().await,
// Some(Status::ConnectionError { .. })
// ));
// auth_and_connect.await.unwrap_err();
// Allow the connection to be established.
let server = FakeServer::for_client(user_id, &client, cx).await;
assert!(matches!(
status.next().await,
Some(Status::Connected { .. })
));
// // Allow the connection to be established.
// let server = FakeServer::for_client(user_id, &client, cx).await;
// assert!(matches!(
// status.next().await,
// Some(Status::Connected { .. })
// ));
// Disconnect client.
server.forbid_connections();
server.disconnect();
while !matches!(status.next().await, Some(Status::ReconnectionError { .. })) {}
// // Disconnect client.
// server.forbid_connections();
// server.disconnect();
// while !matches!(status.next().await, Some(Status::ReconnectionError { .. })) {}
// Time out when re-establishing the connection.
server.allow_connections();
client.override_establish_connection(|_, cx| {
cx.executor().spawn(async move {
future::pending::<()>().await;
unreachable!()
})
});
executor.advance_clock(2 * INITIAL_RECONNECTION_DELAY);
assert!(matches!(
status.next().await,
Some(Status::Reconnecting { .. })
));
// // Time out when re-establishing the connection.
// server.allow_connections();
// client.override_establish_connection(|_, cx| {
// cx.foreground().spawn(async move {
// future::pending::<()>().await;
// unreachable!()
// })
// });
// deterministic.advance_clock(2 * INITIAL_RECONNECTION_DELAY);
// assert!(matches!(
// status.next().await,
// Some(Status::Reconnecting { .. })
// ));
executor.advance_clock(CONNECTION_TIMEOUT);
assert!(matches!(
status.next().await,
Some(Status::ReconnectionError { .. })
));
}
// deterministic.advance_clock(CONNECTION_TIMEOUT);
// assert!(matches!(
// status.next().await,
// Some(Status::ReconnectionError { .. })
// ));
// }
#[gpui2::test(iterations = 10)]
async fn test_authenticating_more_than_once(cx: &mut TestAppContext, executor: Executor) {
let auth_count = Arc::new(Mutex::new(0));
let dropped_auth_count = Arc::new(Mutex::new(0));
let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
client.override_authenticate({
let auth_count = auth_count.clone();
let dropped_auth_count = dropped_auth_count.clone();
move |cx| {
let auth_count = auth_count.clone();
let dropped_auth_count = dropped_auth_count.clone();
cx.executor().spawn(async move {
*auth_count.lock() += 1;
let _drop = util::defer(move || *dropped_auth_count.lock() += 1);
future::pending::<()>().await;
unreachable!()
})
}
});
// #[gpui::test(iterations = 10)]
// async fn test_authenticating_more_than_once(
// cx: &mut TestAppContext,
// deterministic: Arc<Deterministic>,
// ) {
// cx.foreground().forbid_parking();
let _authenticate = cx.spawn({
let client = client.clone();
move |cx| async move { client.authenticate_and_connect(false, &cx).await }
});
executor.run_until_parked();
assert_eq!(*auth_count.lock(), 1);
assert_eq!(*dropped_auth_count.lock(), 0);
// let auth_count = Arc::new(Mutex::new(0));
// let dropped_auth_count = Arc::new(Mutex::new(0));
// let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
// client.override_authenticate({
// let auth_count = auth_count.clone();
// let dropped_auth_count = dropped_auth_count.clone();
// move |cx| {
// let auth_count = auth_count.clone();
// let dropped_auth_count = dropped_auth_count.clone();
// cx.foreground().spawn(async move {
// *auth_count.lock() += 1;
// let _drop = util::defer(move || *dropped_auth_count.lock() += 1);
// future::pending::<()>().await;
// unreachable!()
// })
// }
// });
let _authenticate = cx.spawn({
let client = client.clone();
|cx| async move { client.authenticate_and_connect(false, &cx).await }
});
executor.run_until_parked();
assert_eq!(*auth_count.lock(), 2);
assert_eq!(*dropped_auth_count.lock(), 1);
}
// let _authenticate = cx.spawn(|cx| {
// let client = client.clone();
// async move { client.authenticate_and_connect(false, &cx).await }
// });
// deterministic.run_until_parked();
// assert_eq!(*auth_count.lock(), 1);
// assert_eq!(*dropped_auth_count.lock(), 0);
#[test]
fn test_encode_and_decode_worktree_url() {
let url = encode_worktree_url(5, "deadbeef");
assert_eq!(decode_worktree_url(&url), Some((5, "deadbeef".to_string())));
assert_eq!(
decode_worktree_url(&format!("\n {}\t", url)),
Some((5, "deadbeef".to_string()))
);
assert_eq!(decode_worktree_url("not://the-right-format"), None);
}
// let _authenticate = cx.spawn(|cx| {
// let client = client.clone();
// async move { client.authenticate_and_connect(false, &cx).await }
// });
// deterministic.run_until_parked();
// assert_eq!(*auth_count.lock(), 2);
// assert_eq!(*dropped_auth_count.lock(), 1);
// }
#[gpui2::test]
async fn test_subscribing_to_entity(cx: &mut TestAppContext) {
let user_id = 5;
let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
let server = FakeServer::for_client(user_id, &client, cx).await;
// #[test]
// fn test_encode_and_decode_worktree_url() {
// let url = encode_worktree_url(5, "deadbeef");
// assert_eq!(decode_worktree_url(&url), Some((5, "deadbeef".to_string())));
// assert_eq!(
// decode_worktree_url(&format!("\n {}\t", url)),
// Some((5, "deadbeef".to_string()))
// );
// assert_eq!(decode_worktree_url("not://the-right-format"), None);
// }
let (done_tx1, mut done_rx1) = smol::channel::unbounded();
let (done_tx2, mut done_rx2) = smol::channel::unbounded();
client.add_model_message_handler(
move |model: Handle<Model>, _: TypedEnvelope<proto::JoinProject>, _, mut cx| {
match model.update(&mut cx, |model, _| model.id).unwrap() {
1 => done_tx1.try_send(()).unwrap(),
2 => done_tx2.try_send(()).unwrap(),
_ => unreachable!(),
}
async { Ok(()) }
},
);
let model1 = cx.entity(|_| Model {
id: 1,
subscription: None,
});
let model2 = cx.entity(|_| Model {
id: 2,
subscription: None,
});
let model3 = cx.entity(|_| Model {
id: 3,
subscription: None,
});
// #[gpui::test]
// async fn test_subscribing_to_entity(cx: &mut TestAppContext) {
// cx.foreground().forbid_parking();
let _subscription1 = client
.subscribe_to_entity(1)
.unwrap()
.set_model(&model1, &mut cx.to_async());
let _subscription2 = client
.subscribe_to_entity(2)
.unwrap()
.set_model(&model2, &mut cx.to_async());
// Ensure dropping a subscription for the same entity type still allows receiving of
// messages for other entity IDs of the same type.
let subscription3 = client
.subscribe_to_entity(3)
.unwrap()
.set_model(&model3, &mut cx.to_async());
drop(subscription3);
// let user_id = 5;
// let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
// let server = FakeServer::for_client(user_id, &client, cx).await;
server.send(proto::JoinProject { project_id: 1 });
server.send(proto::JoinProject { project_id: 2 });
done_rx1.next().await.unwrap();
done_rx2.next().await.unwrap();
}
// let (done_tx1, mut done_rx1) = smol::channel::unbounded();
// let (done_tx2, mut done_rx2) = smol::channel::unbounded();
// client.add_model_message_handler(
// move |model: ModelHandle<Model>, _: TypedEnvelope<proto::JoinProject>, _, cx| {
// match model.read_with(&cx, |model, _| model.id) {
// 1 => done_tx1.try_send(()).unwrap(),
// 2 => done_tx2.try_send(()).unwrap(),
// _ => unreachable!(),
// }
// async { Ok(()) }
// },
// );
// let model1 = cx.add_model(|_| Model {
// id: 1,
// subscription: None,
// });
// let model2 = cx.add_model(|_| Model {
// id: 2,
// subscription: None,
// });
// let model3 = cx.add_model(|_| Model {
// id: 3,
// subscription: None,
// });
#[gpui2::test]
async fn test_subscribing_after_dropping_subscription(cx: &mut TestAppContext) {
let user_id = 5;
let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
let server = FakeServer::for_client(user_id, &client, cx).await;
// let _subscription1 = client
// .subscribe_to_entity(1)
// .unwrap()
// .set_model(&model1, &mut cx.to_async());
// let _subscription2 = client
// .subscribe_to_entity(2)
// .unwrap()
// .set_model(&model2, &mut cx.to_async());
// // Ensure dropping a subscription for the same entity type still allows receiving of
// // messages for other entity IDs of the same type.
// let subscription3 = client
// .subscribe_to_entity(3)
// .unwrap()
// .set_model(&model3, &mut cx.to_async());
// drop(subscription3);
let model = cx.entity(|_| Model::default());
let (done_tx1, _done_rx1) = smol::channel::unbounded();
let (done_tx2, mut done_rx2) = smol::channel::unbounded();
let subscription1 = client.add_message_handler(
model.downgrade(),
move |_, _: TypedEnvelope<proto::Ping>, _, _| {
done_tx1.try_send(()).unwrap();
async { Ok(()) }
},
);
drop(subscription1);
let _subscription2 = client.add_message_handler(
model.downgrade(),
move |_, _: TypedEnvelope<proto::Ping>, _, _| {
done_tx2.try_send(()).unwrap();
async { Ok(()) }
},
);
server.send(proto::Ping {});
done_rx2.next().await.unwrap();
}
// server.send(proto::JoinProject { project_id: 1 });
// server.send(proto::JoinProject { project_id: 2 });
// done_rx1.next().await.unwrap();
// done_rx2.next().await.unwrap();
// }
#[gpui2::test]
async fn test_dropping_subscription_in_handler(cx: &mut TestAppContext) {
let user_id = 5;
let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
let server = FakeServer::for_client(user_id, &client, cx).await;
// #[gpui::test]
// async fn test_subscribing_after_dropping_subscription(cx: &mut TestAppContext) {
// cx.foreground().forbid_parking();
let model = cx.entity(|_| Model::default());
let (done_tx, mut done_rx) = smol::channel::unbounded();
let subscription = client.add_message_handler(
model.clone().downgrade(),
move |model: Handle<Model>, _: TypedEnvelope<proto::Ping>, _, mut cx| {
model
.update(&mut cx, |model, _| model.subscription.take())
.unwrap();
done_tx.try_send(()).unwrap();
async { Ok(()) }
},
);
model.update(cx, |model, _| {
model.subscription = Some(subscription);
});
server.send(proto::Ping {});
done_rx.next().await.unwrap();
}
// let user_id = 5;
// let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
// let server = FakeServer::for_client(user_id, &client, cx).await;
// let model = cx.add_model(|_| Model::default());
// let (done_tx1, _done_rx1) = smol::channel::unbounded();
// let (done_tx2, mut done_rx2) = smol::channel::unbounded();
// let subscription1 = client.add_message_handler(
// model.clone(),
// move |_, _: TypedEnvelope<proto::Ping>, _, _| {
// done_tx1.try_send(()).unwrap();
// async { Ok(()) }
// },
// );
// drop(subscription1);
// let _subscription2 = client.add_message_handler(
// model.clone(),
// move |_, _: TypedEnvelope<proto::Ping>, _, _| {
// done_tx2.try_send(()).unwrap();
// async { Ok(()) }
// },
// );
// server.send(proto::Ping {});
// done_rx2.next().await.unwrap();
// }
// #[gpui::test]
// async fn test_dropping_subscription_in_handler(cx: &mut TestAppContext) {
// cx.foreground().forbid_parking();
// let user_id = 5;
// let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
// let server = FakeServer::for_client(user_id, &client, cx).await;
// let model = cx.add_model(|_| Model::default());
// let (done_tx, mut done_rx) = smol::channel::unbounded();
// let subscription = client.add_message_handler(
// model.clone(),
// move |model, _: TypedEnvelope<proto::Ping>, _, mut cx| {
// model.update(&mut cx, |model, _| model.subscription.take());
// done_tx.try_send(()).unwrap();
// async { Ok(()) }
// },
// );
// model.update(cx, |model, _| {
// model.subscription = Some(subscription);
// });
// server.send(proto::Ping {});
// done_rx.next().await.unwrap();
// }
// #[derive(Default)]
// struct Model {
// id: usize,
// subscription: Option<Subscription>,
// }
// impl Entity for Model {
// type Event = ();
// }
// }
#[derive(Default)]
struct Model {
id: usize,
subscription: Option<Subscription>,
}
}

View File

@ -1,215 +1,216 @@
// use crate::{Client, Connection, Credentials, EstablishConnectionError, UserStore};
// use anyhow::{anyhow, Result};
// use futures::{stream::BoxStream, StreamExt};
// use gpui2::{Executor, Handle, TestAppContext};
// use parking_lot::Mutex;
// use rpc::{
// proto::{self, GetPrivateUserInfo, GetPrivateUserInfoResponse},
// ConnectionId, Peer, Receipt, TypedEnvelope,
// };
// use std::{rc::Rc, sync::Arc};
// use util::http::FakeHttpClient;
use crate::{Client, Connection, Credentials, EstablishConnectionError, UserStore};
use anyhow::{anyhow, Result};
use futures::{stream::BoxStream, StreamExt};
use gpui2::{Context, Executor, Handle, TestAppContext};
use parking_lot::Mutex;
use rpc2::{
proto::{self, GetPrivateUserInfo, GetPrivateUserInfoResponse},
ConnectionId, Peer, Receipt, TypedEnvelope,
};
use std::sync::Arc;
use util::http::FakeHttpClient;
// pub struct FakeServer {
// peer: Arc<Peer>,
// state: Arc<Mutex<FakeServerState>>,
// user_id: u64,
// executor: Executor,
// }
pub struct FakeServer {
peer: Arc<Peer>,
state: Arc<Mutex<FakeServerState>>,
user_id: u64,
executor: Executor,
}
// #[derive(Default)]
// struct FakeServerState {
// incoming: Option<BoxStream<'static, Box<dyn proto::AnyTypedEnvelope>>>,
// connection_id: Option<ConnectionId>,
// forbid_connections: bool,
// auth_count: usize,
// access_token: usize,
// }
#[derive(Default)]
struct FakeServerState {
incoming: Option<BoxStream<'static, Box<dyn proto::AnyTypedEnvelope>>>,
connection_id: Option<ConnectionId>,
forbid_connections: bool,
auth_count: usize,
access_token: usize,
}
// impl FakeServer {
// pub async fn for_client(
// client_user_id: u64,
// client: &Arc<Client>,
// cx: &TestAppContext,
// ) -> Self {
// let server = Self {
// peer: Peer::new(0),
// state: Default::default(),
// user_id: client_user_id,
// executor: cx.foreground(),
// };
impl FakeServer {
pub async fn for_client(
client_user_id: u64,
client: &Arc<Client>,
cx: &TestAppContext,
) -> Self {
let server = Self {
peer: Peer::new(0),
state: Default::default(),
user_id: client_user_id,
executor: cx.executor().clone(),
};
// client
// .override_authenticate({
// let state = Arc::downgrade(&server.state);
// move |cx| {
// let state = state.clone();
// cx.spawn(move |_| async move {
// let state = state.upgrade().ok_or_else(|| anyhow!("server dropped"))?;
// let mut state = state.lock();
// state.auth_count += 1;
// let access_token = state.access_token.to_string();
// Ok(Credentials {
// user_id: client_user_id,
// access_token,
// })
// })
// }
// })
// .override_establish_connection({
// let peer = Arc::downgrade(&server.peer);
// let state = Arc::downgrade(&server.state);
// move |credentials, cx| {
// let peer = peer.clone();
// let state = state.clone();
// let credentials = credentials.clone();
// cx.spawn(move |cx| async move {
// let state = state.upgrade().ok_or_else(|| anyhow!("server dropped"))?;
// let peer = peer.upgrade().ok_or_else(|| anyhow!("server dropped"))?;
// if state.lock().forbid_connections {
// Err(EstablishConnectionError::Other(anyhow!(
// "server is forbidding connections"
// )))?
// }
client
.override_authenticate({
let state = Arc::downgrade(&server.state);
move |cx| {
let state = state.clone();
cx.spawn(move |_| async move {
let state = state.upgrade().ok_or_else(|| anyhow!("server dropped"))?;
let mut state = state.lock();
state.auth_count += 1;
let access_token = state.access_token.to_string();
Ok(Credentials {
user_id: client_user_id,
access_token,
})
})
}
})
.override_establish_connection({
let peer = Arc::downgrade(&server.peer);
let state = Arc::downgrade(&server.state);
move |credentials, cx| {
let peer = peer.clone();
let state = state.clone();
let credentials = credentials.clone();
cx.spawn(move |cx| async move {
let state = state.upgrade().ok_or_else(|| anyhow!("server dropped"))?;
let peer = peer.upgrade().ok_or_else(|| anyhow!("server dropped"))?;
if state.lock().forbid_connections {
Err(EstablishConnectionError::Other(anyhow!(
"server is forbidding connections"
)))?
}
// assert_eq!(credentials.user_id, client_user_id);
assert_eq!(credentials.user_id, client_user_id);
// if credentials.access_token != state.lock().access_token.to_string() {
// Err(EstablishConnectionError::Unauthorized)?
// }
if credentials.access_token != state.lock().access_token.to_string() {
Err(EstablishConnectionError::Unauthorized)?
}
// let (client_conn, server_conn, _) = Connection::in_memory(cx.background());
// let (connection_id, io, incoming) =
// peer.add_test_connection(server_conn, cx.background());
// cx.background().spawn(io).detach();
// {
// let mut state = state.lock();
// state.connection_id = Some(connection_id);
// state.incoming = Some(incoming);
// }
// peer.send(
// connection_id,
// proto::Hello {
// peer_id: Some(connection_id.into()),
// },
// )
// .unwrap();
let (client_conn, server_conn, _) =
Connection::in_memory(cx.executor().clone());
let (connection_id, io, incoming) =
peer.add_test_connection(server_conn, cx.executor().clone());
cx.executor().spawn(io).detach();
{
let mut state = state.lock();
state.connection_id = Some(connection_id);
state.incoming = Some(incoming);
}
peer.send(
connection_id,
proto::Hello {
peer_id: Some(connection_id.into()),
},
)
.unwrap();
// Ok(client_conn)
// })
// }
// });
Ok(client_conn)
})
}
});
// client
// .authenticate_and_connect(false, &cx.to_async())
// .await
// .unwrap();
client
.authenticate_and_connect(false, &cx.to_async())
.await
.unwrap();
// server
// }
server
}
// pub fn disconnect(&self) {
// if self.state.lock().connection_id.is_some() {
// self.peer.disconnect(self.connection_id());
// let mut state = self.state.lock();
// state.connection_id.take();
// state.incoming.take();
// }
// }
pub fn disconnect(&self) {
if self.state.lock().connection_id.is_some() {
self.peer.disconnect(self.connection_id());
let mut state = self.state.lock();
state.connection_id.take();
state.incoming.take();
}
}
// pub fn auth_count(&self) -> usize {
// self.state.lock().auth_count
// }
pub fn auth_count(&self) -> usize {
self.state.lock().auth_count
}
// pub fn roll_access_token(&self) {
// self.state.lock().access_token += 1;
// }
pub fn roll_access_token(&self) {
self.state.lock().access_token += 1;
}
// pub fn forbid_connections(&self) {
// self.state.lock().forbid_connections = true;
// }
pub fn forbid_connections(&self) {
self.state.lock().forbid_connections = true;
}
// pub fn allow_connections(&self) {
// self.state.lock().forbid_connections = false;
// }
pub fn allow_connections(&self) {
self.state.lock().forbid_connections = false;
}
// pub fn send<T: proto::EnvelopedMessage>(&self, message: T) {
// self.peer.send(self.connection_id(), message).unwrap();
// }
pub fn send<T: proto::EnvelopedMessage>(&self, message: T) {
self.peer.send(self.connection_id(), message).unwrap();
}
// #[allow(clippy::await_holding_lock)]
// pub async fn receive<M: proto::EnvelopedMessage>(&self) -> Result<TypedEnvelope<M>> {
// self.executor.start_waiting();
#[allow(clippy::await_holding_lock)]
pub async fn receive<M: proto::EnvelopedMessage>(&self) -> Result<TypedEnvelope<M>> {
self.executor.start_waiting();
// loop {
// let message = self
// .state
// .lock()
// .incoming
// .as_mut()
// .expect("not connected")
// .next()
// .await
// .ok_or_else(|| anyhow!("other half hung up"))?;
// self.executor.finish_waiting();
// let type_name = message.payload_type_name();
// let message = message.into_any();
loop {
let message = self
.state
.lock()
.incoming
.as_mut()
.expect("not connected")
.next()
.await
.ok_or_else(|| anyhow!("other half hung up"))?;
self.executor.finish_waiting();
let type_name = message.payload_type_name();
let message = message.into_any();
// if message.is::<TypedEnvelope<M>>() {
// return Ok(*message.downcast().unwrap());
// }
if message.is::<TypedEnvelope<M>>() {
return Ok(*message.downcast().unwrap());
}
// if message.is::<TypedEnvelope<GetPrivateUserInfo>>() {
// self.respond(
// message
// .downcast::<TypedEnvelope<GetPrivateUserInfo>>()
// .unwrap()
// .receipt(),
// GetPrivateUserInfoResponse {
// metrics_id: "the-metrics-id".into(),
// staff: false,
// flags: Default::default(),
// },
// );
// continue;
// }
if message.is::<TypedEnvelope<GetPrivateUserInfo>>() {
self.respond(
message
.downcast::<TypedEnvelope<GetPrivateUserInfo>>()
.unwrap()
.receipt(),
GetPrivateUserInfoResponse {
metrics_id: "the-metrics-id".into(),
staff: false,
flags: Default::default(),
},
);
continue;
}
// panic!(
// "fake server received unexpected message type: {:?}",
// type_name
// );
// }
// }
panic!(
"fake server received unexpected message type: {:?}",
type_name
);
}
}
// pub fn respond<T: proto::RequestMessage>(&self, receipt: Receipt<T>, response: T::Response) {
// self.peer.respond(receipt, response).unwrap()
// }
pub fn respond<T: proto::RequestMessage>(&self, receipt: Receipt<T>, response: T::Response) {
self.peer.respond(receipt, response).unwrap()
}
// fn connection_id(&self) -> ConnectionId {
// self.state.lock().connection_id.expect("not connected")
// }
fn connection_id(&self) -> ConnectionId {
self.state.lock().connection_id.expect("not connected")
}
// pub async fn build_user_store(
// &self,
// client: Arc<Client>,
// cx: &mut TestAppContext,
// ) -> ModelHandle<UserStore> {
// let http_client = FakeHttpClient::with_404_response();
// let user_store = cx.add_model(|cx| UserStore::new(client, http_client, cx));
// assert_eq!(
// self.receive::<proto::GetUsers>()
// .await
// .unwrap()
// .payload
// .user_ids,
// &[self.user_id]
// );
// user_store
// }
// }
pub async fn build_user_store(
&self,
client: Arc<Client>,
cx: &mut TestAppContext,
) -> Handle<UserStore> {
let http_client = FakeHttpClient::with_404_response();
let user_store = cx.entity(|cx| UserStore::new(client, http_client, cx));
assert_eq!(
self.receive::<proto::GetUsers>()
.await
.unwrap()
.payload
.user_ids,
&[self.user_id]
);
user_store
}
}
// impl Drop for FakeServer {
// fn drop(&mut self) {
// self.disconnect();
// }
// }
impl Drop for FakeServer {
fn drop(&mut self) {
self.disconnect();
}
}

View File

@ -5,7 +5,7 @@ use feature_flags2::FeatureFlagAppExt;
use futures::{channel::mpsc, future, AsyncReadExt, Future, StreamExt};
use gpui2::{AsyncAppContext, EventEmitter, Handle, ImageData, ModelContext, Task};
use postage::{sink::Sink, watch};
use rpc::proto::{RequestMessage, UsersResponse};
use rpc2::proto::{RequestMessage, UsersResponse};
use std::sync::{Arc, Weak};
use text::ReplicaId;
use util::http::HttpClient;

View File

@ -36,6 +36,7 @@ serde.workspace = true
serde_derive.workspace = true
smol.workspace = true
futures.workspace = true
parking_lot.workspace = true
[dev-dependencies]
clock = { path = "../clock" }

View File

@ -17,6 +17,7 @@ use language2::{
};
use lsp2::{LanguageServer, LanguageServerBinary, LanguageServerId};
use node_runtime::NodeRuntime;
use parking_lot::Mutex;
use request::StatusNotification;
use settings2::SettingsStore;
use smol::{fs, io::BufReader, stream::StreamExt};
@ -394,8 +395,15 @@ impl Copilot {
path: node_path,
arguments,
};
let server =
LanguageServer::new(new_server_id, binary, Path::new("/"), None, cx.clone())?;
let server = LanguageServer::new(
Arc::new(Mutex::new(None)),
new_server_id,
binary,
Path::new("/"),
None,
cx.clone(),
)?;
server
.on_notification::<StatusNotification, _>(

View File

@ -106,7 +106,7 @@
// data: &PromptUserDeviceFlow,
// style: &theme::Copilot,
// cx: &mut ViewContext<Self>,
// ) -> impl Element<Self> {
// ) -> impl IntoAnyElement<Self> {
// let copied = cx
// .read_from_clipboard()
// .map(|item| item.text() == &data.user_code)

View File

@ -143,7 +143,7 @@ impl TestAppContext {
lock.update_global(update)
}
fn to_async(&self) -> AsyncAppContext {
pub fn to_async(&self) -> AsyncAppContext {
AsyncAppContext {
app: Arc::downgrade(&self.app),
executor: self.executor.clone(),

View File

@ -60,7 +60,6 @@ impl From<Rgba> for u32 {
}
}
struct RgbaVisitor;
impl<'de> Visitor<'de> for RgbaVisitor {
@ -157,10 +156,8 @@ impl Hsla {
}
}
impl Eq for Hsla {}
pub fn hsla(h: f32, s: f32, l: f32, a: f32) -> Hsla {
Hsla {
h: h.clamp(0., 1.),

View File

@ -3,36 +3,37 @@ use derive_more::{Deref, DerefMut};
pub(crate) use smallvec::SmallVec;
use std::{any::Any, mem};
pub trait Element: IntoAnyElement<Self::ViewState> {
type ViewState: 'static;
pub trait Element<V: 'static> {
type ElementState: 'static;
fn id(&self) -> Option<ElementId>;
/// Called to initialize this element for the current frame. If this
/// element had state in a previous frame, it will be passed in for the 3rd argument.
fn initialize(
&mut self,
view_state: &mut Self::ViewState,
view_state: &mut V,
element_state: Option<Self::ElementState>,
cx: &mut ViewContext<Self::ViewState>,
cx: &mut ViewContext<V>,
) -> Self::ElementState;
// where
// Self::ViewState: Any + Send + Sync;
// V: Any + Send + Sync;
fn layout(
&mut self,
view_state: &mut Self::ViewState,
view_state: &mut V,
element_state: &mut Self::ElementState,
cx: &mut ViewContext<Self::ViewState>,
cx: &mut ViewContext<V>,
) -> LayoutId;
// where
// Self::ViewState: Any + Send + Sync;
// V: Any + Send + Sync;
fn paint(
&mut self,
bounds: Bounds<Pixels>,
view_state: &mut Self::ViewState,
view_state: &mut V,
element_state: &mut Self::ElementState,
cx: &mut ViewContext<Self::ViewState>,
cx: &mut ViewContext<V>,
);
// where
@ -42,26 +43,23 @@ pub trait Element: IntoAnyElement<Self::ViewState> {
#[derive(Deref, DerefMut, Default, Clone, Debug, Eq, PartialEq, Hash)]
pub struct GlobalElementId(SmallVec<[ElementId; 32]>);
pub trait ParentElement: Element {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::ViewState>; 2]>;
pub trait ParentElement<V: 'static> {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]>;
fn child(mut self, child: impl IntoAnyElement<Self::ViewState>) -> Self
fn child(mut self, child: impl Component<V>) -> Self
where
Self: Sized,
{
self.children_mut().push(child.into_any());
self.children_mut().push(child.render());
self
}
fn children(
mut self,
iter: impl IntoIterator<Item = impl IntoAnyElement<Self::ViewState>>,
) -> Self
fn children(mut self, iter: impl IntoIterator<Item = impl Component<V>>) -> Self
where
Self: Sized,
{
self.children_mut()
.extend(iter.into_iter().map(|item| item.into_any()));
.extend(iter.into_iter().map(|item| item.render()));
self
}
}
@ -72,7 +70,7 @@ trait ElementObject<V> {
fn paint(&mut self, view_state: &mut V, cx: &mut ViewContext<V>);
}
struct RenderedElement<E: Element> {
struct RenderedElement<V: 'static, E: Element<V>> {
element: E,
phase: ElementRenderPhase<E::ElementState>,
}
@ -94,7 +92,7 @@ enum ElementRenderPhase<V> {
/// Internal struct that wraps an element to store Layout and ElementState after the element is rendered.
/// It's allocated as a trait object to erase the element type and wrapped in AnyElement<E::State> for
/// improved usability.
impl<E: Element> RenderedElement<E> {
impl<V, E: Element<V>> RenderedElement<V, E> {
fn new(element: E) -> Self {
RenderedElement {
element,
@ -103,13 +101,13 @@ impl<E: Element> RenderedElement<E> {
}
}
impl<E> ElementObject<E::ViewState> for RenderedElement<E>
impl<V, E> ElementObject<V> for RenderedElement<V, E>
where
E: Element,
E: Element<V>,
// E::ViewState: Any + Send + Sync,
E::ElementState: Any + Send + Sync,
{
fn initialize(&mut self, view_state: &mut E::ViewState, cx: &mut ViewContext<E::ViewState>) {
fn initialize(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) {
let frame_state = if let Some(id) = self.element.id() {
cx.with_element_state(id, |element_state, cx| {
let element_state = self.element.initialize(view_state, element_state, cx);
@ -124,7 +122,7 @@ where
self.phase = ElementRenderPhase::Initialized { frame_state };
}
fn layout(&mut self, state: &mut E::ViewState, cx: &mut ViewContext<E::ViewState>) -> LayoutId {
fn layout(&mut self, state: &mut V, cx: &mut ViewContext<V>) -> LayoutId {
let layout_id;
let mut frame_state;
match mem::take(&mut self.phase) {
@ -154,7 +152,7 @@ where
layout_id
}
fn paint(&mut self, view_state: &mut E::ViewState, cx: &mut ViewContext<E::ViewState>) {
fn paint(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) {
self.phase = match mem::take(&mut self.phase) {
ElementRenderPhase::LayoutRequested {
layout_id,
@ -182,11 +180,15 @@ where
pub struct AnyElement<V>(Box<dyn ElementObject<V> + Send + Sync>);
unsafe impl<V> Send for AnyElement<V> {}
unsafe impl<V> Sync for AnyElement<V> {}
impl<V> AnyElement<V> {
pub fn new<E>(element: E) -> Self
where
V: 'static,
E: 'static + Send + Sync,
E: Element<ViewState = V>,
E: Element<V>,
E::ElementState: Any + Send + Sync,
{
AnyElement(Box::new(RenderedElement::new(element)))
@ -205,12 +207,88 @@ impl<V> AnyElement<V> {
}
}
pub trait IntoAnyElement<V> {
fn into_any(self) -> AnyElement<V>;
}
pub trait Component<V> {
fn render(self) -> AnyElement<V>;
impl<V> IntoAnyElement<V> for AnyElement<V> {
fn into_any(self) -> AnyElement<V> {
fn when(mut self, condition: bool, then: impl FnOnce(Self) -> Self) -> Self
where
Self: Sized,
{
if condition {
self = then(self);
}
self
}
}
impl<V> Component<V> for AnyElement<V> {
fn render(self) -> AnyElement<V> {
self
}
}
impl<V, E, F> Element<V> for Option<F>
where
V: 'static,
E: 'static + Component<V> + Send + Sync,
F: FnOnce(&mut V, &mut ViewContext<'_, '_, V>) -> E + Send + Sync + 'static,
{
type ElementState = AnyElement<V>;
fn id(&self) -> Option<ElementId> {
None
}
fn initialize(
&mut self,
view_state: &mut V,
_rendered_element: Option<Self::ElementState>,
cx: &mut ViewContext<V>,
) -> Self::ElementState {
let render = self.take().unwrap();
let mut rendered_element = (render)(view_state, cx).render();
rendered_element.initialize(view_state, cx);
rendered_element
}
fn layout(
&mut self,
view_state: &mut V,
rendered_element: &mut Self::ElementState,
cx: &mut ViewContext<V>,
) -> LayoutId {
rendered_element.layout(view_state, cx)
}
fn paint(
&mut self,
_bounds: Bounds<Pixels>,
view_state: &mut V,
rendered_element: &mut Self::ElementState,
cx: &mut ViewContext<V>,
) {
rendered_element.paint(view_state, cx)
}
}
impl<V, E, F> Component<V> for Option<F>
where
V: 'static,
E: 'static + Component<V> + Send + Sync,
F: FnOnce(&mut V, &mut ViewContext<'_, '_, V>) -> E + Send + Sync + 'static,
{
fn render(self) -> AnyElement<V> {
AnyElement::new(self)
}
}
impl<V, E, F> Component<V> for F
where
V: 'static,
E: 'static + Component<V> + Send + Sync,
F: FnOnce(&mut V, &mut ViewContext<'_, '_, V>) -> E + Send + Sync + 'static,
{
fn render(self) -> AnyElement<V> {
AnyElement::new(Some(self))
}
}

View File

@ -1,7 +1,7 @@
use crate::{
point, AnyElement, BorrowWindow, Bounds, Element, ElementFocus, ElementId, ElementInteraction,
FocusDisabled, FocusEnabled, FocusHandle, FocusListeners, Focusable, GlobalElementId,
GroupBounds, InteractiveElementState, IntoAnyElement, LayoutId, Overflow, ParentElement,
point, AnyElement, BorrowWindow, Bounds, Component, Element, ElementFocus, ElementId,
ElementInteraction, FocusDisabled, FocusEnabled, FocusHandle, FocusListeners, Focusable,
GlobalElementId, GroupBounds, InteractiveElementState, LayoutId, Overflow, ParentElement,
Pixels, Point, SharedString, StatefulInteraction, StatefulInteractive, StatelessInteraction,
StatelessInteractive, Style, StyleRefinement, Styled, ViewContext,
};
@ -160,7 +160,7 @@ impl<V: 'static> Div<V, StatelessInteraction<V>, FocusDisabled> {
}
}
impl<V, I> Focusable for Div<V, I, FocusEnabled<V>>
impl<V, I> Focusable<V> for Div<V, I, FocusEnabled<V>>
where
V: 'static,
I: ElementInteraction<V>,
@ -189,12 +189,11 @@ pub struct DivState {
child_layout_ids: SmallVec<[LayoutId; 4]>,
}
impl<V, I, F> Element for Div<V, I, F>
impl<V, I, F> Element<V> for Div<V, I, F>
where
I: ElementInteraction<V>,
F: ElementFocus<V>,
{
type ViewState = V;
type ElementState = DivState;
fn id(&self) -> Option<ElementId> {
@ -205,9 +204,9 @@ where
fn initialize(
&mut self,
view_state: &mut Self::ViewState,
view_state: &mut V,
element_state: Option<Self::ElementState>,
cx: &mut ViewContext<Self::ViewState>,
cx: &mut ViewContext<V>,
) -> Self::ElementState {
let mut element_state = element_state.unwrap_or_default();
self.focus
@ -224,9 +223,9 @@ where
fn layout(
&mut self,
view_state: &mut Self::ViewState,
view_state: &mut V,
element_state: &mut Self::ElementState,
cx: &mut ViewContext<Self::ViewState>,
cx: &mut ViewContext<V>,
) -> LayoutId {
let style = self.compute_style(Bounds::default(), element_state, cx);
style.apply_text_style(cx, |cx| {
@ -245,9 +244,9 @@ where
fn paint(
&mut self,
bounds: Bounds<Pixels>,
view_state: &mut Self::ViewState,
view_state: &mut V,
element_state: &mut Self::ElementState,
cx: &mut ViewContext<Self::ViewState>,
cx: &mut ViewContext<V>,
) {
self.with_element_id(cx, |this, _global_id, cx| {
if let Some(group) = this.group.clone() {
@ -304,23 +303,23 @@ where
}
}
impl<V, I, F> IntoAnyElement<V> for Div<V, I, F>
impl<V, I, F> Component<V> for Div<V, I, F>
where
// V: Any + Send + Sync,
I: ElementInteraction<V>,
F: ElementFocus<V>,
{
fn into_any(self) -> AnyElement<V> {
fn render(self) -> AnyElement<V> {
AnyElement::new(self)
}
}
impl<V, I, F> ParentElement for Div<V, I, F>
impl<V, I, F> ParentElement<V> for Div<V, I, F>
where
I: ElementInteraction<V>,
F: ElementFocus<V>,
{
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::ViewState>; 2]> {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
&mut self.children
}
}
@ -335,7 +334,7 @@ where
}
}
impl<V, I, F> StatelessInteractive for Div<V, I, F>
impl<V, I, F> StatelessInteractive<V> for Div<V, I, F>
where
I: ElementInteraction<V>,
F: ElementFocus<V>,
@ -345,11 +344,11 @@ where
}
}
impl<V, F> StatefulInteractive for Div<V, StatefulInteraction<V>, F>
impl<V, F> StatefulInteractive<V> for Div<V, StatefulInteraction<V>, F>
where
F: ElementFocus<V>,
{
fn stateful_interaction(&mut self) -> &mut StatefulInteraction<Self::ViewState> {
fn stateful_interaction(&mut self) -> &mut StatefulInteraction<V> {
&mut self.interaction
}
}

View File

@ -1,6 +1,6 @@
use crate::{
div, AnyElement, BorrowWindow, Bounds, Div, DivState, Element, ElementFocus, ElementId,
ElementInteraction, FocusDisabled, FocusEnabled, FocusListeners, Focusable, IntoAnyElement,
div, AnyElement, BorrowWindow, Bounds, Component, Div, DivState, Element, ElementFocus,
ElementId, ElementInteraction, FocusDisabled, FocusEnabled, FocusListeners, Focusable,
LayoutId, Pixels, SharedString, StatefulInteraction, StatefulInteractive, StatelessInteraction,
StatelessInteractive, StyleRefinement, Styled, ViewContext,
};
@ -55,22 +55,21 @@ where
}
}
impl<V, I, F> IntoAnyElement<V> for Img<V, I, F>
impl<V, I, F> Component<V> for Img<V, I, F>
where
I: ElementInteraction<V>,
F: ElementFocus<V>,
{
fn into_any(self) -> AnyElement<V> {
fn render(self) -> AnyElement<V> {
AnyElement::new(self)
}
}
impl<V, I, F> Element for Img<V, I, F>
impl<V, I, F> Element<V> for Img<V, I, F>
where
I: ElementInteraction<V>,
F: ElementFocus<V>,
{
type ViewState = V;
type ElementState = DivState;
fn id(&self) -> Option<crate::ElementId> {
@ -90,7 +89,7 @@ where
&mut self,
view_state: &mut V,
element_state: &mut Self::ElementState,
cx: &mut ViewContext<Self::ViewState>,
cx: &mut ViewContext<V>,
) -> LayoutId {
self.base.layout(view_state, element_state, cx)
}
@ -143,7 +142,7 @@ where
}
}
impl<V, I, F> StatelessInteractive for Img<V, I, F>
impl<V, I, F> StatelessInteractive<V> for Img<V, I, F>
where
I: ElementInteraction<V>,
F: ElementFocus<V>,
@ -153,21 +152,21 @@ where
}
}
impl<V, F> StatefulInteractive for Img<V, StatefulInteraction<V>, F>
impl<V, F> StatefulInteractive<V> for Img<V, StatefulInteraction<V>, F>
where
F: ElementFocus<V>,
{
fn stateful_interaction(&mut self) -> &mut StatefulInteraction<Self::ViewState> {
fn stateful_interaction(&mut self) -> &mut StatefulInteraction<V> {
self.base.stateful_interaction()
}
}
impl<V, I> Focusable for Img<V, I, FocusEnabled<V>>
impl<V, I> Focusable<V> for Img<V, I, FocusEnabled<V>>
where
V: 'static,
I: ElementInteraction<V>,
{
fn focus_listeners(&mut self) -> &mut FocusListeners<Self::ViewState> {
fn focus_listeners(&mut self) -> &mut FocusListeners<V> {
self.base.focus_listeners()
}

View File

@ -1,6 +1,6 @@
use crate::{
div, AnyElement, Bounds, Div, DivState, Element, ElementFocus, ElementId, ElementInteraction,
FocusDisabled, FocusEnabled, FocusListeners, Focusable, IntoAnyElement, LayoutId, Pixels,
div, AnyElement, Bounds, Component, Div, DivState, Element, ElementFocus, ElementId,
ElementInteraction, FocusDisabled, FocusEnabled, FocusListeners, Focusable, LayoutId, Pixels,
SharedString, StatefulInteraction, StatefulInteractive, StatelessInteraction,
StatelessInteractive, StyleRefinement, Styled, ViewContext,
};
@ -45,22 +45,21 @@ where
}
}
impl<V, I, F> IntoAnyElement<V> for Svg<V, I, F>
impl<V, I, F> Component<V> for Svg<V, I, F>
where
I: ElementInteraction<V>,
F: ElementFocus<V>,
{
fn into_any(self) -> AnyElement<V> {
fn render(self) -> AnyElement<V> {
AnyElement::new(self)
}
}
impl<V, I, F> Element for Svg<V, I, F>
impl<V, I, F> Element<V> for Svg<V, I, F>
where
I: ElementInteraction<V>,
F: ElementFocus<V>,
{
type ViewState = V;
type ElementState = DivState;
fn id(&self) -> Option<crate::ElementId> {
@ -80,7 +79,7 @@ where
&mut self,
view_state: &mut V,
element_state: &mut Self::ElementState,
cx: &mut ViewContext<Self::ViewState>,
cx: &mut ViewContext<V>,
) -> LayoutId {
self.base.layout(view_state, element_state, cx)
}
@ -88,7 +87,7 @@ where
fn paint(
&mut self,
bounds: Bounds<Pixels>,
view: &mut Self::ViewState,
view: &mut V,
element_state: &mut Self::ElementState,
cx: &mut ViewContext<V>,
) where
@ -116,7 +115,7 @@ where
}
}
impl<V, I, F> StatelessInteractive for Svg<V, I, F>
impl<V, I, F> StatelessInteractive<V> for Svg<V, I, F>
where
I: ElementInteraction<V>,
F: ElementFocus<V>,
@ -126,21 +125,21 @@ where
}
}
impl<V, F> StatefulInteractive for Svg<V, StatefulInteraction<V>, F>
impl<V, F> StatefulInteractive<V> for Svg<V, StatefulInteraction<V>, F>
where
V: 'static,
F: ElementFocus<V>,
{
fn stateful_interaction(&mut self) -> &mut StatefulInteraction<Self::ViewState> {
fn stateful_interaction(&mut self) -> &mut StatefulInteraction<V> {
self.base.stateful_interaction()
}
}
impl<V: 'static, I> Focusable for Svg<V, I, FocusEnabled<V>>
impl<V: 'static, I> Focusable<V> for Svg<V, I, FocusEnabled<V>>
where
I: ElementInteraction<V>,
{
fn focus_listeners(&mut self) -> &mut FocusListeners<Self::ViewState> {
fn focus_listeners(&mut self) -> &mut FocusListeners<V> {
self.base.focus_listeners()
}

View File

@ -1,41 +1,41 @@
use crate::{
AnyElement, BorrowWindow, Bounds, Element, IntoAnyElement, LayoutId, Line, Pixels,
SharedString, Size, ViewContext,
AnyElement, BorrowWindow, Bounds, Component, Element, LayoutId, Line, Pixels, SharedString,
Size, ViewContext,
};
use parking_lot::Mutex;
use smallvec::SmallVec;
use std::{marker::PhantomData, sync::Arc};
use util::ResultExt;
impl<V: 'static> IntoAnyElement<V> for SharedString {
fn into_any(self) -> AnyElement<V> {
impl<V: 'static> Component<V> for SharedString {
fn render(self) -> AnyElement<V> {
Text {
text: self,
state_type: PhantomData,
}
.into_any()
.render()
}
}
impl<V: 'static> IntoAnyElement<V> for &'static str {
fn into_any(self) -> AnyElement<V> {
impl<V: 'static> Component<V> for &'static str {
fn render(self) -> AnyElement<V> {
Text {
text: self.into(),
state_type: PhantomData,
}
.into_any()
.render()
}
}
// TODO: Figure out how to pass `String` to `child` without this.
// This impl doesn't exist in the `gpui2` crate.
impl<V: 'static> IntoAnyElement<V> for String {
fn into_any(self) -> AnyElement<V> {
impl<V: 'static> Component<V> for String {
fn render(self) -> AnyElement<V> {
Text {
text: self.into(),
state_type: PhantomData,
}
.into_any()
.render()
}
}
@ -47,14 +47,13 @@ pub struct Text<V> {
unsafe impl<V> Send for Text<V> {}
unsafe impl<V> Sync for Text<V> {}
impl<V: 'static> IntoAnyElement<V> for Text<V> {
fn into_any(self) -> AnyElement<V> {
impl<V: 'static> Component<V> for Text<V> {
fn render(self) -> AnyElement<V> {
AnyElement::new(self)
}
}
impl<V: 'static> Element for Text<V> {
type ViewState = V;
impl<V: 'static> Element<V> for Text<V> {
type ElementState = Arc<Mutex<Option<TextElementState>>>;
fn id(&self) -> Option<crate::ElementId> {

View File

@ -146,7 +146,10 @@ impl Executor {
Poll::Ready(result) => return result,
Poll::Pending => {
if !self.dispatcher.poll() {
// todo!("forbid_parking")
#[cfg(any(test, feature = "test-support"))]
if let Some(_) = self.dispatcher.as_test() {
panic!("blocked with nothing left to run")
}
parker.park();
}
}
@ -206,11 +209,26 @@ impl Executor {
todo!("start_waiting")
}
#[cfg(any(test, feature = "test-support"))]
pub fn finish_waiting(&self) {
todo!("finish_waiting")
}
#[cfg(any(test, feature = "test-support"))]
pub fn simulate_random_delay(&self) -> impl Future<Output = ()> {
self.dispatcher.as_test().unwrap().simulate_random_delay()
}
#[cfg(any(test, feature = "test-support"))]
pub fn advance_clock(&self, duration: Duration) {
self.dispatcher.as_test().unwrap().advance_clock(duration)
}
#[cfg(any(test, feature = "test-support"))]
pub fn run_until_parked(&self) {
self.dispatcher.as_test().unwrap().run_until_parked()
}
pub fn num_cpus(&self) -> usize {
num_cpus::get()
}

View File

@ -11,8 +11,8 @@ pub type FocusListeners<V> = SmallVec<[FocusListener<V>; 2]>;
pub type FocusListener<V> =
Arc<dyn Fn(&mut V, &FocusHandle, &FocusEvent, &mut ViewContext<V>) + Send + Sync + 'static>;
pub trait Focusable: Element {
fn focus_listeners(&mut self) -> &mut FocusListeners<Self::ViewState>;
pub trait Focusable<V: 'static>: Element<V> {
fn focus_listeners(&mut self) -> &mut FocusListeners<V>;
fn set_focus_style(&mut self, style: StyleRefinement);
fn set_focus_in_style(&mut self, style: StyleRefinement);
fn set_in_focus_style(&mut self, style: StyleRefinement);
@ -43,10 +43,7 @@ pub trait Focusable: Element {
fn on_focus(
mut self,
listener: impl Fn(&mut Self::ViewState, &FocusEvent, &mut ViewContext<Self::ViewState>)
+ Send
+ Sync
+ 'static,
listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + Send + Sync + 'static,
) -> Self
where
Self: Sized,
@ -62,10 +59,7 @@ pub trait Focusable: Element {
fn on_blur(
mut self,
listener: impl Fn(&mut Self::ViewState, &FocusEvent, &mut ViewContext<Self::ViewState>)
+ Send
+ Sync
+ 'static,
listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + Send + Sync + 'static,
) -> Self
where
Self: Sized,
@ -81,10 +75,7 @@ pub trait Focusable: Element {
fn on_focus_in(
mut self,
listener: impl Fn(&mut Self::ViewState, &FocusEvent, &mut ViewContext<Self::ViewState>)
+ Send
+ Sync
+ 'static,
listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + Send + Sync + 'static,
) -> Self
where
Self: Sized,
@ -109,10 +100,7 @@ pub trait Focusable: Element {
fn on_focus_out(
mut self,
listener: impl Fn(&mut Self::ViewState, &FocusEvent, &mut ViewContext<Self::ViewState>)
+ Send
+ Sync
+ 'static,
listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + Send + Sync + 'static,
) -> Self
where
Self: Sized,

View File

@ -1,7 +1,7 @@
use crate::{
point, px, view, Action, AnyBox, AnyDrag, AppContext, BorrowWindow, Bounds, DispatchContext,
DispatchPhase, Element, ElementId, FocusHandle, KeyMatch, Keystroke, Modifiers, Overflow,
Pixels, Point, SharedString, Size, Style, StyleRefinement, ViewContext,
point, px, view, Action, AnyBox, AnyDrag, AppContext, BorrowWindow, Bounds, Component,
DispatchContext, DispatchPhase, Element, ElementId, FocusHandle, KeyMatch, Keystroke,
Modifiers, Overflow, Pixels, Point, SharedString, Size, Style, StyleRefinement, ViewContext,
};
use collections::HashMap;
use derive_more::{Deref, DerefMut};
@ -19,8 +19,8 @@ use std::{
const DRAG_THRESHOLD: f64 = 2.;
pub trait StatelessInteractive: Element {
fn stateless_interaction(&mut self) -> &mut StatelessInteraction<Self::ViewState>;
pub trait StatelessInteractive<V: 'static>: Element<V> {
fn stateless_interaction(&mut self) -> &mut StatelessInteraction<V>;
fn hover(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
where
@ -48,10 +48,7 @@ pub trait StatelessInteractive: Element {
fn on_mouse_down(
mut self,
button: MouseButton,
handler: impl Fn(&mut Self::ViewState, &MouseDownEvent, &mut ViewContext<Self::ViewState>)
+ Send
+ Sync
+ 'static,
handler: impl Fn(&mut V, &MouseDownEvent, &mut ViewContext<V>) + Send + Sync + 'static,
) -> Self
where
Self: Sized,
@ -72,10 +69,7 @@ pub trait StatelessInteractive: Element {
fn on_mouse_up(
mut self,
button: MouseButton,
handler: impl Fn(&mut Self::ViewState, &MouseUpEvent, &mut ViewContext<Self::ViewState>)
+ Send
+ Sync
+ 'static,
handler: impl Fn(&mut V, &MouseUpEvent, &mut ViewContext<V>) + Send + Sync + 'static,
) -> Self
where
Self: Sized,
@ -96,10 +90,7 @@ pub trait StatelessInteractive: Element {
fn on_mouse_down_out(
mut self,
button: MouseButton,
handler: impl Fn(&mut Self::ViewState, &MouseDownEvent, &mut ViewContext<Self::ViewState>)
+ Send
+ Sync
+ 'static,
handler: impl Fn(&mut V, &MouseDownEvent, &mut ViewContext<V>) + Send + Sync + 'static,
) -> Self
where
Self: Sized,
@ -120,10 +111,7 @@ pub trait StatelessInteractive: Element {
fn on_mouse_up_out(
mut self,
button: MouseButton,
handler: impl Fn(&mut Self::ViewState, &MouseUpEvent, &mut ViewContext<Self::ViewState>)
+ Send
+ Sync
+ 'static,
handler: impl Fn(&mut V, &MouseUpEvent, &mut ViewContext<V>) + Send + Sync + 'static,
) -> Self
where
Self: Sized,
@ -143,10 +131,7 @@ pub trait StatelessInteractive: Element {
fn on_mouse_move(
mut self,
handler: impl Fn(&mut Self::ViewState, &MouseMoveEvent, &mut ViewContext<Self::ViewState>)
+ Send
+ Sync
+ 'static,
handler: impl Fn(&mut V, &MouseMoveEvent, &mut ViewContext<V>) + Send + Sync + 'static,
) -> Self
where
Self: Sized,
@ -163,10 +148,7 @@ pub trait StatelessInteractive: Element {
fn on_scroll_wheel(
mut self,
handler: impl Fn(&mut Self::ViewState, &ScrollWheelEvent, &mut ViewContext<Self::ViewState>)
+ Send
+ Sync
+ 'static,
handler: impl Fn(&mut V, &ScrollWheelEvent, &mut ViewContext<V>) + Send + Sync + 'static,
) -> Self
where
Self: Sized,
@ -194,10 +176,7 @@ pub trait StatelessInteractive: Element {
fn on_action<A: 'static>(
mut self,
listener: impl Fn(&mut Self::ViewState, &A, DispatchPhase, &mut ViewContext<Self::ViewState>)
+ Send
+ Sync
+ 'static,
listener: impl Fn(&mut V, &A, DispatchPhase, &mut ViewContext<V>) + Send + Sync + 'static,
) -> Self
where
Self: Sized,
@ -215,12 +194,8 @@ pub trait StatelessInteractive: Element {
fn on_key_down(
mut self,
listener: impl Fn(
&mut Self::ViewState,
&KeyDownEvent,
DispatchPhase,
&mut ViewContext<Self::ViewState>,
) + Send
listener: impl Fn(&mut V, &KeyDownEvent, DispatchPhase, &mut ViewContext<V>)
+ Send
+ Sync
+ 'static,
) -> Self
@ -240,7 +215,7 @@ pub trait StatelessInteractive: Element {
fn on_key_up(
mut self,
listener: impl Fn(&mut Self::ViewState, &KeyUpEvent, DispatchPhase, &mut ViewContext<Self::ViewState>)
listener: impl Fn(&mut V, &KeyUpEvent, DispatchPhase, &mut ViewContext<V>)
+ Send
+ Sync
+ 'static,
@ -289,10 +264,7 @@ pub trait StatelessInteractive: Element {
fn on_drop<S: 'static>(
mut self,
listener: impl Fn(&mut Self::ViewState, S, &mut ViewContext<Self::ViewState>)
+ Send
+ Sync
+ 'static,
listener: impl Fn(&mut V, S, &mut ViewContext<V>) + Send + Sync + 'static,
) -> Self
where
Self: Sized,
@ -307,8 +279,8 @@ pub trait StatelessInteractive: Element {
}
}
pub trait StatefulInteractive: StatelessInteractive {
fn stateful_interaction(&mut self) -> &mut StatefulInteraction<Self::ViewState>;
pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
fn stateful_interaction(&mut self) -> &mut StatefulInteraction<V>;
fn active(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
where
@ -335,10 +307,7 @@ pub trait StatefulInteractive: StatelessInteractive {
fn on_click(
mut self,
listener: impl Fn(&mut Self::ViewState, &ClickEvent, &mut ViewContext<Self::ViewState>)
+ Send
+ Sync
+ 'static,
listener: impl Fn(&mut V, &ClickEvent, &mut ViewContext<V>) + Send + Sync + 'static,
) -> Self
where
Self: Sized,
@ -351,20 +320,14 @@ pub trait StatefulInteractive: StatelessInteractive {
fn on_drag<S, R, E>(
mut self,
listener: impl Fn(
&mut Self::ViewState,
&mut ViewContext<Self::ViewState>,
) -> Drag<S, R, Self::ViewState, E>
+ Send
+ Sync
+ 'static,
listener: impl Fn(&mut V, &mut ViewContext<V>) -> Drag<S, R, V, E> + Send + Sync + 'static,
) -> Self
where
Self: Sized,
S: Any + Send + Sync,
R: Fn(&mut Self::ViewState, &mut ViewContext<Self::ViewState>) -> E,
R: Fn(&mut V, &mut ViewContext<V>) -> E,
R: 'static + Send + Sync,
E: Element<ViewState = Self::ViewState>,
E: Component<V>,
{
debug_assert!(
self.stateful_interaction().drag_listener.is_none(),
@ -907,7 +870,8 @@ pub struct ClickEvent {
pub struct Drag<S, R, V, E>
where
R: Fn(&mut V, &mut ViewContext<V>) -> E,
E: Element<ViewState = V>,
V: 'static,
E: Component<V>,
{
pub state: S,
pub render_drag_handle: R,
@ -917,7 +881,8 @@ where
impl<S, R, V, E> Drag<S, R, V, E>
where
R: Fn(&mut V, &mut ViewContext<V>) -> E,
E: Element<ViewState = V>,
V: 'static,
E: Component<V>,
{
pub fn new(state: S, render_drag_handle: R) -> Self {
Drag {

View File

@ -1,6 +1,6 @@
use crate::PlatformDispatcher;
use async_task::Runnable;
use collections::{BTreeMap, HashMap, VecDeque};
use collections::{HashMap, VecDeque};
use parking_lot::Mutex;
use rand::prelude::*;
use std::{
@ -8,7 +8,7 @@ use std::{
pin::Pin,
sync::Arc,
task::{Context, Poll},
time::{Duration, Instant},
time::Duration,
};
use util::post_inc;
@ -24,8 +24,8 @@ struct TestDispatcherState {
random: StdRng,
foreground: HashMap<TestDispatcherId, VecDeque<Runnable>>,
background: Vec<Runnable>,
delayed: BTreeMap<Instant, Runnable>,
time: Instant,
delayed: Vec<(Duration, Runnable)>,
time: Duration,
is_main_thread: bool,
next_id: TestDispatcherId,
}
@ -36,8 +36,8 @@ impl TestDispatcher {
random,
foreground: HashMap::default(),
background: Vec::new(),
delayed: BTreeMap::new(),
time: Instant::now(),
delayed: Vec::new(),
time: Duration::ZERO,
is_main_thread: true,
next_id: TestDispatcherId(1),
};
@ -49,7 +49,21 @@ impl TestDispatcher {
}
pub fn advance_clock(&self, by: Duration) {
self.state.lock().time += by;
let new_now = self.state.lock().time + by;
loop {
self.run_until_parked();
let state = self.state.lock();
let next_due_time = state.delayed.first().map(|(time, _)| *time);
drop(state);
if let Some(due_time) = next_due_time {
if due_time <= new_now {
self.state.lock().time = due_time;
continue;
}
}
break;
}
self.state.lock().time = new_now;
}
pub fn simulate_random_delay(&self) -> impl Future<Output = ()> {
@ -112,17 +126,20 @@ impl PlatformDispatcher for TestDispatcher {
fn dispatch_after(&self, duration: std::time::Duration, runnable: Runnable) {
let mut state = self.state.lock();
let next_time = state.time + duration;
state.delayed.insert(next_time, runnable);
let ix = match state.delayed.binary_search_by_key(&next_time, |e| e.0) {
Ok(ix) | Err(ix) => ix,
};
state.delayed.insert(ix, (next_time, runnable));
}
fn poll(&self) -> bool {
let mut state = self.state.lock();
while let Some((deadline, _)) = state.delayed.first_key_value() {
while let Some((deadline, _)) = state.delayed.first() {
if *deadline > state.time {
break;
}
let (_, runnable) = state.delayed.pop_first().unwrap();
let (_, runnable) = state.delayed.remove(0);
state.background.push(runnable);
}

View File

@ -173,14 +173,14 @@ impl Platform for TestPlatform {
}
fn write_credentials(&self, _url: &str, _username: &str, _password: &[u8]) -> Result<()> {
unimplemented!()
Ok(())
}
fn read_credentials(&self, _url: &str) -> Result<Option<(String, Vec<u8>)>> {
unimplemented!()
Ok(None)
}
fn delete_credentials(&self, _url: &str) -> Result<()> {
unimplemented!()
Ok(())
}
}

View File

@ -1,4 +1,4 @@
use crate::{DevicePixels, IsZero, Result, SharedString, Size, AssetSource};
use crate::{AssetSource, DevicePixels, IsZero, Result, SharedString, Size};
use anyhow::anyhow;
use std::{hash::Hash, sync::Arc};

View File

@ -1,8 +1,8 @@
use parking_lot::Mutex;
use crate::{
AnyBox, AnyElement, AnyHandle, BorrowWindow, Bounds, Element, ElementId, Handle,
IntoAnyElement, LayoutId, Pixels, ViewContext, WindowContext,
AnyBox, AnyElement, AnyHandle, BorrowWindow, Bounds, Component, Element, ElementId, Handle,
LayoutId, Pixels, ViewContext, WindowContext,
};
use std::{marker::PhantomData, sync::Arc};
@ -33,16 +33,16 @@ pub fn view<V, E>(
render: impl Fn(&mut V, &mut ViewContext<V>) -> E + Send + Sync + 'static,
) -> View<V>
where
E: IntoAnyElement<V>,
E: Component<V>,
{
View {
state,
render: Arc::new(move |state, cx| render(state, cx).into_any()),
render: Arc::new(move |state, cx| render(state, cx).render()),
}
}
impl<V: 'static, ParentViewState: 'static> IntoAnyElement<ParentViewState> for View<V> {
fn into_any(self) -> AnyElement<ParentViewState> {
impl<V: 'static, ParentViewState: 'static> Component<ParentViewState> for View<V> {
fn render(self) -> AnyElement<ParentViewState> {
AnyElement::new(EraseViewState {
view: self,
parent_view_state_type: PhantomData,
@ -50,8 +50,7 @@ impl<V: 'static, ParentViewState: 'static> IntoAnyElement<ParentViewState> for V
}
}
impl<V: 'static> Element for View<V> {
type ViewState = ();
impl<V: 'static> Element<()> for View<V> {
type ElementState = AnyElement<V>;
fn id(&self) -> Option<ElementId> {
@ -99,14 +98,13 @@ struct EraseViewState<V, ParentV> {
unsafe impl<V, ParentV> Send for EraseViewState<V, ParentV> {}
unsafe impl<V, ParentV> Sync for EraseViewState<V, ParentV> {}
impl<V: 'static, ParentV: 'static> IntoAnyElement<ParentV> for EraseViewState<V, ParentV> {
fn into_any(self) -> AnyElement<ParentV> {
impl<V: 'static, ParentV: 'static> Component<ParentV> for EraseViewState<V, ParentV> {
fn render(self) -> AnyElement<ParentV> {
AnyElement::new(self)
}
}
impl<V: 'static, ParentV: 'static> Element for EraseViewState<V, ParentV> {
type ViewState = ParentV;
impl<V: 'static, ParentV: 'static> Element<ParentV> for EraseViewState<V, ParentV> {
type ElementState = AnyBox;
fn id(&self) -> Option<ElementId> {
@ -115,18 +113,18 @@ impl<V: 'static, ParentV: 'static> Element for EraseViewState<V, ParentV> {
fn initialize(
&mut self,
_: &mut Self::ViewState,
_: &mut ParentV,
_: Option<Self::ElementState>,
cx: &mut ViewContext<Self::ViewState>,
cx: &mut ViewContext<ParentV>,
) -> Self::ElementState {
ViewObject::initialize(&mut self.view, cx)
}
fn layout(
&mut self,
_: &mut Self::ViewState,
_: &mut ParentV,
element: &mut Self::ElementState,
cx: &mut ViewContext<Self::ViewState>,
cx: &mut ViewContext<ParentV>,
) -> LayoutId {
ViewObject::layout(&mut self.view, element, cx)
}
@ -134,9 +132,9 @@ impl<V: 'static, ParentV: 'static> Element for EraseViewState<V, ParentV> {
fn paint(
&mut self,
bounds: Bounds<Pixels>,
_: &mut Self::ViewState,
_: &mut ParentV,
element: &mut Self::ElementState,
cx: &mut ViewContext<Self::ViewState>,
cx: &mut ViewContext<ParentV>,
) {
ViewObject::paint(&mut self.view, bounds, element, cx)
}
@ -193,8 +191,8 @@ impl AnyView {
}
}
impl<ParentV: 'static> IntoAnyElement<ParentV> for AnyView {
fn into_any(self) -> AnyElement<ParentV> {
impl<ParentV: 'static> Component<ParentV> for AnyView {
fn render(self) -> AnyElement<ParentV> {
AnyElement::new(EraseAnyViewState {
view: self,
parent_view_state_type: PhantomData,
@ -202,8 +200,7 @@ impl<ParentV: 'static> IntoAnyElement<ParentV> for AnyView {
}
}
impl Element for AnyView {
type ViewState = ();
impl Element<()> for AnyView {
type ElementState = AnyBox;
fn id(&self) -> Option<ElementId> {
@ -212,18 +209,18 @@ impl Element for AnyView {
fn initialize(
&mut self,
_: &mut Self::ViewState,
_: &mut (),
_: Option<Self::ElementState>,
cx: &mut ViewContext<Self::ViewState>,
cx: &mut ViewContext<()>,
) -> Self::ElementState {
self.view.lock().initialize(cx)
}
fn layout(
&mut self,
_: &mut Self::ViewState,
_: &mut (),
element: &mut Self::ElementState,
cx: &mut ViewContext<Self::ViewState>,
cx: &mut ViewContext<()>,
) -> LayoutId {
self.view.lock().layout(element, cx)
}
@ -233,7 +230,7 @@ impl Element for AnyView {
bounds: Bounds<Pixels>,
_: &mut (),
element: &mut AnyBox,
cx: &mut ViewContext<Self::ViewState>,
cx: &mut ViewContext<()>,
) {
self.view.lock().paint(bounds, element, cx)
}
@ -247,14 +244,13 @@ struct EraseAnyViewState<ParentViewState> {
unsafe impl<ParentV> Send for EraseAnyViewState<ParentV> {}
unsafe impl<ParentV> Sync for EraseAnyViewState<ParentV> {}
impl<ParentV: 'static> IntoAnyElement<ParentV> for EraseAnyViewState<ParentV> {
fn into_any(self) -> AnyElement<ParentV> {
impl<ParentV: 'static> Component<ParentV> for EraseAnyViewState<ParentV> {
fn render(self) -> AnyElement<ParentV> {
AnyElement::new(self)
}
}
impl<ParentV: 'static> Element for EraseAnyViewState<ParentV> {
type ViewState = ParentV;
impl<ParentV: 'static> Element<ParentV> for EraseAnyViewState<ParentV> {
type ElementState = AnyBox;
fn id(&self) -> Option<ElementId> {
@ -263,18 +259,18 @@ impl<ParentV: 'static> Element for EraseAnyViewState<ParentV> {
fn initialize(
&mut self,
_: &mut Self::ViewState,
_: &mut ParentV,
_: Option<Self::ElementState>,
cx: &mut ViewContext<Self::ViewState>,
cx: &mut ViewContext<ParentV>,
) -> Self::ElementState {
self.view.view.lock().initialize(cx)
}
fn layout(
&mut self,
_: &mut Self::ViewState,
_: &mut ParentV,
element: &mut Self::ElementState,
cx: &mut ViewContext<Self::ViewState>,
cx: &mut ViewContext<ParentV>,
) -> LayoutId {
self.view.view.lock().layout(element, cx)
}
@ -282,9 +278,9 @@ impl<ParentV: 'static> Element for EraseAnyViewState<ParentV> {
fn paint(
&mut self,
bounds: Bounds<Pixels>,
_: &mut Self::ViewState,
_: &mut ParentV,
element: &mut Self::ElementState,
cx: &mut ViewContext<Self::ViewState>,
cx: &mut ViewContext<ParentV>,
) {
self.view.view.lock().paint(bounds, element, cx)
}

View File

@ -901,6 +901,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
}
InputEvent::FileDrop(file_drop) => match file_drop {
FileDropEvent::Entered { position, files } => {
self.window.mouse_position = position;
self.active_drag.get_or_insert_with(|| AnyDrag {
drag_handle_view: None,
cursor_offset: position,
@ -914,17 +915,23 @@ impl<'a, 'w> WindowContext<'a, 'w> {
modifiers: Modifiers::default(),
})
}
FileDropEvent::Pending { position } => InputEvent::MouseMove(MouseMoveEvent {
FileDropEvent::Pending { position } => {
self.window.mouse_position = position;
InputEvent::MouseMove(MouseMoveEvent {
position,
pressed_button: Some(MouseButton::Left),
modifiers: Modifiers::default(),
}),
FileDropEvent::Submit { position } => InputEvent::MouseUp(MouseUpEvent {
})
}
FileDropEvent::Submit { position } => {
self.window.mouse_position = position;
InputEvent::MouseUp(MouseUpEvent {
button: MouseButton::Left,
position,
modifiers: Modifiers::default(),
click_count: 1,
}),
})
}
FileDropEvent::Exited => InputEvent::MouseUp(MouseUpEvent {
button: MouseButton::Left,
position: Point::default(),

View File

@ -0,0 +1,66 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, parse_quote, DeriveInput};
pub fn derive_component(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let name = &ast.ident;
let mut trait_generics = ast.generics.clone();
let view_type = if let Some(view_type) = specified_view_type(&ast) {
quote! { #view_type }
} else {
if let Some(first_type_param) = ast.generics.params.iter().find_map(|param| {
if let syn::GenericParam::Type(type_param) = param {
Some(type_param.ident.clone())
} else {
None
}
}) {
quote! { #first_type_param }
} else {
trait_generics.params.push(parse_quote! { V: 'static });
quote! { V }
}
};
let (impl_generics, _, where_clause) = trait_generics.split_for_impl();
let (_, ty_generics, _) = ast.generics.split_for_impl();
let expanded = quote! {
impl #impl_generics gpui2::Component<#view_type> for #name #ty_generics #where_clause {
fn render(self) -> gpui2::AnyElement<#view_type> {
(move |view_state: &mut #view_type, cx: &mut gpui2::ViewContext<'_, '_, #view_type>| self.render(view_state, cx))
.render()
}
}
};
TokenStream::from(expanded)
}
fn specified_view_type(ast: &DeriveInput) -> Option<proc_macro2::Ident> {
let component_attr = ast
.attrs
.iter()
.find(|attr| attr.path.is_ident("component"))?;
if let Ok(syn::Meta::List(meta_list)) = component_attr.parse_meta() {
meta_list.nested.iter().find_map(|nested| {
if let syn::NestedMeta::Meta(syn::Meta::NameValue(nv)) = nested {
if nv.path.is_ident("view_type") {
if let syn::Lit::Str(lit_str) = &nv.lit {
return Some(
lit_str
.parse::<syn::Ident>()
.expect("Failed to parse view_type"),
);
}
}
}
None
})
} else {
None
}
}

View File

@ -1,95 +0,0 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput, GenericParam};
pub fn derive_element(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let type_name = ast.ident;
let mut state_type = quote! { () };
for param in &ast.generics.params {
if let GenericParam::Type(type_param) = param {
let type_ident = &type_param.ident;
state_type = quote! {#type_ident};
break;
}
}
let attrs = &ast.attrs;
for attr in attrs {
if attr.path.is_ident("element") {
match attr.parse_meta() {
Ok(syn::Meta::List(i)) => {
for nested_meta in i.nested {
if let syn::NestedMeta::Meta(syn::Meta::NameValue(nv)) = nested_meta {
if nv.path.is_ident("view_state") {
if let syn::Lit::Str(lit_str) = nv.lit {
state_type = lit_str.value().parse().unwrap();
}
}
}
}
}
_ => (),
}
}
}
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
let gen = quote! {
impl #impl_generics gpui2::IntoAnyElement<#state_type> for #type_name #ty_generics
#where_clause
{
fn into_any(self) -> gpui2::AnyElement<#state_type> {
gpui2::AnyElement::new(self)
}
}
impl #impl_generics gpui2::Element for #type_name #ty_generics
#where_clause
{
type ViewState = #state_type;
type ElementState = gpui2::AnyElement<#state_type>;
fn id(&self) -> Option<gpui2::ElementId> {
None
}
fn initialize(
&mut self,
view_state: &mut Self::ViewState,
_: Option<Self::ElementState>,
cx: &mut gpui2::ViewContext<Self::ViewState>
) -> Self::ElementState {
use gpui2::IntoAnyElement;
let mut element = self.render(view_state, cx).into_any();
element.initialize(view_state, cx);
element
}
fn layout(
&mut self,
view_state: &mut Self::ViewState,
rendered_element: &mut Self::ElementState,
cx: &mut gpui2::ViewContext<Self::ViewState>,
) -> gpui2::LayoutId {
rendered_element.layout(view_state, cx)
}
fn paint(
&mut self,
bounds: gpui2::Bounds<gpui2::Pixels>,
view_state: &mut Self::ViewState,
rendered_element: &mut Self::ElementState,
cx: &mut gpui2::ViewContext<Self::ViewState>,
) {
rendered_element.paint(view_state, cx)
}
}
};
gen.into()
}

View File

@ -1,6 +1,6 @@
use proc_macro::TokenStream;
mod derive_element;
mod derive_component;
mod style_helpers;
mod test;
@ -9,9 +9,9 @@ pub fn style_helpers(args: TokenStream) -> TokenStream {
style_helpers::style_helpers(args)
}
#[proc_macro_derive(Element, attributes(element))]
pub fn derive_element(input: TokenStream) -> TokenStream {
derive_element::derive_element(input)
#[proc_macro_derive(Component, attributes(component))]
pub fn derive_component(input: TokenStream) -> TokenStream {
derive_component::derive_component(input)
}
#[proc_macro_attribute]

View File

@ -25,11 +25,10 @@ test-support = [
clock = { path = "../clock" }
collections = { path = "../collections" }
fuzzy2 = { path = "../fuzzy2" }
fs = { path = "../fs" }
git = { path = "../git" }
gpui2 = { path = "../gpui2" }
lsp2 = { path = "../lsp2" }
rpc = { path = "../rpc" }
rpc2 = { path = "../rpc2" }
settings2 = { path = "../settings2" }
sum_tree = { path = "../sum_tree" }
text = { path = "../text" }

View File

@ -226,7 +226,7 @@ pub trait File: Send + Sync {
fn as_any(&self) -> &dyn Any;
fn to_proto(&self) -> rpc::proto::File;
fn to_proto(&self) -> rpc2::proto::File;
}
pub trait LocalFile: File {
@ -375,7 +375,7 @@ impl Buffer {
file,
);
this.text.set_line_ending(proto::deserialize_line_ending(
rpc::proto::LineEnding::from_i32(message.line_ending)
rpc2::proto::LineEnding::from_i32(message.line_ending)
.ok_or_else(|| anyhow!("missing line_ending"))?,
));
this.saved_version = proto::deserialize_version(&message.saved_version);

View File

@ -667,7 +667,7 @@ struct LanguageRegistryState {
pub struct PendingLanguageServer {
pub server_id: LanguageServerId,
pub task: Task<Result<Option<lsp2::LanguageServer>>>,
pub task: Task<Result<lsp2::LanguageServer>>,
pub container_dir: Option<Arc<Path>>,
}
@ -906,6 +906,7 @@ impl LanguageRegistry {
pub fn create_pending_language_server(
self: &Arc<Self>,
stderr_capture: Arc<Mutex<Option<String>>>,
language: Arc<Language>,
adapter: Arc<CachedLspAdapter>,
root_path: Arc<Path>,
@ -945,7 +946,7 @@ impl LanguageRegistry {
})
.detach();
Ok(Some(server))
Ok(server)
});
return Some(PendingLanguageServer {
@ -996,24 +997,23 @@ impl LanguageRegistry {
})
.clone();
let binary = match entry.await.log_err() {
Some(binary) => binary,
None => return Ok(None),
let binary = match entry.await {
Ok(binary) => binary,
Err(err) => anyhow::bail!("{err}"),
};
if let Some(task) = adapter.will_start_server(&delegate, &mut cx) {
if task.await.log_err().is_none() {
return Ok(None);
}
task.await?;
}
Ok(Some(lsp2::LanguageServer::new(
lsp2::LanguageServer::new(
stderr_capture,
server_id,
binary,
&root_path,
adapter.code_action_kinds(),
cx,
)?))
)
})
};
@ -1862,111 +1862,112 @@ pub fn range_from_lsp(range: lsp2::Range) -> Range<Unclipped<PointUtf16>> {
start..end
}
// #[cfg(test)]
// mod tests {
// use super::*;
// use gpui::TestAppContext;
#[cfg(test)]
mod tests {
use super::*;
use gpui2::TestAppContext;
// #[gpui::test(iterations = 10)]
// async fn test_first_line_pattern(cx: &mut TestAppContext) {
// let mut languages = LanguageRegistry::test();
// languages.set_executor(cx.background());
// let languages = Arc::new(languages);
// languages.register(
// "/javascript",
// LanguageConfig {
// name: "JavaScript".into(),
// path_suffixes: vec!["js".into()],
// first_line_pattern: Some(Regex::new(r"\bnode\b").unwrap()),
// ..Default::default()
// },
// tree_sitter_typescript::language_tsx(),
// vec![],
// |_| Default::default(),
// );
#[gpui2::test(iterations = 10)]
async fn test_first_line_pattern(cx: &mut TestAppContext) {
let mut languages = LanguageRegistry::test();
// languages
// .language_for_file("the/script", None)
// .await
// .unwrap_err();
// languages
// .language_for_file("the/script", Some(&"nothing".into()))
// .await
// .unwrap_err();
// assert_eq!(
// languages
// .language_for_file("the/script", Some(&"#!/bin/env node".into()))
// .await
// .unwrap()
// .name()
// .as_ref(),
// "JavaScript"
// );
// }
languages.set_executor(cx.executor().clone());
let languages = Arc::new(languages);
languages.register(
"/javascript",
LanguageConfig {
name: "JavaScript".into(),
path_suffixes: vec!["js".into()],
first_line_pattern: Some(Regex::new(r"\bnode\b").unwrap()),
..Default::default()
},
tree_sitter_typescript::language_tsx(),
vec![],
|_| Default::default(),
);
// #[gpui::test(iterations = 10)]
// async fn test_language_loading(cx: &mut TestAppContext) {
// let mut languages = LanguageRegistry::test();
// languages.set_executor(cx.background());
// let languages = Arc::new(languages);
// languages.register(
// "/JSON",
// LanguageConfig {
// name: "JSON".into(),
// path_suffixes: vec!["json".into()],
// ..Default::default()
// },
// tree_sitter_json::language(),
// vec![],
// |_| Default::default(),
// );
// languages.register(
// "/rust",
// LanguageConfig {
// name: "Rust".into(),
// path_suffixes: vec!["rs".into()],
// ..Default::default()
// },
// tree_sitter_rust::language(),
// vec![],
// |_| Default::default(),
// );
// assert_eq!(
// languages.language_names(),
// &[
// "JSON".to_string(),
// "Plain Text".to_string(),
// "Rust".to_string(),
// ]
// );
languages
.language_for_file("the/script", None)
.await
.unwrap_err();
languages
.language_for_file("the/script", Some(&"nothing".into()))
.await
.unwrap_err();
assert_eq!(
languages
.language_for_file("the/script", Some(&"#!/bin/env node".into()))
.await
.unwrap()
.name()
.as_ref(),
"JavaScript"
);
}
// let rust1 = languages.language_for_name("Rust");
// let rust2 = languages.language_for_name("Rust");
#[gpui2::test(iterations = 10)]
async fn test_language_loading(cx: &mut TestAppContext) {
let mut languages = LanguageRegistry::test();
languages.set_executor(cx.executor().clone());
let languages = Arc::new(languages);
languages.register(
"/JSON",
LanguageConfig {
name: "JSON".into(),
path_suffixes: vec!["json".into()],
..Default::default()
},
tree_sitter_json::language(),
vec![],
|_| Default::default(),
);
languages.register(
"/rust",
LanguageConfig {
name: "Rust".into(),
path_suffixes: vec!["rs".into()],
..Default::default()
},
tree_sitter_rust::language(),
vec![],
|_| Default::default(),
);
assert_eq!(
languages.language_names(),
&[
"JSON".to_string(),
"Plain Text".to_string(),
"Rust".to_string(),
]
);
// // Ensure language is still listed even if it's being loaded.
// assert_eq!(
// languages.language_names(),
// &[
// "JSON".to_string(),
// "Plain Text".to_string(),
// "Rust".to_string(),
// ]
// );
let rust1 = languages.language_for_name("Rust");
let rust2 = languages.language_for_name("Rust");
// let (rust1, rust2) = futures::join!(rust1, rust2);
// assert!(Arc::ptr_eq(&rust1.unwrap(), &rust2.unwrap()));
// Ensure language is still listed even if it's being loaded.
assert_eq!(
languages.language_names(),
&[
"JSON".to_string(),
"Plain Text".to_string(),
"Rust".to_string(),
]
);
// // Ensure language is still listed even after loading it.
// assert_eq!(
// languages.language_names(),
// &[
// "JSON".to_string(),
// "Plain Text".to_string(),
// "Rust".to_string(),
// ]
// );
let (rust1, rust2) = futures::join!(rust1, rust2);
assert!(Arc::ptr_eq(&rust1.unwrap(), &rust2.unwrap()));
// // Loading an unknown language returns an error.
// assert!(languages.language_for_name("Unknown").await.is_err());
// }
// }
// Ensure language is still listed even after loading it.
assert_eq!(
languages.language_names(),
&[
"JSON".to_string(),
"Plain Text".to_string(),
"Rust".to_string(),
]
);
// Loading an unknown language returns an error.
assert!(languages.language_for_name("Unknown").await.is_err());
}
}

View File

@ -5,7 +5,7 @@ use crate::{
use anyhow::{anyhow, Result};
use clock::ReplicaId;
use lsp2::{DiagnosticSeverity, LanguageServerId};
use rpc::proto;
use rpc2::proto;
use std::{ops::Range, sync::Arc};
use text::*;

View File

@ -136,6 +136,7 @@ struct Error {
impl LanguageServer {
pub fn new(
stderr_capture: Arc<Mutex<Option<String>>>,
server_id: LanguageServerId,
binary: LanguageServerBinary,
root_path: &Path,
@ -165,6 +166,7 @@ impl LanguageServer {
stdin,
stdout,
Some(stderr),
stderr_capture,
Some(server),
root_path,
code_action_kinds,
@ -197,6 +199,7 @@ impl LanguageServer {
stdin: Stdin,
stdout: Stdout,
stderr: Option<Stderr>,
stderr_capture: Arc<Mutex<Option<String>>>,
server: Option<Child>,
root_path: &Path,
code_action_kinds: Option<Vec<CodeActionKind>>,
@ -237,7 +240,8 @@ impl LanguageServer {
let stderr_input_task = stderr
.map(|stderr| {
let io_handlers = io_handlers.clone();
cx.spawn(|_| Self::handle_stderr(stderr, io_handlers).log_err())
let stderr_captures = stderr_capture.clone();
cx.spawn(|_| Self::handle_stderr(stderr, io_handlers, stderr_captures).log_err())
})
.unwrap_or_else(|| Task::Ready(Some(None)));
let input_task = cx.spawn(|_| async move {
@ -360,12 +364,14 @@ impl LanguageServer {
async fn handle_stderr<Stderr>(
stderr: Stderr,
io_handlers: Arc<Mutex<HashMap<usize, IoHandler>>>,
stderr_capture: Arc<Mutex<Option<String>>>,
) -> anyhow::Result<()>
where
Stderr: AsyncRead + Unpin + Send + 'static,
{
let mut stderr = BufReader::new(stderr);
let mut buffer = Vec::new();
loop {
buffer.clear();
stderr.read_until(b'\n', &mut buffer).await?;
@ -374,6 +380,10 @@ impl LanguageServer {
for handler in io_handlers.lock().values_mut() {
handler(IoKind::StdErr, message);
}
if let Some(stderr) = stderr_capture.lock().as_mut() {
stderr.push_str(message);
}
}
// Don't starve the main thread when receiving lots of messages at once.
@ -933,6 +943,7 @@ impl LanguageServer {
stdin_writer,
stdout_reader,
None::<async_pipe::PipeReader>,
Arc::new(Mutex::new(None)),
None,
Path::new("/"),
None,
@ -945,6 +956,7 @@ impl LanguageServer {
stdout_writer,
stdin_reader,
None::<async_pipe::PipeReader>,
Arc::new(Mutex::new(None)),
None,
Path::new("/"),
None,

View File

@ -27,6 +27,7 @@ serde_derive.workspace = true
serde_json.workspace = true
anyhow.workspace = true
futures.workspace = true
parking_lot.workspace = true
[dev-dependencies]
language2 = { path = "../language2", features = ["test-support"] }

View File

@ -210,6 +210,7 @@ impl Prettier {
.spawn(async move { node.binary_path().await })
.await?;
let server = LanguageServer::new(
Arc::new(parking_lot::Mutex::new(None)),
server_id,
LanguageServerBinary {
path: node_path,

View File

@ -34,7 +34,7 @@ language2 = { path = "../language2" }
lsp2 = { path = "../lsp2" }
node_runtime = { path = "../node_runtime" }
prettier2 = { path = "../prettier2" }
rpc = { path = "../rpc" }
rpc2 = { path = "../rpc2" }
settings2 = { path = "../settings2" }
sum_tree = { path = "../sum_tree" }
terminal2 = { path = "../terminal2" }
@ -78,7 +78,7 @@ lsp2 = { path = "../lsp2", features = ["test-support"] }
settings2 = { path = "../settings2", features = ["test-support"] }
prettier2 = { path = "../prettier2", features = ["test-support"] }
util = { path = "../util", features = ["test-support"] }
rpc = { path = "../rpc", features = ["test-support"] }
rpc2 = { path = "../rpc2", features = ["test-support"] }
git2.workspace = true
tempdir.workspace = true
unindent.workspace = true

View File

@ -52,6 +52,7 @@ use lsp2::{
};
use lsp_command::*;
use node_runtime::NodeRuntime;
use parking_lot::Mutex;
use postage::watch;
use prettier2::{LocateStart, Prettier, PRETTIER_SERVER_FILE, PRETTIER_SERVER_JS};
use project_settings::{LspSettings, ProjectSettings};
@ -2778,7 +2779,9 @@ impl Project {
return;
}
let stderr_capture = Arc::new(Mutex::new(Some(String::new())));
let pending_server = match self.languages.create_pending_language_server(
stderr_capture.clone(),
language.clone(),
adapter.clone(),
worktree_path,
@ -2824,10 +2827,14 @@ impl Project {
.await;
match result {
Ok(server) => server,
Ok(server) => {
stderr_capture.lock().take();
server
}
Err(err) => {
log::error!("failed to start language server {:?}: {}", server_name, err);
log::error!("server stderr: {:?}", stderr_capture.lock().take());
if let Some(this) = this.upgrade() {
if let Some(container_dir) = container_dir {
@ -2931,19 +2938,16 @@ impl Project {
key: (WorktreeId, LanguageServerName),
cx: &mut AsyncAppContext,
) -> Result<Option<Arc<LanguageServer>>> {
let setup = Self::setup_pending_language_server(
let language_server = Self::setup_pending_language_server(
this.clone(),
initialization_options,
pending_server,
adapter.clone(),
server_id,
cx,
);
)
.await?;
let language_server = match setup.await? {
Some(language_server) => language_server,
None => return Ok(None),
};
let this = match this.upgrade() {
Some(this) => this,
None => return Err(anyhow!("failed to upgrade project handle")),
@ -2970,12 +2974,9 @@ impl Project {
adapter: Arc<CachedLspAdapter>,
server_id: LanguageServerId,
cx: &mut AsyncAppContext,
) -> Result<Option<Arc<LanguageServer>>> {
) -> Result<Arc<LanguageServer>> {
let workspace_config = cx.update(|cx| adapter.workspace_configuration(cx))?.await;
let language_server = match pending_server.task.await? {
Some(server) => server,
None => return Ok(None),
};
let language_server = pending_server.task.await?;
language_server
.on_notification::<lsp2::notification::PublishDiagnostics, _>({
@ -3050,6 +3051,7 @@ impl Project {
}
})
.detach();
language_server
.on_request::<lsp2::request::RegisterCapability, _, _>({
let this = this.clone();
@ -3138,7 +3140,7 @@ impl Project {
)
.ok();
Ok(Some(language_server))
Ok(language_server)
}
fn insert_newly_running_language_server(

View File

@ -2646,8 +2646,8 @@ impl language2::File for File {
self
}
fn to_proto(&self) -> rpc::proto::File {
rpc::proto::File {
fn to_proto(&self) -> rpc2::proto::File {
rpc2::proto::File {
worktree_id: self.worktree.entity_id().as_u64(),
entry_id: self.entry_id.to_proto(),
path: self.path.to_string_lossy().into(),
@ -2713,7 +2713,7 @@ impl File {
}
pub fn from_proto(
proto: rpc::proto::File,
proto: rpc2::proto::File,
worktree: Handle<Worktree>,
cx: &AppContext,
) -> Result<Self> {

44
crates/rpc2/Cargo.toml Normal file
View File

@ -0,0 +1,44 @@
[package]
description = "Shared logic for communication between the Zed app and the zed.dev server"
edition = "2021"
name = "rpc2"
version = "0.1.0"
publish = false
[lib]
path = "src/rpc.rs"
doctest = false
[features]
test-support = ["collections/test-support", "gpui2/test-support"]
[dependencies]
clock = { path = "../clock" }
collections = { path = "../collections" }
gpui2 = { path = "../gpui2", optional = true }
util = { path = "../util" }
anyhow.workspace = true
async-lock = "2.4"
async-tungstenite = "0.16"
base64 = "0.13"
futures.workspace = true
parking_lot.workspace = true
prost.workspace = true
rand.workspace = true
rsa = "0.4"
serde.workspace = true
serde_derive.workspace = true
smol-timeout = "0.6"
tracing = { version = "0.1.34", features = ["log"] }
zstd = "0.11"
[build-dependencies]
prost-build = "0.9"
[dev-dependencies]
collections = { path = "../collections", features = ["test-support"] }
gpui2 = { path = "../gpui2", features = ["test-support"] }
smol.workspace = true
tempdir.workspace = true
ctor.workspace = true
env_logger.workspace = true

8
crates/rpc2/build.rs Normal file
View File

@ -0,0 +1,8 @@
fn main() {
let mut build = prost_build::Config::new();
// build.protoc_arg("--experimental_allow_proto3_optional");
build
.type_attribute(".", "#[derive(serde::Serialize)]")
.compile_protos(&["proto/zed.proto"], &["proto"])
.unwrap();
}

1559
crates/rpc2/proto/zed.proto Normal file

File diff suppressed because it is too large Load Diff

136
crates/rpc2/src/auth.rs Normal file
View File

@ -0,0 +1,136 @@
use anyhow::{Context, Result};
use rand::{thread_rng, Rng as _};
use rsa::{PublicKey as _, PublicKeyEncoding, RSAPrivateKey, RSAPublicKey};
use std::convert::TryFrom;
pub struct PublicKey(RSAPublicKey);
pub struct PrivateKey(RSAPrivateKey);
/// Generate a public and private key for asymmetric encryption.
pub fn keypair() -> Result<(PublicKey, PrivateKey)> {
let mut rng = thread_rng();
let bits = 1024;
let private_key = RSAPrivateKey::new(&mut rng, bits)?;
let public_key = RSAPublicKey::from(&private_key);
Ok((PublicKey(public_key), PrivateKey(private_key)))
}
/// Generate a random 64-character base64 string.
pub fn random_token() -> String {
let mut rng = thread_rng();
let mut token_bytes = [0; 48];
for byte in token_bytes.iter_mut() {
*byte = rng.gen();
}
base64::encode_config(token_bytes, base64::URL_SAFE)
}
impl PublicKey {
/// Convert a string to a base64-encoded string that can only be decoded with the corresponding
/// private key.
pub fn encrypt_string(&self, string: &str) -> Result<String> {
let mut rng = thread_rng();
let bytes = string.as_bytes();
let encrypted_bytes = self
.0
.encrypt(&mut rng, PADDING_SCHEME, bytes)
.context("failed to encrypt string with public key")?;
let encrypted_string = base64::encode_config(&encrypted_bytes, base64::URL_SAFE);
Ok(encrypted_string)
}
}
impl PrivateKey {
/// Decrypt a base64-encoded string that was encrypted by the corresponding public key.
pub fn decrypt_string(&self, encrypted_string: &str) -> Result<String> {
let encrypted_bytes = base64::decode_config(encrypted_string, base64::URL_SAFE)
.context("failed to base64-decode encrypted string")?;
let bytes = self
.0
.decrypt(PADDING_SCHEME, &encrypted_bytes)
.context("failed to decrypt string with private key")?;
let string = String::from_utf8(bytes).context("decrypted content was not valid utf8")?;
Ok(string)
}
}
impl TryFrom<PublicKey> for String {
type Error = anyhow::Error;
fn try_from(key: PublicKey) -> Result<Self> {
let bytes = key.0.to_pkcs1().context("failed to serialize public key")?;
let string = base64::encode_config(&bytes, base64::URL_SAFE);
Ok(string)
}
}
impl TryFrom<String> for PublicKey {
type Error = anyhow::Error;
fn try_from(value: String) -> Result<Self> {
let bytes = base64::decode_config(&value, base64::URL_SAFE)
.context("failed to base64-decode public key string")?;
let key = Self(RSAPublicKey::from_pkcs1(&bytes).context("failed to parse public key")?);
Ok(key)
}
}
const PADDING_SCHEME: rsa::PaddingScheme = rsa::PaddingScheme::PKCS1v15Encrypt;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generate_encrypt_and_decrypt_token() {
// CLIENT:
// * generate a keypair for asymmetric encryption
// * serialize the public key to send it to the server.
let (public, private) = keypair().unwrap();
let public_string = String::try_from(public).unwrap();
assert_printable(&public_string);
// SERVER:
// * parse the public key
// * generate a random token.
// * encrypt the token using the public key.
let public = PublicKey::try_from(public_string).unwrap();
let token = random_token();
let encrypted_token = public.encrypt_string(&token).unwrap();
assert_eq!(token.len(), 64);
assert_ne!(encrypted_token, token);
assert_printable(&token);
assert_printable(&encrypted_token);
// CLIENT:
// * decrypt the token using the private key.
let decrypted_token = private.decrypt_string(&encrypted_token).unwrap();
assert_eq!(decrypted_token, token);
}
#[test]
fn test_tokens_are_always_url_safe() {
for _ in 0..5 {
let token = random_token();
let (public_key, _) = keypair().unwrap();
let encrypted_token = public_key.encrypt_string(&token).unwrap();
let public_key_str = String::try_from(public_key).unwrap();
assert_printable(&token);
assert_printable(&public_key_str);
assert_printable(&encrypted_token);
}
}
fn assert_printable(token: &str) {
for c in token.chars() {
assert!(
c.is_ascii_graphic(),
"token {:?} has non-printable char {}",
token,
c
);
assert_ne!(c, '/', "token {:?} is not URL-safe", token);
assert_ne!(c, '&', "token {:?} is not URL-safe", token);
}
}
}

108
crates/rpc2/src/conn.rs Normal file
View File

@ -0,0 +1,108 @@
use async_tungstenite::tungstenite::Message as WebSocketMessage;
use futures::{SinkExt as _, StreamExt as _};
pub struct Connection {
pub(crate) tx:
Box<dyn 'static + Send + Unpin + futures::Sink<WebSocketMessage, Error = anyhow::Error>>,
pub(crate) rx: Box<
dyn 'static
+ Send
+ Unpin
+ futures::Stream<Item = Result<WebSocketMessage, anyhow::Error>>,
>,
}
impl Connection {
pub fn new<S>(stream: S) -> Self
where
S: 'static
+ Send
+ Unpin
+ futures::Sink<WebSocketMessage, Error = anyhow::Error>
+ futures::Stream<Item = Result<WebSocketMessage, anyhow::Error>>,
{
let (tx, rx) = stream.split();
Self {
tx: Box::new(tx),
rx: Box::new(rx),
}
}
pub async fn send(&mut self, message: WebSocketMessage) -> Result<(), anyhow::Error> {
self.tx.send(message).await
}
#[cfg(any(test, feature = "test-support"))]
pub fn in_memory(
executor: gpui2::Executor,
) -> (Self, Self, std::sync::Arc<std::sync::atomic::AtomicBool>) {
use std::sync::{
atomic::{AtomicBool, Ordering::SeqCst},
Arc,
};
let killed = Arc::new(AtomicBool::new(false));
let (a_tx, a_rx) = channel(killed.clone(), executor.clone());
let (b_tx, b_rx) = channel(killed.clone(), executor);
return (
Self { tx: a_tx, rx: b_rx },
Self { tx: b_tx, rx: a_rx },
killed,
);
#[allow(clippy::type_complexity)]
fn channel(
killed: Arc<AtomicBool>,
executor: gpui2::Executor,
) -> (
Box<dyn Send + Unpin + futures::Sink<WebSocketMessage, Error = anyhow::Error>>,
Box<dyn Send + Unpin + futures::Stream<Item = Result<WebSocketMessage, anyhow::Error>>>,
) {
use anyhow::anyhow;
use futures::channel::mpsc;
use std::io::{Error, ErrorKind};
let (tx, rx) = mpsc::unbounded::<WebSocketMessage>();
let tx = tx.sink_map_err(|error| anyhow!(error)).with({
let killed = killed.clone();
let executor = executor.clone();
move |msg| {
let killed = killed.clone();
let executor = executor.clone();
Box::pin(async move {
executor.simulate_random_delay().await;
// Writes to a half-open TCP connection will error.
if killed.load(SeqCst) {
std::io::Result::Err(Error::new(ErrorKind::Other, "connection lost"))?;
}
Ok(msg)
})
}
});
let rx = rx.then({
let killed = killed;
let executor = executor.clone();
move |msg| {
let killed = killed.clone();
let executor = executor.clone();
Box::pin(async move {
executor.simulate_random_delay().await;
// Reads from a half-open TCP connection will hang.
if killed.load(SeqCst) {
futures::future::pending::<()>().await;
}
Ok(msg)
})
}
});
(Box::new(tx), Box::new(rx))
}
}
}

70
crates/rpc2/src/macros.rs Normal file
View File

@ -0,0 +1,70 @@
#[macro_export]
macro_rules! messages {
($(($name:ident, $priority:ident)),* $(,)?) => {
pub fn build_typed_envelope(sender_id: ConnectionId, envelope: Envelope) -> Option<Box<dyn AnyTypedEnvelope>> {
match envelope.payload {
$(Some(envelope::Payload::$name(payload)) => {
Some(Box::new(TypedEnvelope {
sender_id,
original_sender_id: envelope.original_sender_id.map(|original_sender| PeerId {
owner_id: original_sender.owner_id,
id: original_sender.id
}),
message_id: envelope.id,
payload,
}))
}, )*
_ => None
}
}
$(
impl EnvelopedMessage for $name {
const NAME: &'static str = std::stringify!($name);
const PRIORITY: MessagePriority = MessagePriority::$priority;
fn into_envelope(
self,
id: u32,
responding_to: Option<u32>,
original_sender_id: Option<PeerId>,
) -> Envelope {
Envelope {
id,
responding_to,
original_sender_id,
payload: Some(envelope::Payload::$name(self)),
}
}
fn from_envelope(envelope: Envelope) -> Option<Self> {
if let Some(envelope::Payload::$name(msg)) = envelope.payload {
Some(msg)
} else {
None
}
}
}
)*
};
}
#[macro_export]
macro_rules! request_messages {
($(($request_name:ident, $response_name:ident)),* $(,)?) => {
$(impl RequestMessage for $request_name {
type Response = $response_name;
})*
};
}
#[macro_export]
macro_rules! entity_messages {
($id_field:ident, $($name:ident),* $(,)?) => {
$(impl EntityMessage for $name {
fn remote_entity_id(&self) -> u64 {
self.$id_field
}
})*
};
}

933
crates/rpc2/src/peer.rs Normal file
View File

@ -0,0 +1,933 @@
use super::{
proto::{self, AnyTypedEnvelope, EnvelopedMessage, MessageStream, PeerId, RequestMessage},
Connection,
};
use anyhow::{anyhow, Context, Result};
use collections::HashMap;
use futures::{
channel::{mpsc, oneshot},
stream::BoxStream,
FutureExt, SinkExt, StreamExt, TryFutureExt,
};
use parking_lot::{Mutex, RwLock};
use serde::{ser::SerializeStruct, Serialize};
use std::{fmt, sync::atomic::Ordering::SeqCst};
use std::{
future::Future,
marker::PhantomData,
sync::{
atomic::{self, AtomicU32},
Arc,
},
time::Duration,
};
use tracing::instrument;
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize)]
pub struct ConnectionId {
pub owner_id: u32,
pub id: u32,
}
impl Into<PeerId> for ConnectionId {
fn into(self) -> PeerId {
PeerId {
owner_id: self.owner_id,
id: self.id,
}
}
}
impl From<PeerId> for ConnectionId {
fn from(peer_id: PeerId) -> Self {
Self {
owner_id: peer_id.owner_id,
id: peer_id.id,
}
}
}
impl fmt::Display for ConnectionId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}/{}", self.owner_id, self.id)
}
}
pub struct Receipt<T> {
pub sender_id: ConnectionId,
pub message_id: u32,
payload_type: PhantomData<T>,
}
impl<T> Clone for Receipt<T> {
fn clone(&self) -> Self {
Self {
sender_id: self.sender_id,
message_id: self.message_id,
payload_type: PhantomData,
}
}
}
impl<T> Copy for Receipt<T> {}
#[derive(Clone, Debug)]
pub struct TypedEnvelope<T> {
pub sender_id: ConnectionId,
pub original_sender_id: Option<PeerId>,
pub message_id: u32,
pub payload: T,
}
impl<T> TypedEnvelope<T> {
pub fn original_sender_id(&self) -> Result<PeerId> {
self.original_sender_id
.ok_or_else(|| anyhow!("missing original_sender_id"))
}
}
impl<T: RequestMessage> TypedEnvelope<T> {
pub fn receipt(&self) -> Receipt<T> {
Receipt {
sender_id: self.sender_id,
message_id: self.message_id,
payload_type: PhantomData,
}
}
}
pub struct Peer {
epoch: AtomicU32,
pub connections: RwLock<HashMap<ConnectionId, ConnectionState>>,
next_connection_id: AtomicU32,
}
#[derive(Clone, Serialize)]
pub struct ConnectionState {
#[serde(skip)]
outgoing_tx: mpsc::UnboundedSender<proto::Message>,
next_message_id: Arc<AtomicU32>,
#[allow(clippy::type_complexity)]
#[serde(skip)]
response_channels:
Arc<Mutex<Option<HashMap<u32, oneshot::Sender<(proto::Envelope, oneshot::Sender<()>)>>>>>,
}
const KEEPALIVE_INTERVAL: Duration = Duration::from_secs(1);
const WRITE_TIMEOUT: Duration = Duration::from_secs(2);
pub const RECEIVE_TIMEOUT: Duration = Duration::from_secs(10);
impl Peer {
pub fn new(epoch: u32) -> Arc<Self> {
Arc::new(Self {
epoch: AtomicU32::new(epoch),
connections: Default::default(),
next_connection_id: Default::default(),
})
}
pub fn epoch(&self) -> u32 {
self.epoch.load(SeqCst)
}
#[instrument(skip_all)]
pub fn add_connection<F, Fut, Out>(
self: &Arc<Self>,
connection: Connection,
create_timer: F,
) -> (
ConnectionId,
impl Future<Output = anyhow::Result<()>> + Send,
BoxStream<'static, Box<dyn AnyTypedEnvelope>>,
)
where
F: Send + Fn(Duration) -> Fut,
Fut: Send + Future<Output = Out>,
Out: Send,
{
// For outgoing messages, use an unbounded channel so that application code
// can always send messages without yielding. For incoming messages, use a
// bounded channel so that other peers will receive backpressure if they send
// messages faster than this peer can process them.
#[cfg(any(test, feature = "test-support"))]
const INCOMING_BUFFER_SIZE: usize = 1;
#[cfg(not(any(test, feature = "test-support")))]
const INCOMING_BUFFER_SIZE: usize = 64;
let (mut incoming_tx, incoming_rx) = mpsc::channel(INCOMING_BUFFER_SIZE);
let (outgoing_tx, mut outgoing_rx) = mpsc::unbounded();
let connection_id = ConnectionId {
owner_id: self.epoch.load(SeqCst),
id: self.next_connection_id.fetch_add(1, SeqCst),
};
let connection_state = ConnectionState {
outgoing_tx,
next_message_id: Default::default(),
response_channels: Arc::new(Mutex::new(Some(Default::default()))),
};
let mut writer = MessageStream::new(connection.tx);
let mut reader = MessageStream::new(connection.rx);
let this = self.clone();
let response_channels = connection_state.response_channels.clone();
let handle_io = async move {
tracing::trace!(%connection_id, "handle io future: start");
let _end_connection = util::defer(|| {
response_channels.lock().take();
this.connections.write().remove(&connection_id);
tracing::trace!(%connection_id, "handle io future: end");
});
// Send messages on this frequency so the connection isn't closed.
let keepalive_timer = create_timer(KEEPALIVE_INTERVAL).fuse();
futures::pin_mut!(keepalive_timer);
// Disconnect if we don't receive messages at least this frequently.
let receive_timeout = create_timer(RECEIVE_TIMEOUT).fuse();
futures::pin_mut!(receive_timeout);
loop {
tracing::trace!(%connection_id, "outer loop iteration start");
let read_message = reader.read().fuse();
futures::pin_mut!(read_message);
loop {
tracing::trace!(%connection_id, "inner loop iteration start");
futures::select_biased! {
outgoing = outgoing_rx.next().fuse() => match outgoing {
Some(outgoing) => {
tracing::trace!(%connection_id, "outgoing rpc message: writing");
futures::select_biased! {
result = writer.write(outgoing).fuse() => {
tracing::trace!(%connection_id, "outgoing rpc message: done writing");
result.context("failed to write RPC message")?;
tracing::trace!(%connection_id, "keepalive interval: resetting after sending message");
keepalive_timer.set(create_timer(KEEPALIVE_INTERVAL).fuse());
}
_ = create_timer(WRITE_TIMEOUT).fuse() => {
tracing::trace!(%connection_id, "outgoing rpc message: writing timed out");
Err(anyhow!("timed out writing message"))?;
}
}
}
None => {
tracing::trace!(%connection_id, "outgoing rpc message: channel closed");
return Ok(())
},
},
_ = keepalive_timer => {
tracing::trace!(%connection_id, "keepalive interval: pinging");
futures::select_biased! {
result = writer.write(proto::Message::Ping).fuse() => {
tracing::trace!(%connection_id, "keepalive interval: done pinging");
result.context("failed to send keepalive")?;
tracing::trace!(%connection_id, "keepalive interval: resetting after pinging");
keepalive_timer.set(create_timer(KEEPALIVE_INTERVAL).fuse());
}
_ = create_timer(WRITE_TIMEOUT).fuse() => {
tracing::trace!(%connection_id, "keepalive interval: pinging timed out");
Err(anyhow!("timed out sending keepalive"))?;
}
}
}
incoming = read_message => {
let incoming = incoming.context("error reading rpc message from socket")?;
tracing::trace!(%connection_id, "incoming rpc message: received");
tracing::trace!(%connection_id, "receive timeout: resetting");
receive_timeout.set(create_timer(RECEIVE_TIMEOUT).fuse());
if let proto::Message::Envelope(incoming) = incoming {
tracing::trace!(%connection_id, "incoming rpc message: processing");
futures::select_biased! {
result = incoming_tx.send(incoming).fuse() => match result {
Ok(_) => {
tracing::trace!(%connection_id, "incoming rpc message: processed");
}
Err(_) => {
tracing::trace!(%connection_id, "incoming rpc message: channel closed");
return Ok(())
}
},
_ = create_timer(WRITE_TIMEOUT).fuse() => {
tracing::trace!(%connection_id, "incoming rpc message: processing timed out");
Err(anyhow!("timed out processing incoming message"))?
}
}
}
break;
},
_ = receive_timeout => {
tracing::trace!(%connection_id, "receive timeout: delay between messages too long");
Err(anyhow!("delay between messages too long"))?
}
}
}
}
};
let response_channels = connection_state.response_channels.clone();
self.connections
.write()
.insert(connection_id, connection_state);
let incoming_rx = incoming_rx.filter_map(move |incoming| {
let response_channels = response_channels.clone();
async move {
let message_id = incoming.id;
tracing::trace!(?incoming, "incoming message future: start");
let _end = util::defer(move || {
tracing::trace!(%connection_id, message_id, "incoming message future: end");
});
if let Some(responding_to) = incoming.responding_to {
tracing::trace!(
%connection_id,
message_id,
responding_to,
"incoming response: received"
);
let channel = response_channels.lock().as_mut()?.remove(&responding_to);
if let Some(tx) = channel {
let requester_resumed = oneshot::channel();
if let Err(error) = tx.send((incoming, requester_resumed.0)) {
tracing::trace!(
%connection_id,
message_id,
responding_to = responding_to,
?error,
"incoming response: request future dropped",
);
}
tracing::trace!(
%connection_id,
message_id,
responding_to,
"incoming response: waiting to resume requester"
);
let _ = requester_resumed.1.await;
tracing::trace!(
%connection_id,
message_id,
responding_to,
"incoming response: requester resumed"
);
} else {
tracing::warn!(
%connection_id,
message_id,
responding_to,
"incoming response: unknown request"
);
}
None
} else {
tracing::trace!(%connection_id, message_id, "incoming message: received");
proto::build_typed_envelope(connection_id, incoming).or_else(|| {
tracing::error!(
%connection_id,
message_id,
"unable to construct a typed envelope"
);
None
})
}
}
});
(connection_id, handle_io, incoming_rx.boxed())
}
#[cfg(any(test, feature = "test-support"))]
pub fn add_test_connection(
self: &Arc<Self>,
connection: Connection,
executor: gpui2::Executor,
) -> (
ConnectionId,
impl Future<Output = anyhow::Result<()>> + Send,
BoxStream<'static, Box<dyn AnyTypedEnvelope>>,
) {
let executor = executor.clone();
self.add_connection(connection, move |duration| executor.timer(duration))
}
pub fn disconnect(&self, connection_id: ConnectionId) {
self.connections.write().remove(&connection_id);
}
pub fn reset(&self, epoch: u32) {
self.teardown();
self.next_connection_id.store(0, SeqCst);
self.epoch.store(epoch, SeqCst);
}
pub fn teardown(&self) {
self.connections.write().clear();
}
pub fn request<T: RequestMessage>(
&self,
receiver_id: ConnectionId,
request: T,
) -> impl Future<Output = Result<T::Response>> {
self.request_internal(None, receiver_id, request)
.map_ok(|envelope| envelope.payload)
}
pub fn request_envelope<T: RequestMessage>(
&self,
receiver_id: ConnectionId,
request: T,
) -> impl Future<Output = Result<TypedEnvelope<T::Response>>> {
self.request_internal(None, receiver_id, request)
}
pub fn forward_request<T: RequestMessage>(
&self,
sender_id: ConnectionId,
receiver_id: ConnectionId,
request: T,
) -> impl Future<Output = Result<T::Response>> {
self.request_internal(Some(sender_id), receiver_id, request)
.map_ok(|envelope| envelope.payload)
}
pub fn request_internal<T: RequestMessage>(
&self,
original_sender_id: Option<ConnectionId>,
receiver_id: ConnectionId,
request: T,
) -> impl Future<Output = Result<TypedEnvelope<T::Response>>> {
let (tx, rx) = oneshot::channel();
let send = self.connection_state(receiver_id).and_then(|connection| {
let message_id = connection.next_message_id.fetch_add(1, SeqCst);
connection
.response_channels
.lock()
.as_mut()
.ok_or_else(|| anyhow!("connection was closed"))?
.insert(message_id, tx);
connection
.outgoing_tx
.unbounded_send(proto::Message::Envelope(request.into_envelope(
message_id,
None,
original_sender_id.map(Into::into),
)))
.map_err(|_| anyhow!("connection was closed"))?;
Ok(())
});
async move {
send?;
let (response, _barrier) = rx.await.map_err(|_| anyhow!("connection was closed"))?;
if let Some(proto::envelope::Payload::Error(error)) = &response.payload {
Err(anyhow!(
"RPC request {} failed - {}",
T::NAME,
error.message
))
} else {
Ok(TypedEnvelope {
message_id: response.id,
sender_id: receiver_id,
original_sender_id: response.original_sender_id,
payload: T::Response::from_envelope(response)
.ok_or_else(|| anyhow!("received response of the wrong type"))?,
})
}
}
}
pub fn send<T: EnvelopedMessage>(&self, receiver_id: ConnectionId, message: T) -> Result<()> {
let connection = self.connection_state(receiver_id)?;
let message_id = connection
.next_message_id
.fetch_add(1, atomic::Ordering::SeqCst);
connection
.outgoing_tx
.unbounded_send(proto::Message::Envelope(
message.into_envelope(message_id, None, None),
))?;
Ok(())
}
pub fn forward_send<T: EnvelopedMessage>(
&self,
sender_id: ConnectionId,
receiver_id: ConnectionId,
message: T,
) -> Result<()> {
let connection = self.connection_state(receiver_id)?;
let message_id = connection
.next_message_id
.fetch_add(1, atomic::Ordering::SeqCst);
connection
.outgoing_tx
.unbounded_send(proto::Message::Envelope(message.into_envelope(
message_id,
None,
Some(sender_id.into()),
)))?;
Ok(())
}
pub fn respond<T: RequestMessage>(
&self,
receipt: Receipt<T>,
response: T::Response,
) -> Result<()> {
let connection = self.connection_state(receipt.sender_id)?;
let message_id = connection
.next_message_id
.fetch_add(1, atomic::Ordering::SeqCst);
connection
.outgoing_tx
.unbounded_send(proto::Message::Envelope(response.into_envelope(
message_id,
Some(receipt.message_id),
None,
)))?;
Ok(())
}
pub fn respond_with_error<T: RequestMessage>(
&self,
receipt: Receipt<T>,
response: proto::Error,
) -> Result<()> {
let connection = self.connection_state(receipt.sender_id)?;
let message_id = connection
.next_message_id
.fetch_add(1, atomic::Ordering::SeqCst);
connection
.outgoing_tx
.unbounded_send(proto::Message::Envelope(response.into_envelope(
message_id,
Some(receipt.message_id),
None,
)))?;
Ok(())
}
pub fn respond_with_unhandled_message(
&self,
envelope: Box<dyn AnyTypedEnvelope>,
) -> Result<()> {
let connection = self.connection_state(envelope.sender_id())?;
let response = proto::Error {
message: format!("message {} was not handled", envelope.payload_type_name()),
};
let message_id = connection
.next_message_id
.fetch_add(1, atomic::Ordering::SeqCst);
connection
.outgoing_tx
.unbounded_send(proto::Message::Envelope(response.into_envelope(
message_id,
Some(envelope.message_id()),
None,
)))?;
Ok(())
}
fn connection_state(&self, connection_id: ConnectionId) -> Result<ConnectionState> {
let connections = self.connections.read();
let connection = connections
.get(&connection_id)
.ok_or_else(|| anyhow!("no such connection: {}", connection_id))?;
Ok(connection.clone())
}
}
impl Serialize for Peer {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut state = serializer.serialize_struct("Peer", 2)?;
state.serialize_field("connections", &*self.connections.read())?;
state.end()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::TypedEnvelope;
use async_tungstenite::tungstenite::Message as WebSocketMessage;
use gpui2::TestAppContext;
#[ctor::ctor]
fn init_logger() {
if std::env::var("RUST_LOG").is_ok() {
env_logger::init();
}
}
#[gpui2::test(iterations = 50)]
async fn test_request_response(cx: &mut TestAppContext) {
let executor = cx.executor();
// create 2 clients connected to 1 server
let server = Peer::new(0);
let client1 = Peer::new(0);
let client2 = Peer::new(0);
let (client1_to_server_conn, server_to_client_1_conn, _kill) =
Connection::in_memory(cx.executor().clone());
let (client1_conn_id, io_task1, client1_incoming) =
client1.add_test_connection(client1_to_server_conn, cx.executor().clone());
let (_, io_task2, server_incoming1) =
server.add_test_connection(server_to_client_1_conn, cx.executor().clone());
let (client2_to_server_conn, server_to_client_2_conn, _kill) =
Connection::in_memory(cx.executor().clone());
let (client2_conn_id, io_task3, client2_incoming) =
client2.add_test_connection(client2_to_server_conn, cx.executor().clone());
let (_, io_task4, server_incoming2) =
server.add_test_connection(server_to_client_2_conn, cx.executor().clone());
executor.spawn(io_task1).detach();
executor.spawn(io_task2).detach();
executor.spawn(io_task3).detach();
executor.spawn(io_task4).detach();
executor
.spawn(handle_messages(server_incoming1, server.clone()))
.detach();
executor
.spawn(handle_messages(client1_incoming, client1.clone()))
.detach();
executor
.spawn(handle_messages(server_incoming2, server.clone()))
.detach();
executor
.spawn(handle_messages(client2_incoming, client2.clone()))
.detach();
assert_eq!(
client1
.request(client1_conn_id, proto::Ping {},)
.await
.unwrap(),
proto::Ack {}
);
assert_eq!(
client2
.request(client2_conn_id, proto::Ping {},)
.await
.unwrap(),
proto::Ack {}
);
assert_eq!(
client1
.request(client1_conn_id, proto::Test { id: 1 },)
.await
.unwrap(),
proto::Test { id: 1 }
);
assert_eq!(
client2
.request(client2_conn_id, proto::Test { id: 2 })
.await
.unwrap(),
proto::Test { id: 2 }
);
client1.disconnect(client1_conn_id);
client2.disconnect(client1_conn_id);
async fn handle_messages(
mut messages: BoxStream<'static, Box<dyn AnyTypedEnvelope>>,
peer: Arc<Peer>,
) -> Result<()> {
while let Some(envelope) = messages.next().await {
let envelope = envelope.into_any();
if let Some(envelope) = envelope.downcast_ref::<TypedEnvelope<proto::Ping>>() {
let receipt = envelope.receipt();
peer.respond(receipt, proto::Ack {})?
} else if let Some(envelope) = envelope.downcast_ref::<TypedEnvelope<proto::Test>>()
{
peer.respond(envelope.receipt(), envelope.payload.clone())?
} else {
panic!("unknown message type");
}
}
Ok(())
}
}
#[gpui2::test(iterations = 50)]
async fn test_order_of_response_and_incoming(cx: &mut TestAppContext) {
let executor = cx.executor();
let server = Peer::new(0);
let client = Peer::new(0);
let (client_to_server_conn, server_to_client_conn, _kill) =
Connection::in_memory(executor.clone());
let (client_to_server_conn_id, io_task1, mut client_incoming) =
client.add_test_connection(client_to_server_conn, executor.clone());
let (server_to_client_conn_id, io_task2, mut server_incoming) =
server.add_test_connection(server_to_client_conn, executor.clone());
executor.spawn(io_task1).detach();
executor.spawn(io_task2).detach();
executor
.spawn(async move {
let future = server_incoming.next().await;
let request = future
.unwrap()
.into_any()
.downcast::<TypedEnvelope<proto::Ping>>()
.unwrap();
server
.send(
server_to_client_conn_id,
proto::Error {
message: "message 1".to_string(),
},
)
.unwrap();
server
.send(
server_to_client_conn_id,
proto::Error {
message: "message 2".to_string(),
},
)
.unwrap();
server.respond(request.receipt(), proto::Ack {}).unwrap();
// Prevent the connection from being dropped
server_incoming.next().await;
})
.detach();
let events = Arc::new(Mutex::new(Vec::new()));
let response = client.request(client_to_server_conn_id, proto::Ping {});
let response_task = executor.spawn({
let events = events.clone();
async move {
response.await.unwrap();
events.lock().push("response".to_string());
}
});
executor
.spawn({
let events = events.clone();
async move {
let incoming1 = client_incoming
.next()
.await
.unwrap()
.into_any()
.downcast::<TypedEnvelope<proto::Error>>()
.unwrap();
events.lock().push(incoming1.payload.message);
let incoming2 = client_incoming
.next()
.await
.unwrap()
.into_any()
.downcast::<TypedEnvelope<proto::Error>>()
.unwrap();
events.lock().push(incoming2.payload.message);
// Prevent the connection from being dropped
client_incoming.next().await;
}
})
.detach();
response_task.await;
assert_eq!(
&*events.lock(),
&[
"message 1".to_string(),
"message 2".to_string(),
"response".to_string()
]
);
}
#[gpui2::test(iterations = 50)]
async fn test_dropping_request_before_completion(cx: &mut TestAppContext) {
let executor = cx.executor().clone();
let server = Peer::new(0);
let client = Peer::new(0);
let (client_to_server_conn, server_to_client_conn, _kill) =
Connection::in_memory(cx.executor().clone());
let (client_to_server_conn_id, io_task1, mut client_incoming) =
client.add_test_connection(client_to_server_conn, cx.executor().clone());
let (server_to_client_conn_id, io_task2, mut server_incoming) =
server.add_test_connection(server_to_client_conn, cx.executor().clone());
executor.spawn(io_task1).detach();
executor.spawn(io_task2).detach();
executor
.spawn(async move {
let request1 = server_incoming
.next()
.await
.unwrap()
.into_any()
.downcast::<TypedEnvelope<proto::Ping>>()
.unwrap();
let request2 = server_incoming
.next()
.await
.unwrap()
.into_any()
.downcast::<TypedEnvelope<proto::Ping>>()
.unwrap();
server
.send(
server_to_client_conn_id,
proto::Error {
message: "message 1".to_string(),
},
)
.unwrap();
server
.send(
server_to_client_conn_id,
proto::Error {
message: "message 2".to_string(),
},
)
.unwrap();
server.respond(request1.receipt(), proto::Ack {}).unwrap();
server.respond(request2.receipt(), proto::Ack {}).unwrap();
// Prevent the connection from being dropped
server_incoming.next().await;
})
.detach();
let events = Arc::new(Mutex::new(Vec::new()));
let request1 = client.request(client_to_server_conn_id, proto::Ping {});
let request1_task = executor.spawn(request1);
let request2 = client.request(client_to_server_conn_id, proto::Ping {});
let request2_task = executor.spawn({
let events = events.clone();
async move {
request2.await.unwrap();
events.lock().push("response 2".to_string());
}
});
executor
.spawn({
let events = events.clone();
async move {
let incoming1 = client_incoming
.next()
.await
.unwrap()
.into_any()
.downcast::<TypedEnvelope<proto::Error>>()
.unwrap();
events.lock().push(incoming1.payload.message);
let incoming2 = client_incoming
.next()
.await
.unwrap()
.into_any()
.downcast::<TypedEnvelope<proto::Error>>()
.unwrap();
events.lock().push(incoming2.payload.message);
// Prevent the connection from being dropped
client_incoming.next().await;
}
})
.detach();
// Allow the request to make some progress before dropping it.
cx.executor().simulate_random_delay().await;
drop(request1_task);
request2_task.await;
assert_eq!(
&*events.lock(),
&[
"message 1".to_string(),
"message 2".to_string(),
"response 2".to_string()
]
);
}
#[gpui2::test(iterations = 50)]
async fn test_disconnect(cx: &mut TestAppContext) {
let executor = cx.executor();
let (client_conn, mut server_conn, _kill) = Connection::in_memory(executor.clone());
let client = Peer::new(0);
let (connection_id, io_handler, mut incoming) =
client.add_test_connection(client_conn, executor.clone());
let (io_ended_tx, io_ended_rx) = oneshot::channel();
executor
.spawn(async move {
io_handler.await.ok();
io_ended_tx.send(()).unwrap();
})
.detach();
let (messages_ended_tx, messages_ended_rx) = oneshot::channel();
executor
.spawn(async move {
incoming.next().await;
messages_ended_tx.send(()).unwrap();
})
.detach();
client.disconnect(connection_id);
let _ = io_ended_rx.await;
let _ = messages_ended_rx.await;
assert!(server_conn
.send(WebSocketMessage::Binary(vec![]))
.await
.is_err());
}
#[gpui2::test(iterations = 50)]
async fn test_io_error(cx: &mut TestAppContext) {
let executor = cx.executor();
let (client_conn, mut server_conn, _kill) = Connection::in_memory(executor.clone());
let client = Peer::new(0);
let (connection_id, io_handler, mut incoming) =
client.add_test_connection(client_conn, executor.clone());
executor.spawn(io_handler).detach();
executor
.spawn(async move { incoming.next().await })
.detach();
let response = executor.spawn(client.request(connection_id, proto::Ping {}));
let _request = server_conn.rx.next().await.unwrap().unwrap();
drop(server_conn);
assert_eq!(
response.await.unwrap_err().to_string(),
"connection was closed"
);
}
}

674
crates/rpc2/src/proto.rs Normal file
View File

@ -0,0 +1,674 @@
#![allow(non_snake_case)]
use super::{entity_messages, messages, request_messages, ConnectionId, TypedEnvelope};
use anyhow::{anyhow, Result};
use async_tungstenite::tungstenite::Message as WebSocketMessage;
use collections::HashMap;
use futures::{SinkExt as _, StreamExt as _};
use prost::Message as _;
use serde::Serialize;
use std::any::{Any, TypeId};
use std::{
cmp,
fmt::Debug,
io, iter,
time::{Duration, SystemTime, UNIX_EPOCH},
};
use std::{fmt, mem};
include!(concat!(env!("OUT_DIR"), "/zed.messages.rs"));
pub trait EnvelopedMessage: Clone + Debug + Serialize + Sized + Send + Sync + 'static {
const NAME: &'static str;
const PRIORITY: MessagePriority;
fn into_envelope(
self,
id: u32,
responding_to: Option<u32>,
original_sender_id: Option<PeerId>,
) -> Envelope;
fn from_envelope(envelope: Envelope) -> Option<Self>;
}
pub trait EntityMessage: EnvelopedMessage {
fn remote_entity_id(&self) -> u64;
}
pub trait RequestMessage: EnvelopedMessage {
type Response: EnvelopedMessage;
}
pub trait AnyTypedEnvelope: 'static + Send + Sync {
fn payload_type_id(&self) -> TypeId;
fn payload_type_name(&self) -> &'static str;
fn as_any(&self) -> &dyn Any;
fn into_any(self: Box<Self>) -> Box<dyn Any + Send + Sync>;
fn is_background(&self) -> bool;
fn original_sender_id(&self) -> Option<PeerId>;
fn sender_id(&self) -> ConnectionId;
fn message_id(&self) -> u32;
}
pub enum MessagePriority {
Foreground,
Background,
}
impl<T: EnvelopedMessage> AnyTypedEnvelope for TypedEnvelope<T> {
fn payload_type_id(&self) -> TypeId {
TypeId::of::<T>()
}
fn payload_type_name(&self) -> &'static str {
T::NAME
}
fn as_any(&self) -> &dyn Any {
self
}
fn into_any(self: Box<Self>) -> Box<dyn Any + Send + Sync> {
self
}
fn is_background(&self) -> bool {
matches!(T::PRIORITY, MessagePriority::Background)
}
fn original_sender_id(&self) -> Option<PeerId> {
self.original_sender_id
}
fn sender_id(&self) -> ConnectionId {
self.sender_id
}
fn message_id(&self) -> u32 {
self.message_id
}
}
impl PeerId {
pub fn from_u64(peer_id: u64) -> Self {
let owner_id = (peer_id >> 32) as u32;
let id = peer_id as u32;
Self { owner_id, id }
}
pub fn as_u64(self) -> u64 {
((self.owner_id as u64) << 32) | (self.id as u64)
}
}
impl Copy for PeerId {}
impl Eq for PeerId {}
impl Ord for PeerId {
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.owner_id
.cmp(&other.owner_id)
.then_with(|| self.id.cmp(&other.id))
}
}
impl PartialOrd for PeerId {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl std::hash::Hash for PeerId {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.owner_id.hash(state);
self.id.hash(state);
}
}
impl fmt::Display for PeerId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}/{}", self.owner_id, self.id)
}
}
messages!(
(Ack, Foreground),
(AddProjectCollaborator, Foreground),
(ApplyCodeAction, Background),
(ApplyCodeActionResponse, Background),
(ApplyCompletionAdditionalEdits, Background),
(ApplyCompletionAdditionalEditsResponse, Background),
(BufferReloaded, Foreground),
(BufferSaved, Foreground),
(Call, Foreground),
(CallCanceled, Foreground),
(CancelCall, Foreground),
(CopyProjectEntry, Foreground),
(CreateBufferForPeer, Foreground),
(CreateChannel, Foreground),
(CreateChannelResponse, Foreground),
(ChannelMessageSent, Foreground),
(CreateProjectEntry, Foreground),
(CreateRoom, Foreground),
(CreateRoomResponse, Foreground),
(DeclineCall, Foreground),
(DeleteProjectEntry, Foreground),
(Error, Foreground),
(ExpandProjectEntry, Foreground),
(Follow, Foreground),
(FollowResponse, Foreground),
(FormatBuffers, Foreground),
(FormatBuffersResponse, Foreground),
(FuzzySearchUsers, Foreground),
(GetCodeActions, Background),
(GetCodeActionsResponse, Background),
(GetHover, Background),
(GetHoverResponse, Background),
(GetChannelMessages, Background),
(GetChannelMessagesResponse, Background),
(SendChannelMessage, Background),
(SendChannelMessageResponse, Background),
(GetCompletions, Background),
(GetCompletionsResponse, Background),
(GetDefinition, Background),
(GetDefinitionResponse, Background),
(GetTypeDefinition, Background),
(GetTypeDefinitionResponse, Background),
(GetDocumentHighlights, Background),
(GetDocumentHighlightsResponse, Background),
(GetReferences, Background),
(GetReferencesResponse, Background),
(GetProjectSymbols, Background),
(GetProjectSymbolsResponse, Background),
(GetUsers, Foreground),
(Hello, Foreground),
(IncomingCall, Foreground),
(InviteChannelMember, Foreground),
(UsersResponse, Foreground),
(JoinProject, Foreground),
(JoinProjectResponse, Foreground),
(JoinRoom, Foreground),
(JoinRoomResponse, Foreground),
(JoinChannelChat, Foreground),
(JoinChannelChatResponse, Foreground),
(LeaveChannelChat, Foreground),
(LeaveProject, Foreground),
(LeaveRoom, Foreground),
(OpenBufferById, Background),
(OpenBufferByPath, Background),
(OpenBufferForSymbol, Background),
(OpenBufferForSymbolResponse, Background),
(OpenBufferResponse, Background),
(PerformRename, Background),
(PerformRenameResponse, Background),
(OnTypeFormatting, Background),
(OnTypeFormattingResponse, Background),
(InlayHints, Background),
(InlayHintsResponse, Background),
(ResolveInlayHint, Background),
(ResolveInlayHintResponse, Background),
(RefreshInlayHints, Foreground),
(Ping, Foreground),
(PrepareRename, Background),
(PrepareRenameResponse, Background),
(ExpandProjectEntryResponse, Foreground),
(ProjectEntryResponse, Foreground),
(RejoinRoom, Foreground),
(RejoinRoomResponse, Foreground),
(RemoveContact, Foreground),
(RemoveChannelMember, Foreground),
(RemoveChannelMessage, Foreground),
(ReloadBuffers, Foreground),
(ReloadBuffersResponse, Foreground),
(RemoveProjectCollaborator, Foreground),
(RenameProjectEntry, Foreground),
(RequestContact, Foreground),
(RespondToContactRequest, Foreground),
(RespondToChannelInvite, Foreground),
(JoinChannel, Foreground),
(RoomUpdated, Foreground),
(SaveBuffer, Foreground),
(RenameChannel, Foreground),
(RenameChannelResponse, Foreground),
(SetChannelMemberAdmin, Foreground),
(SearchProject, Background),
(SearchProjectResponse, Background),
(ShareProject, Foreground),
(ShareProjectResponse, Foreground),
(ShowContacts, Foreground),
(StartLanguageServer, Foreground),
(SynchronizeBuffers, Foreground),
(SynchronizeBuffersResponse, Foreground),
(RejoinChannelBuffers, Foreground),
(RejoinChannelBuffersResponse, Foreground),
(Test, Foreground),
(Unfollow, Foreground),
(UnshareProject, Foreground),
(UpdateBuffer, Foreground),
(UpdateBufferFile, Foreground),
(UpdateContacts, Foreground),
(DeleteChannel, Foreground),
(MoveChannel, Foreground),
(LinkChannel, Foreground),
(UnlinkChannel, Foreground),
(UpdateChannels, Foreground),
(UpdateDiagnosticSummary, Foreground),
(UpdateFollowers, Foreground),
(UpdateInviteInfo, Foreground),
(UpdateLanguageServer, Foreground),
(UpdateParticipantLocation, Foreground),
(UpdateProject, Foreground),
(UpdateProjectCollaborator, Foreground),
(UpdateWorktree, Foreground),
(UpdateWorktreeSettings, Foreground),
(UpdateDiffBase, Foreground),
(GetPrivateUserInfo, Foreground),
(GetPrivateUserInfoResponse, Foreground),
(GetChannelMembers, Foreground),
(GetChannelMembersResponse, Foreground),
(JoinChannelBuffer, Foreground),
(JoinChannelBufferResponse, Foreground),
(LeaveChannelBuffer, Background),
(UpdateChannelBuffer, Foreground),
(UpdateChannelBufferCollaborators, Foreground),
(AckBufferOperation, Background),
(AckChannelMessage, Background),
);
request_messages!(
(ApplyCodeAction, ApplyCodeActionResponse),
(
ApplyCompletionAdditionalEdits,
ApplyCompletionAdditionalEditsResponse
),
(Call, Ack),
(CancelCall, Ack),
(CopyProjectEntry, ProjectEntryResponse),
(CreateProjectEntry, ProjectEntryResponse),
(CreateRoom, CreateRoomResponse),
(CreateChannel, CreateChannelResponse),
(DeclineCall, Ack),
(DeleteProjectEntry, ProjectEntryResponse),
(ExpandProjectEntry, ExpandProjectEntryResponse),
(Follow, FollowResponse),
(FormatBuffers, FormatBuffersResponse),
(GetCodeActions, GetCodeActionsResponse),
(GetHover, GetHoverResponse),
(GetCompletions, GetCompletionsResponse),
(GetDefinition, GetDefinitionResponse),
(GetTypeDefinition, GetTypeDefinitionResponse),
(GetDocumentHighlights, GetDocumentHighlightsResponse),
(GetReferences, GetReferencesResponse),
(GetPrivateUserInfo, GetPrivateUserInfoResponse),
(GetProjectSymbols, GetProjectSymbolsResponse),
(FuzzySearchUsers, UsersResponse),
(GetUsers, UsersResponse),
(InviteChannelMember, Ack),
(JoinProject, JoinProjectResponse),
(JoinRoom, JoinRoomResponse),
(JoinChannelChat, JoinChannelChatResponse),
(LeaveRoom, Ack),
(RejoinRoom, RejoinRoomResponse),
(IncomingCall, Ack),
(OpenBufferById, OpenBufferResponse),
(OpenBufferByPath, OpenBufferResponse),
(OpenBufferForSymbol, OpenBufferForSymbolResponse),
(Ping, Ack),
(PerformRename, PerformRenameResponse),
(PrepareRename, PrepareRenameResponse),
(OnTypeFormatting, OnTypeFormattingResponse),
(InlayHints, InlayHintsResponse),
(ResolveInlayHint, ResolveInlayHintResponse),
(RefreshInlayHints, Ack),
(ReloadBuffers, ReloadBuffersResponse),
(RequestContact, Ack),
(RemoveChannelMember, Ack),
(RemoveContact, Ack),
(RespondToContactRequest, Ack),
(RespondToChannelInvite, Ack),
(SetChannelMemberAdmin, Ack),
(SendChannelMessage, SendChannelMessageResponse),
(GetChannelMessages, GetChannelMessagesResponse),
(GetChannelMembers, GetChannelMembersResponse),
(JoinChannel, JoinRoomResponse),
(RemoveChannelMessage, Ack),
(DeleteChannel, Ack),
(RenameProjectEntry, ProjectEntryResponse),
(RenameChannel, RenameChannelResponse),
(LinkChannel, Ack),
(UnlinkChannel, Ack),
(MoveChannel, Ack),
(SaveBuffer, BufferSaved),
(SearchProject, SearchProjectResponse),
(ShareProject, ShareProjectResponse),
(SynchronizeBuffers, SynchronizeBuffersResponse),
(RejoinChannelBuffers, RejoinChannelBuffersResponse),
(Test, Test),
(UpdateBuffer, Ack),
(UpdateParticipantLocation, Ack),
(UpdateProject, Ack),
(UpdateWorktree, Ack),
(JoinChannelBuffer, JoinChannelBufferResponse),
(LeaveChannelBuffer, Ack)
);
entity_messages!(
project_id,
AddProjectCollaborator,
ApplyCodeAction,
ApplyCompletionAdditionalEdits,
BufferReloaded,
BufferSaved,
CopyProjectEntry,
CreateBufferForPeer,
CreateProjectEntry,
DeleteProjectEntry,
ExpandProjectEntry,
FormatBuffers,
GetCodeActions,
GetCompletions,
GetDefinition,
GetTypeDefinition,
GetDocumentHighlights,
GetHover,
GetReferences,
GetProjectSymbols,
JoinProject,
LeaveProject,
OpenBufferById,
OpenBufferByPath,
OpenBufferForSymbol,
PerformRename,
OnTypeFormatting,
InlayHints,
ResolveInlayHint,
RefreshInlayHints,
PrepareRename,
ReloadBuffers,
RemoveProjectCollaborator,
RenameProjectEntry,
SaveBuffer,
SearchProject,
StartLanguageServer,
SynchronizeBuffers,
UnshareProject,
UpdateBuffer,
UpdateBufferFile,
UpdateDiagnosticSummary,
UpdateLanguageServer,
UpdateProject,
UpdateProjectCollaborator,
UpdateWorktree,
UpdateWorktreeSettings,
UpdateDiffBase
);
entity_messages!(
channel_id,
ChannelMessageSent,
UpdateChannelBuffer,
RemoveChannelMessage,
UpdateChannelBufferCollaborators,
);
const KIB: usize = 1024;
const MIB: usize = KIB * 1024;
const MAX_BUFFER_LEN: usize = MIB;
/// A stream of protobuf messages.
pub struct MessageStream<S> {
stream: S,
encoding_buffer: Vec<u8>,
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug)]
pub enum Message {
Envelope(Envelope),
Ping,
Pong,
}
impl<S> MessageStream<S> {
pub fn new(stream: S) -> Self {
Self {
stream,
encoding_buffer: Vec::new(),
}
}
pub fn inner_mut(&mut self) -> &mut S {
&mut self.stream
}
}
impl<S> MessageStream<S>
where
S: futures::Sink<WebSocketMessage, Error = anyhow::Error> + Unpin,
{
pub async fn write(&mut self, message: Message) -> Result<(), anyhow::Error> {
#[cfg(any(test, feature = "test-support"))]
const COMPRESSION_LEVEL: i32 = -7;
#[cfg(not(any(test, feature = "test-support")))]
const COMPRESSION_LEVEL: i32 = 4;
match message {
Message::Envelope(message) => {
self.encoding_buffer.reserve(message.encoded_len());
message
.encode(&mut self.encoding_buffer)
.map_err(io::Error::from)?;
let buffer =
zstd::stream::encode_all(self.encoding_buffer.as_slice(), COMPRESSION_LEVEL)
.unwrap();
self.encoding_buffer.clear();
self.encoding_buffer.shrink_to(MAX_BUFFER_LEN);
self.stream.send(WebSocketMessage::Binary(buffer)).await?;
}
Message::Ping => {
self.stream
.send(WebSocketMessage::Ping(Default::default()))
.await?;
}
Message::Pong => {
self.stream
.send(WebSocketMessage::Pong(Default::default()))
.await?;
}
}
Ok(())
}
}
impl<S> MessageStream<S>
where
S: futures::Stream<Item = Result<WebSocketMessage, anyhow::Error>> + Unpin,
{
pub async fn read(&mut self) -> Result<Message, anyhow::Error> {
while let Some(bytes) = self.stream.next().await {
match bytes? {
WebSocketMessage::Binary(bytes) => {
zstd::stream::copy_decode(bytes.as_slice(), &mut self.encoding_buffer).unwrap();
let envelope = Envelope::decode(self.encoding_buffer.as_slice())
.map_err(io::Error::from)?;
self.encoding_buffer.clear();
self.encoding_buffer.shrink_to(MAX_BUFFER_LEN);
return Ok(Message::Envelope(envelope));
}
WebSocketMessage::Ping(_) => return Ok(Message::Ping),
WebSocketMessage::Pong(_) => return Ok(Message::Pong),
WebSocketMessage::Close(_) => break,
_ => {}
}
}
Err(anyhow!("connection closed"))
}
}
impl From<Timestamp> for SystemTime {
fn from(val: Timestamp) -> Self {
UNIX_EPOCH
.checked_add(Duration::new(val.seconds, val.nanos))
.unwrap()
}
}
impl From<SystemTime> for Timestamp {
fn from(time: SystemTime) -> Self {
let duration = time.duration_since(UNIX_EPOCH).unwrap();
Self {
seconds: duration.as_secs(),
nanos: duration.subsec_nanos(),
}
}
}
impl From<u128> for Nonce {
fn from(nonce: u128) -> Self {
let upper_half = (nonce >> 64) as u64;
let lower_half = nonce as u64;
Self {
upper_half,
lower_half,
}
}
}
impl From<Nonce> for u128 {
fn from(nonce: Nonce) -> Self {
let upper_half = (nonce.upper_half as u128) << 64;
let lower_half = nonce.lower_half as u128;
upper_half | lower_half
}
}
pub fn split_worktree_update(
mut message: UpdateWorktree,
max_chunk_size: usize,
) -> impl Iterator<Item = UpdateWorktree> {
let mut done_files = false;
let mut repository_map = message
.updated_repositories
.into_iter()
.map(|repo| (repo.work_directory_id, repo))
.collect::<HashMap<_, _>>();
iter::from_fn(move || {
if done_files {
return None;
}
let updated_entries_chunk_size = cmp::min(message.updated_entries.len(), max_chunk_size);
let updated_entries: Vec<_> = message
.updated_entries
.drain(..updated_entries_chunk_size)
.collect();
let removed_entries_chunk_size = cmp::min(message.removed_entries.len(), max_chunk_size);
let removed_entries = message
.removed_entries
.drain(..removed_entries_chunk_size)
.collect();
done_files = message.updated_entries.is_empty() && message.removed_entries.is_empty();
let mut updated_repositories = Vec::new();
if !repository_map.is_empty() {
for entry in &updated_entries {
if let Some(repo) = repository_map.remove(&entry.id) {
updated_repositories.push(repo)
}
}
}
let removed_repositories = if done_files {
mem::take(&mut message.removed_repositories)
} else {
Default::default()
};
if done_files {
updated_repositories.extend(mem::take(&mut repository_map).into_values());
}
Some(UpdateWorktree {
project_id: message.project_id,
worktree_id: message.worktree_id,
root_name: message.root_name.clone(),
abs_path: message.abs_path.clone(),
updated_entries,
removed_entries,
scan_id: message.scan_id,
is_last_update: done_files && message.is_last_update,
updated_repositories,
removed_repositories,
})
})
}
#[cfg(test)]
mod tests {
use super::*;
#[gpui2::test]
async fn test_buffer_size() {
let (tx, rx) = futures::channel::mpsc::unbounded();
let mut sink = MessageStream::new(tx.sink_map_err(|_| anyhow!("")));
sink.write(Message::Envelope(Envelope {
payload: Some(envelope::Payload::UpdateWorktree(UpdateWorktree {
root_name: "abcdefg".repeat(10),
..Default::default()
})),
..Default::default()
}))
.await
.unwrap();
assert!(sink.encoding_buffer.capacity() <= MAX_BUFFER_LEN);
sink.write(Message::Envelope(Envelope {
payload: Some(envelope::Payload::UpdateWorktree(UpdateWorktree {
root_name: "abcdefg".repeat(1000000),
..Default::default()
})),
..Default::default()
}))
.await
.unwrap();
assert!(sink.encoding_buffer.capacity() <= MAX_BUFFER_LEN);
let mut stream = MessageStream::new(rx.map(anyhow::Ok));
stream.read().await.unwrap();
assert!(stream.encoding_buffer.capacity() <= MAX_BUFFER_LEN);
stream.read().await.unwrap();
assert!(stream.encoding_buffer.capacity() <= MAX_BUFFER_LEN);
}
#[gpui2::test]
fn test_converting_peer_id_from_and_to_u64() {
let peer_id = PeerId {
owner_id: 10,
id: 3,
};
assert_eq!(PeerId::from_u64(peer_id.as_u64()), peer_id);
let peer_id = PeerId {
owner_id: u32::MAX,
id: 3,
};
assert_eq!(PeerId::from_u64(peer_id.as_u64()), peer_id);
let peer_id = PeerId {
owner_id: 10,
id: u32::MAX,
};
assert_eq!(PeerId::from_u64(peer_id.as_u64()), peer_id);
let peer_id = PeerId {
owner_id: u32::MAX,
id: u32::MAX,
};
assert_eq!(PeerId::from_u64(peer_id.as_u64()), peer_id);
}
}

9
crates/rpc2/src/rpc.rs Normal file
View File

@ -0,0 +1,9 @@
pub mod auth;
mod conn;
mod peer;
pub mod proto;
pub use conn::Connection;
pub use peer::*;
mod macros;
pub const PROTOCOL_VERSION: u32 = 64;

View File

@ -14,7 +14,7 @@ impl<V, D> Default for ButtonHandlers<V, D> {
}
}
#[derive(Element)]
#[derive(Component)]
pub struct Button<V: 'static, D: 'static> {
handlers: ButtonHandlers<V, D>,
label: Option<ArcCow<'static, str>>,

View File

@ -16,7 +16,7 @@ impl KitchenSinkStory {
view(cx.entity(|cx| Self::new()), Self::render)
}
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Element<ViewState = Self> {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Component<Self> {
let element_stories = ElementStory::iter()
.map(|selector| selector.story(cx))
.collect::<Vec<_>>();

View File

@ -1,8 +1,7 @@
use crate::themes::rose_pine;
use gpui2::{
div, px, view, Context, Element, ParentElement, SharedString, Styled, View, WindowContext,
div, px, view, Component, Context, ParentElement, SharedString, Styled, View, WindowContext,
};
use ui::ElementExt;
pub struct ScrollStory {
text: View<()>,
@ -16,7 +15,7 @@ impl ScrollStory {
}
}
fn checkerboard<S>(depth: usize) -> impl Element<ViewState = S>
fn checkerboard<S>(depth: usize) -> impl Component<S>
where
S: 'static + Send + Sync,
{

View File

@ -1,4 +1,3 @@
use std::marker::PhantomData;
use gpui2::{px, rgb, Div, Hsla};
use ui::prelude::*;
@ -7,19 +6,15 @@ use crate::story::Story;
/// A reimplementation of the MDN `z-index` example, found here:
/// [https://developer.mozilla.org/en-US/docs/Web/CSS/z-index](https://developer.mozilla.org/en-US/docs/Web/CSS/z-index).
#[derive(Element)]
pub struct ZIndexStory<S: 'static + Send + Sync> {
state_type: PhantomData<S>,
}
#[derive(Component)]
pub struct ZIndexStory;
impl<S: 'static + Send + Sync> ZIndexStory<S> {
impl ZIndexStory {
pub fn new() -> Self {
Self {
state_type: PhantomData,
}
Self
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render<S: 'static>(self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
Story::container(cx)
.child(Story::title(cx, "z-index"))
.child(
@ -86,23 +81,19 @@ trait Styles: Styled + Sized {
}
}
impl<S: 'static + Send + Sync> Styles for Div<S> {}
impl<V: 'static> Styles for Div<V> {}
#[derive(Element)]
struct ZIndexExample<S: 'static + Send + Sync> {
state_type: PhantomData<S>,
#[derive(Component)]
struct ZIndexExample {
z_index: u32,
}
impl<S: 'static + Send + Sync> ZIndexExample<S> {
impl ZIndexExample {
pub fn new(z_index: u32) -> Self {
Self {
state_type: PhantomData,
z_index,
}
Self { z_index }
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
div()
.relative()
.size_full()

View File

@ -28,29 +28,29 @@ impl ElementStory {
pub fn story(&self, cx: &mut WindowContext) -> AnyView {
match self {
Self::Avatar => {
view(cx.entity(|cx| ()), |_, _| ui::AvatarStory::new().into_any()).into_any()
view(cx.entity(|cx| ()), |_, _| ui::AvatarStory::new().render()).into_any()
}
Self::Button => {
view(cx.entity(|cx| ()), |_, _| ui::ButtonStory::new().into_any()).into_any()
view(cx.entity(|cx| ()), |_, _| ui::ButtonStory::new().render()).into_any()
}
Self::Details => view(cx.entity(|cx| ()), |_, _| {
ui::DetailsStory::new().into_any()
ui::DetailsStory::new().render()
})
.into_any(),
Self::Focus => FocusStory::view(cx).into_any(),
Self::Icon => {
view(cx.entity(|cx| ()), |_, _| ui::IconStory::new().into_any()).into_any()
view(cx.entity(|cx| ()), |_, _| ui::IconStory::new().render()).into_any()
}
Self::Input => {
view(cx.entity(|cx| ()), |_, _| ui::InputStory::new().into_any()).into_any()
view(cx.entity(|cx| ()), |_, _| ui::InputStory::new().render()).into_any()
}
Self::Label => {
view(cx.entity(|cx| ()), |_, _| ui::LabelStory::new().into_any()).into_any()
view(cx.entity(|cx| ()), |_, _| ui::LabelStory::new().render()).into_any()
}
Self::Scroll => ScrollStory::view(cx).into_any(),
Self::Text => TextStory::view(cx).into_any(),
Self::ZIndex => {
view(cx.entity(|cx| ()), |_, _| ZIndexStory::new().into_any()).into_any()
view(cx.entity(|cx| ()), |_, _| ZIndexStory::new().render()).into_any()
}
}
}
@ -91,93 +91,93 @@ impl ComponentStory {
pub fn story(&self, cx: &mut WindowContext) -> AnyView {
match self {
Self::AssistantPanel => view(cx.entity(|cx| ()), |_, _| {
ui::AssistantPanelStory::new().into_any()
ui::AssistantPanelStory::new().render()
})
.into_any(),
Self::Buffer => {
view(cx.entity(|cx| ()), |_, _| ui::BufferStory::new().into_any()).into_any()
view(cx.entity(|cx| ()), |_, _| ui::BufferStory::new().render()).into_any()
}
Self::Breadcrumb => view(cx.entity(|cx| ()), |_, _| {
ui::BreadcrumbStory::new().into_any()
ui::BreadcrumbStory::new().render()
})
.into_any(),
Self::ChatPanel => view(cx.entity(|cx| ()), |_, _| {
ui::ChatPanelStory::new().into_any()
ui::ChatPanelStory::new().render()
})
.into_any(),
Self::CollabPanel => view(cx.entity(|cx| ()), |_, _| {
ui::CollabPanelStory::new().into_any()
ui::CollabPanelStory::new().render()
})
.into_any(),
Self::CommandPalette => view(cx.entity(|cx| ()), |_, _| {
ui::CommandPaletteStory::new().into_any()
ui::CommandPaletteStory::new().render()
})
.into_any(),
Self::ContextMenu => view(cx.entity(|cx| ()), |_, _| {
ui::ContextMenuStory::new().into_any()
ui::ContextMenuStory::new().render()
})
.into_any(),
Self::Facepile => view(cx.entity(|cx| ()), |_, _| {
ui::FacepileStory::new().into_any()
ui::FacepileStory::new().render()
})
.into_any(),
Self::Keybinding => view(cx.entity(|cx| ()), |_, _| {
ui::KeybindingStory::new().into_any()
ui::KeybindingStory::new().render()
})
.into_any(),
Self::LanguageSelector => view(cx.entity(|cx| ()), |_, _| {
ui::LanguageSelectorStory::new().into_any()
ui::LanguageSelectorStory::new().render()
})
.into_any(),
Self::MultiBuffer => view(cx.entity(|cx| ()), |_, _| {
ui::MultiBufferStory::new().into_any()
ui::MultiBufferStory::new().render()
})
.into_any(),
Self::NotificationsPanel => view(cx.entity(|cx| ()), |_, _| {
ui::NotificationsPanelStory::new().into_any()
ui::NotificationsPanelStory::new().render()
})
.into_any(),
Self::Palette => view(cx.entity(|cx| ()), |_, _| {
ui::PaletteStory::new().into_any()
ui::PaletteStory::new().render()
})
.into_any(),
Self::Panel => {
view(cx.entity(|cx| ()), |_, _| ui::PanelStory::new().into_any()).into_any()
view(cx.entity(|cx| ()), |_, _| ui::PanelStory::new().render()).into_any()
}
Self::ProjectPanel => view(cx.entity(|cx| ()), |_, _| {
ui::ProjectPanelStory::new().into_any()
ui::ProjectPanelStory::new().render()
})
.into_any(),
Self::RecentProjects => view(cx.entity(|cx| ()), |_, _| {
ui::RecentProjectsStory::new().into_any()
ui::RecentProjectsStory::new().render()
})
.into_any(),
Self::Tab => view(cx.entity(|cx| ()), |_, _| ui::TabStory::new().into_any()).into_any(),
Self::Tab => view(cx.entity(|cx| ()), |_, _| ui::TabStory::new().render()).into_any(),
Self::TabBar => {
view(cx.entity(|cx| ()), |_, _| ui::TabBarStory::new().into_any()).into_any()
view(cx.entity(|cx| ()), |_, _| ui::TabBarStory::new().render()).into_any()
}
Self::Terminal => view(cx.entity(|cx| ()), |_, _| {
ui::TerminalStory::new().into_any()
ui::TerminalStory::new().render()
})
.into_any(),
Self::ThemeSelector => view(cx.entity(|cx| ()), |_, _| {
ui::ThemeSelectorStory::new().into_any()
ui::ThemeSelectorStory::new().render()
})
.into_any(),
Self::TitleBar => ui::TitleBarStory::view(cx).into_any(),
Self::Toast => {
view(cx.entity(|cx| ()), |_, _| ui::ToastStory::new().into_any()).into_any()
view(cx.entity(|cx| ()), |_, _| ui::ToastStory::new().render()).into_any()
}
Self::Toolbar => view(cx.entity(|cx| ()), |_, _| {
ui::ToolbarStory::new().into_any()
ui::ToolbarStory::new().render()
})
.into_any(),
Self::TrafficLights => view(cx.entity(|cx| ()), |_, _| {
ui::TrafficLightsStory::new().into_any()
ui::TrafficLightsStory::new().render()
})
.into_any(),
Self::Copilot => view(cx.entity(|cx| ()), |_, _| {
ui::CopilotModalStory::new().into_any()
ui::CopilotModalStory::new().render()
})
.into_any(),
Self::Workspace => ui::WorkspaceStory::view(cx).into_any(),

View File

@ -10,7 +10,7 @@ use std::sync::Arc;
use clap::Parser;
use gpui2::{
div, px, size, view, AnyView, AppContext, Bounds, Context, Element, ViewContext, WindowBounds,
div, px, size, view, AnyView, AppContext, Bounds, Context, ViewContext, WindowBounds,
WindowOptions,
};
use log::LevelFilter;
@ -107,7 +107,7 @@ impl StoryWrapper {
Self { story, theme }
}
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Element<ViewState = Self> {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Component<Self> {
themed(self.theme.clone(), cx, |cx| {
div()
.flex()

View File

@ -1,22 +1,18 @@
use std::marker::PhantomData;
use gpui2::{rems, AbsoluteLength};
use crate::prelude::*;
use crate::{Icon, IconButton, Label, Panel, PanelSide};
#[derive(Element)]
pub struct AssistantPanel<S: 'static + Send + Sync> {
#[derive(Component)]
pub struct AssistantPanel {
id: ElementId,
state_type: PhantomData<S>,
current_side: PanelSide,
}
impl<S: 'static + Send + Sync> AssistantPanel<S> {
impl AssistantPanel {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
state_type: PhantomData,
current_side: PanelSide::default(),
}
}
@ -26,7 +22,7 @@ impl<S: 'static + Send + Sync> AssistantPanel<S> {
self
}
fn render(&mut self, view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render<V: 'static>(self, view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
Panel::new(self.id.clone(), cx)
.children(vec![div()
.flex()
@ -69,7 +65,7 @@ impl<S: 'static + Send + Sync> AssistantPanel<S> {
.overflow_y_scroll()
.child(Label::new("Is this thing on?")),
)
.into_any()])
.render()])
.side(self.current_side)
.width(AbsoluteLength::Rems(rems(32.)))
}
@ -84,25 +80,17 @@ mod stories {
use super::*;
#[derive(Element)]
pub struct AssistantPanelStory<S: 'static + Send + Sync> {
state_type: PhantomData<S>,
}
#[derive(Component)]
pub struct AssistantPanelStory {}
impl<S: 'static + Send + Sync> AssistantPanelStory<S> {
impl AssistantPanelStory {
pub fn new() -> Self {
Self {
state_type: PhantomData,
}
Self {}
}
fn render(
&mut self,
_view: &mut S,
cx: &mut ViewContext<S>,
) -> impl Element<ViewState = S> {
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
Story::container(cx)
.child(Story::title_for::<_, AssistantPanel<S>>(cx))
.child(Story::title_for::<_, AssistantPanel>(cx))
.child(Story::label(cx, "Default"))
.child(AssistantPanel::new("assistant-panel"))
}

View File

@ -1,41 +1,33 @@
use std::marker::PhantomData;
use std::path::PathBuf;
use gpui2::Div;
use crate::prelude::*;
use crate::{h_stack, HighlightedText};
#[derive(Clone)]
pub struct Symbol(pub Vec<HighlightedText>);
#[derive(Element)]
pub struct Breadcrumb<S: 'static + Send + Sync> {
state_type: PhantomData<S>,
#[derive(Component)]
pub struct Breadcrumb {
path: PathBuf,
symbols: Vec<Symbol>,
}
impl<S: 'static + Send + Sync> Breadcrumb<S> {
impl Breadcrumb {
pub fn new(path: PathBuf, symbols: Vec<Symbol>) -> Self {
Self {
state_type: PhantomData,
path,
symbols,
}
}
fn render_separator(&self, cx: &WindowContext) -> Div<S> {
fn render_separator<V: 'static>(&self, cx: &WindowContext) -> Div<V> {
let theme = theme(cx);
div().child(" ").text_color(theme.text_muted)
}
fn render(
&mut self,
view_state: &mut S,
cx: &mut ViewContext<S>,
) -> impl Element<ViewState = S> {
fn render<V: 'static>(self, view_state: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
let theme = theme(cx);
let symbols_len = self.symbols.len();
@ -90,27 +82,19 @@ mod stories {
use super::*;
#[derive(Element)]
pub struct BreadcrumbStory<S: 'static + Send + Sync> {
state_type: PhantomData<S>,
}
#[derive(Component)]
pub struct BreadcrumbStory;
impl<S: 'static + Send + Sync> BreadcrumbStory<S> {
impl BreadcrumbStory {
pub fn new() -> Self {
Self {
state_type: PhantomData,
}
Self
}
fn render(
&mut self,
view_state: &mut S,
cx: &mut ViewContext<S>,
) -> impl Element<ViewState = S> {
fn render<V: 'static>(self, view_state: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
let theme = theme(cx);
Story::container(cx)
.child(Story::title_for::<_, Breadcrumb<S>>(cx))
.child(Story::title_for::<_, Breadcrumb>(cx))
.child(Story::label(cx, "Default"))
.child(Breadcrumb::new(
PathBuf::from_str("crates/ui/src/components/toolbar.rs").unwrap(),

View File

@ -1,5 +1,3 @@
use std::marker::PhantomData;
use gpui2::{Hsla, WindowContext};
use crate::prelude::*;
@ -109,10 +107,9 @@ impl BufferRow {
}
}
#[derive(Element, Clone)]
pub struct Buffer<S: 'static + Send + Sync + Clone> {
#[derive(Component, Clone)]
pub struct Buffer {
id: ElementId,
state_type: PhantomData<S>,
rows: Option<BufferRows>,
readonly: bool,
language: Option<String>,
@ -120,11 +117,10 @@ pub struct Buffer<S: 'static + Send + Sync + Clone> {
path: Option<String>,
}
impl<S: 'static + Send + Sync + Clone> Buffer<S> {
impl Buffer {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
state_type: PhantomData,
rows: Some(BufferRows::default()),
readonly: false,
language: None,
@ -158,7 +154,7 @@ impl<S: 'static + Send + Sync + Clone> Buffer<S> {
self
}
fn render_row(row: BufferRow, cx: &WindowContext) -> impl Element<ViewState = S> {
fn render_row<S: 'static>(row: BufferRow, cx: &WindowContext) -> impl Component<S> {
let theme = theme(cx);
let line_background = if row.current {
@ -208,7 +204,7 @@ impl<S: 'static + Send + Sync + Clone> Buffer<S> {
}))
}
fn render_rows(&self, cx: &WindowContext) -> Vec<impl Element<ViewState = S>> {
fn render_rows<S: 'static>(&self, cx: &WindowContext) -> Vec<impl Component<S>> {
match &self.rows {
Some(rows) => rows
.rows
@ -219,7 +215,7 @@ impl<S: 'static + Send + Sync + Clone> Buffer<S> {
}
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render<S: 'static>(self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
let theme = theme(cx);
let rows = self.render_rows(cx);
@ -246,27 +242,19 @@ mod stories {
use super::*;
#[derive(Element)]
pub struct BufferStory<S: 'static + Send + Sync + Clone> {
state_type: PhantomData<S>,
}
#[derive(Component)]
pub struct BufferStory;
impl<S: 'static + Send + Sync + Clone> BufferStory<S> {
impl BufferStory {
pub fn new() -> Self {
Self {
state_type: PhantomData,
}
Self
}
fn render(
&mut self,
_view: &mut S,
cx: &mut ViewContext<S>,
) -> impl Element<ViewState = S> {
fn render<S: 'static>(self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
let theme = theme(cx);
Story::container(cx)
.child(Story::title_for::<_, Buffer<S>>(cx))
.child(Story::title_for::<_, Buffer>(cx))
.child(Story::label(cx, "Default"))
.child(div().w(rems(64.)).h_96().child(empty_buffer_example()))
.child(Story::label(cx, "Hello World (Rust)"))

View File

@ -25,7 +25,7 @@ impl BufferSearch {
view(cx.entity(|cx| Self::new()), Self::render)
}
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Element<ViewState = Self> {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Component<Self> {
let theme = theme(cx);
h_stack().bg(theme.toolbar).p_2().child(

View File

@ -1,17 +1,15 @@
use std::marker::PhantomData;
use chrono::NaiveDateTime;
use crate::prelude::*;
use crate::{Icon, IconButton, Input, Label, LabelColor};
#[derive(Element)]
pub struct ChatPanel<S: 'static + Send + Sync> {
#[derive(Component)]
pub struct ChatPanel {
element_id: ElementId,
messages: Vec<ChatMessage<S>>,
messages: Vec<ChatMessage>,
}
impl<S: 'static + Send + Sync> ChatPanel<S> {
impl ChatPanel {
pub fn new(element_id: impl Into<ElementId>) -> Self {
Self {
element_id: element_id.into(),
@ -19,12 +17,12 @@ impl<S: 'static + Send + Sync> ChatPanel<S> {
}
}
pub fn messages(mut self, messages: Vec<ChatMessage<S>>) -> Self {
pub fn messages(mut self, messages: Vec<ChatMessage>) -> Self {
self.messages = messages;
self
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render<S: 'static>(mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
div()
.id(self.element_id.clone())
.flex()
@ -70,25 +68,23 @@ impl<S: 'static + Send + Sync> ChatPanel<S> {
}
}
#[derive(Element)]
pub struct ChatMessage<S: 'static + Send + Sync> {
state_type: PhantomData<S>,
#[derive(Component)]
pub struct ChatMessage {
author: String,
text: String,
sent_at: NaiveDateTime,
}
impl<S: 'static + Send + Sync> ChatMessage<S> {
impl ChatMessage {
pub fn new(author: String, text: String, sent_at: NaiveDateTime) -> Self {
Self {
state_type: PhantomData,
author,
text,
sent_at,
}
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render<S: 'static>(self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
div()
.flex()
.flex_col()
@ -117,25 +113,17 @@ mod stories {
use super::*;
#[derive(Element)]
pub struct ChatPanelStory<S: 'static + Send + Sync> {
state_type: PhantomData<S>,
}
#[derive(Component)]
pub struct ChatPanelStory;
impl<S: 'static + Send + Sync> ChatPanelStory<S> {
impl ChatPanelStory {
pub fn new() -> Self {
Self {
state_type: PhantomData,
}
Self
}
fn render(
&mut self,
_view: &mut S,
cx: &mut ViewContext<S>,
) -> impl Element<ViewState = S> {
fn render<S: 'static>(self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
Story::container(cx)
.child(Story::title_for::<_, ChatPanel<S>>(cx))
.child(Story::title_for::<_, ChatPanel>(cx))
.child(Story::label(cx, "Default"))
.child(
Panel::new("chat-panel-1-outer", cx)

View File

@ -3,23 +3,18 @@ use crate::{
static_collab_panel_channels, static_collab_panel_current_call, v_stack, Icon, List,
ListHeader, ToggleState,
};
use std::marker::PhantomData;
#[derive(Element)]
pub struct CollabPanel<S: 'static + Send + Sync> {
#[derive(Component)]
pub struct CollabPanel {
id: ElementId,
state_type: PhantomData<S>,
}
impl<S: 'static + Send + Sync> CollabPanel<S> {
impl CollabPanel {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
state_type: PhantomData,
}
Self { id: id.into() }
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render<S: 'static>(self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
let theme = theme(cx);
v_stack()
@ -98,25 +93,17 @@ mod stories {
use super::*;
#[derive(Element)]
pub struct CollabPanelStory<S: 'static + Send + Sync> {
state_type: PhantomData<S>,
}
#[derive(Component)]
pub struct CollabPanelStory;
impl<S: 'static + Send + Sync> CollabPanelStory<S> {
impl CollabPanelStory {
pub fn new() -> Self {
Self {
state_type: PhantomData,
}
Self
}
fn render(
&mut self,
_view: &mut S,
cx: &mut ViewContext<S>,
) -> impl Element<ViewState = S> {
fn render<S: 'static>(self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
Story::container(cx)
.child(Story::title_for::<_, CollabPanel<S>>(cx))
.child(Story::title_for::<_, CollabPanel>(cx))
.child(Story::label(cx, "Default"))
.child(CollabPanel::new("collab-panel"))
}

View File

@ -1,23 +1,17 @@
use std::marker::PhantomData;
use crate::prelude::*;
use crate::{example_editor_actions, OrderMethod, Palette};
#[derive(Element)]
pub struct CommandPalette<S: 'static + Send + Sync> {
#[derive(Component)]
pub struct CommandPalette {
id: ElementId,
state_type: PhantomData<S>,
}
impl<S: 'static + Send + Sync> CommandPalette<S> {
impl CommandPalette {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
state_type: PhantomData,
}
Self { id: id.into() }
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render<S: 'static>(self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
div().id(self.id.clone()).child(
Palette::new("palette")
.items(example_editor_actions())
@ -37,25 +31,17 @@ mod stories {
use super::*;
#[derive(Element)]
pub struct CommandPaletteStory<S: 'static + Send + Sync> {
state_type: PhantomData<S>,
}
#[derive(Component)]
pub struct CommandPaletteStory;
impl<S: 'static + Send + Sync> CommandPaletteStory<S> {
impl CommandPaletteStory {
pub fn new() -> Self {
Self {
state_type: PhantomData,
}
Self
}
fn render(
&mut self,
_view: &mut S,
cx: &mut ViewContext<S>,
) -> impl Element<ViewState = S> {
fn render<S: 'static>(self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
Story::container(cx)
.child(Story::title_for::<_, CommandPalette<S>>(cx))
.child(Story::title_for::<_, CommandPalette>(cx))
.child(Story::label(cx, "Default"))
.child(CommandPalette::new("command-palette"))
}

View File

@ -1,14 +1,14 @@
use crate::{prelude::*, ListItemVariant};
use crate::{v_stack, Label, List, ListEntry, ListItem, ListSeparator, ListSubHeader};
pub enum ContextMenuItem<S: 'static + Send + Sync> {
pub enum ContextMenuItem {
Header(SharedString),
Entry(Label<S>),
Entry(Label),
Separator,
}
impl<S: 'static + Send + Sync> ContextMenuItem<S> {
fn to_list_item(self) -> ListItem<S> {
impl ContextMenuItem {
fn to_list_item<V: 'static>(self) -> ListItem<V> {
match self {
ContextMenuItem::Header(label) => ListSubHeader::new(label).into(),
ContextMenuItem::Entry(label) => {
@ -26,23 +26,23 @@ impl<S: 'static + Send + Sync> ContextMenuItem<S> {
Self::Separator
}
pub fn entry(label: Label<S>) -> Self {
pub fn entry(label: Label) -> Self {
Self::Entry(label)
}
}
#[derive(Element)]
pub struct ContextMenu<S: 'static + Send + Sync> {
items: Vec<ContextMenuItem<S>>,
#[derive(Component)]
pub struct ContextMenu {
items: Vec<ContextMenuItem>,
}
impl<S: 'static + Send + Sync> ContextMenu<S> {
pub fn new(items: impl IntoIterator<Item = ContextMenuItem<S>>) -> Self {
impl ContextMenu {
pub fn new(items: impl IntoIterator<Item = ContextMenuItem>) -> Self {
Self {
items: items.into_iter().collect(),
}
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render<S: 'static>(mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
let theme = theme(cx);
v_stack()
@ -67,31 +67,21 @@ pub use stories::*;
#[cfg(feature = "stories")]
mod stories {
use std::marker::PhantomData;
use crate::story::Story;
use super::*;
#[derive(Element)]
pub struct ContextMenuStory<S: 'static + Send + Sync> {
state_type: PhantomData<S>,
}
#[derive(Component)]
pub struct ContextMenuStory;
impl<S: 'static + Send + Sync> ContextMenuStory<S> {
impl ContextMenuStory {
pub fn new() -> Self {
Self {
state_type: PhantomData,
}
Self
}
fn render(
&mut self,
_view: &mut S,
cx: &mut ViewContext<S>,
) -> impl Element<ViewState = S> {
fn render<S: 'static>(self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
Story::container(cx)
.child(Story::title_for::<_, ContextMenu<S>>(cx))
.child(Story::title_for::<_, ContextMenu>(cx))
.child(Story::label(cx, "Default"))
.child(ContextMenu::new([
ContextMenuItem::header("Section header"),

View File

@ -1,22 +1,16 @@
use std::marker::PhantomData;
use crate::{prelude::*, Button, Label, LabelColor, Modal};
#[derive(Element)]
pub struct CopilotModal<S: 'static + Send + Sync + Clone> {
#[derive(Component)]
pub struct CopilotModal {
id: ElementId,
state_type: PhantomData<S>,
}
impl<S: 'static + Send + Sync + Clone> CopilotModal<S> {
impl CopilotModal {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
state_type: PhantomData,
}
Self { id: id.into() }
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render<S: 'static>(self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
div().id(self.id.clone()).child(
Modal::new("some-id")
.title("Connect Copilot to Zed")
@ -35,25 +29,17 @@ mod stories {
use super::*;
#[derive(Element)]
pub struct CopilotModalStory<S: 'static + Send + Sync + Clone> {
state_type: PhantomData<S>,
}
#[derive(Component)]
pub struct CopilotModalStory;
impl<S: 'static + Send + Sync + Clone> CopilotModalStory<S> {
impl CopilotModalStory {
pub fn new() -> Self {
Self {
state_type: PhantomData,
}
Self
}
fn render(
&mut self,
_view: &mut S,
cx: &mut ViewContext<S>,
) -> impl Element<ViewState = S> {
fn render<S: 'static>(self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
Story::container(cx)
.child(Story::title_for::<_, CopilotModal<S>>(cx))
.child(Story::title_for::<_, CopilotModal>(cx))
.child(Story::label(cx, "Default"))
.child(CopilotModal::new("copilot-modal"))
}

View File

@ -10,10 +10,10 @@ use crate::{
#[derive(Clone)]
pub struct EditorPane {
tabs: Vec<Tab<Self>>,
tabs: Vec<Tab>,
path: PathBuf,
symbols: Vec<Symbol>,
buffer: Buffer<Self>,
buffer: Buffer,
buffer_search: View<BufferSearch>,
is_buffer_search_open: bool,
}
@ -21,10 +21,10 @@ pub struct EditorPane {
impl EditorPane {
pub fn new(
cx: &mut WindowContext,
tabs: Vec<Tab<Self>>,
tabs: Vec<Tab>,
path: PathBuf,
symbols: Vec<Symbol>,
buffer: Buffer<Self>,
buffer: Buffer,
) -> Self {
Self {
tabs,
@ -49,7 +49,7 @@ impl EditorPane {
)
}
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Element<ViewState = Self> {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Component<Self> {
v_stack()
.w_full()
.h_full()

View File

@ -1,23 +1,19 @@
use std::marker::PhantomData;
use crate::prelude::*;
use crate::{Avatar, Player};
#[derive(Element)]
pub struct Facepile<S: 'static + Send + Sync> {
state_type: PhantomData<S>,
#[derive(Component)]
pub struct Facepile {
players: Vec<Player>,
}
impl<S: 'static + Send + Sync> Facepile<S> {
impl Facepile {
pub fn new<P: Iterator<Item = Player>>(players: P) -> Self {
Self {
state_type: PhantomData,
players: players.collect(),
}
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render<S: 'static>(self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
let player_count = self.players.len();
let player_list = self.players.iter().enumerate().map(|(ix, player)| {
let isnt_last = ix < player_count - 1;
@ -39,27 +35,19 @@ mod stories {
use super::*;
#[derive(Element)]
pub struct FacepileStory<S: 'static + Send + Sync> {
state_type: PhantomData<S>,
}
#[derive(Component)]
pub struct FacepileStory;
impl<S: 'static + Send + Sync> FacepileStory<S> {
impl FacepileStory {
pub fn new() -> Self {
Self {
state_type: PhantomData,
}
Self
}
fn render(
&mut self,
_view: &mut S,
cx: &mut ViewContext<S>,
) -> impl Element<ViewState = S> {
fn render<S: 'static>(self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
let players = static_players();
Story::container(cx)
.child(Story::title_for::<_, Facepile<S>>(cx))
.child(Story::title_for::<_, Facepile>(cx))
.child(Story::label(cx, "Default"))
.child(
div()

View File

@ -1,4 +1,3 @@
use std::marker::PhantomData;
use std::sync::Arc;
use gpui2::MouseButton;
@ -6,19 +5,18 @@ use gpui2::MouseButton;
use crate::{h_stack, prelude::*};
use crate::{ClickHandler, Icon, IconColor, IconElement};
struct IconButtonHandlers<S: 'static + Send + Sync> {
struct IconButtonHandlers<S: 'static> {
click: Option<ClickHandler<S>>,
}
impl<S: 'static + Send + Sync> Default for IconButtonHandlers<S> {
impl<S: 'static> Default for IconButtonHandlers<S> {
fn default() -> Self {
Self { click: None }
}
}
#[derive(Element)]
pub struct IconButton<S: 'static + Send + Sync> {
state_type: PhantomData<S>,
#[derive(Component)]
pub struct IconButton<S: 'static> {
id: ElementId,
icon: Icon,
color: IconColor,
@ -27,10 +25,9 @@ pub struct IconButton<S: 'static + Send + Sync> {
handlers: IconButtonHandlers<S>,
}
impl<S: 'static + Send + Sync> IconButton<S> {
impl<S: 'static> IconButton<S> {
pub fn new(id: impl Into<ElementId>, icon: Icon) -> Self {
Self {
state_type: PhantomData,
id: id.into(),
icon,
color: IconColor::default(),
@ -60,15 +57,12 @@ impl<S: 'static + Send + Sync> IconButton<S> {
self
}
pub fn on_click(
mut self,
handler: impl Fn(&mut S, &mut ViewContext<S>) + 'static + Send + Sync,
) -> Self {
pub fn on_click(mut self, handler: impl 'static + Fn(&mut S, &mut ViewContext<S>) + Send + Sync) -> Self {
self.handlers.click = Some(Arc::new(handler));
self
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render(self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
let theme = theme(cx);
let icon_color = match (self.state, self.color) {

View File

@ -1,14 +1,11 @@
use std::collections::HashSet;
use std::marker::PhantomData;
use strum::{EnumIter, IntoEnumIterator};
use crate::prelude::*;
#[derive(Element)]
pub struct Keybinding<S: 'static + Send + Sync> {
state_type: PhantomData<S>,
#[derive(Component)]
pub struct Keybinding {
/// A keybinding consists of a key and a set of modifier keys.
/// More then one keybinding produces a chord.
///
@ -16,10 +13,9 @@ pub struct Keybinding<S: 'static + Send + Sync> {
keybinding: Vec<(String, ModifierKeys)>,
}
impl<S: 'static + Send + Sync> Keybinding<S> {
impl Keybinding {
pub fn new(key: String, modifiers: ModifierKeys) -> Self {
Self {
state_type: PhantomData,
keybinding: vec![(key, modifiers)],
}
}
@ -29,12 +25,11 @@ impl<S: 'static + Send + Sync> Keybinding<S> {
second_note: (String, ModifierKeys),
) -> Self {
Self {
state_type: PhantomData,
keybinding: vec![first_note, second_note],
}
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render<S: 'static>(self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
div()
.flex()
.gap_2()
@ -54,21 +49,17 @@ impl<S: 'static + Send + Sync> Keybinding<S> {
}
}
#[derive(Element)]
pub struct Key<S: 'static + Send + Sync> {
state_type: PhantomData<S>,
#[derive(Component)]
pub struct Key {
key: SharedString,
}
impl<S: 'static + Send + Sync> Key<S> {
impl Key {
pub fn new(key: impl Into<SharedString>) -> Self {
Self {
state_type: PhantomData,
key: key.into(),
}
Self { key: key.into() }
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render<S: 'static>(self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
let theme = theme(cx);
div()
@ -173,27 +164,19 @@ mod stories {
use super::*;
#[derive(Element)]
pub struct KeybindingStory<S: 'static + Send + Sync + Clone> {
state_type: PhantomData<S>,
}
#[derive(Component)]
pub struct KeybindingStory;
impl<S: 'static + Send + Sync + Clone> KeybindingStory<S> {
impl KeybindingStory {
pub fn new() -> Self {
Self {
state_type: PhantomData,
}
Self
}
fn render(
&mut self,
_view: &mut S,
cx: &mut ViewContext<S>,
) -> impl Element<ViewState = S> {
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
let all_modifier_permutations = ModifierKey::iter().permutations(2);
Story::container(cx)
.child(Story::title_for::<_, Keybinding<S>>(cx))
.child(Story::title_for::<_, Keybinding>(cx))
.child(Story::label(cx, "Single Key"))
.child(Keybinding::new("Z".to_string(), ModifierKeys::new()))
.child(Story::label(cx, "Single Key with Modifier"))

View File

@ -1,23 +1,17 @@
use std::marker::PhantomData;
use crate::prelude::*;
use crate::{OrderMethod, Palette, PaletteItem};
#[derive(Element)]
pub struct LanguageSelector<S: 'static + Send + Sync + Clone> {
#[derive(Component)]
pub struct LanguageSelector {
id: ElementId,
state_type: PhantomData<S>,
}
impl<S: 'static + Send + Sync + Clone> LanguageSelector<S> {
impl LanguageSelector {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
state_type: PhantomData,
}
Self { id: id.into() }
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render<S: 'static>(self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
div().id(self.id.clone()).child(
Palette::new("palette")
.items(vec![
@ -48,25 +42,17 @@ mod stories {
use super::*;
#[derive(Element)]
pub struct LanguageSelectorStory<S: 'static + Send + Sync + Clone> {
state_type: PhantomData<S>,
}
#[derive(Component)]
pub struct LanguageSelectorStory;
impl<S: 'static + Send + Sync + Clone> LanguageSelectorStory<S> {
impl LanguageSelectorStory {
pub fn new() -> Self {
Self {
state_type: PhantomData,
}
Self
}
fn render(
&mut self,
_view: &mut S,
cx: &mut ViewContext<S>,
) -> impl Element<ViewState = S> {
fn render<S: 'static>(self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
Story::container(cx)
.child(Story::title_for::<_, LanguageSelector<S>>(cx))
.child(Story::title_for::<_, LanguageSelector>(cx))
.child(Story::label(cx, "Default"))
.child(LanguageSelector::new("language-selector"))
}

View File

@ -1,5 +1,3 @@
use std::marker::PhantomData;
use gpui2::{div, relative, Div};
use crate::settings::user_settings;
@ -17,9 +15,8 @@ pub enum ListItemVariant {
Inset,
}
#[derive(Element)]
pub struct ListHeader<S: 'static + Send + Sync> {
state_type: PhantomData<S>,
#[derive(Component)]
pub struct ListHeader {
label: SharedString,
left_icon: Option<Icon>,
variant: ListItemVariant,
@ -27,10 +24,9 @@ pub struct ListHeader<S: 'static + Send + Sync> {
toggleable: Toggleable,
}
impl<S: 'static + Send + Sync> ListHeader<S> {
impl ListHeader {
pub fn new(label: impl Into<SharedString>) -> Self {
Self {
state_type: PhantomData,
label: label.into(),
left_icon: None,
variant: ListItemVariant::default(),
@ -59,7 +55,7 @@ impl<S: 'static + Send + Sync> ListHeader<S> {
self
}
fn disclosure_control(&self) -> Div<S> {
fn disclosure_control<S: 'static>(&self) -> Div<S> {
let is_toggleable = self.toggleable != Toggleable::NotToggleable;
let is_toggled = Toggleable::is_toggled(&self.toggleable);
@ -92,7 +88,7 @@ impl<S: 'static + Send + Sync> ListHeader<S> {
}
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render<S: 'static>(self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
let theme = theme(cx);
let is_toggleable = self.toggleable != Toggleable::NotToggleable;
@ -134,18 +130,16 @@ impl<S: 'static + Send + Sync> ListHeader<S> {
}
}
#[derive(Element)]
pub struct ListSubHeader<S: 'static + Send + Sync> {
state_type: PhantomData<S>,
#[derive(Component)]
pub struct ListSubHeader {
label: SharedString,
left_icon: Option<Icon>,
variant: ListItemVariant,
}
impl<S: 'static + Send + Sync> ListSubHeader<S> {
impl ListSubHeader {
pub fn new(label: impl Into<SharedString>) -> Self {
Self {
state_type: PhantomData,
label: label.into(),
left_icon: None,
variant: ListItemVariant::default(),
@ -157,7 +151,7 @@ impl<S: 'static + Send + Sync> ListSubHeader<S> {
self
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render<S: 'static>(self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
h_stack().flex_1().w_full().relative().py_1().child(
div()
.h_6()
@ -197,40 +191,40 @@ pub enum ListEntrySize {
Medium,
}
#[derive(Element)]
pub enum ListItem<S: 'static + Send + Sync> {
Entry(ListEntry<S>),
#[derive(Component)]
pub enum ListItem<S: 'static> {
Entry(ListEntry),
Details(ListDetailsEntry<S>),
Separator(ListSeparator<S>),
Header(ListSubHeader<S>),
Separator(ListSeparator),
Header(ListSubHeader),
}
impl<S: 'static + Send + Sync> From<ListEntry<S>> for ListItem<S> {
fn from(entry: ListEntry<S>) -> Self {
impl<S: 'static> From<ListEntry> for ListItem<S> {
fn from(entry: ListEntry) -> Self {
Self::Entry(entry)
}
}
impl<S: 'static + Send + Sync> From<ListDetailsEntry<S>> for ListItem<S> {
impl<S: 'static> From<ListDetailsEntry<S>> for ListItem<S> {
fn from(entry: ListDetailsEntry<S>) -> Self {
Self::Details(entry)
}
}
impl<S: 'static + Send + Sync> From<ListSeparator<S>> for ListItem<S> {
fn from(entry: ListSeparator<S>) -> Self {
impl<S: 'static> From<ListSeparator> for ListItem<S> {
fn from(entry: ListSeparator) -> Self {
Self::Separator(entry)
}
}
impl<S: 'static + Send + Sync> From<ListSubHeader<S>> for ListItem<S> {
fn from(entry: ListSubHeader<S>) -> Self {
impl<S: 'static> From<ListSubHeader> for ListItem<S> {
fn from(entry: ListSubHeader) -> Self {
Self::Header(entry)
}
}
impl<S: 'static + Send + Sync> ListItem<S> {
fn render(&mut self, view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
impl<S: 'static> ListItem<S> {
fn render(self, view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
match self {
ListItem::Entry(entry) => div().child(entry.render(view, cx)),
ListItem::Separator(separator) => div().child(separator.render(view, cx)),
@ -239,11 +233,11 @@ impl<S: 'static + Send + Sync> ListItem<S> {
}
}
pub fn new(label: Label<S>) -> Self {
pub fn new(label: Label) -> Self {
Self::Entry(ListEntry::new(label))
}
pub fn as_entry(&mut self) -> Option<&mut ListEntry<S>> {
pub fn as_entry(&mut self) -> Option<&mut ListEntry> {
if let Self::Entry(entry) = self {
Some(entry)
} else {
@ -252,11 +246,11 @@ impl<S: 'static + Send + Sync> ListItem<S> {
}
}
#[derive(Element)]
pub struct ListEntry<S: 'static + Send + Sync> {
#[derive(Component)]
pub struct ListEntry {
disclosure_control_style: DisclosureControlVisibility,
indent_level: u32,
label: Option<Label<S>>,
label: Option<Label>,
left_content: Option<LeftContent>,
variant: ListItemVariant,
size: ListEntrySize,
@ -265,8 +259,8 @@ pub struct ListEntry<S: 'static + Send + Sync> {
overflow: OverflowStyle,
}
impl<S: 'static + Send + Sync> ListEntry<S> {
pub fn new(label: Label<S>) -> Self {
impl ListEntry {
pub fn new(label: Label) -> Self {
Self {
disclosure_control_style: DisclosureControlVisibility::default(),
indent_level: 0,
@ -344,10 +338,10 @@ impl<S: 'static + Send + Sync> ListEntry<S> {
}
}
fn disclosure_control(
fn disclosure_control<V: 'static>(
&mut self,
cx: &mut ViewContext<S>,
) -> Option<impl Element<ViewState = S>> {
cx: &mut ViewContext<V>,
) -> Option<impl Component<V>> {
let disclosure_control_icon = if let Some(ToggleState::Toggled) = self.toggle {
IconElement::new(Icon::ChevronDown)
} else {
@ -367,7 +361,7 @@ impl<S: 'static + Send + Sync> ListEntry<S> {
}
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render<S: 'static>(mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
let settings = user_settings(cx);
let theme = theme(cx);
@ -423,18 +417,18 @@ impl<S: 'static + Send + Sync> ListEntry<S> {
}
}
struct ListDetailsEntryHandlers<S: 'static + Send + Sync> {
struct ListDetailsEntryHandlers<S: 'static> {
click: Option<ClickHandler<S>>,
}
impl<S: 'static + Send + Sync> Default for ListDetailsEntryHandlers<S> {
impl<S: 'static> Default for ListDetailsEntryHandlers<S> {
fn default() -> Self {
Self { click: None }
}
}
#[derive(Element)]
pub struct ListDetailsEntry<S: 'static + Send + Sync> {
#[derive(Component)]
pub struct ListDetailsEntry<S: 'static> {
label: SharedString,
meta: Option<SharedString>,
left_content: Option<LeftContent>,
@ -445,7 +439,7 @@ pub struct ListDetailsEntry<S: 'static + Send + Sync> {
seen: bool,
}
impl<S: 'static + Send + Sync> ListDetailsEntry<S> {
impl<S: 'static> ListDetailsEntry<S> {
pub fn new(label: impl Into<SharedString>) -> Self {
Self {
label: label.into(),
@ -477,7 +471,7 @@ impl<S: 'static + Send + Sync> ListDetailsEntry<S> {
self
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render(mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
let theme = theme(cx);
let settings = user_settings(cx);
@ -522,34 +516,30 @@ impl<S: 'static + Send + Sync> ListDetailsEntry<S> {
}
}
#[derive(Clone, Element)]
pub struct ListSeparator<S: 'static + Send + Sync> {
state_type: PhantomData<S>,
}
#[derive(Clone, Component)]
pub struct ListSeparator;
impl<S: 'static + Send + Sync> ListSeparator<S> {
impl ListSeparator {
pub fn new() -> Self {
Self {
state_type: PhantomData,
}
Self
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
let theme = theme(cx);
div().h_px().w_full().bg(theme.border)
}
}
#[derive(Element)]
pub struct List<S: 'static + Send + Sync> {
#[derive(Component)]
pub struct List<S: 'static> {
items: Vec<ListItem<S>>,
empty_message: SharedString,
header: Option<ListHeader<S>>,
header: Option<ListHeader>,
toggleable: Toggleable,
}
impl<S: 'static + Send + Sync> List<S> {
impl<S: 'static> List<S> {
pub fn new(items: Vec<ListItem<S>>) -> Self {
Self {
items,
@ -564,7 +554,7 @@ impl<S: 'static + Send + Sync> List<S> {
self
}
pub fn header(mut self, header: ListHeader<S>) -> Self {
pub fn header(mut self, header: ListHeader) -> Self {
self.header = Some(header);
self
}
@ -574,7 +564,7 @@ impl<S: 'static + Send + Sync> List<S> {
self
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render(mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
let is_toggleable = self.toggleable != Toggleable::NotToggleable;
let is_toggled = Toggleable::is_toggled(&self.toggleable);

View File

@ -1,25 +1,21 @@
use std::marker::PhantomData;
use gpui2::AnyElement;
use smallvec::SmallVec;
use crate::{h_stack, prelude::*, v_stack, Button, Icon, IconButton, Label};
#[derive(Element)]
pub struct Modal<S: 'static + Send + Sync> {
#[derive(Component)]
pub struct Modal<S: 'static> {
id: ElementId,
state_type: PhantomData<S>,
title: Option<SharedString>,
primary_action: Option<Button<S>>,
secondary_action: Option<Button<S>>,
children: SmallVec<[AnyElement<S>; 2]>,
}
impl<S: 'static + Send + Sync> Modal<S> {
impl<S: 'static> Modal<S> {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
state_type: PhantomData,
title: None,
primary_action: None,
secondary_action: None,
@ -42,7 +38,7 @@ impl<S: 'static + Send + Sync> Modal<S> {
self
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render(mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
let theme = theme(cx);
v_stack()
@ -80,8 +76,8 @@ impl<S: 'static + Send + Sync> Modal<S> {
}
}
impl<S: 'static + Send + Sync> ParentElement for Modal<S> {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::ViewState>; 2]> {
impl<S: 'static> ParentElement<S> for Modal<S> {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<S>; 2]> {
&mut self.children
}
}

View File

@ -1,23 +1,17 @@
use std::marker::PhantomData;
use crate::prelude::*;
use crate::{v_stack, Buffer, Icon, IconButton, Label};
#[derive(Element)]
pub struct MultiBuffer<S: 'static + Send + Sync + Clone> {
state_type: PhantomData<S>,
buffers: Vec<Buffer<S>>,
#[derive(Component)]
pub struct MultiBuffer {
buffers: Vec<Buffer>,
}
impl<S: 'static + Send + Sync + Clone> MultiBuffer<S> {
pub fn new(buffers: Vec<Buffer<S>>) -> Self {
Self {
state_type: PhantomData,
buffers,
}
impl MultiBuffer {
pub fn new(buffers: Vec<Buffer>) -> Self {
Self { buffers }
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
let theme = theme(cx);
v_stack()
@ -50,27 +44,19 @@ mod stories {
use super::*;
#[derive(Element)]
pub struct MultiBufferStory<S: 'static + Send + Sync + Clone> {
state_type: PhantomData<S>,
}
#[derive(Component)]
pub struct MultiBufferStory;
impl<S: 'static + Send + Sync + Clone> MultiBufferStory<S> {
impl MultiBufferStory {
pub fn new() -> Self {
Self {
state_type: PhantomData,
}
Self
}
fn render(
&mut self,
_view: &mut S,
cx: &mut ViewContext<S>,
) -> impl Element<ViewState = S> {
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
let theme = theme(cx);
Story::container(cx)
.child(Story::title_for::<_, MultiBuffer<S>>(cx))
.child(Story::title_for::<_, MultiBuffer>(cx))
.child(Story::label(cx, "Default"))
.child(MultiBuffer::new(vec![
hello_world_rust_buffer_example(&theme),

View File

@ -1,20 +1,16 @@
use std::marker::PhantomData;
use gpui2::rems;
use crate::{h_stack, prelude::*, Icon};
#[derive(Element)]
pub struct NotificationToast<S: 'static + Send + Sync + Clone> {
state_type: PhantomData<S>,
#[derive(Component)]
pub struct NotificationToast {
label: SharedString,
icon: Option<Icon>,
}
impl<S: 'static + Send + Sync + Clone> NotificationToast<S> {
impl NotificationToast {
pub fn new(label: SharedString) -> Self {
Self {
state_type: PhantomData,
label,
icon: None,
}
@ -28,7 +24,7 @@ impl<S: 'static + Send + Sync + Clone> NotificationToast<S> {
self
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render<S: 'static>(self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
let theme = theme(cx);
h_stack()

View File

@ -1,23 +1,20 @@
use std::marker::PhantomData;
use crate::{prelude::*, static_new_notification_items, static_read_notification_items};
use crate::{List, ListHeader};
#[derive(Element)]
pub struct NotificationsPanel<S: 'static + Send + Sync> {
#[derive(Component)]
pub struct NotificationsPanel {
id: ElementId,
state_type: PhantomData<S>,
}
impl<S: 'static + Send + Sync> NotificationsPanel<S> {
impl NotificationsPanel {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
state_type: PhantomData,
}
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render<S: 'static>(self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
let theme = theme(cx);
div()
@ -58,25 +55,17 @@ mod stories {
use super::*;
#[derive(Element)]
pub struct NotificationsPanelStory<S: 'static + Send + Sync + Clone> {
state_type: PhantomData<S>,
}
#[derive(Component)]
pub struct NotificationsPanelStory;
impl<S: 'static + Send + Sync + Clone> NotificationsPanelStory<S> {
impl NotificationsPanelStory {
pub fn new() -> Self {
Self {
state_type: PhantomData,
}
Self
}
fn render(
&mut self,
_view: &mut S,
cx: &mut ViewContext<S>,
) -> impl Element<ViewState = S> {
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
Story::container(cx)
.child(Story::title_for::<_, NotificationsPanel<S>>(cx))
.child(Story::title_for::<_, NotificationsPanel>(cx))
.child(Story::label(cx, "Default"))
.child(
Panel::new("panel", cx).child(NotificationsPanel::new("notifications_panel")),

View File

@ -1,23 +1,19 @@
use std::marker::PhantomData;
use crate::prelude::*;
use crate::{h_stack, v_stack, Keybinding, Label, LabelColor};
#[derive(Element)]
pub struct Palette<S: 'static + Send + Sync> {
#[derive(Component)]
pub struct Palette {
id: ElementId,
state_type: PhantomData<S>,
input_placeholder: SharedString,
empty_string: SharedString,
items: Vec<PaletteItem<S>>,
items: Vec<PaletteItem>,
default_order: OrderMethod,
}
impl<S: 'static + Send + Sync> Palette<S> {
impl Palette {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
state_type: PhantomData,
input_placeholder: "Find something...".into(),
empty_string: "No items found.".into(),
items: vec![],
@ -25,7 +21,7 @@ impl<S: 'static + Send + Sync> Palette<S> {
}
}
pub fn items(mut self, items: Vec<PaletteItem<S>>) -> Self {
pub fn items(mut self, items: Vec<PaletteItem>) -> Self {
self.items = items;
self
}
@ -46,7 +42,7 @@ impl<S: 'static + Send + Sync> Palette<S> {
self
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render<S: 'static>(mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
let theme = theme(cx);
v_stack()
@ -101,14 +97,14 @@ impl<S: 'static + Send + Sync> Palette<S> {
}
}
#[derive(Element)]
pub struct PaletteItem<S: 'static + Send + Sync> {
#[derive(Component)]
pub struct PaletteItem {
pub label: SharedString,
pub sublabel: Option<SharedString>,
pub keybinding: Option<Keybinding<S>>,
pub keybinding: Option<Keybinding>,
}
impl<S: 'static + Send + Sync> PaletteItem<S> {
impl PaletteItem {
pub fn new(label: impl Into<SharedString>) -> Self {
Self {
label: label.into(),
@ -129,13 +125,13 @@ impl<S: 'static + Send + Sync> PaletteItem<S> {
pub fn keybinding<K>(mut self, keybinding: K) -> Self
where
K: Into<Option<Keybinding<S>>>,
K: Into<Option<Keybinding>>,
{
self.keybinding = keybinding.into();
self
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render<S: 'static>(mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
div()
.flex()
.flex_row()
@ -160,25 +156,17 @@ mod stories {
use super::*;
#[derive(Element)]
pub struct PaletteStory<S: 'static + Send + Sync + Clone> {
state_type: PhantomData<S>,
}
#[derive(Component)]
pub struct PaletteStory;
impl<S: 'static + Send + Sync + Clone> PaletteStory<S> {
impl PaletteStory {
pub fn new() -> Self {
Self {
state_type: PhantomData,
}
Self
}
fn render(
&mut self,
_view: &mut S,
cx: &mut ViewContext<S>,
) -> impl Element<ViewState = S> {
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
Story::container(cx)
.child(Story::title_for::<_, Palette<S>>(cx))
.child(Story::title_for::<_, Palette>(cx))
.child(Story::label(cx, "Default"))
.child(Palette::new("palette-1"))
.child(Story::label(cx, "With Items"))

View File

@ -1,5 +1,3 @@
use std::marker::PhantomData;
use gpui2::{AbsoluteLength, AnyElement};
use smallvec::SmallVec;
@ -40,10 +38,9 @@ pub enum PanelSide {
use std::collections::HashSet;
#[derive(Element)]
pub struct Panel<S: 'static + Send + Sync> {
#[derive(Component)]
pub struct Panel<S: 'static> {
id: ElementId,
state_type: PhantomData<S>,
current_side: PanelSide,
/// Defaults to PanelAllowedSides::LeftAndRight
allowed_sides: PanelAllowedSides,
@ -52,13 +49,12 @@ pub struct Panel<S: 'static + Send + Sync> {
children: SmallVec<[AnyElement<S>; 2]>,
}
impl<S: 'static + Send + Sync> Panel<S> {
impl<S: 'static> Panel<S> {
pub fn new(id: impl Into<ElementId>, cx: &mut WindowContext) -> Self {
let settings = user_settings(cx);
Self {
id: id.into(),
state_type: PhantomData,
current_side: PanelSide::default(),
allowed_sides: PanelAllowedSides::default(),
initial_width: *settings.default_panel_size,
@ -96,7 +92,7 @@ impl<S: 'static + Send + Sync> Panel<S> {
self
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render(mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
let theme = theme(cx);
let current_size = self.width.unwrap_or(self.initial_width);
@ -121,8 +117,8 @@ impl<S: 'static + Send + Sync> Panel<S> {
}
}
impl<S: 'static + Send + Sync> ParentElement for Panel<S> {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::ViewState>; 2]> {
impl<S: 'static> ParentElement<S> for Panel<S> {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<S>; 2]> {
&mut self.children
}
}
@ -136,23 +132,15 @@ mod stories {
use super::*;
#[derive(Element)]
pub struct PanelStory<S: 'static + Send + Sync + Clone> {
state_type: PhantomData<S>,
}
#[derive(Component)]
pub struct PanelStory;
impl<S: 'static + Send + Sync + Clone> PanelStory<S> {
impl PanelStory {
pub fn new() -> Self {
Self {
state_type: PhantomData,
}
Self
}
fn render(
&mut self,
_view: &mut S,
cx: &mut ViewContext<S>,
) -> impl Element<ViewState = S> {
fn render<S: 'static>(self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
Story::container(cx)
.child(Story::title_for::<_, Panel<S>>(cx))
.child(Story::label(cx, "Default"))

View File

@ -1,5 +1,3 @@
use std::marker::PhantomData;
use gpui2::{hsla, red, AnyElement, ElementId, ExternalPaths, Hsla, Length, Size};
use smallvec::SmallVec;
@ -12,25 +10,29 @@ pub enum SplitDirection {
Vertical,
}
#[derive(Element)]
pub struct Pane<S: 'static + Send + Sync> {
#[derive(Component)]
pub struct Pane<V: 'static> {
id: ElementId,
state_type: PhantomData<S>,
size: Size<Length>,
fill: Hsla,
children: SmallVec<[AnyElement<S>; 2]>,
children: SmallVec<[AnyElement<V>; 2]>,
}
impl<S: 'static + Send + Sync> Pane<S> {
// impl<V: 'static> IntoAnyElement<V> for Pane<V> {
// fn into_any(self) -> AnyElement<V> {
// (move |view_state: &mut V, cx: &mut ViewContext<'_, '_, V>| self.render(view_state, cx))
// .into_any()
// }
// }
impl<V: 'static> Pane<V> {
pub fn new(id: impl Into<ElementId>, size: Size<Length>) -> Self {
// Fill is only here for debugging purposes, remove before release
Self {
id: id.into(),
state_type: PhantomData,
size,
fill: hsla(0.3, 0.3, 0.3, 1.),
// fill: system_color.transparent,
children: SmallVec::new(),
}
}
@ -40,7 +42,7 @@ impl<S: 'static + Send + Sync> Pane<S> {
self
}
fn render(&mut self, view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
div()
.id(self.id.clone())
.flex()
@ -49,14 +51,8 @@ impl<S: 'static + Send + Sync> Pane<S> {
.w(self.size.width)
.h(self.size.height)
.relative()
.child(div().z_index(0).size_full().children(self.children))
.child(
div()
.z_index(0)
.size_full()
.children(self.children.drain(..)),
)
.child(
// TODO kb! Figure out why we can't we see the red background when we drag a file over this div.
div()
.z_index(1)
.id("drag-target")
@ -70,40 +66,37 @@ impl<S: 'static + Send + Sync> Pane<S> {
}
}
impl<S: 'static + Send + Sync> ParentElement for Pane<S> {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::ViewState>; 2]> {
impl<V: 'static> ParentElement<V> for Pane<V> {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
&mut self.children
}
}
#[derive(Element)]
pub struct PaneGroup<S: 'static + Send + Sync> {
state_type: PhantomData<S>,
groups: Vec<PaneGroup<S>>,
panes: Vec<Pane<S>>,
#[derive(Component)]
pub struct PaneGroup<V: 'static> {
groups: Vec<PaneGroup<V>>,
panes: Vec<Pane<V>>,
split_direction: SplitDirection,
}
impl<S: 'static + Send + Sync> PaneGroup<S> {
pub fn new_groups(groups: Vec<PaneGroup<S>>, split_direction: SplitDirection) -> Self {
impl<V: 'static> PaneGroup<V> {
pub fn new_groups(groups: Vec<PaneGroup<V>>, split_direction: SplitDirection) -> Self {
Self {
state_type: PhantomData,
groups,
panes: Vec::new(),
split_direction,
}
}
pub fn new_panes(panes: Vec<Pane<S>>, split_direction: SplitDirection) -> Self {
pub fn new_panes(panes: Vec<Pane<V>>, split_direction: SplitDirection) -> Self {
Self {
state_type: PhantomData,
groups: Vec::new(),
panes,
split_direction,
}
}
fn render(&mut self, view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render(mut self, view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
let theme = theme(cx);
if !self.panes.is_empty() {
@ -113,7 +106,7 @@ impl<S: 'static + Send + Sync> PaneGroup<S> {
.gap_px()
.w_full()
.h_full()
.children(self.panes.iter_mut().map(|pane| pane.render(view, cx)));
.children(self.panes.drain(..).map(|pane| pane.render(view, cx)));
if self.split_direction == SplitDirection::Horizontal {
return el;
@ -130,7 +123,7 @@ impl<S: 'static + Send + Sync> PaneGroup<S> {
.w_full()
.h_full()
.bg(theme.editor)
.children(self.groups.iter_mut().map(|group| group.render(view, cx)));
.children(self.groups.drain(..).map(|group| group.render(view, cx)));
if self.split_direction == SplitDirection::Horizontal {
return el;

View File

@ -1,23 +1,19 @@
use std::marker::PhantomData;
use crate::prelude::*;
use crate::{Avatar, Facepile, PlayerWithCallStatus};
#[derive(Element)]
pub struct PlayerStack<S: 'static + Send + Sync> {
state_type: PhantomData<S>,
#[derive(Component)]
pub struct PlayerStack {
player_with_call_status: PlayerWithCallStatus,
}
impl<S: 'static + Send + Sync> PlayerStack<S> {
impl PlayerStack {
pub fn new(player_with_call_status: PlayerWithCallStatus) -> Self {
Self {
state_type: PhantomData,
player_with_call_status,
}
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render<S: 'static>(self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
let theme = theme(cx);
let player = self.player_with_call_status.get_player();
self.player_with_call_status.get_call_status();

View File

@ -1,25 +1,19 @@
use std::marker::PhantomData;
use crate::prelude::*;
use crate::{
static_project_panel_project_items, static_project_panel_single_items, Input, List, ListHeader,
};
#[derive(Element)]
pub struct ProjectPanel<S: 'static + Send + Sync> {
#[derive(Component)]
pub struct ProjectPanel {
id: ElementId,
state_type: PhantomData<S>,
}
impl<S: 'static + Send + Sync> ProjectPanel<S> {
impl ProjectPanel {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
state_type: PhantomData,
}
Self { id: id.into() }
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render<S: 'static>(self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
let theme = theme(cx);
div()
@ -67,25 +61,17 @@ mod stories {
use super::*;
#[derive(Element)]
pub struct ProjectPanelStory<S: 'static + Send + Sync + Clone> {
state_type: PhantomData<S>,
}
#[derive(Component)]
pub struct ProjectPanelStory;
impl<S: 'static + Send + Sync + Clone> ProjectPanelStory<S> {
impl ProjectPanelStory {
pub fn new() -> Self {
Self {
state_type: PhantomData,
}
Self
}
fn render(
&mut self,
_view: &mut S,
cx: &mut ViewContext<S>,
) -> impl Element<ViewState = S> {
fn render<S: 'static>(self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
Story::container(cx)
.child(Story::title_for::<_, ProjectPanel<S>>(cx))
.child(Story::title_for::<_, ProjectPanel>(cx))
.child(Story::label(cx, "Default"))
.child(
Panel::new("project-panel-outer", cx)

View File

@ -1,23 +1,17 @@
use std::marker::PhantomData;
use crate::prelude::*;
use crate::{OrderMethod, Palette, PaletteItem};
#[derive(Element)]
pub struct RecentProjects<S: 'static + Send + Sync + Clone> {
#[derive(Component)]
pub struct RecentProjects {
id: ElementId,
state_type: PhantomData<S>,
}
impl<S: 'static + Send + Sync + Clone> RecentProjects<S> {
impl RecentProjects {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
state_type: PhantomData,
}
Self { id: id.into() }
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render<S: 'static>(self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
div().id(self.id.clone()).child(
Palette::new("palette")
.items(vec![
@ -44,25 +38,17 @@ mod stories {
use super::*;
#[derive(Element)]
pub struct RecentProjectsStory<S: 'static + Send + Sync + Clone> {
state_type: PhantomData<S>,
}
#[derive(Component)]
pub struct RecentProjectsStory;
impl<S: 'static + Send + Sync + Clone> RecentProjectsStory<S> {
impl RecentProjectsStory {
pub fn new() -> Self {
Self {
state_type: PhantomData,
}
Self
}
fn render(
&mut self,
_view: &mut S,
cx: &mut ViewContext<S>,
) -> impl Element<ViewState = S> {
fn render<S: 'static>(self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
Story::container(cx)
.child(Story::title_for::<_, RecentProjects<S>>(cx))
.child(Story::title_for::<_, RecentProjects>(cx))
.child(Story::label(cx, "Default"))
.child(RecentProjects::new("recent-projects"))
}

View File

@ -28,8 +28,8 @@ impl Default for ToolGroup {
}
}
#[derive(Element)]
#[element(view_state = "Workspace")]
#[derive(Component)]
#[component(view_type = "Workspace")]
pub struct StatusBar {
left_tools: Option<ToolGroup>,
right_tools: Option<ToolGroup>,
@ -83,10 +83,10 @@ impl StatusBar {
}
fn render(
&mut self,
self,
view: &mut Workspace,
cx: &mut ViewContext<Workspace>,
) -> impl Element<ViewState = Workspace> {
) -> impl Component<Workspace> {
let theme = theme(cx);
div()
@ -105,7 +105,7 @@ impl StatusBar {
&self,
workspace: &mut Workspace,
cx: &WindowContext,
) -> impl Element<ViewState = Workspace> {
) -> impl Component<Workspace> {
div()
.flex()
.items_center()
@ -136,7 +136,7 @@ impl StatusBar {
&self,
workspace: &mut Workspace,
cx: &WindowContext,
) -> impl Element<ViewState = Workspace> {
) -> impl Component<Workspace> {
div()
.flex()
.items_center()

View File

@ -1,11 +1,8 @@
use std::marker::PhantomData;
use crate::prelude::*;
use crate::{Icon, IconColor, IconElement, Label, LabelColor};
#[derive(Element, Clone)]
pub struct Tab<S: 'static + Send + Sync + Clone> {
state_type: PhantomData<S>,
#[derive(Component, Clone)]
pub struct Tab {
id: ElementId,
title: String,
icon: Option<Icon>,
@ -22,10 +19,9 @@ struct TabDragState {
title: String,
}
impl<S: 'static + Send + Sync + Clone> Tab<S> {
impl Tab {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
state_type: PhantomData,
id: id.into(),
title: "untitled".to_string(),
icon: None,
@ -81,7 +77,7 @@ impl<S: 'static + Send + Sync + Clone> Tab<S> {
self
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
let theme = theme(cx);
let has_fs_conflict = self.fs_status == FileSystemStatus::Conflict;
let is_deleted = self.fs_status == FileSystemStatus::Deleted;
@ -176,28 +172,20 @@ mod stories {
use super::*;
#[derive(Element)]
pub struct TabStory<S: 'static + Send + Sync + Clone> {
state_type: PhantomData<S>,
}
#[derive(Component)]
pub struct TabStory;
impl<S: 'static + Send + Sync + Clone> TabStory<S> {
impl TabStory {
pub fn new() -> Self {
Self {
state_type: PhantomData,
}
Self
}
fn render(
&mut self,
_view: &mut S,
cx: &mut ViewContext<S>,
) -> impl Element<ViewState = S> {
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
let git_statuses = GitStatus::iter();
let fs_statuses = FileSystemStatus::iter();
Story::container(cx)
.child(Story::title_for::<_, Tab<S>>(cx))
.child(Story::title_for::<_, Tab>(cx))
.child(
h_stack().child(
v_stack()

View File

@ -1,22 +1,18 @@
use std::marker::PhantomData;
use crate::prelude::*;
use crate::{Icon, IconButton, Tab};
#[derive(Element)]
pub struct TabBar<S: 'static + Send + Sync + Clone> {
#[derive(Component)]
pub struct TabBar {
id: ElementId,
state_type: PhantomData<S>,
/// Backwards, Forwards
can_navigate: (bool, bool),
tabs: Vec<Tab<S>>,
tabs: Vec<Tab>,
}
impl<S: 'static + Send + Sync + Clone> TabBar<S> {
pub fn new(id: impl Into<ElementId>, tabs: Vec<Tab<S>>) -> Self {
impl TabBar {
pub fn new(id: impl Into<ElementId>, tabs: Vec<Tab>) -> Self {
Self {
id: id.into(),
state_type: PhantomData,
can_navigate: (false, false),
tabs,
}
@ -27,7 +23,7 @@ impl<S: 'static + Send + Sync + Clone> TabBar<S> {
self
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render<S: 'static>(self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
let theme = theme(cx);
let (can_navigate_back, can_navigate_forward) = self.can_navigate;
@ -100,25 +96,17 @@ mod stories {
use super::*;
#[derive(Element)]
pub struct TabBarStory<S: 'static + Send + Sync + Clone> {
state_type: PhantomData<S>,
}
#[derive(Component)]
pub struct TabBarStory;
impl<S: 'static + Send + Sync + Clone> TabBarStory<S> {
impl TabBarStory {
pub fn new() -> Self {
Self {
state_type: PhantomData,
}
Self
}
fn render(
&mut self,
_view: &mut S,
cx: &mut ViewContext<S>,
) -> impl Element<ViewState = S> {
fn render<S: 'static>(self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
Story::container(cx)
.child(Story::title_for::<_, TabBar<S>>(cx))
.child(Story::title_for::<_, TabBar>(cx))
.child(Story::label(cx, "Default"))
.child(TabBar::new(
"tab-bar",

View File

@ -1,23 +1,17 @@
use std::marker::PhantomData;
use gpui2::{relative, rems, Size};
use crate::prelude::*;
use crate::{Icon, IconButton, Pane, Tab};
#[derive(Element)]
pub struct Terminal<S: 'static + Send + Sync + Clone> {
state_type: PhantomData<S>,
}
#[derive(Component)]
pub struct Terminal;
impl<S: 'static + Send + Sync + Clone> Terminal<S> {
impl Terminal {
pub fn new() -> Self {
Self {
state_type: PhantomData,
}
Self
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render<S: 'static>(self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
let theme = theme(cx);
let can_navigate_back = true;
@ -93,25 +87,17 @@ mod stories {
use super::*;
#[derive(Element)]
pub struct TerminalStory<S: 'static + Send + Sync + Clone> {
state_type: PhantomData<S>,
}
#[derive(Component)]
pub struct TerminalStory;
impl<S: 'static + Send + Sync + Clone> TerminalStory<S> {
impl TerminalStory {
pub fn new() -> Self {
Self {
state_type: PhantomData,
}
Self
}
fn render(
&mut self,
_view: &mut S,
cx: &mut ViewContext<S>,
) -> impl Element<ViewState = S> {
fn render<S: 'static>(self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
Story::container(cx)
.child(Story::title_for::<_, Terminal<S>>(cx))
.child(Story::title_for::<_, Terminal>(cx))
.child(Story::label(cx, "Default"))
.child(Terminal::new())
}

View File

@ -1,23 +1,17 @@
use std::marker::PhantomData;
use crate::prelude::*;
use crate::{OrderMethod, Palette, PaletteItem};
#[derive(Element)]
pub struct ThemeSelector<S: 'static + Send + Sync> {
#[derive(Component)]
pub struct ThemeSelector {
id: ElementId,
state_type: PhantomData<S>,
}
impl<S: 'static + Send + Sync> ThemeSelector<S> {
impl ThemeSelector {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
state_type: PhantomData,
}
Self { id: id.into() }
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render<S: 'static>(self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
div().child(
Palette::new(self.id.clone())
.items(vec![
@ -49,25 +43,17 @@ mod stories {
use super::*;
#[derive(Element)]
pub struct ThemeSelectorStory<S: 'static + Send + Sync + Clone> {
state_type: PhantomData<S>,
}
#[derive(Component)]
pub struct ThemeSelectorStory;
impl<S: 'static + Send + Sync + Clone> ThemeSelectorStory<S> {
impl ThemeSelectorStory {
pub fn new() -> Self {
Self {
state_type: PhantomData,
}
Self
}
fn render(
&mut self,
_view: &mut S,
cx: &mut ViewContext<S>,
) -> impl Element<ViewState = S> {
fn render<S: 'static>(self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
Story::container(cx)
.child(Story::title_for::<_, ThemeSelector<S>>(cx))
.child(Story::title_for::<_, ThemeSelector>(cx))
.child(Story::label(cx, "Default"))
.child(ThemeSelector::new("theme-selector"))
}

View File

@ -87,7 +87,7 @@ impl TitleBar {
)
}
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Element<ViewState = Self> {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Component<Self> {
let theme = theme(cx);
let settings = user_settings(cx);
@ -204,7 +204,7 @@ mod stories {
)
}
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Element<ViewState = Self> {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Component<Self> {
Story::container(cx)
.child(Story::title_for::<_, TitleBar>(cx))
.child(Story::label(cx, "Default"))

View File

@ -22,13 +22,13 @@ pub enum ToastOrigin {
/// they are actively showing the a process in progress.
///
/// Only one toast may be visible at a time.
#[derive(Element)]
pub struct Toast<S: 'static + Send + Sync> {
#[derive(Component)]
pub struct Toast<S: 'static> {
origin: ToastOrigin,
children: SmallVec<[AnyElement<S>; 2]>,
}
impl<S: 'static + Send + Sync> Toast<S> {
impl<S: 'static> Toast<S> {
pub fn new(origin: ToastOrigin) -> Self {
Self {
origin,
@ -36,7 +36,7 @@ impl<S: 'static + Send + Sync> Toast<S> {
}
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render(self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
let theme = theme(cx);
let mut div = div();
@ -57,12 +57,12 @@ impl<S: 'static + Send + Sync> Toast<S> {
.shadow_md()
.overflow_hidden()
.bg(theme.elevated_surface)
.children(self.children.drain(..))
.children(self.children)
}
}
impl<S: 'static + Send + Sync> ParentElement for Toast<S> {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::ViewState>; 2]> {
impl<S: 'static> ParentElement<S> for Toast<S> {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<S>; 2]> {
&mut self.children
}
}
@ -72,29 +72,19 @@ pub use stories::*;
#[cfg(feature = "stories")]
mod stories {
use std::marker::PhantomData;
use crate::{Label, Story};
use super::*;
#[derive(Element)]
pub struct ToastStory<S: 'static + Send + Sync> {
state_type: PhantomData<S>,
}
#[derive(Component)]
pub struct ToastStory;
impl<S: 'static + Send + Sync> ToastStory<S> {
impl ToastStory {
pub fn new() -> Self {
Self {
state_type: PhantomData,
}
Self
}
fn render(
&mut self,
_view: &mut S,
cx: &mut ViewContext<S>,
) -> impl Element<ViewState = S> {
fn render<S: 'static>(self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
Story::container(cx)
.child(Story::title_for::<_, Toast<S>>(cx))
.child(Story::label(cx, "Default"))

View File

@ -6,13 +6,13 @@ use crate::prelude::*;
#[derive(Clone)]
pub struct ToolbarItem {}
#[derive(Element)]
pub struct Toolbar<S: 'static + Send + Sync> {
#[derive(Component)]
pub struct Toolbar<S: 'static> {
left_items: SmallVec<[AnyElement<S>; 2]>,
right_items: SmallVec<[AnyElement<S>; 2]>,
}
impl<S: 'static + Send + Sync> Toolbar<S> {
impl<S: 'static> Toolbar<S> {
pub fn new() -> Self {
Self {
left_items: SmallVec::new(),
@ -20,41 +20,41 @@ impl<S: 'static + Send + Sync> Toolbar<S> {
}
}
pub fn left_item(mut self, child: impl IntoAnyElement<S>) -> Self
pub fn left_item(mut self, child: impl Component<S>) -> Self
where
Self: Sized,
{
self.left_items.push(child.into_any());
self.left_items.push(child.render());
self
}
pub fn left_items(mut self, iter: impl IntoIterator<Item = impl IntoAnyElement<S>>) -> Self
pub fn left_items(mut self, iter: impl IntoIterator<Item = impl Component<S>>) -> Self
where
Self: Sized,
{
self.left_items
.extend(iter.into_iter().map(|item| item.into_any()));
.extend(iter.into_iter().map(|item| item.render()));
self
}
pub fn right_item(mut self, child: impl IntoAnyElement<S>) -> Self
pub fn right_item(mut self, child: impl Component<S>) -> Self
where
Self: Sized,
{
self.right_items.push(child.into_any());
self.right_items.push(child.render());
self
}
pub fn right_items(mut self, iter: impl IntoIterator<Item = impl IntoAnyElement<S>>) -> Self
pub fn right_items(mut self, iter: impl IntoIterator<Item = impl Component<S>>) -> Self
where
Self: Sized,
{
self.right_items
.extend(iter.into_iter().map(|item| item.into_any()));
.extend(iter.into_iter().map(|item| item.render()));
self
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render(mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
let theme = theme(cx);
div()
@ -72,7 +72,6 @@ pub use stories::*;
#[cfg(feature = "stories")]
mod stories {
use std::marker::PhantomData;
use std::path::PathBuf;
use std::str::FromStr;
@ -80,27 +79,19 @@ mod stories {
use super::*;
#[derive(Element)]
pub struct ToolbarStory<S: 'static + Send + Sync + Clone> {
state_type: PhantomData<S>,
}
#[derive(Component)]
pub struct ToolbarStory;
impl<S: 'static + Send + Sync + Clone> ToolbarStory<S> {
impl ToolbarStory {
pub fn new() -> Self {
Self {
state_type: PhantomData,
}
Self
}
fn render(
&mut self,
_view: &mut S,
cx: &mut ViewContext<S>,
) -> impl Element<ViewState = S> {
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
let theme = theme(cx);
Story::container(cx)
.child(Story::title_for::<_, Toolbar<S>>(cx))
.child(Story::title_for::<_, Toolbar<V>>(cx))
.child(Story::label(cx, "Default"))
.child(
Toolbar::new()

View File

@ -1,5 +1,3 @@
use std::marker::PhantomData;
use crate::prelude::*;
#[derive(Clone, Copy)]
@ -9,23 +7,21 @@ enum TrafficLightColor {
Green,
}
#[derive(Element)]
struct TrafficLight<S: 'static + Send + Sync> {
state_type: PhantomData<S>,
#[derive(Component)]
struct TrafficLight {
color: TrafficLightColor,
window_has_focus: bool,
}
impl<S: 'static + Send + Sync> TrafficLight<S> {
impl TrafficLight {
fn new(color: TrafficLightColor, window_has_focus: bool) -> Self {
Self {
state_type: PhantomData,
color,
window_has_focus,
}
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render<S: 'static>(self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
let theme = theme(cx);
let fill = match (self.window_has_focus, self.color) {
@ -39,16 +35,14 @@ impl<S: 'static + Send + Sync> TrafficLight<S> {
}
}
#[derive(Element)]
pub struct TrafficLights<S: 'static + Send + Sync> {
state_type: PhantomData<S>,
#[derive(Component)]
pub struct TrafficLights {
window_has_focus: bool,
}
impl<S: 'static + Send + Sync> TrafficLights<S> {
impl TrafficLights {
pub fn new() -> Self {
Self {
state_type: PhantomData,
window_has_focus: true,
}
}
@ -58,7 +52,7 @@ impl<S: 'static + Send + Sync> TrafficLights<S> {
self
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render<S: 'static>(self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
div()
.flex()
.items_center()
@ -87,25 +81,17 @@ mod stories {
use super::*;
#[derive(Element)]
pub struct TrafficLightsStory<S: 'static + Send + Sync> {
state_type: PhantomData<S>,
}
#[derive(Component)]
pub struct TrafficLightsStory;
impl<S: 'static + Send + Sync> TrafficLightsStory<S> {
impl TrafficLightsStory {
pub fn new() -> Self {
Self {
state_type: PhantomData,
}
Self
}
fn render(
&mut self,
_view: &mut S,
cx: &mut ViewContext<S>,
) -> impl Element<ViewState = S> {
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
Story::container(cx)
.child(Story::title_for::<_, TrafficLights<S>>(cx))
.child(Story::title_for::<_, TrafficLights>(cx))
.child(Story::label(cx, "Default"))
.child(TrafficLights::new())
.child(Story::label(cx, "Unfocused"))

View File

@ -3,13 +3,13 @@ use std::sync::Arc;
use chrono::DateTime;
use gpui2::{px, relative, rems, view, Context, Size, View};
use crate::{prelude::*, NotificationsPanel};
use crate::{
static_livestream, old_theme, user_settings_mut, v_stack, AssistantPanel, Button, ChatMessage,
old_theme, static_livestream, user_settings_mut, v_stack, AssistantPanel, Button, ChatMessage,
ChatPanel, CollabPanel, EditorPane, FakeSettings, Label, LanguageSelector, Pane, PaneGroup,
Panel, PanelAllowedSides, PanelSide, ProjectPanel, SettingValue, SplitDirection, StatusBar,
Terminal, TitleBar, Toast, ToastOrigin,
};
use crate::{prelude::*, NotificationsPanel};
#[derive(Clone)]
pub struct Gpui2UiDebug {
@ -174,7 +174,7 @@ impl Workspace {
view(cx.entity(|cx| Self::new(cx)), Self::render)
}
pub fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Element<ViewState = Self> {
pub fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Component<Self> {
let theme = old_theme(cx).clone();
// HACK: This should happen inside of `debug_toggle_user_settings`, but

View File

@ -1,18 +1,15 @@
use gpui2::Element;
pub trait ElementExt<S: 'static + Send + Sync>: Element<ViewState = S> {
/// Applies a given function `then` to the current element if `condition` is true.
/// This function is used to conditionally modify the element based on a given condition.
/// If `condition` is false, it just returns the current element as it is.
fn when(mut self, condition: bool, then: impl FnOnce(Self) -> Self) -> Self
where
Self: Sized,
{
if condition {
self = then(self);
}
self
}
pub trait ElementExt<S: 'static>: Element<S> {
// fn when(mut self, condition: bool, then: impl FnOnce(Self) -> Self) -> Self
// where
// Self: Sized,
// {
// if condition {
// self = then(self);
// }
// self
// }
// fn when_some<T, U>(mut self, option: Option<T>, then: impl FnOnce(Self, T) -> U) -> U
// where
@ -25,4 +22,4 @@ pub trait ElementExt<S: 'static + Send + Sync>: Element<ViewState = S> {
// }
}
impl<S: 'static + Send + Sync, E: Element<ViewState = S>> ElementExt<S> for E {}
impl<S: 'static, E: Element<S>> ElementExt<S> for E {}

View File

@ -1,20 +1,16 @@
use std::marker::PhantomData;
use gpui2::img;
use crate::prelude::*;
#[derive(Element)]
pub struct Avatar<S: 'static + Send + Sync> {
state_type: PhantomData<S>,
#[derive(Component)]
pub struct Avatar {
src: SharedString,
shape: Shape,
}
impl<S: 'static + Send + Sync> Avatar<S> {
impl Avatar {
pub fn new(src: impl Into<SharedString>) -> Self {
Self {
state_type: PhantomData,
src: src.into(),
shape: Shape::Circle,
}
@ -25,7 +21,7 @@ impl<S: 'static + Send + Sync> Avatar<S> {
self
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
let theme = theme(cx);
let mut img = img();
@ -51,25 +47,17 @@ mod stories {
use super::*;
#[derive(Element)]
pub struct AvatarStory<S: 'static + Send + Sync> {
state_type: PhantomData<S>,
}
#[derive(Component)]
pub struct AvatarStory;
impl<S: 'static + Send + Sync> AvatarStory<S> {
impl AvatarStory {
pub fn new() -> Self {
Self {
state_type: PhantomData,
}
Self
}
fn render(
&mut self,
_view: &mut S,
cx: &mut ViewContext<S>,
) -> impl Element<ViewState = S> {
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
Story::container(cx)
.child(Story::title_for::<_, Avatar<S>>(cx))
.child(Story::title_for::<_, Avatar>(cx))
.child(Story::label(cx, "Default"))
.child(Avatar::new(
"https://avatars.githubusercontent.com/u/1714999?v=4",

View File

@ -1,4 +1,3 @@
use std::marker::PhantomData;
use std::sync::Arc;
use gpui2::{div, DefiniteLength, Hsla, MouseButton, WindowContext};
@ -49,21 +48,23 @@ impl ButtonVariant {
}
}
pub type ClickHandler<S> = Arc<dyn Fn(&mut S, &mut ViewContext<S>) + 'static + Send + Sync>;
pub type ClickHandler<S> = Arc<dyn Fn(&mut S, &mut ViewContext<S>) + Send + Sync>;
struct ButtonHandlers<S: 'static + Send + Sync> {
struct ButtonHandlers<S: 'static> {
click: Option<ClickHandler<S>>,
}
impl<S: 'static + Send + Sync> Default for ButtonHandlers<S> {
unsafe impl<S> Send for ButtonHandlers<S> {}
unsafe impl<S> Sync for ButtonHandlers<S> {}
impl<S: 'static> Default for ButtonHandlers<S> {
fn default() -> Self {
Self { click: None }
}
}
#[derive(Element)]
pub struct Button<S: 'static + Send + Sync> {
state_type: PhantomData<S>,
#[derive(Component)]
pub struct Button<S: 'static> {
disabled: bool,
handlers: ButtonHandlers<S>,
icon: Option<Icon>,
@ -73,10 +74,9 @@ pub struct Button<S: 'static + Send + Sync> {
width: Option<DefiniteLength>,
}
impl<S: 'static + Send + Sync> Button<S> {
impl<S: 'static> Button<S> {
pub fn new(label: impl Into<SharedString>) -> Self {
Self {
state_type: PhantomData,
disabled: false,
handlers: ButtonHandlers::default(),
icon: None,
@ -140,21 +140,17 @@ impl<S: 'static + Send + Sync> Button<S> {
}
}
fn render_label(&self) -> Label<S> {
fn render_label(&self) -> Label {
Label::new(self.label.clone())
.color(self.label_color())
.line_height_style(LineHeightStyle::UILabel)
}
fn render_icon(&self, icon_color: IconColor) -> Option<IconElement<S>> {
fn render_icon(&self, icon_color: IconColor) -> Option<IconElement> {
self.icon.map(|i| IconElement::new(i).color(icon_color))
}
pub fn render(
&mut self,
_view: &mut S,
cx: &mut ViewContext<S>,
) -> impl Element<ViewState = S> {
pub fn render(self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
let icon_color = self.icon_color();
let mut button = h_stack()
@ -197,24 +193,20 @@ impl<S: 'static + Send + Sync> Button<S> {
}
}
#[derive(Element)]
pub struct ButtonGroup<S: 'static + Send + Sync> {
state_type: PhantomData<S>,
buttons: Vec<Button<S>>,
#[derive(Component)]
pub struct ButtonGroup<V: 'static> {
buttons: Vec<Button<V>>,
}
impl<S: 'static + Send + Sync> ButtonGroup<S> {
pub fn new(buttons: Vec<Button<S>>) -> Self {
Self {
state_type: PhantomData,
buttons,
}
impl<V: 'static> ButtonGroup<V> {
pub fn new(buttons: Vec<Button<V>>) -> Self {
Self { buttons }
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
let mut el = h_stack().text_size(ui_size(cx, 1.));
for button in &mut self.buttons {
for button in self.buttons {
el = el.child(button.render(_view, cx));
}
@ -234,27 +226,19 @@ mod stories {
use super::*;
#[derive(Element)]
pub struct ButtonStory<S: 'static + Send + Sync + Clone> {
state_type: PhantomData<S>,
}
#[derive(Component)]
pub struct ButtonStory;
impl<S: 'static + Send + Sync + Clone> ButtonStory<S> {
impl ButtonStory {
pub fn new() -> Self {
Self {
state_type: PhantomData,
}
Self
}
fn render(
&mut self,
_view: &mut S,
cx: &mut ViewContext<S>,
) -> impl Element<ViewState = S> {
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
let states = InteractionState::iter();
Story::container(cx)
.child(Story::title_for::<_, Button<S>>(cx))
.child(Story::title_for::<_, Button<V>>(cx))
.child(
div()
.flex()

View File

@ -1,19 +1,15 @@
use std::marker::PhantomData;
use crate::{prelude::*, v_stack, ButtonGroup};
#[derive(Element)]
pub struct Details<S: 'static + Send + Sync> {
state_type: PhantomData<S>,
#[derive(Component)]
pub struct Details<V: 'static> {
text: &'static str,
meta: Option<&'static str>,
actions: Option<ButtonGroup<S>>,
actions: Option<ButtonGroup<V>>,
}
impl<S: 'static + Send + Sync> Details<S> {
impl<S: 'static> Details<S> {
pub fn new(text: &'static str) -> Self {
Self {
state_type: PhantomData,
text,
meta: None,
actions: None,
@ -30,7 +26,7 @@ impl<S: 'static + Send + Sync> Details<S> {
self
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render(mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
let theme = theme(cx);
v_stack()
@ -54,25 +50,17 @@ mod stories {
use super::*;
#[derive(Element)]
pub struct DetailsStory<S: 'static + Send + Sync + Clone> {
state_type: PhantomData<S>,
}
#[derive(Component)]
pub struct DetailsStory;
impl<S: 'static + Send + Sync + Clone> DetailsStory<S> {
impl DetailsStory {
pub fn new() -> Self {
Self {
state_type: PhantomData,
}
Self
}
fn render(
&mut self,
_view: &mut S,
cx: &mut ViewContext<S>,
) -> impl Element<ViewState = S> {
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
Story::container(cx)
.child(Story::title_for::<_, Details<S>>(cx))
.child(Story::title_for::<_, Details<V>>(cx))
.child(Story::label(cx, "Default"))
.child(Details::new("The quick brown fox jumps over the lazy dog"))
.child(Story::label(cx, "With meta"))

View File

@ -1,5 +1,3 @@
use std::marker::PhantomData;
use gpui2::{svg, Hsla};
use strum::EnumIter;
@ -148,18 +146,16 @@ impl Icon {
}
}
#[derive(Element)]
pub struct IconElement<S: 'static + Send + Sync> {
state_type: PhantomData<S>,
#[derive(Component)]
pub struct IconElement {
icon: Icon,
color: IconColor,
size: IconSize,
}
impl<S: 'static + Send + Sync> IconElement<S> {
impl IconElement {
pub fn new(icon: Icon) -> Self {
Self {
state_type: PhantomData,
icon,
color: IconColor::default(),
size: IconSize::default(),
@ -176,7 +172,7 @@ impl<S: 'static + Send + Sync> IconElement<S> {
self
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
let fill = self.color.color(cx);
let svg_size = match self.size {
IconSize::Small => ui_size(cx, 12. / 14.),
@ -202,27 +198,19 @@ mod stories {
use super::*;
#[derive(Element, Default)]
pub struct IconStory<S: 'static + Send + Sync> {
state_type: PhantomData<S>,
}
#[derive(Component)]
pub struct IconStory;
impl<S: 'static + Send + Sync> IconStory<S> {
impl IconStory {
pub fn new() -> Self {
Self {
state_type: PhantomData,
}
Self
}
fn render(
&mut self,
_view: &mut S,
cx: &mut ViewContext<S>,
) -> impl Element<ViewState = S> {
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
let icons = Icon::iter();
Story::container(cx)
.child(Story::title_for::<_, IconElement<S>>(cx))
.child(Story::title_for::<_, IconElement>(cx))
.child(Story::label(cx, "All Icons"))
.child(div().flex().gap_3().children(icons.map(IconElement::new)))
}

View File

@ -1,5 +1,3 @@
use std::marker::PhantomData;
use crate::prelude::*;
use crate::Label;
use crate::LabelColor;
@ -11,9 +9,8 @@ pub enum InputVariant {
Filled,
}
#[derive(Element)]
pub struct Input<S: 'static + Send + Sync> {
state_type: PhantomData<S>,
#[derive(Component)]
pub struct Input {
placeholder: SharedString,
value: String,
state: InteractionState,
@ -22,10 +19,9 @@ pub struct Input<S: 'static + Send + Sync> {
is_active: bool,
}
impl<S: 'static + Send + Sync> Input<S> {
impl Input {
pub fn new(placeholder: impl Into<SharedString>) -> Self {
Self {
state_type: PhantomData,
placeholder: placeholder.into(),
value: "".to_string(),
state: InteractionState::default(),
@ -60,7 +56,7 @@ impl<S: 'static + Send + Sync> Input<S> {
self
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render<S: 'static>(self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Component<S> {
let theme = theme(cx);
let (input_bg, input_hover_bg, input_active_bg) = match self.variant {
@ -120,25 +116,17 @@ mod stories {
use super::*;
#[derive(Element)]
pub struct InputStory<S: 'static + Send + Sync> {
state_type: PhantomData<S>,
}
#[derive(Component)]
pub struct InputStory;
impl<S: 'static + Send + Sync> InputStory<S> {
impl InputStory {
pub fn new() -> Self {
Self {
state_type: PhantomData,
}
Self
}
fn render(
&mut self,
_view: &mut S,
cx: &mut ViewContext<S>,
) -> impl Element<ViewState = S> {
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
Story::container(cx)
.child(Story::title_for::<_, Input<S>>(cx))
.child(Story::title_for::<_, Input>(cx))
.child(Story::label(cx, "Default"))
.child(div().flex().child(Input::new("Search")))
}

View File

@ -1,5 +1,3 @@
use std::marker::PhantomData;
use gpui2::{relative, Hsla, WindowContext};
use smallvec::SmallVec;
@ -48,19 +46,17 @@ pub enum LineHeightStyle {
UILabel,
}
#[derive(Element)]
pub struct Label<S: 'static + Send + Sync> {
state_type: PhantomData<S>,
#[derive(Component)]
pub struct Label {
label: SharedString,
line_height_style: LineHeightStyle,
color: LabelColor,
strikethrough: bool,
}
impl<S: 'static + Send + Sync> Label<S> {
impl Label {
pub fn new(label: impl Into<SharedString>) -> Self {
Self {
state_type: PhantomData,
label: label.into(),
line_height_style: LineHeightStyle::default(),
color: LabelColor::Default,
@ -83,7 +79,7 @@ impl<S: 'static + Send + Sync> Label<S> {
self
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
div()
.when(self.strikethrough, |this| {
this.relative().child(
@ -105,19 +101,17 @@ impl<S: 'static + Send + Sync> Label<S> {
}
}
#[derive(Element)]
pub struct HighlightedLabel<S: 'static + Send + Sync> {
state_type: PhantomData<S>,
#[derive(Component)]
pub struct HighlightedLabel {
label: SharedString,
color: LabelColor,
highlight_indices: Vec<usize>,
strikethrough: bool,
}
impl<S: 'static + Send + Sync> HighlightedLabel<S> {
impl HighlightedLabel {
pub fn new(label: impl Into<SharedString>, highlight_indices: Vec<usize>) -> Self {
Self {
state_type: PhantomData,
label: label.into(),
color: LabelColor::Default,
highlight_indices,
@ -135,7 +129,7 @@ impl<S: 'static + Send + Sync> HighlightedLabel<S> {
self
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
let theme = theme(cx);
let highlight_color = theme.text_accent;
@ -211,25 +205,17 @@ mod stories {
use super::*;
#[derive(Element)]
pub struct LabelStory<S: 'static + Send + Sync> {
state_type: PhantomData<S>,
}
#[derive(Component)]
pub struct LabelStory;
impl<S: 'static + Send + Sync> LabelStory<S> {
impl LabelStory {
pub fn new() -> Self {
Self {
state_type: PhantomData,
}
Self
}
fn render(
&mut self,
_view: &mut S,
cx: &mut ViewContext<S>,
) -> impl Element<ViewState = S> {
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
Story::container(cx)
.child(Story::title_for::<_, Label<S>>(cx))
.child(Story::title_for::<_, Label>(cx))
.child(Story::label(cx, "Default"))
.child(Label::new("Hello, world!"))
.child(Story::label(cx, "Highlighted"))

View File

@ -14,18 +14,18 @@ pub trait Stack: Styled + Sized {
}
}
impl<S: 'static + Send + Sync> Stack for Div<S> {}
impl<S: 'static> Stack for Div<S> {}
/// Horizontally stacks elements.
///
/// Sets `flex()`, `flex_row()`, `items_center()`
pub fn h_stack<S: 'static + Send + Sync>() -> Div<S> {
pub fn h_stack<S: 'static>() -> Div<S> {
div().h_stack()
}
/// Vertically stacks elements.
///
/// Sets `flex()`, `flex_col()`
pub fn v_stack<S: 'static + Send + Sync>() -> Div<S> {
pub fn v_stack<S: 'static>() -> Div<S> {
div().v_stack()
}

View File

@ -1,20 +1,14 @@
use std::marker::PhantomData;
use crate::prelude::*;
#[derive(Element)]
pub struct ToolDivider<S: 'static + Send + Sync> {
state_type: PhantomData<S>,
}
#[derive(Component)]
pub struct ToolDivider;
impl<S: 'static + Send + Sync> ToolDivider<S> {
impl ToolDivider {
pub fn new() -> Self {
Self {
state_type: PhantomData,
}
Self
}
fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
let theme = theme(cx);
div().w_px().h_3().bg(theme.border)

View File

@ -1,5 +1,5 @@
pub use gpui2::{
div, Element, ElementId, IntoAnyElement, ParentElement, SharedString, StatefulInteractive,
div, Element, ElementId, Component, ParentElement, SharedString, StatefulInteractive,
StatelessInteractive, Styled, ViewContext, WindowContext,
};

View File

@ -13,7 +13,7 @@ use crate::{
};
use crate::{HighlightedText, ListDetailsEntry};
pub fn static_tabs_example<S: 'static + Send + Sync + Clone>() -> Vec<Tab<S>> {
pub fn static_tabs_example() -> Vec<Tab> {
vec![
Tab::new("wip.rs")
.title("wip.rs".to_string())
@ -63,7 +63,7 @@ pub fn static_tabs_example<S: 'static + Send + Sync + Clone>() -> Vec<Tab<S>> {
]
}
pub fn static_tabs_1<S: 'static + Send + Sync + Clone>() -> Vec<Tab<S>> {
pub fn static_tabs_1() -> Vec<Tab> {
vec![
Tab::new("project_panel.rs")
.title("project_panel.rs".to_string())
@ -87,7 +87,7 @@ pub fn static_tabs_1<S: 'static + Send + Sync + Clone>() -> Vec<Tab<S>> {
]
}
pub fn static_tabs_2<S: 'static + Send + Sync + Clone>() -> Vec<Tab<S>> {
pub fn static_tabs_2() -> Vec<Tab> {
vec![
Tab::new("tab_bar.rs")
.title("tab_bar.rs".to_string())
@ -102,7 +102,7 @@ pub fn static_tabs_2<S: 'static + Send + Sync + Clone>() -> Vec<Tab<S>> {
]
}
pub fn static_tabs_3<S: 'static + Send + Sync + Clone>() -> Vec<Tab<S>> {
pub fn static_tabs_3() -> Vec<Tab> {
vec![Tab::new("static_tabs_3")
.git_status(GitStatus::Created)
.current(true)]
@ -325,7 +325,7 @@ pub fn static_players_with_call_status() -> Vec<PlayerWithCallStatus> {
]
}
pub fn static_new_notification_items<S: 'static + Send + Sync>() -> Vec<ListItem<S>> {
pub fn static_new_notification_items<S: 'static>() -> Vec<ListItem<S>> {
vec![
ListDetailsEntry::new("maxdeviant invited you to join a stream in #design.")
.meta("4 people in stream."),
@ -336,7 +336,7 @@ pub fn static_new_notification_items<S: 'static + Send + Sync>() -> Vec<ListItem
.collect()
}
pub fn static_read_notification_items<S: 'static + Send + Sync>() -> Vec<ListItem<S>> {
pub fn static_read_notification_items<S: 'static>() -> Vec<ListItem<S>> {
vec![
ListDetailsEntry::new("mikaylamaki added you as a contact.").actions(vec![
Button::new("Decline"),
@ -352,7 +352,7 @@ pub fn static_read_notification_items<S: 'static + Send + Sync>() -> Vec<ListIte
.collect()
}
pub fn static_project_panel_project_items<S: 'static + Send + Sync>() -> Vec<ListItem<S>> {
pub fn static_project_panel_project_items<S: 'static>() -> Vec<ListItem<S>> {
vec![
ListEntry::new(Label::new("zed"))
.left_icon(Icon::FolderOpen.into())
@ -479,7 +479,7 @@ pub fn static_project_panel_project_items<S: 'static + Send + Sync>() -> Vec<Lis
.collect()
}
pub fn static_project_panel_single_items<S: 'static + Send + Sync>() -> Vec<ListItem<S>> {
pub fn static_project_panel_single_items<S: 'static>() -> Vec<ListItem<S>> {
vec![
ListEntry::new(Label::new("todo.md"))
.left_icon(Icon::FileDoc.into())
@ -496,7 +496,7 @@ pub fn static_project_panel_single_items<S: 'static + Send + Sync>() -> Vec<List
.collect()
}
pub fn static_collab_panel_current_call<S: 'static + Send + Sync>() -> Vec<ListItem<S>> {
pub fn static_collab_panel_current_call<S: 'static>() -> Vec<ListItem<S>> {
vec![
ListEntry::new(Label::new("as-cii")).left_avatar("http://github.com/as-cii.png?s=50"),
ListEntry::new(Label::new("nathansobo"))
@ -509,7 +509,7 @@ pub fn static_collab_panel_current_call<S: 'static + Send + Sync>() -> Vec<ListI
.collect()
}
pub fn static_collab_panel_channels<S: 'static + Send + Sync>() -> Vec<ListItem<S>> {
pub fn static_collab_panel_channels<S: 'static>() -> Vec<ListItem<S>> {
vec![
ListEntry::new(Label::new("zed"))
.left_icon(Icon::Hash.into())
@ -573,7 +573,7 @@ pub fn static_collab_panel_channels<S: 'static + Send + Sync>() -> Vec<ListItem<
.collect()
}
pub fn example_editor_actions<S: 'static + Send + Sync>() -> Vec<PaletteItem<S>> {
pub fn example_editor_actions() -> Vec<PaletteItem> {
vec![
PaletteItem::new("New File").keybinding(Keybinding::new(
"N".to_string(),
@ -638,7 +638,7 @@ pub fn empty_editor_example(cx: &mut WindowContext) -> EditorPane {
)
}
pub fn empty_buffer_example<S: 'static + Send + Sync + Clone>() -> Buffer<S> {
pub fn empty_buffer_example() -> Buffer {
Buffer::new("empty-buffer").set_rows(Some(BufferRows::default()))
}
@ -663,9 +663,7 @@ pub fn hello_world_rust_editor_example(cx: &mut WindowContext) -> EditorPane {
)
}
pub fn hello_world_rust_buffer_example<S: 'static + Send + Sync + Clone>(
theme: &Theme,
) -> Buffer<S> {
pub fn hello_world_rust_buffer_example(theme: &Theme) -> Buffer {
Buffer::new("hello-world-rust-buffer")
.set_title("hello_world.rs".to_string())
.set_path("src/hello_world.rs".to_string())
@ -804,9 +802,7 @@ pub fn hello_world_rust_editor_with_status_example(cx: &mut WindowContext) -> Ed
)
}
pub fn hello_world_rust_buffer_with_status_example<S: 'static + Send + Sync + Clone>(
theme: &Theme,
) -> Buffer<S> {
pub fn hello_world_rust_buffer_with_status_example(theme: &Theme) -> Buffer {
Buffer::new("hello-world-rust-buffer-with-status")
.set_title("hello_world.rs".to_string())
.set_path("src/hello_world.rs".to_string())
@ -952,7 +948,7 @@ pub fn hello_world_rust_with_status_buffer_rows(theme: &Theme) -> Vec<BufferRow>
]
}
pub fn terminal_buffer<S: 'static + Send + Sync + Clone>(theme: &Theme) -> Buffer<S> {
pub fn terminal_buffer(theme: &Theme) -> Buffer {
Buffer::new("terminal")
.set_title("zed — fish".to_string())
.set_rows(Some(BufferRows {

View File

@ -5,7 +5,7 @@ use crate::prelude::*;
pub struct Story {}
impl Story {
pub fn container<S: 'static + Send + Sync>(cx: &mut ViewContext<S>) -> Div<S> {
pub fn container<S: 'static>(cx: &mut ViewContext<S>) -> Div<S> {
let theme = theme(cx);
div()
@ -18,10 +18,10 @@ impl Story {
.bg(theme.background)
}
pub fn title<S: 'static + Send + Sync>(
pub fn title<S: 'static>(
cx: &mut ViewContext<S>,
title: &str,
) -> impl Element<ViewState = S> {
) -> impl Component<S> {
let theme = theme(cx);
div()
@ -30,16 +30,14 @@ impl Story {
.child(title.to_owned())
}
pub fn title_for<S: 'static + Send + Sync, T>(
cx: &mut ViewContext<S>,
) -> impl Element<ViewState = S> {
pub fn title_for<S: 'static, T>(cx: &mut ViewContext<S>) -> impl Component<S> {
Self::title(cx, std::any::type_name::<T>())
}
pub fn label<S: 'static + Send + Sync>(
pub fn label<S: 'static>(
cx: &mut ViewContext<S>,
label: &str,
) -> impl Element<ViewState = S> {
) -> impl Component<S> {
let theme = theme(cx);
div()

View File

@ -1,5 +1,5 @@
use gpui2::{
AnyElement, Bounds, Element, Hsla, IntoAnyElement, LayoutId, Pixels, Result, ViewContext,
AnyElement, Bounds, Component, Element, Hsla, LayoutId, Pixels, Result, ViewContext,
WindowContext,
};
use serde::{de::Visitor, Deserialize, Deserializer};
@ -132,10 +132,11 @@ where
deserializer.deserialize_map(SyntaxVisitor)
}
pub fn themed<E, F>(theme: Theme, cx: &mut ViewContext<E::ViewState>, build_child: F) -> Themed<E>
pub fn themed<V, E, F>(theme: Theme, cx: &mut ViewContext<V>, build_child: F) -> Themed<E>
where
E: Element,
F: FnOnce(&mut ViewContext<E::ViewState>) -> E,
V: 'static,
E: Element<V>,
F: FnOnce(&mut ViewContext<V>) -> E,
{
cx.default_global::<ThemeStack>().0.push(theme.clone());
let child = build_child(cx);
@ -148,12 +149,13 @@ pub struct Themed<E> {
pub(crate) child: E,
}
impl<E> IntoAnyElement<E::ViewState> for Themed<E>
impl<V, E> Component<V> for Themed<E>
where
E: 'static + Element + Send + Sync,
V: 'static,
E: 'static + Element<V> + Send + Sync,
E::ElementState: Send + Sync,
{
fn into_any(self) -> AnyElement<E::ViewState> {
fn render(self) -> AnyElement<V> {
AnyElement::new(self)
}
}
@ -161,11 +163,11 @@ where
#[derive(Default)]
struct ThemeStack(Vec<Theme>);
impl<E: 'static + Element + Send + Sync> Element for Themed<E>
impl<V, E: 'static + Element<V> + Send + Sync> Element<V> for Themed<E>
where
V: 'static,
E::ElementState: Send + Sync,
{
type ViewState = E::ViewState;
type ElementState = E::ElementState;
fn id(&self) -> Option<gpui2::ElementId> {
@ -174,9 +176,9 @@ where
fn initialize(
&mut self,
view_state: &mut Self::ViewState,
view_state: &mut V,
element_state: Option<Self::ElementState>,
cx: &mut ViewContext<Self::ViewState>,
cx: &mut ViewContext<V>,
) -> Self::ElementState {
cx.default_global::<ThemeStack>().0.push(self.theme.clone());
let element_state = self.child.initialize(view_state, element_state, cx);
@ -186,9 +188,9 @@ where
fn layout(
&mut self,
view_state: &mut E::ViewState,
view_state: &mut V,
element_state: &mut Self::ElementState,
cx: &mut ViewContext<E::ViewState>,
cx: &mut ViewContext<V>,
) -> LayoutId
where
Self: Sized,
@ -202,9 +204,9 @@ where
fn paint(
&mut self,
bounds: Bounds<Pixels>,
view_state: &mut Self::ViewState,
view_state: &mut V,
frame_state: &mut Self::ElementState,
cx: &mut ViewContext<Self::ViewState>,
cx: &mut ViewContext<V>,
) where
Self: Sized,
{

Some files were not shown because too many files have changed in this diff Show More