diff --git a/crates/call/src/cross_platform/participant.rs b/crates/call/src/cross_platform/participant.rs index 669c0b45fc..16e0107158 100644 --- a/crates/call/src/cross_platform/participant.rs +++ b/crates/call/src/cross_platform/participant.rs @@ -40,6 +40,15 @@ pub struct LocalParticipant { pub role: proto::ChannelRole, } +impl LocalParticipant { + pub fn can_write(&self) -> bool { + matches!( + self.role, + proto::ChannelRole::Admin | proto::ChannelRole::Member + ) + } +} + pub struct RemoteParticipant { pub user: Arc, pub peer_id: proto::PeerId, @@ -57,4 +66,11 @@ impl RemoteParticipant { pub fn has_video_tracks(&self) -> bool { !self.video_tracks.is_empty() } + + pub fn can_write(&self) -> bool { + matches!( + self.role, + proto::ChannelRole::Admin | proto::ChannelRole::Member + ) + } } diff --git a/crates/call/src/macos/participant.rs b/crates/call/src/macos/participant.rs index 82d946a928..dc7527487f 100644 --- a/crates/call/src/macos/participant.rs +++ b/crates/call/src/macos/participant.rs @@ -39,6 +39,15 @@ pub struct LocalParticipant { pub role: proto::ChannelRole, } +impl LocalParticipant { + pub fn can_write(&self) -> bool { + matches!( + self.role, + proto::ChannelRole::Admin | proto::ChannelRole::Member + ) + } +} + #[derive(Clone, Debug)] pub struct RemoteParticipant { pub user: Arc, @@ -57,4 +66,11 @@ impl RemoteParticipant { pub fn has_video_tracks(&self) -> bool { !self.video_tracks.is_empty() } + + pub fn can_write(&self) -> bool { + matches!( + self.role, + proto::ChannelRole::Admin | proto::ChannelRole::Member + ) + } } diff --git a/crates/channel/src/channel_store_tests.rs b/crates/channel/src/channel_store_tests.rs index 11f618d196..ef657d3739 100644 --- a/crates/channel/src/channel_store_tests.rs +++ b/crates/channel/src/channel_store_tests.rs @@ -164,6 +164,8 @@ async fn test_channel_messages(cx: &mut TestAppContext) { id: 5, github_login: "nathansobo".into(), avatar_url: "http://avatar.com/nathansobo".into(), + name: None, + email: None, }], }, ); @@ -216,6 +218,8 @@ async fn test_channel_messages(cx: &mut TestAppContext) { id: 6, github_login: "maxbrunsfeld".into(), avatar_url: "http://avatar.com/maxbrunsfeld".into(), + name: None, + email: None, }], }, ); @@ -259,6 +263,8 @@ async fn test_channel_messages(cx: &mut TestAppContext) { id: 7, github_login: "as-cii".into(), avatar_url: "http://avatar.com/as-cii".into(), + name: None, + email: None, }], }, ); diff --git a/crates/client/src/user.rs b/crates/client/src/user.rs index e476ec8872..17da726954 100644 --- a/crates/client/src/user.rs +++ b/crates/client/src/user.rs @@ -43,6 +43,8 @@ pub struct User { pub id: UserId, pub github_login: String, pub avatar_uri: SharedUri, + pub name: Option, + pub email: Option, } #[derive(Clone, Debug, PartialEq, Eq)] @@ -798,6 +800,8 @@ impl User { id: message.id, github_login: message.github_login, avatar_uri: message.avatar_url.into(), + name: message.name, + email: message.email, }) } } diff --git a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql index a45307b831..fb2de18b63 100644 --- a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql +++ b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql @@ -3,6 +3,7 @@ CREATE TABLE "users" ( "github_login" VARCHAR, "admin" BOOLEAN, "email_address" VARCHAR(255) DEFAULT NULL, + "name" TEXT, "invite_code" VARCHAR(64), "invite_count" INTEGER NOT NULL DEFAULT 0, "inviter_id" INTEGER REFERENCES users (id), diff --git a/crates/collab/migrations/20250117100620_add_user_name.sql b/crates/collab/migrations/20250117100620_add_user_name.sql new file mode 100644 index 0000000000..fff7f95b60 --- /dev/null +++ b/crates/collab/migrations/20250117100620_add_user_name.sql @@ -0,0 +1 @@ +ALTER TABLE users ADD COLUMN name TEXT; diff --git a/crates/collab/src/api.rs b/crates/collab/src/api.rs index 7adf17ac06..2b8b5ac6fb 100644 --- a/crates/collab/src/api.rs +++ b/crates/collab/src/api.rs @@ -144,6 +144,7 @@ struct AuthenticatedUserParams { github_user_id: i32, github_login: String, github_email: Option, + github_name: Option, github_user_created_at: chrono::DateTime, } @@ -165,6 +166,7 @@ async fn get_authenticated_user( ¶ms.github_login, params.github_user_id, params.github_email.as_deref(), + params.github_name.as_deref(), params.github_user_created_at, initial_channel_id, ) diff --git a/crates/collab/src/api/contributors.rs b/crates/collab/src/api/contributors.rs index c3e60c25ca..51d0e060df 100644 --- a/crates/collab/src/api/contributors.rs +++ b/crates/collab/src/api/contributors.rs @@ -115,6 +115,7 @@ async fn add_contributor( ¶ms.github_login, params.github_user_id, params.github_email.as_deref(), + params.github_name.as_deref(), params.github_user_created_at, initial_channel_id, ) diff --git a/crates/collab/src/auth.rs b/crates/collab/src/auth.rs index 1d7edd8172..bd60ee0cd0 100644 --- a/crates/collab/src/auth.rs +++ b/crates/collab/src/auth.rs @@ -248,6 +248,7 @@ mod test { let user = db .create_user( "example@example.com", + None, false, NewUserParams { github_login: "example".into(), diff --git a/crates/collab/src/db/queries/channels.rs b/crates/collab/src/db/queries/channels.rs index b107358eff..da301090ca 100644 --- a/crates/collab/src/db/queries/channels.rs +++ b/crates/collab/src/db/queries/channels.rs @@ -726,6 +726,8 @@ impl Database { user.github_login ), github_login: user.github_login, + name: user.name, + email: user.email_address, }) } proto::ChannelMember { diff --git a/crates/collab/src/db/queries/contributors.rs b/crates/collab/src/db/queries/contributors.rs index fda39c34b4..dbb4231653 100644 --- a/crates/collab/src/db/queries/contributors.rs +++ b/crates/collab/src/db/queries/contributors.rs @@ -65,6 +65,7 @@ impl Database { github_login: &str, github_user_id: i32, github_email: Option<&str>, + github_name: Option<&str>, github_user_created_at: DateTimeUtc, initial_channel_id: Option, ) -> Result<()> { @@ -74,6 +75,7 @@ impl Database { github_login, github_user_id, github_email, + github_name, github_user_created_at.naive_utc(), initial_channel_id, &tx, diff --git a/crates/collab/src/db/queries/users.rs b/crates/collab/src/db/queries/users.rs index 4443d75154..12aa97b41a 100644 --- a/crates/collab/src/db/queries/users.rs +++ b/crates/collab/src/db/queries/users.rs @@ -7,6 +7,7 @@ impl Database { pub async fn create_user( &self, email_address: &str, + name: Option<&str>, admin: bool, params: NewUserParams, ) -> Result { @@ -14,6 +15,7 @@ impl Database { let tx = tx; let user = user::Entity::insert(user::ActiveModel { email_address: ActiveValue::set(Some(email_address.into())), + name: ActiveValue::set(name.map(|s| s.into())), github_login: ActiveValue::set(params.github_login.clone()), github_user_id: ActiveValue::set(params.github_user_id), admin: ActiveValue::set(admin), @@ -101,6 +103,7 @@ impl Database { github_login: &str, github_user_id: i32, github_email: Option<&str>, + github_name: Option<&str>, github_user_created_at: DateTimeUtc, initial_channel_id: Option, ) -> Result { @@ -109,6 +112,7 @@ impl Database { github_login, github_user_id, github_email, + github_name, github_user_created_at.naive_utc(), initial_channel_id, &tx, @@ -118,11 +122,13 @@ impl Database { .await } + #[allow(clippy::too_many_arguments)] pub async fn get_or_create_user_by_github_account_tx( &self, github_login: &str, github_user_id: i32, github_email: Option<&str>, + github_name: Option<&str>, github_user_created_at: NaiveDateTime, initial_channel_id: Option, tx: &DatabaseTransaction, @@ -150,6 +156,7 @@ impl Database { } else { let user = user::Entity::insert(user::ActiveModel { email_address: ActiveValue::set(github_email.map(|email| email.into())), + name: ActiveValue::set(github_name.map(|name| name.into())), github_login: ActiveValue::set(github_login.into()), github_user_id: ActiveValue::set(github_user_id), github_user_created_at: ActiveValue::set(Some(github_user_created_at)), diff --git a/crates/collab/src/db/tables/user.rs b/crates/collab/src/db/tables/user.rs index 3b66225af8..65f6604b29 100644 --- a/crates/collab/src/db/tables/user.rs +++ b/crates/collab/src/db/tables/user.rs @@ -13,6 +13,7 @@ pub struct Model { pub github_user_id: i32, pub github_user_created_at: Option, pub email_address: Option, + pub name: Option, pub admin: bool, pub invite_code: Option, pub invite_count: i32, diff --git a/crates/collab/src/db/tests.rs b/crates/collab/src/db/tests.rs index 6705a5c832..f0d9b9211d 100644 --- a/crates/collab/src/db/tests.rs +++ b/crates/collab/src/db/tests.rs @@ -177,6 +177,7 @@ static GITHUB_USER_ID: AtomicI32 = AtomicI32::new(5); async fn new_test_user(db: &Arc, email: &str) -> UserId { db.create_user( email, + None, false, NewUserParams { github_login: email[0..email.find('@').unwrap()].to_string(), diff --git a/crates/collab/src/db/tests/buffer_tests.rs b/crates/collab/src/db/tests/buffer_tests.rs index 9575ed505b..63986b8efc 100644 --- a/crates/collab/src/db/tests/buffer_tests.rs +++ b/crates/collab/src/db/tests/buffer_tests.rs @@ -13,6 +13,7 @@ async fn test_channel_buffers(db: &Arc) { let a_id = db .create_user( "user_a@example.com", + None, false, NewUserParams { github_login: "user_a".into(), @@ -25,6 +26,7 @@ async fn test_channel_buffers(db: &Arc) { let b_id = db .create_user( "user_b@example.com", + None, false, NewUserParams { github_login: "user_b".into(), @@ -39,6 +41,7 @@ async fn test_channel_buffers(db: &Arc) { let c_id = db .create_user( "user_c@example.com", + None, false, NewUserParams { github_login: "user_c".into(), @@ -176,6 +179,7 @@ async fn test_channel_buffers_last_operations(db: &Database) { let user_id = db .create_user( "user_a@example.com", + None, false, NewUserParams { github_login: "user_a".into(), @@ -188,6 +192,7 @@ async fn test_channel_buffers_last_operations(db: &Database) { let observer_id = db .create_user( "user_b@example.com", + None, false, NewUserParams { github_login: "user_b".into(), diff --git a/crates/collab/src/db/tests/channel_tests.rs b/crates/collab/src/db/tests/channel_tests.rs index d409867447..387454a784 100644 --- a/crates/collab/src/db/tests/channel_tests.rs +++ b/crates/collab/src/db/tests/channel_tests.rs @@ -269,6 +269,7 @@ async fn test_channel_renames(db: &Arc) { let user_1 = db .create_user( "user1@example.com", + None, false, NewUserParams { github_login: "user1".into(), @@ -282,6 +283,7 @@ async fn test_channel_renames(db: &Arc) { let user_2 = db .create_user( "user2@example.com", + None, false, NewUserParams { github_login: "user2".into(), @@ -318,6 +320,7 @@ async fn test_db_channel_moving(db: &Arc) { let a_id = db .create_user( "user1@example.com", + None, false, NewUserParams { github_login: "user1".into(), @@ -372,6 +375,7 @@ async fn test_db_channel_moving_bugs(db: &Arc) { let user_id = db .create_user( "user1@example.com", + None, false, NewUserParams { github_login: "user1".into(), diff --git a/crates/collab/src/db/tests/contributor_tests.rs b/crates/collab/src/db/tests/contributor_tests.rs index aaeb2301e4..d1ff516d1a 100644 --- a/crates/collab/src/db/tests/contributor_tests.rs +++ b/crates/collab/src/db/tests/contributor_tests.rs @@ -13,6 +13,7 @@ test_both_dbs!( async fn test_contributors(db: &Arc) { db.create_user( "user1@example.com", + None, false, NewUserParams { github_login: "user1".to_string(), @@ -25,7 +26,7 @@ async fn test_contributors(db: &Arc) { assert_eq!(db.get_contributors().await.unwrap(), Vec::::new()); let user1_created_at = Utc::now(); - db.add_contributor("user1", 1, None, user1_created_at, None) + db.add_contributor("user1", 1, None, None, user1_created_at, None) .await .unwrap(); assert_eq!( @@ -34,7 +35,7 @@ async fn test_contributors(db: &Arc) { ); let user2_created_at = Utc::now(); - db.add_contributor("user2", 2, None, user2_created_at, None) + db.add_contributor("user2", 2, None, None, user2_created_at, None) .await .unwrap(); assert_eq!( diff --git a/crates/collab/src/db/tests/db_tests.rs b/crates/collab/src/db/tests/db_tests.rs index cd3a194357..3fa69f3bf6 100644 --- a/crates/collab/src/db/tests/db_tests.rs +++ b/crates/collab/src/db/tests/db_tests.rs @@ -17,6 +17,7 @@ async fn test_get_users(db: &Arc) { let user = db .create_user( &format!("user{i}@example.com"), + None, false, NewUserParams { github_login: format!("user{i}"), @@ -79,6 +80,7 @@ test_both_dbs!( async fn test_get_or_create_user_by_github_account(db: &Arc) { db.create_user( "user1@example.com", + None, false, NewUserParams { github_login: "login1".into(), @@ -90,6 +92,7 @@ async fn test_get_or_create_user_by_github_account(db: &Arc) { let user_id2 = db .create_user( "user2@example.com", + None, false, NewUserParams { github_login: "login2".into(), @@ -101,7 +104,7 @@ async fn test_get_or_create_user_by_github_account(db: &Arc) { .user_id; let user = db - .get_or_create_user_by_github_account("the-new-login2", 102, None, Utc::now(), None) + .get_or_create_user_by_github_account("the-new-login2", 102, None, None, Utc::now(), None) .await .unwrap(); assert_eq!(user.id, user_id2); @@ -113,6 +116,7 @@ async fn test_get_or_create_user_by_github_account(db: &Arc) { "login3", 103, Some("user3@example.com"), + None, Utc::now(), None, ) @@ -133,6 +137,7 @@ async fn test_create_access_tokens(db: &Arc) { let user_1 = db .create_user( "u1@example.com", + None, false, NewUserParams { github_login: "u1".into(), @@ -145,6 +150,7 @@ async fn test_create_access_tokens(db: &Arc) { let user_2 = db .create_user( "u2@example.com", + None, false, NewUserParams { github_login: "u2".into(), @@ -296,6 +302,7 @@ async fn test_add_contacts(db: &Arc) { user_ids.push( db.create_user( &format!("user{i}@example.com"), + None, false, NewUserParams { github_login: format!("user{i}"), @@ -457,6 +464,7 @@ async fn test_metrics_id(db: &Arc) { } = db .create_user( "person1@example.com", + None, false, NewUserParams { github_login: "person1".into(), @@ -472,6 +480,7 @@ async fn test_metrics_id(db: &Arc) { } = db .create_user( "person2@example.com", + None, false, NewUserParams { github_login: "person2".into(), @@ -500,6 +509,7 @@ async fn test_project_count(db: &Arc) { let user1 = db .create_user( "admin@example.com", + None, true, NewUserParams { github_login: "admin".into(), @@ -511,6 +521,7 @@ async fn test_project_count(db: &Arc) { let user2 = db .create_user( "user@example.com", + None, false, NewUserParams { github_login: "user".into(), @@ -588,6 +599,7 @@ async fn test_fuzzy_search_users(cx: &mut gpui::TestAppContext) { { db.create_user( &format!("{github_login}@example.com"), + None, false, NewUserParams { github_login: github_login.into(), diff --git a/crates/collab/src/db/tests/feature_flag_tests.rs b/crates/collab/src/db/tests/feature_flag_tests.rs index 972b45e1bc..0e68dcc941 100644 --- a/crates/collab/src/db/tests/feature_flag_tests.rs +++ b/crates/collab/src/db/tests/feature_flag_tests.rs @@ -15,6 +15,7 @@ async fn test_get_user_flags(db: &Arc) { let user_1 = db .create_user( "user1@example.com", + None, false, NewUserParams { github_login: "user1".to_string(), @@ -28,6 +29,7 @@ async fn test_get_user_flags(db: &Arc) { let user_2 = db .create_user( "user2@example.com", + None, false, NewUserParams { github_login: "user2".to_string(), diff --git a/crates/collab/src/db/tests/user_tests.rs b/crates/collab/src/db/tests/user_tests.rs index e2ef1eeba4..bb2dac1f77 100644 --- a/crates/collab/src/db/tests/user_tests.rs +++ b/crates/collab/src/db/tests/user_tests.rs @@ -16,6 +16,7 @@ async fn test_accepted_tos(db: &Arc) { let user_id = db .create_user( "user1@example.com", + None, false, NewUserParams { github_login: "user1".to_string(), diff --git a/crates/collab/src/rate_limiter.rs b/crates/collab/src/rate_limiter.rs index 2c2364d7a3..21f8d0318f 100644 --- a/crates/collab/src/rate_limiter.rs +++ b/crates/collab/src/rate_limiter.rs @@ -189,6 +189,7 @@ mod tests { let user_1 = db .create_user( "user-1@zed.dev", + None, false, NewUserParams { github_login: "user-1".into(), @@ -201,6 +202,7 @@ mod tests { let user_2 = db .create_user( "user-2@zed.dev", + None, false, NewUserParams { github_login: "user-2".into(), diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 37d3d15df7..f7ef98a28f 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -2418,6 +2418,8 @@ async fn get_users( id: user.id.to_proto(), avatar_url: format!("https://github.com/{}.png?size=128", user.github_login), github_login: user.github_login, + email: user.email_address, + name: user.name, }) .collect(); response.send(proto::UsersResponse { users })?; @@ -2449,6 +2451,8 @@ async fn fuzzy_search_users( id: user.id.to_proto(), avatar_url: format!("https://github.com/{}.png?size=128", user.github_login), github_login: user.github_login, + name: user.name, + email: user.email_address, }) .collect(); response.send(proto::UsersResponse { users })?; diff --git a/crates/collab/src/seed.rs b/crates/collab/src/seed.rs index 5de6515ae3..d57fc9a256 100644 --- a/crates/collab/src/seed.rs +++ b/crates/collab/src/seed.rs @@ -16,6 +16,7 @@ struct GithubUser { id: i32, login: String, email: Option, + name: Option, created_at: DateTime, } @@ -75,6 +76,7 @@ pub async fn seed(config: &Config, db: &Database, force: bool) -> anyhow::Result let user = db .create_user( &user.email.unwrap_or(format!("{admin_login}@example.com")), + user.name.as_deref(), true, NewUserParams { github_login: user.login, @@ -129,6 +131,7 @@ pub async fn seed(config: &Config, db: &Database, force: bool) -> anyhow::Result &github_user.login, github_user.id, github_user.email.as_deref(), + github_user.name.as_deref(), github_user.created_at, None, ) @@ -152,14 +155,20 @@ fn load_admins(path: impl AsRef) -> anyhow::Result { } async fn fetch_github(client: &reqwest::Client, url: &str) -> T { - let response = client - .get(url) + let mut request_builder = client.get(url); + if let Ok(github_token) = std::env::var("GITHUB_TOKEN") { + request_builder = + request_builder.header("Authorization", format!("Bearer {}", github_token)); + } + let response = request_builder .header("user-agent", "zed") .send() .await .unwrap_or_else(|error| panic!("failed to fetch '{url}': {error}")); - response - .json() - .await - .unwrap_or_else(|error| panic!("failed to deserialize github user from '{url}': {error}")) + let response_text = response.text().await.unwrap_or_else(|error| { + panic!("failed to fetch '{url}': {error}"); + }); + serde_json::from_str(&response_text).unwrap_or_else(|error| { + panic!("failed to deserialize github user from '{url}'. Error: '{error}', text: '{response_text}'"); + }) } diff --git a/crates/collab/src/tests/channel_guest_tests.rs b/crates/collab/src/tests/channel_guest_tests.rs index 22da44a324..ebde1d9e50 100644 --- a/crates/collab/src/tests/channel_guest_tests.rs +++ b/crates/collab/src/tests/channel_guest_tests.rs @@ -174,7 +174,7 @@ async fn test_channel_requires_zed_cla(cx_a: &mut TestAppContext, cx_b: &mut Tes server .app_state .db - .get_or_create_user_by_github_account("user_b", 100, None, Utc::now(), None) + .get_or_create_user_by_github_account("user_b", 100, None, None, Utc::now(), None) .await .unwrap(); @@ -278,7 +278,7 @@ async fn test_channel_requires_zed_cla(cx_a: &mut TestAppContext, cx_b: &mut Tes server .app_state .db - .add_contributor("user_b", 100, None, Utc::now(), None) + .add_contributor("user_b", 100, None, None, Utc::now(), None) .await .unwrap(); diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 988ac3bcd9..37fff19a03 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -1840,6 +1840,8 @@ async fn test_active_call_events( id: client_a.user_id().unwrap(), github_login: "user_a".to_string(), avatar_uri: "avatar_a".into(), + name: None, + email: None, }), project_id: project_a_id, worktree_root_names: vec!["a".to_string()], @@ -1858,6 +1860,8 @@ async fn test_active_call_events( id: client_b.user_id().unwrap(), github_login: "user_b".to_string(), avatar_uri: "avatar_b".into(), + name: None, + email: None, }), project_id: project_b_id, worktree_root_names: vec!["b".to_string()] diff --git a/crates/collab/src/tests/randomized_test_helpers.rs b/crates/collab/src/tests/randomized_test_helpers.rs index 7bf1034cea..aee83f0411 100644 --- a/crates/collab/src/tests/randomized_test_helpers.rs +++ b/crates/collab/src/tests/randomized_test_helpers.rs @@ -220,6 +220,7 @@ impl TestPlan { .db .create_user( &format!("{username}@example.com"), + None, false, NewUserParams { github_login: username.clone(), diff --git a/crates/collab/src/tests/test_server.rs b/crates/collab/src/tests/test_server.rs index 39236f168d..2e246b1408 100644 --- a/crates/collab/src/tests/test_server.rs +++ b/crates/collab/src/tests/test_server.rs @@ -186,6 +186,7 @@ impl TestServer { .db .create_user( &format!("{name}@example.com"), + None, false, NewUserParams { github_login: name.into(), diff --git a/crates/collab/src/user_backfiller.rs b/crates/collab/src/user_backfiller.rs index 842128361c..277e9dc80e 100644 --- a/crates/collab/src/user_backfiller.rs +++ b/crates/collab/src/user_backfiller.rs @@ -86,6 +86,7 @@ impl UserBackfiller { &user.github_login, github_user.id, user.email_address.as_deref(), + user.name.as_deref(), github_user.created_at, initial_channel_id, ) @@ -159,4 +160,5 @@ impl UserBackfiller { struct GithubUser { id: i32, created_at: DateTime, + name: Option, } diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index f8cae3b1cd..daa1f1440d 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -1194,6 +1194,8 @@ mod tests { github_login: "fgh".into(), avatar_uri: "avatar_fgh".into(), id: 103, + name: None, + email: None, }), nonce: 5, mentions: vec![(ranges[0].clone(), 101), (ranges[1].clone(), 102)], @@ -1248,6 +1250,8 @@ mod tests { github_login: "fgh".into(), avatar_uri: "avatar_fgh".into(), id: 103, + name: None, + email: None, }), nonce: 5, mentions: Vec::new(), @@ -1295,6 +1299,8 @@ mod tests { github_login: "fgh".into(), avatar_uri: "avatar_fgh".into(), id: 103, + name: None, + email: None, }), nonce: 5, mentions: Vec::new(), diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index 61dd8f2927..6cde10bdfb 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -2,6 +2,7 @@ use crate::git_panel_settings::StatusStyle; use crate::{git_panel_settings::GitPanelSettings, git_status_icon}; use anyhow::{Context as _, Result}; use db::kvp::KEY_VALUE_STORE; +use editor::actions::MoveToEnd; use editor::scroll::ScrollbarAutoHide; use editor::{Editor, EditorSettings, ShowScrollbar}; use futures::channel::mpsc; @@ -39,7 +40,8 @@ actions!( OpenMenu, OpenSelected, FocusEditor, - FocusChanges + FocusChanges, + FillCoAuthors, ] ); @@ -85,6 +87,7 @@ pub struct GitPanel { fs: Arc, hide_scrollbar_task: Option>, pending_serialization: Task>, + workspace: WeakView, project: Model, scroll_handle: UniformListScrollHandle, scrollbar_state: ScrollbarState, @@ -154,8 +157,8 @@ impl GitPanel { let current_commit_message = git_state .as_ref() .map(|git_state| git_state.read(cx).commit_message.clone()); - let (err_sender, mut err_receiver) = mpsc::channel(1); + let workspace = cx.view().downgrade(); let git_panel = cx.new_view(|cx: &mut ViewContext| { let focus_handle = cx.focus_handle(); @@ -345,6 +348,7 @@ impl GitPanel { project, reveal_in_editor: Task::ready(()), err_sender, + workspace, }; git_panel.schedule_update(); git_panel.show_scrollbar = git_panel.should_show_scrollbar(cx); @@ -732,6 +736,70 @@ impl GitPanel { .update(cx, |editor, cx| editor.set_text("", cx)); } + fn fill_co_authors(&mut self, _: &FillCoAuthors, cx: &mut ViewContext) { + const CO_AUTHOR_PREFIX: &str = "co-authored-by: "; + + let Some(room) = self + .workspace + .upgrade() + .and_then(|workspace| workspace.read(cx).active_call()?.read(cx).room().cloned()) + else { + return; + }; + + let mut existing_text = self.commit_editor.read(cx).text(cx); + existing_text.make_ascii_lowercase(); + let mut ends_with_co_authors = false; + let existing_co_authors = existing_text + .lines() + .filter_map(|line| { + let line = line.trim(); + if line.starts_with(CO_AUTHOR_PREFIX) { + ends_with_co_authors = true; + Some(line) + } else { + ends_with_co_authors = false; + None + } + }) + .collect::>(); + + let new_co_authors = room + .read(cx) + .remote_participants() + .values() + .filter(|participant| participant.can_write()) + .map(|participant| participant.user.clone()) + .filter_map(|user| { + let email = user.email.as_deref()?; + let name = user.name.as_deref().unwrap_or(&user.github_login); + Some(format!("{CO_AUTHOR_PREFIX}{name} <{email}>")) + }) + .filter(|co_author| { + !existing_co_authors.contains(co_author.to_ascii_lowercase().as_str()) + }) + .collect::>(); + if new_co_authors.is_empty() { + return; + } + + self.commit_editor.update(cx, |editor, cx| { + let editor_end = editor.buffer().read(cx).read(cx).len(); + let mut edit = String::new(); + if !ends_with_co_authors { + edit.push('\n'); + } + for co_author in new_co_authors { + edit.push('\n'); + edit.push_str(&co_author); + } + + editor.edit(Some((editor_end..editor_end, edit)), cx); + editor.move_to_end(&MoveToEnd, cx); + editor.focus(cx); + }); + } + fn no_entries(&self, cx: &mut ViewContext) -> bool { self.git_state(cx) .map_or(true, |git_state| git_state.read(cx).entry_count() == 0) @@ -1332,6 +1400,19 @@ impl GitPanel { impl Render for GitPanel { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { let project = self.project.read(cx); + let has_co_authors = self + .workspace + .upgrade() + .and_then(|workspace| workspace.read(cx).active_call()?.read(cx).room().cloned()) + .map(|room| { + let room = room.read(cx); + room.local_participant().can_write() + && room + .remote_participants() + .values() + .any(|remote_participant| remote_participant.can_write()) + }) + .unwrap_or(false); v_flex() .id("git_panel") @@ -1363,6 +1444,9 @@ impl Render for GitPanel { .on_action(cx.listener(Self::focus_changes_list)) .on_action(cx.listener(Self::focus_editor)) .on_action(cx.listener(Self::toggle_staged_for_selected)) + .when(has_co_authors, |git_panel| { + git_panel.on_action(cx.listener(Self::fill_co_authors)) + }) // .on_action(cx.listener(|this, &OpenSelected, cx| this.open_selected(&OpenSelected, cx))) .on_hover(cx.listener(|this, hovered, cx| { if *hovered { diff --git a/crates/proto/proto/zed.proto b/crates/proto/proto/zed.proto index cd7ef7955c..137dba7bcf 100644 --- a/crates/proto/proto/zed.proto +++ b/crates/proto/proto/zed.proto @@ -1750,6 +1750,8 @@ message User { uint64 id = 1; string github_login = 2; string avatar_url = 3; + optional string email = 4; + optional string name = 5; } message File { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index e4488650ec..e69b6d9353 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -4085,7 +4085,7 @@ impl Workspace { } } - fn active_call(&self) -> Option<&Model> { + pub fn active_call(&self) -> Option<&Model> { self.active_call.as_ref().map(|(call, _)| call) }