From a3bc48261e49d7cb483c5371fade67cc568ebc9d Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 8 Jan 2024 12:23:40 -0700 Subject: [PATCH 01/78] First pass of real access control Co-Authored-By: Max --- crates/collab/src/db/ids.rs | 8 ++ crates/collab/src/db/queries/projects.rs | 38 ++++++++ crates/collab/src/rpc.rs | 86 ++++++++++++------- .../collab/src/tests/channel_guest_tests.rs | 10 ++- 4 files changed, 112 insertions(+), 30 deletions(-) diff --git a/crates/collab/src/db/ids.rs b/crates/collab/src/db/ids.rs index 9bb766147f5b0b2084b76665262962179a62e6eb..2e2218c4d9da181719cf6404245a4d84d4285dab 100644 --- a/crates/collab/src/db/ids.rs +++ b/crates/collab/src/db/ids.rs @@ -140,6 +140,14 @@ impl ChannelRole { Guest | Banned => false, } } + + pub fn can_edit_projects(&self) -> bool { + use ChannelRole::*; + match self { + Admin | Member => true, + Guest | Banned => false, + } + } } impl From for ChannelRole { diff --git a/crates/collab/src/db/queries/projects.rs b/crates/collab/src/db/queries/projects.rs index 5b8d54f8d36a746b2c09a77cbf9e75b77b557543..ca59c851e738865c6ffc50c970490fa3e2a93d1f 100644 --- a/crates/collab/src/db/queries/projects.rs +++ b/crates/collab/src/db/queries/projects.rs @@ -777,6 +777,44 @@ impl Database { .await } + pub async fn host_for_mutating_project_request( + &self, + project_id: ProjectId, + connection_id: ConnectionId, + ) -> Result { + let room_id = self.room_id_for_project(project_id).await?; + self.room_transaction(room_id, |tx| async move { + let current_participant = room_participant::Entity::find() + .filter(room_participant::Column::RoomId.eq(room_id)) + .filter(room_participant::Column::AnsweringConnectionId.eq(connection_id.id)) + .one(&*tx) + .await? + .ok_or_else(|| anyhow!("no such room"))?; + + if !current_participant + .role + .unwrap_or(ChannelRole::Guest) + .can_edit_projects() + { + Err(anyhow!("not authorized to edit projects"))?; + } + + let host = project_collaborator::Entity::find() + .filter( + project_collaborator::Column::ProjectId + .eq(project_id) + .and(project_collaborator::Column::IsHost.eq(true)), + ) + .one(&*tx) + .await? + .ok_or_else(|| anyhow!("failed to read project host"))?; + + Ok(host.connection()) + }) + .await + .map(|guard| guard.into_inner()) + } + pub async fn project_collaborators( &self, project_id: ProjectId, diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 835b48809da94dc60cd872d473e564a7456da81e..68774c22e6ec67782c76c6878dad974dfd7db984 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -217,39 +217,43 @@ impl Server { .add_message_handler(update_diagnostic_summary) .add_message_handler(update_worktree_settings) .add_message_handler(refresh_inlay_hints) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) - .add_request_handler(forward_project_request::) + .add_request_handler(forward_read_only_project_request::) + .add_request_handler(forward_read_only_project_request::) + .add_request_handler(forward_read_only_project_request::) + .add_request_handler(forward_read_only_project_request::) + .add_request_handler(forward_read_only_project_request::) + .add_request_handler(forward_read_only_project_request::) + .add_request_handler(forward_read_only_project_request::) + .add_request_handler(forward_read_only_project_request::) + .add_request_handler(forward_read_only_project_request::) + .add_request_handler(forward_read_only_project_request::) + .add_request_handler(forward_read_only_project_request::) + .add_request_handler(forward_mutating_project_request::) + .add_request_handler(forward_mutating_project_request::) + .add_request_handler( + forward_mutating_project_request::, + ) + .add_request_handler( + forward_mutating_project_request::, + ) + .add_request_handler(forward_mutating_project_request::) + .add_request_handler(forward_mutating_project_request::) + .add_request_handler(forward_mutating_project_request::) + .add_request_handler(forward_mutating_project_request::) + .add_request_handler(forward_mutating_project_request::) + .add_request_handler(forward_mutating_project_request::) + .add_request_handler(forward_mutating_project_request::) + .add_request_handler(forward_mutating_project_request::) + .add_request_handler(forward_mutating_project_request::) + .add_request_handler(forward_mutating_project_request::) + .add_request_handler(forward_mutating_project_request::) + .add_request_handler(forward_mutating_project_request::) + .add_request_handler(forward_mutating_project_request::) .add_message_handler(create_buffer_for_peer) .add_request_handler(update_buffer) .add_message_handler(update_buffer_file) .add_message_handler(buffer_reloaded) .add_message_handler(buffer_saved) - .add_request_handler(forward_project_request::) .add_request_handler(get_users) .add_request_handler(fuzzy_search_users) .add_request_handler(request_contact) @@ -1741,7 +1745,7 @@ async fn update_language_server( Ok(()) } -async fn forward_project_request( +async fn forward_read_only_project_request( request: T, response: Response, session: Session, @@ -1772,6 +1776,30 @@ where Ok(()) } +async fn forward_mutating_project_request( + request: T, + response: Response, + session: Session, +) -> Result<()> +where + T: EntityMessage + RequestMessage, +{ + let project_id = ProjectId::from_proto(request.remote_entity_id()); + let host_connection_id = session + .db() + .await + .host_for_mutating_project_request(project_id, session.connection_id) + .await?; + + let payload = session + .peer + .forward_request(session.connection_id, host_connection_id, request) + .await?; + + response.send(payload)?; + Ok(()) +} + async fn create_buffer_for_peer( request: proto::CreateBufferForPeer, session: Session, diff --git a/crates/collab/src/tests/channel_guest_tests.rs b/crates/collab/src/tests/channel_guest_tests.rs index e2051c44a038ced0724b616ac4e3b4cd851b4508..32cc074ec96a11e3806a256eedd8f74191689ace 100644 --- a/crates/collab/src/tests/channel_guest_tests.rs +++ b/crates/collab/src/tests/channel_guest_tests.rs @@ -82,5 +82,13 @@ async fn test_channel_guests( project_b.read_with(cx_b, |project, _| project.remote_id()), Some(project_id), ); - assert!(project_b.read_with(cx_b, |project, _| project.is_read_only())) + assert!(project_b.read_with(cx_b, |project, _| project.is_read_only())); + + assert!(project_b + .update(cx_b, |project, cx| { + let worktree_id = project.worktrees().next().unwrap().read(cx).id(); + project.create_entry((worktree_id, "b.txt"), false, cx) + }) + .await + .is_err()) } From 18b31f1552db6f7a25ae8011fe5ffd467095cd79 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 8 Jan 2024 12:04:59 -0800 Subject: [PATCH 02/78] Check user is host for host-broadcasted project messages --- crates/collab/src/db/queries/projects.rs | 28 ++++++++ crates/collab/src/rpc.rs | 92 +++++------------------- crates/rpc/src/macros.rs | 4 +- crates/rpc/src/proto.rs | 5 +- 4 files changed, 51 insertions(+), 78 deletions(-) diff --git a/crates/collab/src/db/queries/projects.rs b/crates/collab/src/db/queries/projects.rs index ca59c851e738865c6ffc50c970490fa3e2a93d1f..04c77c80771b29a6be3d1603898b681da3e79569 100644 --- a/crates/collab/src/db/queries/projects.rs +++ b/crates/collab/src/db/queries/projects.rs @@ -777,6 +777,34 @@ impl Database { .await } + pub async fn check_user_is_project_host( + &self, + project_id: ProjectId, + connection_id: ConnectionId, + ) -> Result<()> { + let room_id = self.room_id_for_project(project_id).await?; + self.room_transaction(room_id, |tx| async move { + project_collaborator::Entity::find() + .filter( + Condition::all() + .add(project_collaborator::Column::ProjectId.eq(project_id)) + .add(project_collaborator::Column::IsHost.eq(true)) + .add(project_collaborator::Column::ConnectionId.eq(connection_id.id)) + .add( + project_collaborator::Column::ConnectionServerId + .eq(connection_id.owner_id), + ), + ) + .one(&*tx) + .await? + .ok_or_else(|| anyhow!("failed to read project host"))?; + + Ok(()) + }) + .await + .map(|guard| guard.into_inner()) + } + pub async fn host_for_mutating_project_request( &self, project_id: ProjectId, diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 68774c22e6ec67782c76c6878dad974dfd7db984..572670d78f4dca73cff7e57b8d0f0b030d9bdd11 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -42,7 +42,7 @@ use prometheus::{register_int_gauge, IntGauge}; use rpc::{ proto::{ self, Ack, AnyTypedEnvelope, EntityMessage, EnvelopedMessage, LiveKitConnectionInfo, - RequestMessage, UpdateChannelBufferCollaborators, + RequestMessage, ShareProject, UpdateChannelBufferCollaborators, }, Connection, ConnectionId, Peer, Receipt, TypedEnvelope, }; @@ -216,7 +216,6 @@ impl Server { .add_message_handler(update_language_server) .add_message_handler(update_diagnostic_summary) .add_message_handler(update_worktree_settings) - .add_message_handler(refresh_inlay_hints) .add_request_handler(forward_read_only_project_request::) .add_request_handler(forward_read_only_project_request::) .add_request_handler(forward_read_only_project_request::) @@ -251,9 +250,11 @@ impl Server { .add_request_handler(forward_mutating_project_request::) .add_message_handler(create_buffer_for_peer) .add_request_handler(update_buffer) - .add_message_handler(update_buffer_file) - .add_message_handler(buffer_reloaded) - .add_message_handler(buffer_saved) + .add_message_handler(broadcast_project_message_from_host::) + .add_message_handler(broadcast_project_message_from_host::) + .add_message_handler(broadcast_project_message_from_host::) + .add_message_handler(broadcast_project_message_from_host::) + .add_message_handler(broadcast_project_message_from_host::) .add_request_handler(get_users) .add_request_handler(fuzzy_search_users) .add_request_handler(request_contact) @@ -285,7 +286,6 @@ impl Server { .add_request_handler(follow) .add_message_handler(unfollow) .add_message_handler(update_followers) - .add_message_handler(update_diff_base) .add_request_handler(get_private_user_info) .add_message_handler(acknowledge_channel_message) .add_message_handler(acknowledge_buffer_version); @@ -1697,10 +1697,6 @@ async fn update_worktree_settings( Ok(()) } -async fn refresh_inlay_hints(request: proto::RefreshInlayHints, session: Session) -> Result<()> { - broadcast_project_message(request.project_id, request, session).await -} - async fn start_language_server( request: proto::StartLanguageServer, session: Session, @@ -1804,6 +1800,14 @@ async fn create_buffer_for_peer( request: proto::CreateBufferForPeer, session: Session, ) -> Result<()> { + session + .db() + .await + .check_user_is_project_host( + ProjectId::from_proto(request.project_id), + session.connection_id, + ) + .await?; let peer_id = request.peer_id.ok_or_else(|| anyhow!("invalid peer id"))?; session .peer @@ -1856,60 +1860,17 @@ async fn update_buffer( Ok(()) } -async fn update_buffer_file(request: proto::UpdateBufferFile, session: Session) -> Result<()> { - let project_id = ProjectId::from_proto(request.project_id); - let project_connection_ids = session - .db() - .await - .project_connection_ids(project_id, session.connection_id) - .await?; - - broadcast( - Some(session.connection_id), - project_connection_ids.iter().copied(), - |connection_id| { - session - .peer - .forward_send(session.connection_id, connection_id, request.clone()) - }, - ); - Ok(()) -} - -async fn buffer_reloaded(request: proto::BufferReloaded, session: Session) -> Result<()> { - let project_id = ProjectId::from_proto(request.project_id); - let project_connection_ids = session - .db() - .await - .project_connection_ids(project_id, session.connection_id) - .await?; - broadcast( - Some(session.connection_id), - project_connection_ids.iter().copied(), - |connection_id| { - session - .peer - .forward_send(session.connection_id, connection_id, request.clone()) - }, - ); - Ok(()) -} - -async fn buffer_saved(request: proto::BufferSaved, session: Session) -> Result<()> { - broadcast_project_message(request.project_id, request, session).await -} - -async fn broadcast_project_message( - project_id: u64, +async fn broadcast_project_message_from_host>( request: T, session: Session, ) -> Result<()> { - let project_id = ProjectId::from_proto(project_id); + let project_id = ProjectId::from_proto(request.remote_entity_id()); let project_connection_ids = session .db() .await .project_connection_ids(project_id, session.connection_id) .await?; + broadcast( Some(session.connection_id), project_connection_ids.iter().copied(), @@ -3138,25 +3099,6 @@ async fn mark_notification_as_read( Ok(()) } -async fn update_diff_base(request: proto::UpdateDiffBase, session: Session) -> Result<()> { - let project_id = ProjectId::from_proto(request.project_id); - let project_connection_ids = session - .db() - .await - .project_connection_ids(project_id, session.connection_id) - .await?; - broadcast( - Some(session.connection_id), - project_connection_ids.iter().copied(), - |connection_id| { - session - .peer - .forward_send(session.connection_id, connection_id, request.clone()) - }, - ); - Ok(()) -} - async fn get_private_user_info( _request: proto::GetPrivateUserInfo, response: Response, diff --git a/crates/rpc/src/macros.rs b/crates/rpc/src/macros.rs index 89e605540da1157f5530ad7236b23358dc127c1a..85e2b0cf879608aec655d8eb663a73baf65fe7f0 100644 --- a/crates/rpc/src/macros.rs +++ b/crates/rpc/src/macros.rs @@ -60,8 +60,10 @@ macro_rules! request_messages { #[macro_export] macro_rules! entity_messages { - ($id_field:ident, $($name:ident),* $(,)?) => { + ({$id_field:ident, $entity_type:ty}, $($name:ident),* $(,)?) => { $(impl EntityMessage for $name { + type Entity = $entity_type; + fn remote_entity_id(&self) -> u64 { self.$id_field } diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index 336c252630ea51ea9d11a0658fc59d0cb09b214f..25b8b00dae99fe3e4c11677e480c6c48fa9f08c8 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -31,6 +31,7 @@ pub trait EnvelopedMessage: Clone + Debug + Serialize + Sized + Send + Sync + 's } pub trait EntityMessage: EnvelopedMessage { + type Entity; fn remote_entity_id(&self) -> u64; } @@ -369,7 +370,7 @@ request_messages!( ); entity_messages!( - project_id, + {project_id, ShareProject}, AddProjectCollaborator, ApplyCodeAction, ApplyCompletionAdditionalEdits, @@ -422,7 +423,7 @@ entity_messages!( ); entity_messages!( - channel_id, + {channel_id, Channel}, ChannelMessageSent, RemoveChannelMessage, UpdateChannelBuffer, From 604fcd8f1d2c9a720176192f982064e4f7df4aeb Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 8 Jan 2024 13:46:08 -0700 Subject: [PATCH 03/78] No .. paths... --- crates/project/src/project.rs | 9 +++- crates/project/src/project_tests.rs | 75 +++++++++++++++++++++++++++++ crates/project/src/worktree.rs | 32 +++++++----- 3 files changed, 103 insertions(+), 13 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index fb3eae1945f13fb1403b89b7556d548caaa76fff..584638a47a355495d2ffdf9f7da4712c23e30c4a 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -6581,7 +6581,14 @@ impl Project { let removed = *change == PathChange::Removed; let abs_path = worktree.absolutize(path); settings_contents.push(async move { - (settings_dir, (!removed).then_some(fs.load(&abs_path).await)) + ( + settings_dir, + if removed { + None + } else { + Some(async move { fs.load(&abs_path?).await }.await) + }, + ) }); } } diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 8f41c75fb4de0415089c1ce66c30cb93278c079c..5c3c21fd08c988a0987a1e1acf0a3c8eb28e38d7 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -4278,6 +4278,81 @@ fn test_glob_literal_prefix() { assert_eq!(glob_literal_prefix("foo/bar/baz.js"), "foo/bar/baz.js"); } +#[gpui::test] +async fn test_create_entry(cx: &mut gpui::TestAppContext) { + init_test(cx); + + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree( + "/one/two", + json!({ + "three": { + "a.txt": "", + "four": {} + }, + "c.rs": "" + }), + ) + .await; + + let project = Project::test(fs.clone(), ["/one/two/three".as_ref()], cx).await; + project + .update(cx, |project, cx| { + let id = project.worktrees().next().unwrap().read(cx).id(); + project.create_entry((id, "b.."), true, cx) + }) + .unwrap() + .await + .unwrap(); + + // Can't create paths outside the project + let result = project + .update(cx, |project, cx| { + let id = project.worktrees().next().unwrap().read(cx).id(); + project.create_entry((id, "../../boop"), true, cx) + }) + .await; + assert!(result.is_err()); + + // Can't create paths with '..' + let result = project + .update(cx, |project, cx| { + let id = project.worktrees().next().unwrap().read(cx).id(); + project.create_entry((id, "four/../beep"), true, cx) + }) + .await; + assert!(result.is_err()); + + assert_eq!( + fs.paths(true), + vec![ + PathBuf::from("/"), + PathBuf::from("/one"), + PathBuf::from("/one/two"), + PathBuf::from("/one/two/c.rs"), + PathBuf::from("/one/two/three"), + PathBuf::from("/one/two/three/a.txt"), + PathBuf::from("/one/two/three/b.."), + PathBuf::from("/one/two/three/four"), + ] + ); + + // ************************************ + // Note: unsure if this is the best fix for the integration failure, but assuming we want + // to keep that behavior, then this test should cover it + // ************************************ + + // But we can open buffers with '..' + let result = project + .update(cx, |project, cx| { + let id = project.worktrees().next().unwrap().read(cx).id(); + project.open_buffer((id, "../c.rs"), cx) + }) + .await; + + assert!(dbg!(result).is_ok()) +} + async fn search( project: &Model, query: SearchQuery, diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index ae0c074188274b95fbed3b078f68378ce715e570..461ea303b3d42cedb577d53f22c9e1de75a14b6a 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -965,6 +965,7 @@ impl LocalWorktree { let entry = self.refresh_entry(path.clone(), None, cx); cx.spawn(|this, mut cx| async move { + let abs_path = abs_path?; let text = fs.load(&abs_path).await?; let mut index_task = None; let snapshot = this.update(&mut cx, |this, _| this.as_local().unwrap().snapshot())?; @@ -1050,6 +1051,7 @@ impl LocalWorktree { cx.spawn(move |this, mut cx| async move { let entry = save.await?; + let abs_path = abs_path?; let this = this.upgrade().context("worktree dropped")?; let (entry_id, mtime, path) = match entry { @@ -1139,9 +1141,9 @@ impl LocalWorktree { let fs = self.fs.clone(); let write = cx.background_executor().spawn(async move { if is_dir { - fs.create_dir(&abs_path).await + fs.create_dir(&abs_path?).await } else { - fs.save(&abs_path, &Default::default(), Default::default()) + fs.save(&abs_path?, &Default::default(), Default::default()) .await } }); @@ -1188,7 +1190,7 @@ impl LocalWorktree { let fs = self.fs.clone(); let write = cx .background_executor() - .spawn(async move { fs.save(&abs_path, &text, line_ending).await }); + .spawn(async move { fs.save(&abs_path?, &text, line_ending).await }); cx.spawn(|this, mut cx| async move { write.await?; @@ -1210,10 +1212,10 @@ impl LocalWorktree { let delete = cx.background_executor().spawn(async move { if entry.is_file() { - fs.remove_file(&abs_path, Default::default()).await?; + fs.remove_file(&abs_path?, Default::default()).await?; } else { fs.remove_dir( - &abs_path, + &abs_path?, RemoveOptions { recursive: true, ignore_if_not_exists: false, @@ -1252,7 +1254,7 @@ impl LocalWorktree { let abs_new_path = self.absolutize(&new_path); let fs = self.fs.clone(); let rename = cx.background_executor().spawn(async move { - fs.rename(&abs_old_path, &abs_new_path, Default::default()) + fs.rename(&abs_old_path?, &abs_new_path?, Default::default()) .await }); @@ -1284,8 +1286,8 @@ impl LocalWorktree { let copy = cx.background_executor().spawn(async move { copy_recursive( fs.as_ref(), - &abs_old_path, - &abs_new_path, + &abs_old_path?, + &abs_new_path?, Default::default(), ) .await @@ -1609,11 +1611,17 @@ impl Snapshot { &self.abs_path } - pub fn absolutize(&self, path: &Path) -> PathBuf { + pub fn absolutize(&self, path: &Path) -> Result { + if path + .components() + .any(|component| !matches!(component, std::path::Component::Normal(_))) + { + return Err(anyhow!("invalid path")); + } if path.file_name().is_some() { - self.abs_path.join(path) + Ok(self.abs_path.join(path)) } else { - self.abs_path.to_path_buf() + Ok(self.abs_path.to_path_buf()) } } @@ -2823,7 +2831,7 @@ impl language::LocalFile for File { let abs_path = worktree.absolutize(&self.path); let fs = worktree.fs.clone(); cx.background_executor() - .spawn(async move { fs.load(&abs_path).await }) + .spawn(async move { fs.load(&abs_path?).await }) } fn buffer_reloaded( From 80f204fabbe7f88c3d2757cc994d1f0d6f93436e Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 8 Jan 2024 16:45:18 -0500 Subject: [PATCH 04/78] Style the chat panel message input (#3956) This PR styles the message input in the chat panel. Screenshot 2024-01-08 at 4 28 33 PM Release Notes: - Improved the styling of the message editor in the chat panel. --- crates/collab_ui/src/chat_panel.rs | 19 ++---- .../src/chat_panel/message_editor.rs | 59 ++++++++++++++++--- 2 files changed, 56 insertions(+), 22 deletions(-) diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index 8bbfbe8c70cdfa9bf122dbd42d9583c00ccedd98..4f8e12f1e8ea2be3ca2d5354a80e684e94269c22 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -19,7 +19,6 @@ use rich_text::RichText; use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; use std::sync::Arc; -use theme::ActiveTheme as _; use time::{OffsetDateTime, UtcOffset}; use ui::{prelude::*, Avatar, Button, Icon, IconButton, Label, TabBar, Tooltip}; use util::{ResultExt, TryFutureExt}; @@ -48,7 +47,7 @@ pub struct ChatPanel { languages: Arc, message_list: ListState, active_chat: Option<(Model, Subscription)>, - input_editor: View, + message_editor: View, local_timezone: UtcOffset, fs: Arc, width: Option, @@ -120,7 +119,7 @@ impl ChatPanel { message_list, active_chat: Default::default(), pending_serialization: Task::ready(None), - input_editor, + message_editor: input_editor, local_timezone: cx.local_timezone(), subscriptions: Vec::new(), workspace: workspace_handle, @@ -209,7 +208,7 @@ impl ChatPanel { self.message_list.reset(chat.message_count()); let channel_name = chat.channel(cx).map(|channel| channel.name.clone()); - self.input_editor.update(cx, |editor, cx| { + self.message_editor.update(cx, |editor, cx| { editor.set_channel(channel_id, channel_name, cx); }); }; @@ -300,13 +299,7 @@ impl ChatPanel { this } })) - .child( - div() - .z_index(1) - .p_2() - .bg(cx.theme().colors().background) - .child(self.input_editor.clone()), - ) + .child(h_stack().p_2().child(self.message_editor.clone())) .into_any() } @@ -469,7 +462,7 @@ impl ChatPanel { fn send(&mut self, _: &Confirm, cx: &mut ViewContext) { if let Some((chat, _)) = self.active_chat.as_ref() { let message = self - .input_editor + .message_editor .update(cx, |editor, cx| editor.take_message(cx)); if let Some(task) = chat @@ -585,7 +578,7 @@ impl Render for ChatPanel { impl FocusableView for ChatPanel { fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle { - self.input_editor.read(cx).focus_handle(cx) + self.message_editor.read(cx).focus_handle(cx) } } diff --git a/crates/collab_ui/src/chat_panel/message_editor.rs b/crates/collab_ui/src/chat_panel/message_editor.rs index 517fac4fbb377f425210d2468d3f18bb8d1ebb6a..7999db529a43985ae2b52cdde9f2108f9620b35c 100644 --- a/crates/collab_ui/src/chat_panel/message_editor.rs +++ b/crates/collab_ui/src/chat_panel/message_editor.rs @@ -1,16 +1,19 @@ +use std::{sync::Arc, time::Duration}; + use channel::{ChannelId, ChannelMembership, ChannelStore, MessageParams}; use client::UserId; use collections::HashMap; -use editor::{AnchorRangeExt, Editor}; +use editor::{AnchorRangeExt, Editor, EditorElement, EditorStyle}; use gpui::{ - AsyncWindowContext, FocusableView, IntoElement, Model, Render, SharedString, Task, View, - ViewContext, WeakView, + AsyncWindowContext, FocusableView, FontStyle, FontWeight, HighlightStyle, IntoElement, Model, + Render, SharedString, Task, TextStyle, View, ViewContext, WeakView, WhiteSpace, }; use language::{language_settings::SoftWrap, Buffer, BufferSnapshot, LanguageRegistry}; use lazy_static::lazy_static; use project::search::SearchQuery; -use std::{sync::Arc, time::Duration}; -use workspace::item::ItemHandle; +use settings::Settings; +use theme::ThemeSettings; +use ui::prelude::*; const MENTIONS_DEBOUNCE_INTERVAL: Duration = Duration::from_millis(50); @@ -181,7 +184,14 @@ impl MessageEditor { } editor.clear_highlights::(cx); - editor.highlight_text::(anchor_ranges, gpui::red().into(), cx) + editor.highlight_text::( + anchor_ranges, + HighlightStyle { + font_weight: Some(FontWeight::BOLD), + ..Default::default() + }, + cx, + ) }); this.mentions = mentioned_user_ids; @@ -196,8 +206,39 @@ impl MessageEditor { } impl Render for MessageEditor { - fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { - self.editor.to_any() + fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { + let settings = ThemeSettings::get_global(cx); + let text_style = TextStyle { + color: if self.editor.read(cx).read_only(cx) { + cx.theme().colors().text_disabled + } else { + cx.theme().colors().text + }, + font_family: settings.ui_font.family.clone(), + font_features: settings.ui_font.features, + font_size: rems(0.875).into(), + font_weight: FontWeight::NORMAL, + font_style: FontStyle::Normal, + line_height: relative(1.3).into(), + background_color: None, + underline: None, + white_space: WhiteSpace::Normal, + }; + + div() + .w_full() + .px_2() + .py_1() + .bg(cx.theme().colors().editor_background) + .rounded_md() + .child(EditorElement::new( + &self.editor, + EditorStyle { + local_player: cx.theme().players().local(), + text: text_style, + ..Default::default() + }, + )) } } @@ -205,7 +246,7 @@ impl Render for MessageEditor { mod tests { use super::*; use client::{Client, User, UserStore}; - use gpui::{Context as _, TestAppContext, VisualContext as _}; + use gpui::TestAppContext; use language::{Language, LanguageConfig}; use rpc::proto; use settings::SettingsStore; From ae6d09b9b25abbdb0877eda35c8c79803bf47d1c Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 8 Jan 2024 22:49:14 +0100 Subject: [PATCH 05/78] chore: Extract `assets` module out of zed crate. (#3957) This essentially shaves off about 10% off of an incremental build after project change and potentially more if you're changing stuff like `welcome` that's very close to the `zed` crate in the dep graph. That's because macro expansion takes place even in incremental builds it seems? And zed (lib) + zed (bin) could take up to 4 seconds out of an incremental build, which is a *lot* in a 10s build. In reality though it shaves 1 second off of 5 seconds incremental 'welcome'/ 1s off of 10s 'project' builds. Note that we had `assets` crate in the past (removed in #2575 /cc @maxbrunsfeld), but this is a bit different, because `assets` is a dependency of *just* zed and nothing else. We essentially cache macro expansion results ourselves. Release Notes: - N/A --- Cargo.lock | 10 ++++++++++ Cargo.toml | 1 + crates/assets/Cargo.toml | 11 +++++++++++ crates/{zed/src/assets.rs => assets/src/lib.rs} | 1 + crates/zed/Cargo.toml | 1 + crates/zed/src/main.rs | 5 +++-- crates/zed/src/zed.rs | 3 +-- 7 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 crates/assets/Cargo.toml rename crates/{zed/src/assets.rs => assets/src/lib.rs} (82%) diff --git a/Cargo.lock b/Cargo.lock index b9cb8ad4d1170ae8c33677d41fe5e94a6144567f..ec2e544dd54a68dd2235ac2887a56d1a7e02e3fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -292,6 +292,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" +[[package]] +name = "assets" +version = "0.1.0" +dependencies = [ + "anyhow", + "gpui", + "rust-embed", +] + [[package]] name = "assistant" version = "0.1.0" @@ -9529,6 +9538,7 @@ dependencies = [ "activity_indicator", "ai", "anyhow", + "assets", "assistant", "async-compression", "async-recursion 0.3.2", diff --git a/Cargo.toml b/Cargo.toml index 008b8406ecbb9c655b1d64406f697053cb5714c2..79d28821d4b040f35fc1ab1dd391cddeee93bc47 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = [ + "crates/assets", "crates/activity_indicator", "crates/ai", "crates/assistant", diff --git a/crates/assets/Cargo.toml b/crates/assets/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..18e6c7fa659c3bced7721b865e51fc5625f50ee3 --- /dev/null +++ b/crates/assets/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "assets" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +gpui = {path = "../gpui"} +rust-embed.workspace = true +anyhow.workspace = true diff --git a/crates/zed/src/assets.rs b/crates/assets/src/lib.rs similarity index 82% rename from crates/zed/src/assets.rs rename to crates/assets/src/lib.rs index 5d5e81a60e4feaff4c04bf3a0f3aff9a87659686..010b7ebda3d1be4532f47d6b5e7cdb79088694e2 100644 --- a/crates/zed/src/assets.rs +++ b/crates/assets/src/lib.rs @@ -1,3 +1,4 @@ +// This crate was essentially pulled out verbatim from main `zed` crate to avoid having to run RustEmbed macro whenever zed has to be rebuilt. It saves a second or two on an incremental build. use anyhow::anyhow; use gpui::{AssetSource, Result, SharedString}; diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index ae2c3701d6ece648f58204ac38dabc5f668a0def..734c225cb1e610c64dab92112accad9634632fee 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -74,6 +74,7 @@ vim = { path = "../vim" } workspace = { path = "../workspace" } welcome = { path = "../welcome" } zed_actions = {path = "../zed_actions"} +assets = {path = "../assets"} anyhow.workspace = true async-compression.workspace = true async-tar = "0.4.2" diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 56109d9c9a532d97de0f8b76101b4057203879e2..e10c52a175c8633fa3b3bebdb09223f3505587ad 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -16,6 +16,7 @@ use isahc::{prelude::Configurable, Request}; use language::LanguageRegistry; use log::LevelFilter; +use assets::Assets; use node_runtime::RealNodeRuntime; use parking_lot::Mutex; use serde::{Deserialize, Serialize}; @@ -49,8 +50,8 @@ use welcome::{show_welcome_view, BaseKeymap, FIRST_OPEN}; use workspace::{AppState, WorkspaceStore}; use zed::{ app_menus, build_window_options, ensure_only_instance, handle_cli_connection, - handle_keymap_file_changes, initialize_workspace, languages, Assets, IsOnlyInstance, - OpenListener, OpenRequest, + handle_keymap_file_changes, initialize_workspace, languages, IsOnlyInstance, OpenListener, + OpenRequest, }; fn main() { diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index b77ba1a23945000bd7f8d6b9edf6c4c486fef008..73368a988377e8edb927fb1d629c78385112a492 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -1,11 +1,9 @@ mod app_menus; -mod assets; pub mod languages; mod only_instance; mod open_listener; pub use app_menus::*; -pub use assets::*; use assistant::AssistantPanel; use breadcrumbs::Breadcrumbs; use collections::VecDeque; @@ -18,6 +16,7 @@ pub use only_instance::*; pub use open_listener::*; use anyhow::{anyhow, Context as _}; +use assets::Assets; use futures::{channel::mpsc, select_biased, StreamExt}; use project_panel::ProjectPanel; use quick_action_bar::QuickActionBar; From 71149bc7ccdebaa38d5cae8e4e66a21d8ab88075 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 8 Jan 2024 13:56:52 -0700 Subject: [PATCH 06/78] Fix relative path opening from project symbols Co-Authored-By: Max --- crates/collab/src/tests/integration_tests.rs | 6 +++--- crates/file_finder/src/file_finder.rs | 2 +- crates/project/src/project.rs | 17 ++++++++++++++++- crates/project/src/project_tests.rs | 10 ++-------- crates/semantic_index/src/semantic_index.rs | 12 +++++------- 5 files changed, 27 insertions(+), 20 deletions(-) diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 457f085f8fe9a1d6de8df497fbff435277f6cfef..a21235b6f3914a8cc6e181c9ae128da011f8bce3 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -4936,10 +4936,10 @@ async fn test_project_symbols( .await .unwrap(); - buffer_b_2.read_with(cx_b, |buffer, _| { + buffer_b_2.read_with(cx_b, |buffer, cx| { assert_eq!( - buffer.file().unwrap().path().as_ref(), - Path::new("../crate-2/two.rs") + buffer.file().unwrap().full_path(cx), + Path::new("/code/crate-2/two.rs") ); }); diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index ce68819646c9911ff8d89037527e1743d8c59fb7..d49eb9ee603d7e819bab5097adac874107fd9a3f 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -1297,7 +1297,7 @@ mod tests { // so that one should be sorted earlier let b_path = ProjectPath { worktree_id, - path: Arc::from(Path::new("/root/dir2/b.txt")), + path: Arc::from(Path::new("dir2/b.txt")), }; workspace .update(cx, |workspace, cx| { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 584638a47a355495d2ffdf9f7da4712c23e30c4a..044b750ad9d7cecce1a00f491e0fd66199989bb7 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -4732,7 +4732,8 @@ impl Project { } else { return Task::ready(Err(anyhow!("worktree not found for symbol"))); }; - let symbol_abs_path = worktree_abs_path.join(&symbol.path.path); + + let symbol_abs_path = resolve_path(worktree_abs_path, &symbol.path.path); let symbol_uri = if let Ok(uri) = lsp::Url::from_file_path(symbol_abs_path) { uri } else { @@ -8725,6 +8726,20 @@ fn relativize_path(base: &Path, path: &Path) -> PathBuf { components.iter().map(|c| c.as_os_str()).collect() } +fn resolve_path(base: &Path, path: &Path) -> PathBuf { + let mut result = base.to_path_buf(); + for component in path.components() { + match component { + Component::ParentDir => { + result.pop(); + } + Component::CurDir => (), + _ => result.push(component), + } + } + result +} + impl Item for Buffer { fn entry_id(&self, cx: &AppContext) -> Option { File::from_dyn(self.file()).and_then(|file| file.project_entry_id(cx)) diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 5c3c21fd08c988a0987a1e1acf0a3c8eb28e38d7..e90d3237127efa3b29d75ee110a7bbd27caa6de9 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -4337,20 +4337,14 @@ async fn test_create_entry(cx: &mut gpui::TestAppContext) { ] ); - // ************************************ - // Note: unsure if this is the best fix for the integration failure, but assuming we want - // to keep that behavior, then this test should cover it - // ************************************ - - // But we can open buffers with '..' + // And we cannot open buffers with '..' let result = project .update(cx, |project, cx| { let id = project.worktrees().next().unwrap().read(cx).id(); project.open_buffer((id, "../c.rs"), cx) }) .await; - - assert!(dbg!(result).is_ok()) + assert!(result.is_err()) } async fn search( diff --git a/crates/semantic_index/src/semantic_index.rs b/crates/semantic_index/src/semantic_index.rs index dbcdeee5ed0751d96a086fea691cd1195c2b81cc..81c4fbbc3d04b0d0bca6e4b70c074e3f75467999 100644 --- a/crates/semantic_index/src/semantic_index.rs +++ b/crates/semantic_index/src/semantic_index.rs @@ -559,7 +559,7 @@ impl SemanticIndex { .spawn(async move { let mut changed_paths = BTreeMap::new(); for file in worktree.files(false, 0) { - let absolute_path = worktree.absolutize(&file.path); + let absolute_path = worktree.absolutize(&file.path)?; if file.is_external || file.is_ignored || file.is_symlink { continue; @@ -1068,11 +1068,10 @@ impl SemanticIndex { return true; }; - worktree_state.changed_paths.retain(|path, info| { + for (path, info) in &worktree_state.changed_paths { if info.is_deleted { files_to_delete.push((worktree_state.db_id, path.clone())); - } else { - let absolute_path = worktree.read(cx).absolutize(path); + } else if let Ok(absolute_path) = worktree.read(cx).absolutize(path) { let job_handle = JobHandle::new(pending_file_count_tx); pending_files.push(PendingFile { absolute_path, @@ -1083,9 +1082,8 @@ impl SemanticIndex { worktree_db_id: worktree_state.db_id, }); } - - false - }); + } + worktree_state.changed_paths.clear(); true }); From d7c5d29237b1a010f09559b291fc45c3bde450d2 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 8 Jan 2024 15:39:24 -0700 Subject: [PATCH 07/78] Only allow read-write users to update buffers --- crates/collab/src/db/ids.rs | 8 ++++ crates/collab/src/db/queries/projects.rs | 56 ++++++++++++++++++++++-- crates/collab/src/rpc.rs | 26 ++++------- 3 files changed, 69 insertions(+), 21 deletions(-) diff --git a/crates/collab/src/db/ids.rs b/crates/collab/src/db/ids.rs index 2e2218c4d9da181719cf6404245a4d84d4285dab..9f77225fb704c1d4d53051cb7ee29d77c3536f80 100644 --- a/crates/collab/src/db/ids.rs +++ b/crates/collab/src/db/ids.rs @@ -148,6 +148,14 @@ impl ChannelRole { Guest | Banned => false, } } + + pub fn can_read_projects(&self) -> bool { + use ChannelRole::*; + match self { + Admin | Member | Guest => true, + Banned => false, + } + } } impl From for ChannelRole { diff --git a/crates/collab/src/db/queries/projects.rs b/crates/collab/src/db/queries/projects.rs index 04c77c80771b29a6be3d1603898b681da3e79569..6e1bf16309bd69315903a37ce7b57d389e749161 100644 --- a/crates/collab/src/db/queries/projects.rs +++ b/crates/collab/src/db/queries/projects.rs @@ -805,6 +805,43 @@ impl Database { .map(|guard| guard.into_inner()) } + pub async fn host_for_read_only_project_request( + &self, + project_id: ProjectId, + connection_id: ConnectionId, + ) -> Result { + let room_id = self.room_id_for_project(project_id).await?; + self.room_transaction(room_id, |tx| async move { + let current_participant = room_participant::Entity::find() + .filter(room_participant::Column::RoomId.eq(room_id)) + .filter(room_participant::Column::AnsweringConnectionId.eq(connection_id.id)) + .one(&*tx) + .await? + .ok_or_else(|| anyhow!("no such room"))?; + + if !current_participant + .role + .map_or(false, |role| role.can_read_projects()) + { + Err(anyhow!("not authorized to read projects"))?; + } + + let host = project_collaborator::Entity::find() + .filter( + project_collaborator::Column::ProjectId + .eq(project_id) + .and(project_collaborator::Column::IsHost.eq(true)), + ) + .one(&*tx) + .await? + .ok_or_else(|| anyhow!("failed to read project host"))?; + + Ok(host.connection()) + }) + .await + .map(|guard| guard.into_inner()) + } + pub async fn host_for_mutating_project_request( &self, project_id: ProjectId, @@ -821,8 +858,7 @@ impl Database { if !current_participant .role - .unwrap_or(ChannelRole::Guest) - .can_edit_projects() + .map_or(false, |role| role.can_edit_projects()) { Err(anyhow!("not authorized to edit projects"))?; } @@ -843,13 +879,27 @@ impl Database { .map(|guard| guard.into_inner()) } - pub async fn project_collaborators( + pub async fn project_collaborators_for_buffer_update( &self, project_id: ProjectId, connection_id: ConnectionId, ) -> Result>> { let room_id = self.room_id_for_project(project_id).await?; self.room_transaction(room_id, |tx| async move { + let current_participant = room_participant::Entity::find() + .filter(room_participant::Column::RoomId.eq(room_id)) + .filter(room_participant::Column::AnsweringConnectionId.eq(connection_id.id)) + .one(&*tx) + .await? + .ok_or_else(|| anyhow!("no such room"))?; + + if !current_participant + .role + .map_or(false, |role| role.can_edit_projects()) + { + Err(anyhow!("not authorized to edit projects"))?; + } + let collaborators = project_collaborator::Entity::find() .filter(project_collaborator::Column::ProjectId.eq(project_id)) .all(&*tx) diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 572670d78f4dca73cff7e57b8d0f0b030d9bdd11..da0d281f102602a3f7ec38a0d97b9e801a9e35ff 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -227,7 +227,7 @@ impl Server { .add_request_handler(forward_read_only_project_request::) .add_request_handler(forward_read_only_project_request::) .add_request_handler(forward_read_only_project_request::) - .add_request_handler(forward_mutating_project_request::) + .add_request_handler(forward_read_only_project_request::) .add_request_handler(forward_mutating_project_request::) .add_request_handler( forward_mutating_project_request::, @@ -1750,24 +1750,15 @@ where T: EntityMessage + RequestMessage, { let project_id = ProjectId::from_proto(request.remote_entity_id()); - let host_connection_id = { - let collaborators = session - .db() - .await - .project_collaborators(project_id, session.connection_id) - .await?; - collaborators - .iter() - .find(|collaborator| collaborator.is_host) - .ok_or_else(|| anyhow!("host not found"))? - .connection_id - }; - + let host_connection_id = session + .db() + .await + .host_for_read_only_project_request(project_id, session.connection_id) + .await?; let payload = session .peer .forward_request(session.connection_id, host_connection_id, request) .await?; - response.send(payload)?; Ok(()) } @@ -1786,12 +1777,10 @@ where .await .host_for_mutating_project_request(project_id, session.connection_id) .await?; - let payload = session .peer .forward_request(session.connection_id, host_connection_id, request) .await?; - response.send(payload)?; Ok(()) } @@ -1823,11 +1812,12 @@ async fn update_buffer( let project_id = ProjectId::from_proto(request.project_id); let mut guest_connection_ids; let mut host_connection_id = None; + { let collaborators = session .db() .await - .project_collaborators(project_id, session.connection_id) + .project_collaborators_for_buffer_update(project_id, session.connection_id) .await?; guest_connection_ids = Vec::with_capacity(collaborators.len() - 1); for collaborator in collaborators.iter() { From fac8ebf2ccdf863c0d494fc3c18e180e4f3ecb30 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 8 Jan 2024 17:46:25 -0500 Subject: [PATCH 08/78] Fix error border color for email input in feedback dialog (#3959) This PR fixes the border color used when the email input in the feedback dialog is invalid. Previously this was hardcoded just to `red` instead of using the appropriate color from the theme. Screenshot 2024-01-08 at 5 40 07 PM Release Notes: - Fixed the border color used for the email input in the feedback dialog when an invalid email is entered. --- crates/feedback/src/feedback_modal.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/feedback/src/feedback_modal.rs b/crates/feedback/src/feedback_modal.rs index 6c5308c1c64e3b6339dbdfff714c5a317dbe7842..566d34dadd20baf740eb8e3a2d2be1d87c655684 100644 --- a/crates/feedback/src/feedback_modal.rs +++ b/crates/feedback/src/feedback_modal.rs @@ -7,7 +7,7 @@ use db::kvp::KEY_VALUE_STORE; use editor::{Editor, EditorEvent}; use futures::AsyncReadExt; use gpui::{ - div, red, rems, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Model, + div, rems, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Model, PromptLevel, Render, Task, View, ViewContext, }; use isahc::Request; @@ -476,7 +476,7 @@ impl Render for FeedbackModal { .border_color(if self.valid_email_address() { cx.theme().colors().border } else { - red() + cx.theme().status().error_border }) .child(self.email_address_editor.clone()), ) From e876262579c3a52273b26fdce4bb182b4ae9fc64 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 8 Jan 2024 15:47:07 -0700 Subject: [PATCH 09/78] collab 0.35.0 --- Cargo.lock | 2 +- crates/collab/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ec2e544dd54a68dd2235ac2887a56d1a7e02e3fd..00c13d10ff899169f469d0fbe233b9e75a3649f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1452,7 +1452,7 @@ dependencies = [ [[package]] name = "collab" -version = "0.34.0" +version = "0.35.0" dependencies = [ "anyhow", "async-trait", diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index 498ded6d9a70f144bda9aa94259b62c129ecc568..baf279d6340d5024f18cdd1beb863aa76c549326 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] default-run = "collab" edition = "2021" name = "collab" -version = "0.34.0" +version = "0.35.0" publish = false [[bin]] From 32cd95674fb741b0f2e3ebcd07333a072ce89a77 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 8 Jan 2024 15:49:03 -0700 Subject: [PATCH 10/78] Push branch before tag --- script/lib/bump-version.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/lib/bump-version.sh b/script/lib/bump-version.sh index 0e1dfa5131d6a72b4d305511c9e80fab209aaba1..8be7e0b6c80c90262245f0b95e8efd87f38eb43d 100755 --- a/script/lib/bump-version.sh +++ b/script/lib/bump-version.sh @@ -30,7 +30,7 @@ Locally committed and tagged ${package} version ${new_version} To push this: - git push origin ${tag_name} ${branch_name} + git push origin ${branch_name} ${tag_name} To undo this: From 4007b2f72bc67b71d66fccc67a10e6462f9cf063 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 8 Jan 2024 18:28:37 -0500 Subject: [PATCH 11/78] Tighten up feedback modal design (#3960) This PR tightens up the design of the feedback dialog: Screenshot 2024-01-08 at 6 20 50 PM Screenshot 2024-01-08 at 6 21 24 PM Release Notes: - Improved the design of the feedback dialog. --- crates/feedback/src/feedback_modal.rs | 60 +++++++++++++-------------- 1 file changed, 28 insertions(+), 32 deletions(-) diff --git a/crates/feedback/src/feedback_modal.rs b/crates/feedback/src/feedback_modal.rs index 566d34dadd20baf740eb8e3a2d2be1d87c655684..b197d602338e052b319623e9b1ad6a1e6f7d7b53 100644 --- a/crates/feedback/src/feedback_modal.rs +++ b/crates/feedback/src/feedback_modal.rs @@ -179,14 +179,13 @@ impl FeedbackModal { editor }); - // Moved here because providing it inline breaks rustfmt - let placeholder_text = - "You can use markdown to organize your feedback with code and links."; - let feedback_editor = cx.new_view(|cx| { let mut editor = Editor::for_buffer(buffer, Some(project.clone()), cx); - editor.set_placeholder_text(placeholder_text, cx); - // editor.set_show_gutter(false, cx); + editor.set_placeholder_text( + "You can use markdown to organize your feedback with code and links.", + cx, + ); + editor.set_show_gutter(false, cx); editor.set_vertical_scroll_margin(5, cx); editor }); @@ -422,10 +421,6 @@ impl Render for FeedbackModal { let open_community_repo = cx.listener(|_, _, cx| cx.dispatch_action(Box::new(OpenZedCommunityRepo))); - // Moved this here because providing it inline breaks rustfmt - let provide_an_email_address = - "Provide an email address if you want us to be able to reply."; - v_stack() .elevation_3(cx) .key_context("GiveFeedback") @@ -434,11 +429,8 @@ impl Render for FeedbackModal { .max_w(rems(96.)) .h(rems(32.)) .p_4() - .gap_4() - .child(v_stack().child( - // TODO: Add Headline component to `ui2` - div().text_xl().child("Share Feedback"), - )) + .gap_2() + .child(Headline::new("Share Feedback")) .child( Label::new(if self.character_count < *FEEDBACK_CHAR_LIMIT.start() { format!( @@ -468,17 +460,26 @@ impl Render for FeedbackModal { .child(self.feedback_editor.clone()), ) .child( - h_stack() - .bg(cx.theme().colors().editor_background) - .p_2() - .border() - .rounded_md() - .border_color(if self.valid_email_address() { - cx.theme().colors().border - } else { - cx.theme().status().error_border - }) - .child(self.email_address_editor.clone()), + v_stack() + .gap_1() + .child( + h_stack() + .bg(cx.theme().colors().editor_background) + .p_2() + .border() + .rounded_md() + .border_color(if self.valid_email_address() { + cx.theme().colors().border + } else { + cx.theme().status().error_border + }) + .child(self.email_address_editor.clone()), + ) + .child( + Label::new("Provide an email address if you want us to be able to reply.") + .size(LabelSize::Small) + .color(Color::Muted), + ), ) .child( h_stack() @@ -515,12 +516,7 @@ impl Render for FeedbackModal { this.submit(cx).detach(); })) .tooltip(move |cx| { - Tooltip::with_meta( - "Submit feedback to the Zed team.", - None, - provide_an_email_address, - cx, - ) + Tooltip::text("Submit feedback to the Zed team.", cx) }) .when(!self.can_submit(), |this| this.disabled(true)), ), From 51caa743495ec237d528d9e4330c1b2b9b0115e4 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Mon, 8 Jan 2024 15:46:04 -0800 Subject: [PATCH 12/78] Restore the active pane magnification feature --- crates/workspace/src/pane_group.rs | 65 +++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 20 deletions(-) diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 4428e42830be725fb79979d6acf3e65a777d9386..68fd3c084f591e8bd7401c66e056e38beada1278 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -579,12 +579,15 @@ mod element { Size, Style, WeakView, WindowContext, }; use parking_lot::Mutex; + use settings::Settings; use smallvec::SmallVec; use ui::prelude::*; use util::ResultExt; use crate::Workspace; + use crate::WorkspaceSettings; + use super::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE}; const DIVIDER_SIZE: f32 = 1.0; @@ -834,20 +837,39 @@ mod element { debug_assert!(flexes.len() == len); debug_assert!(flex_values_in_bounds(flexes.as_slice())); + let magnification_value = WorkspaceSettings::get(None, cx).active_pane_magnification; + let active_pane_magnification = if magnification_value == 1. { + None + } else { + Some(magnification_value) + }; + + let total_flex = if let Some(flex) = active_pane_magnification { + self.children.len() as f32 - 1. + flex + } else { + len as f32 + }; + let mut origin = bounds.origin; - let space_per_flex = bounds.size.along(self.axis) / len as f32; + let space_per_flex = bounds.size.along(self.axis) / total_flex; let mut bounding_boxes = self.bounding_boxes.lock(); bounding_boxes.clear(); for (ix, child) in self.children.iter_mut().enumerate() { - //todo!(active_pane_magnification) - // If using active pane magnification, need to switch to using - // 1 for all non-active panes, and then the magnification for the - // active pane. + let child_flex = active_pane_magnification + .map(|magnification| { + if self.active_pane_ix == Some(ix) { + magnification + } else { + 1. + } + }) + .unwrap_or_else(|| flexes[ix]); + let child_size = bounds .size - .apply_along(self.axis, |_| space_per_flex * flexes[ix]); + .apply_along(self.axis, |_| space_per_flex * child_flex); let child_bounds = Bounds { origin, @@ -857,20 +879,23 @@ mod element { cx.with_z_index(0, |cx| { child.draw(origin, child_size.into(), cx); }); - cx.with_z_index(1, |cx| { - if ix < len - 1 { - Self::push_handle( - self.flexes.clone(), - state.clone(), - self.axis, - ix, - child_bounds, - bounds, - self.workspace.clone(), - cx, - ); - } - }); + + if active_pane_magnification.is_none() { + cx.with_z_index(1, |cx| { + if ix < len - 1 { + Self::push_handle( + self.flexes.clone(), + state.clone(), + self.axis, + ix, + child_bounds, + bounds, + self.workspace.clone(), + cx, + ); + } + }); + } origin = origin.apply_along(self.axis, |val| val + child_size.along(self.axis)); } From 6f6fa5b79eb04141cda85bb432c754cc8e567876 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 8 Jan 2024 16:53:01 -0700 Subject: [PATCH 13/78] Fix assets build --- crates/assets/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/assets/Cargo.toml b/crates/assets/Cargo.toml index 18e6c7fa659c3bced7721b865e51fc5625f50ee3..7ebae21d7dddb868d85dd9f11ae7fd8a34a277f7 100644 --- a/crates/assets/Cargo.toml +++ b/crates/assets/Cargo.toml @@ -2,6 +2,7 @@ name = "assets" version = "0.1.0" edition = "2021" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From ec7db3f52898119b807b808479d0f5b425db0be4 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Mon, 8 Jan 2024 16:02:39 -0800 Subject: [PATCH 14/78] Restore the terminal cursor settings --- crates/terminal_view/src/terminal_element.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index d936716032a53b432d2f6f1a5dc6b79069656c8b..bcaf147af239f9e36111a0a50980561ac4befe33 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -451,6 +451,18 @@ impl TerminalElement { } }); + let interactive_text_bounds = InteractiveBounds { + bounds, + stacking_order: cx.stacking_order().clone(), + }; + if interactive_text_bounds.visibly_contains(&cx.mouse_position(), cx) { + if self.can_navigate_to_selected_word && last_hovered_word.is_some() { + cx.set_cursor_style(gpui::CursorStyle::PointingHand) + } else { + cx.set_cursor_style(gpui::CursorStyle::IBeam) + } + } + let hyperlink_tooltip = last_hovered_word.clone().map(|hovered_word| { div() .size_full() From 0684369734e7c7fddb12ab3af0c12d400aadc795 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Mon, 8 Jan 2024 17:30:24 -0800 Subject: [PATCH 15/78] Fix off by 1 error when computing available key bindings --- crates/gpui/src/key_dispatch.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/gpui/src/key_dispatch.rs b/crates/gpui/src/key_dispatch.rs index 22c4dffc03a78df8fde5530a3059887e91a2b876..81f66746c536e73a716b5c5559f4385fcbe5d70b 100644 --- a/crates/gpui/src/key_dispatch.rs +++ b/crates/gpui/src/key_dispatch.rs @@ -192,8 +192,8 @@ impl DispatchTree { keymap .bindings_for_action(action) .filter(|binding| { - for i in 1..context_stack.len() { - let context = &context_stack[0..i]; + for i in 0..context_stack.len() { + let context = &context_stack[0..=i]; if keymap.binding_enabled(binding, context) { return true; } From c40a7f344558580e48156f2c6a107a7491fc7b15 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 8 Jan 2024 20:51:14 -0500 Subject: [PATCH 16/78] Stop propagation when deploying the context menu for a project panel entry (#3965) This PR fixes an issue where right-click on any project panel entry would cause the context menu on the root of the project panel (introduced in #3954) to deploy. We need to stop propagation in the handler on the inner project panel list items so that the click event doesn't bubble up the tree. Release Notes: - Fixed an issue where the project panel was always deploying the root context menu rather than on the clicked item. --- crates/project_panel/src/project_panel.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index ee016f399e0b2979423f5d5dc625d616013526d3..727ab7e859d237c8729401e6dcee90f8529616c8 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1433,6 +1433,9 @@ impl ProjectPanel { })) .on_secondary_mouse_down(cx.listener( move |this, event: &MouseDownEvent, cx| { + // Stop propagation to prevent the catch-all context menu for the project + // panel from being deployed. + cx.stop_propagation(); this.deploy_context_menu(event.position, entry_id, cx); }, )), From 27d4d727c3c0392feb83c1d0aeff4ffaff4e1448 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Mon, 8 Jan 2024 18:21:54 -0800 Subject: [PATCH 17/78] Attempt to write test --- crates/gpui/src/action.rs | 22 +++++++-- crates/gpui/src/key_dispatch.rs | 88 ++++++++++++++++++++++++++++++++- 2 files changed, 103 insertions(+), 7 deletions(-) diff --git a/crates/gpui/src/action.rs b/crates/gpui/src/action.rs index e335c4255e4deb0d6c5720b03ce017952a6fa229..b86dafe62cb6e41bdaa5efc357abdb3f55f83ae1 100644 --- a/crates/gpui/src/action.rs +++ b/crates/gpui/src/action.rs @@ -114,14 +114,26 @@ impl ActionRegistry { pub(crate) fn load_actions(&mut self) { for builder in __GPUI_ACTIONS { let action = builder(); - //todo(remove) - let name: SharedString = action.name.into(); - self.builders_by_name.insert(name.clone(), action.build); - self.names_by_type_id.insert(action.type_id, name.clone()); - self.all_names.push(name); + self.insert_action(action); } } + #[cfg(test)] + pub(crate) fn load_action(&mut self) { + self.insert_action(ActionData { + name: A::debug_name(), + type_id: TypeId::of::(), + build: A::build, + }); + } + + fn insert_action(&mut self, action: ActionData) { + let name: SharedString = action.name.into(); + self.builders_by_name.insert(name.clone(), action.build); + self.names_by_type_id.insert(action.type_id, name.clone()); + self.all_names.push(name); + } + /// Construct an action based on its name and optional JSON parameters sourced from the keymap. pub fn build_action_type(&self, type_id: &TypeId) -> Result> { let name = self diff --git a/crates/gpui/src/key_dispatch.rs b/crates/gpui/src/key_dispatch.rs index 81f66746c536e73a716b5c5559f4385fcbe5d70b..c65b169596c8b8627521e029d61ae59e24b3845b 100644 --- a/crates/gpui/src/key_dispatch.rs +++ b/crates/gpui/src/key_dispatch.rs @@ -192,8 +192,9 @@ impl DispatchTree { keymap .bindings_for_action(action) .filter(|binding| { - for i in 0..context_stack.len() { - let context = &context_stack[0..=i]; + for i in 1..context_stack.len() { + dbg!(i); + let context = &context_stack[0..i]; if keymap.binding_enabled(binding, context) { return true; } @@ -283,3 +284,86 @@ impl DispatchTree { *self.node_stack.last().unwrap() } } + +#[cfg(test)] +mod tests { + use std::{rc::Rc, sync::Arc}; + + use parking_lot::Mutex; + + use crate::{Action, ActionRegistry, DispatchTree, KeyBinding, KeyContext, Keymap}; + + #[derive(PartialEq, Eq)] + struct TestAction; + + impl Action for TestAction { + fn name(&self) -> &'static str { + "test::TestAction" + } + + fn debug_name() -> &'static str + where + Self: ::std::marker::Sized, + { + "test::TestAction" + } + + fn partial_eq(&self, action: &dyn Action) -> bool { + action + .as_any() + .downcast_ref::() + .map_or(false, |a| self == a) + } + + fn boxed_clone(&self) -> std::boxed::Box { + Box::new(TestAction) + } + + fn as_any(&self) -> &dyn ::std::any::Any { + self + } + + fn build(_value: serde_json::Value) -> anyhow::Result> + where + Self: Sized, + { + Ok(Box::new(TestAction)) + } + } + + #[test] + fn test_keybinding_for_action_bounds() { + dbg!("got here"); + + let keymap = Keymap::new(vec![KeyBinding::new( + "cmd-n", + TestAction, + Some("ProjectPanel"), + )]); + dbg!("got here"); + + let mut registry = ActionRegistry::default(); + dbg!("got here"); + + registry.load_action::(); + + dbg!("got here"); + + let keymap = Arc::new(Mutex::new(keymap)); + dbg!("got here"); + + let tree = DispatchTree::new(keymap, Rc::new(registry)); + + dbg!("got here"); + let keybinding = tree.bindings_for_action( + &TestAction, + &vec![ + KeyContext::parse(",").unwrap(), + KeyContext::parse("Workspace").unwrap(), + KeyContext::parse("ProjectPanel").unwrap(), + ], + ); + + assert!(keybinding[0].action.partial_eq(&TestAction)) + } +} From 4afa5fb23e6c791bde56c23fa6c57a19fb233395 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 8 Jan 2024 21:54:59 -0500 Subject: [PATCH 18/78] Add stories for collab notifications (#3967) This PR adds some basic stories for collab notifications to make them easier to work on: Screenshot 2024-01-08 at 9 43 39 PM I factored out a `CollabNotification` component that defines the general structure for one of these notifications, and this is the component that we use in the stories, with representative values passed to it to simulate the different instances of the notification. We can't use the actual notification components in the stories due to their data dependencies. Release Notes: - N/A --- Cargo.lock | 2 + crates/collab_ui/Cargo.toml | 3 + crates/collab_ui/src/notifications.rs | 11 ++- .../src/notifications/collab_notification.rs | 52 +++++++++++++ .../incoming_call_notification.rs | 52 +++++-------- .../project_shared_notification.rs | 74 +++++++------------ crates/collab_ui/src/notifications/stories.rs | 3 + .../stories/collab_notification.rs | 50 +++++++++++++ crates/storybook/Cargo.toml | 1 + crates/storybook/src/story_selector.rs | 4 + 10 files changed, 169 insertions(+), 83 deletions(-) create mode 100644 crates/collab_ui/src/notifications/collab_notification.rs create mode 100644 crates/collab_ui/src/notifications/stories.rs create mode 100644 crates/collab_ui/src/notifications/stories/collab_notification.rs diff --git a/Cargo.lock b/Cargo.lock index 00c13d10ff899169f469d0fbe233b9e75a3649f1..c6e7ecebc1300628462750ed714b9103e6a001c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1559,6 +1559,7 @@ dependencies = [ "serde_json", "settings", "smallvec", + "story", "theme", "theme_selector", "time", @@ -7447,6 +7448,7 @@ dependencies = [ "backtrace-on-stack-overflow", "chrono", "clap 4.4.4", + "collab_ui", "dialoguer", "editor", "fuzzy", diff --git a/crates/collab_ui/Cargo.toml b/crates/collab_ui/Cargo.toml index f845de3a939886fefa42343131e0c4ec18543fea..84c1810bc841d904a7a534fb562671dc9d7232c9 100644 --- a/crates/collab_ui/Cargo.toml +++ b/crates/collab_ui/Cargo.toml @@ -9,6 +9,8 @@ path = "src/collab_ui.rs" doctest = false [features] +default = [] +stories = ["dep:story"] test-support = [ "call/test-support", "client/test-support", @@ -44,6 +46,7 @@ project = { path = "../project" } recent_projects = { path = "../recent_projects" } rpc = { path = "../rpc" } settings = { path = "../settings" } +story = { path = "../story", optional = true } feature_flags = { path = "../feature_flags"} theme = { path = "../theme" } theme_selector = { path = "../theme_selector" } diff --git a/crates/collab_ui/src/notifications.rs b/crates/collab_ui/src/notifications.rs index 5c184ec5c86ab268f4455b21d855ca118d40d50b..7759fef52059fb47dbc74a3803be309dded114c1 100644 --- a/crates/collab_ui/src/notifications.rs +++ b/crates/collab_ui/src/notifications.rs @@ -1,9 +1,16 @@ +mod collab_notification; +pub mod incoming_call_notification; +pub mod project_shared_notification; + +#[cfg(feature = "stories")] +mod stories; + use gpui::AppContext; use std::sync::Arc; use workspace::AppState; -pub mod incoming_call_notification; -pub mod project_shared_notification; +#[cfg(feature = "stories")] +pub use stories::*; pub fn init(app_state: &Arc, cx: &mut AppContext) { incoming_call_notification::init(app_state, cx); diff --git a/crates/collab_ui/src/notifications/collab_notification.rs b/crates/collab_ui/src/notifications/collab_notification.rs new file mode 100644 index 0000000000000000000000000000000000000000..e2ef06f9a58f8b131f166ab2a83250839f076ca2 --- /dev/null +++ b/crates/collab_ui/src/notifications/collab_notification.rs @@ -0,0 +1,52 @@ +use gpui::{img, prelude::*, AnyElement}; +use smallvec::SmallVec; +use ui::prelude::*; + +#[derive(IntoElement)] +pub struct CollabNotification { + avatar_uri: SharedString, + accept_button: Button, + dismiss_button: Button, + children: SmallVec<[AnyElement; 2]>, +} + +impl CollabNotification { + pub fn new( + avatar_uri: impl Into, + accept_button: Button, + dismiss_button: Button, + ) -> Self { + Self { + avatar_uri: avatar_uri.into(), + accept_button, + dismiss_button, + children: SmallVec::new(), + } + } +} + +impl ParentElement for CollabNotification { + fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> { + &mut self.children + } +} + +impl RenderOnce for CollabNotification { + fn render(self, cx: &mut WindowContext) -> impl IntoElement { + h_stack() + .text_ui() + .justify_between() + .size_full() + .overflow_hidden() + .elevation_3(cx) + .p_2() + .gap_2() + .child(img(self.avatar_uri).w_12().h_12().rounded_full()) + .child(v_stack().overflow_hidden().children(self.children)) + .child( + v_stack() + .child(self.accept_button) + .child(self.dismiss_button), + ) + } +} diff --git a/crates/collab_ui/src/notifications/incoming_call_notification.rs b/crates/collab_ui/src/notifications/incoming_call_notification.rs index fa28ef9a6030d4009e96d170e7f7aefc46e52783..223415119fca00cca478d56423b20f9b44417c2e 100644 --- a/crates/collab_ui/src/notifications/incoming_call_notification.rs +++ b/crates/collab_ui/src/notifications/incoming_call_notification.rs @@ -1,15 +1,12 @@ use crate::notification_window_options; +use crate::notifications::collab_notification::CollabNotification; use call::{ActiveCall, IncomingCall}; use futures::StreamExt; -use gpui::{ - img, px, AppContext, ParentElement, Render, RenderOnce, Styled, ViewContext, - VisualContext as _, WindowHandle, -}; +use gpui::{prelude::*, AppContext, WindowHandle}; use settings::Settings; use std::sync::{Arc, Weak}; use theme::ThemeSettings; -use ui::prelude::*; -use ui::{h_stack, v_stack, Button, Label}; +use ui::{prelude::*, Button, Label}; use util::ResultExt; use workspace::AppState; @@ -31,8 +28,8 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { if let Some(incoming_call) = incoming_call { let unique_screens = cx.update(|cx| cx.displays()).unwrap(); let window_size = gpui::Size { - width: px(380.), - height: px(64.), + width: px(400.), + height: px(72.), }; for screen in unique_screens { @@ -129,35 +126,22 @@ impl Render for IncomingCallNotification { cx.set_rem_size(ui_font_size); - h_stack() - .font(ui_font) - .text_ui() - .justify_between() - .size_full() - .overflow_hidden() - .elevation_3(cx) - .p_2() - .gap_2() - .child( - img(self.state.call.calling_user.avatar_uri.clone()) - .w_12() - .h_12() - .rounded_full(), + div().size_full().font(ui_font).child( + CollabNotification::new( + self.state.call.calling_user.avatar_uri.clone(), + Button::new("accept", "Accept").on_click({ + let state = self.state.clone(); + move |_, cx| state.respond(true, cx) + }), + Button::new("decline", "Decline").on_click({ + let state = self.state.clone(); + move |_, cx| state.respond(false, cx) + }), ) .child(v_stack().overflow_hidden().child(Label::new(format!( "{} is sharing a project in Zed", self.state.call.calling_user.github_login - )))) - .child( - v_stack() - .child(Button::new("accept", "Accept").render(cx).on_click({ - let state = self.state.clone(); - move |_, cx| state.respond(true, cx) - })) - .child(Button::new("decline", "Decline").render(cx).on_click({ - let state = self.state.clone(); - move |_, cx| state.respond(false, cx) - })), - ) + )))), + ) } } diff --git a/crates/collab_ui/src/notifications/project_shared_notification.rs b/crates/collab_ui/src/notifications/project_shared_notification.rs index 982214c3e596e7290a4837264cc570c5aa786c94..79adc69a801dbe5ed30c7b9afb8d1df19bba6c5e 100644 --- a/crates/collab_ui/src/notifications/project_shared_notification.rs +++ b/crates/collab_ui/src/notifications/project_shared_notification.rs @@ -1,12 +1,13 @@ use crate::notification_window_options; +use crate::notifications::collab_notification::CollabNotification; use call::{room, ActiveCall}; use client::User; use collections::HashMap; -use gpui::{img, px, AppContext, ParentElement, Render, Size, Styled, ViewContext, VisualContext}; +use gpui::{AppContext, Size}; use settings::Settings; use std::sync::{Arc, Weak}; use theme::ThemeSettings; -use ui::{h_stack, prelude::*, v_stack, Button, Label}; +use ui::{prelude::*, Button, Label}; use workspace::AppState; pub fn init(app_state: &Arc, cx: &mut AppContext) { @@ -130,51 +131,30 @@ impl Render for ProjectSharedNotification { cx.set_rem_size(ui_font_size); - h_stack() - .font(ui_font) - .text_ui() - .justify_between() - .size_full() - .overflow_hidden() - .elevation_3(cx) - .p_2() - .gap_2() - .child( - img(self.owner.avatar_uri.clone()) - .w_12() - .h_12() - .rounded_full(), - ) - .child( - v_stack() - .overflow_hidden() - .child(Label::new(self.owner.github_login.clone())) - .child(Label::new(format!( - "is sharing a project in Zed{}", - if self.worktree_root_names.is_empty() { - "" - } else { - ":" - } - ))) - .children(if self.worktree_root_names.is_empty() { - None - } else { - Some(Label::new(self.worktree_root_names.join(", "))) - }), - ) - .child( - v_stack() - .child(Button::new("open", "Open").on_click(cx.listener( - move |this, _event, cx| { - this.join(cx); - }, - ))) - .child(Button::new("dismiss", "Dismiss").on_click(cx.listener( - move |this, _event, cx| { - this.dismiss(cx); - }, - ))), + div().size_full().font(ui_font).child( + CollabNotification::new( + self.owner.avatar_uri.clone(), + Button::new("open", "Open").on_click(cx.listener(move |this, _event, cx| { + this.join(cx); + })), + Button::new("dismiss", "Dismiss").on_click(cx.listener(move |this, _event, cx| { + this.dismiss(cx); + })), ) + .child(Label::new(self.owner.github_login.clone())) + .child(Label::new(format!( + "is sharing a project in Zed{}", + if self.worktree_root_names.is_empty() { + "" + } else { + ":" + } + ))) + .children(if self.worktree_root_names.is_empty() { + None + } else { + Some(Label::new(self.worktree_root_names.join(", "))) + }), + ) } } diff --git a/crates/collab_ui/src/notifications/stories.rs b/crates/collab_ui/src/notifications/stories.rs new file mode 100644 index 0000000000000000000000000000000000000000..36518679c661627346e45357161ba117a6bfe5b3 --- /dev/null +++ b/crates/collab_ui/src/notifications/stories.rs @@ -0,0 +1,3 @@ +mod collab_notification; + +pub use collab_notification::*; diff --git a/crates/collab_ui/src/notifications/stories/collab_notification.rs b/crates/collab_ui/src/notifications/stories/collab_notification.rs new file mode 100644 index 0000000000000000000000000000000000000000..c43cac46d21352ac8375e33cf530891385c36da0 --- /dev/null +++ b/crates/collab_ui/src/notifications/stories/collab_notification.rs @@ -0,0 +1,50 @@ +use gpui::prelude::*; +use story::{StoryContainer, StoryItem, StorySection}; +use ui::prelude::*; + +use crate::notifications::collab_notification::CollabNotification; + +pub struct CollabNotificationStory; + +impl Render for CollabNotificationStory { + fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { + let window_container = |width, height| div().w(px(width)).h(px(height)); + + StoryContainer::new( + "CollabNotification Story", + "crates/collab_ui/src/notifications/stories/collab_notification.rs", + ) + .child( + StorySection::new().child(StoryItem::new( + "Incoming Call Notification", + window_container(400., 72.).child( + CollabNotification::new( + "https://avatars.githubusercontent.com/u/1486634?v=4", + Button::new("accept", "Accept"), + Button::new("decline", "Decline"), + ) + .child( + v_stack() + .overflow_hidden() + .child(Label::new("maxdeviant is sharing a project in Zed")), + ), + ), + )), + ) + .child( + StorySection::new().child(StoryItem::new( + "Project Shared Notification", + window_container(400., 72.).child( + CollabNotification::new( + "https://avatars.githubusercontent.com/u/1714999?v=4", + Button::new("open", "Open"), + Button::new("dismiss", "Dismiss"), + ) + .child(Label::new("iamnbutler")) + .child(Label::new("is sharing a project in Zed:")) + .child(Label::new("zed")), + ), + )), + ) + } +} diff --git a/crates/storybook/Cargo.toml b/crates/storybook/Cargo.toml index 033b3fa8d9077ee9866f0a0a7e382d5f68d3e483..9f08556757b6b59d1aed6fa7361029a36664a40a 100644 --- a/crates/storybook/Cargo.toml +++ b/crates/storybook/Cargo.toml @@ -14,6 +14,7 @@ anyhow.workspace = true backtrace-on-stack-overflow = "0.3.0" chrono = "0.4" clap = { version = "4.4", features = ["derive", "string"] } +collab_ui = { path = "../collab_ui", features = ["stories"] } strum = { version = "0.25.0", features = ["derive"] } dialoguer = { version = "0.11.0", features = ["fuzzy-select"] } editor = { path = "../editor" } diff --git a/crates/storybook/src/story_selector.rs b/crates/storybook/src/story_selector.rs index 27ddfe26ac2884cd327f065c01bdf8bf7617e624..120e60d34a1499ed143394713e0c46fbf1bc2dfa 100644 --- a/crates/storybook/src/story_selector.rs +++ b/crates/storybook/src/story_selector.rs @@ -16,6 +16,7 @@ pub enum ComponentStory { Avatar, Button, Checkbox, + CollabNotification, ContextMenu, Cursor, Disclosure, @@ -45,6 +46,9 @@ impl ComponentStory { Self::Avatar => cx.new_view(|_| ui::AvatarStory).into(), Self::Button => cx.new_view(|_| ui::ButtonStory).into(), Self::Checkbox => cx.new_view(|_| ui::CheckboxStory).into(), + Self::CollabNotification => cx + .new_view(|_| collab_ui::notifications::CollabNotificationStory) + .into(), Self::ContextMenu => cx.new_view(|_| ui::ContextMenuStory).into(), Self::Cursor => cx.new_view(|_| crate::stories::CursorStory).into(), Self::Disclosure => cx.new_view(|_| ui::DisclosureStory).into(), From 844d161c400e5f4e41f56743d010791314d16c0f Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 8 Jan 2024 16:52:20 -0700 Subject: [PATCH 19/78] Allow adding write access to guests --- Cargo.lock | 2 + crates/call/src/room.rs | 23 ++- crates/collab/Cargo.toml | 2 + crates/collab/src/db/queries/rooms.rs | 40 +++++ crates/collab/src/rpc.rs | 22 +++ .../collab/src/tests/channel_guest_tests.rs | 108 +++++++----- crates/collab/src/tests/test_server.rs | 63 ++++++- crates/collab_ui/src/collab_panel.rs | 166 ++++++++---------- crates/gpui/src/app/test_context.rs | 4 + crates/language/src/buffer.rs | 6 + crates/multi_buffer/src/multi_buffer.rs | 7 +- crates/project/src/project.rs | 18 +- crates/rpc/proto/zed.proto | 9 +- crates/rpc/src/proto.rs | 2 + crates/vim/src/test/vim_test_context.rs | 1 - crates/workspace/src/notifications.rs | 17 +- 16 files changed, 347 insertions(+), 143 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 00c13d10ff899169f469d0fbe233b9e75a3649f1..4249503e72bf51356c1b3fc28893c27d74cc6410 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1473,6 +1473,7 @@ dependencies = [ "editor", "env_logger", "envy", + "file_finder", "fs", "futures 0.3.28", "git", @@ -1486,6 +1487,7 @@ dependencies = [ "live_kit_server", "log", "lsp", + "menu", "nanoid", "node_runtime", "notifications", diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index e2c9bf5886faca374334e463b8d0ac4711ce863b..6f8f8e171793931d64948e2972536eb83c739214 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -620,6 +620,27 @@ impl Room { self.local_participant.role == proto::ChannelRole::Admin } + pub fn set_participant_role( + &mut self, + user_id: u64, + role: proto::ChannelRole, + cx: &ModelContext, + ) -> Task> { + let client = self.client.clone(); + let room_id = self.id; + let role = role.into(); + cx.spawn(|_, _| async move { + client + .request(proto::SetRoomParticipantRole { + room_id, + user_id, + role, + }) + .await + .map(|_| ()) + }) + } + pub fn pending_participants(&self) -> &[Arc] { &self.pending_participants } @@ -731,7 +752,7 @@ impl Room { this.joined_projects.retain(|project| { if let Some(project) = project.upgrade() { - project.update(cx, |project, _| project.set_role(role)); + project.update(cx, |project, cx| project.set_role(role, cx)); true } else { false diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index baf279d6340d5024f18cdd1beb863aa76c549326..13bbd0cf2511d8b91015fd167842d82ed7b12df4 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -74,6 +74,8 @@ live_kit_client = { path = "../live_kit_client", features = ["test-support"] } lsp = { path = "../lsp", features = ["test-support"] } node_runtime = { path = "../node_runtime" } notifications = { path = "../notifications", features = ["test-support"] } +file_finder = { path = "../file_finder"} +menu = { path = "../menu"} project = { path = "../project", features = ["test-support"] } rpc = { path = "../rpc", features = ["test-support"] } diff --git a/crates/collab/src/db/queries/rooms.rs b/crates/collab/src/db/queries/rooms.rs index 9a87f91b8182b69b32539810e9fc46f7afdadb14..c3e71880fdc5e8eb871dfd9a9aff3e85251a321e 100644 --- a/crates/collab/src/db/queries/rooms.rs +++ b/crates/collab/src/db/queries/rooms.rs @@ -1004,6 +1004,46 @@ impl Database { .await } + pub async fn set_room_participant_role( + &self, + admin_id: UserId, + room_id: RoomId, + user_id: UserId, + role: ChannelRole, + ) -> Result> { + self.room_transaction(room_id, |tx| async move { + room_participant::Entity::find() + .filter( + Condition::all() + .add(room_participant::Column::RoomId.eq(room_id)) + .add(room_participant::Column::UserId.eq(admin_id)) + .add(room_participant::Column::Role.eq(ChannelRole::Admin)), + ) + .one(&*tx) + .await? + .ok_or_else(|| anyhow!("only admins can set participant role"))?; + + let result = room_participant::Entity::update_many() + .filter( + Condition::all() + .add(room_participant::Column::RoomId.eq(room_id)) + .add(room_participant::Column::UserId.eq(user_id)), + ) + .set(room_participant::ActiveModel { + role: ActiveValue::set(Some(ChannelRole::from(role))), + ..Default::default() + }) + .exec(&*tx) + .await?; + + if result.rows_affected != 1 { + Err(anyhow!("could not update room participant role"))?; + } + Ok(self.get_room(room_id, &tx).await?) + }) + .await + } + pub async fn connection_lost(&self, connection: ConnectionId) -> Result<()> { self.transaction(|tx| async move { self.room_connection_lost(connection, &*tx).await?; diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 5301ca9a23b0a3bd3d0cce44de7eaa640449347e..766cf4bcc0d7344c0780c0a34b62ac4feeb5ea84 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -202,6 +202,7 @@ impl Server { .add_request_handler(join_room) .add_request_handler(rejoin_room) .add_request_handler(leave_room) + .add_request_handler(set_room_participant_role) .add_request_handler(call) .add_request_handler(cancel_call) .add_message_handler(decline_call) @@ -1258,6 +1259,27 @@ async fn leave_room( Ok(()) } +async fn set_room_participant_role( + request: proto::SetRoomParticipantRole, + response: Response, + session: Session, +) -> Result<()> { + let room = session + .db() + .await + .set_room_participant_role( + session.user_id, + RoomId::from_proto(request.room_id), + UserId::from_proto(request.user_id), + ChannelRole::from(request.role()), + ) + .await?; + + room_updated(&room, &session.peer); + response.send(proto::Ack {})?; + Ok(()) +} + async fn call( request: proto::Call, response: Response, diff --git a/crates/collab/src/tests/channel_guest_tests.rs b/crates/collab/src/tests/channel_guest_tests.rs index 32cc074ec96a11e3806a256eedd8f74191689ace..45e9e5a1d7588b26fc314f0293423af5803d6166 100644 --- a/crates/collab/src/tests/channel_guest_tests.rs +++ b/crates/collab/src/tests/channel_guest_tests.rs @@ -1,5 +1,6 @@ use crate::tests::TestServer; use call::ActiveCall; +use editor::Editor; use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext}; use rpc::proto; use workspace::Workspace; @@ -13,37 +14,18 @@ async fn test_channel_guests( let mut server = TestServer::start(executor.clone()).await; let client_a = server.create_client(cx_a, "user_a").await; let client_b = server.create_client(cx_b, "user_b").await; + let active_call_a = cx_a.read(ActiveCall::global); let channel_id = server - .make_channel("the-channel", None, (&client_a, cx_a), &mut []) - .await; - - client_a - .channel_store() - .update(cx_a, |channel_store, cx| { - channel_store.set_channel_visibility(channel_id, proto::ChannelVisibility::Public, cx) - }) - .await - .unwrap(); - - client_a - .fs() - .insert_tree( - "/a", - serde_json::json!({ - "a.txt": "a-contents", - }), - ) + .make_public_channel("the-channel", &client_a, cx_a) .await; - let active_call_a = cx_a.read(ActiveCall::global); - // Client A shares a project in the channel + let project_a = client_a.build_test_project(cx_a).await; active_call_a .update(cx_a, |call, cx| call.join_channel(channel_id, cx)) .await .unwrap(); - let (project_a, _) = client_a.build_local_project("/a", cx_a).await; let project_id = active_call_a .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .await @@ -57,33 +39,16 @@ async fn test_channel_guests( // b should be following a in the shared project. // B is a guest, - cx_a.executor().run_until_parked(); - - // todo!() the test window does not call activation handlers - // correctly yet, so this API does not work. - // let project_b = active_call_b.read_with(cx_b, |call, _| { - // call.location() - // .unwrap() - // .upgrade() - // .expect("should not be weak") - // }); - - let window_b = cx_b.update(|cx| cx.active_window().unwrap()); - let cx_b = &mut VisualTestContext::from_window(window_b, cx_b); - - let workspace_b = window_b - .downcast::() - .unwrap() - .root_view(cx_b) - .unwrap(); - let project_b = workspace_b.update(cx_b, |workspace, _| workspace.project().clone()); + executor.run_until_parked(); + let active_call_b = cx_b.read(ActiveCall::global); + let project_b = + active_call_b.read_with(cx_b, |call, _| call.location().unwrap().upgrade().unwrap()); assert_eq!( project_b.read_with(cx_b, |project, _| project.remote_id()), Some(project_id), ); assert!(project_b.read_with(cx_b, |project, _| project.is_read_only())); - assert!(project_b .update(cx_b, |project, cx| { let worktree_id = project.worktrees().next().unwrap().read(cx).id(); @@ -92,3 +57,60 @@ async fn test_channel_guests( .await .is_err()) } + +#[gpui::test] +async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { + let mut server = TestServer::start(cx_a.executor()).await; + let client_a = server.create_client(cx_a, "user_a").await; + let client_b = server.create_client(cx_b, "user_b").await; + let active_call_a = cx_a.read(ActiveCall::global); + let active_call_b = cx_b.read(ActiveCall::global); + + let channel_id = server + .make_public_channel("the-channel", &client_a, cx_a) + .await; + + let project_a = client_a.build_test_project(cx_a).await; + cx_a.update(|cx| workspace::join_channel(channel_id, client_a.app_state.clone(), None, cx)) + .await + .unwrap(); + + // Client A shares a project in the channel + let project_id = active_call_a + .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) + .await + .unwrap(); + cx_a.run_until_parked(); + + // Client B joins channel A as a guest + cx_b.update(|cx| workspace::join_channel(channel_id, client_b.app_state.clone(), None, cx)) + .await + .unwrap(); + cx_a.run_until_parked(); + + let project_b = + active_call_b.read_with(cx_b, |call, _| call.location().unwrap().upgrade().unwrap()); + + // client B opens 1.txt + let (workspace, cx_b) = client_b.active_workspace(cx_b); + cx_b.simulate_keystrokes("cmd-p 1 enter"); + let editor_b = cx_b.update(|cx| workspace.read(cx).active_item_as::(cx).unwrap()); + + active_call_a + .update(cx_a, |call, cx| { + call.room().unwrap().update(cx, |room, cx| { + room.set_participant_role( + client_b.user_id().unwrap(), + proto::ChannelRole::Member, + cx, + ) + }) + }) + .await + .unwrap(); + + cx_a.run_until_parked(); + + assert!(project_b.read_with(cx_b, |project, _| !project.is_read_only())); + assert!(!editor_b.update(cx_b, |e, cx| e.read_only(cx))); +} diff --git a/crates/collab/src/tests/test_server.rs b/crates/collab/src/tests/test_server.rs index 034a85961f8e98966a2764c4196682fabf4b33cf..6d8bb05d670c1448d94dc398d0ef5a069528a3f4 100644 --- a/crates/collab/src/tests/test_server.rs +++ b/crates/collab/src/tests/test_server.rs @@ -20,7 +20,11 @@ use node_runtime::FakeNodeRuntime; use notifications::NotificationStore; use parking_lot::Mutex; use project::{Project, WorktreeId}; -use rpc::{proto::ChannelRole, RECEIVE_TIMEOUT}; +use rpc::{ + proto::{self, ChannelRole}, + RECEIVE_TIMEOUT, +}; +use serde_json::json; use settings::SettingsStore; use std::{ cell::{Ref, RefCell, RefMut}, @@ -228,12 +232,16 @@ impl TestServer { Project::init(&client, cx); client::init(&client, cx); language::init(cx); - editor::init_settings(cx); + editor::init(cx); workspace::init(app_state.clone(), cx); audio::init((), cx); call::init(client.clone(), user_store.clone(), cx); channel::init(&client, user_store.clone(), cx); notifications::init(client.clone(), user_store, cx); + collab_ui::init(&app_state, cx); + file_finder::init(cx); + menu::init(); + settings::KeymapFile::load_asset("keymaps/default.json", cx).unwrap(); }); client @@ -351,6 +359,31 @@ impl TestServer { channel_id } + pub async fn make_public_channel( + &self, + channel: &str, + client: &TestClient, + cx: &mut TestAppContext, + ) -> u64 { + let channel_id = self + .make_channel(channel, None, (client, cx), &mut []) + .await; + + client + .channel_store() + .update(cx, |channel_store, cx| { + channel_store.set_channel_visibility( + channel_id, + proto::ChannelVisibility::Public, + cx, + ) + }) + .await + .unwrap(); + + channel_id + } + pub async fn make_channel_tree( &self, channels: &[(&str, Option<&str>)], @@ -580,6 +613,20 @@ impl TestClient { (project, worktree.read_with(cx, |tree, _| tree.id())) } + pub async fn build_test_project(&self, cx: &mut TestAppContext) -> Model { + self.fs() + .insert_tree( + "/a", + json!({ + "1.txt": "one\none\none", + "2.txt": "two\ntwo\ntwo", + "3.txt": "three\nthree\nthree", + }), + ) + .await; + self.build_local_project("/a", cx).await.0 + } + pub fn build_empty_local_project(&self, cx: &mut TestAppContext) -> Model { cx.update(|cx| { Project::local( @@ -619,6 +666,18 @@ impl TestClient { ) -> (View, &'a mut VisualTestContext) { cx.add_window_view(|cx| Workspace::new(0, project.clone(), self.app_state.clone(), cx)) } + + pub fn active_workspace<'a>( + &'a self, + cx: &'a mut TestAppContext, + ) -> (View, &'a mut VisualTestContext) { + let window = cx.update(|cx| cx.active_window().unwrap().downcast::().unwrap()); + + let view = window.root_view(cx).unwrap(); + let cx = Box::new(VisualTestContext::from_window(*window.deref(), cx)); + // it might be nice to try and cleanup these at the end of each test. + (view, Box::leak(cx)) + } } impl Drop for TestClient { diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 86d0131d70afa9dbf0a9c51d6943dcb1f1ad2f80..e25ad0d225680e8454ae69ce063a1fb5d67d1b2a 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -37,7 +37,7 @@ use ui::{ use util::{maybe, ResultExt, TryFutureExt}; use workspace::{ dock::{DockPosition, Panel, PanelEvent}, - notifications::NotifyResultExt, + notifications::{NotifyResultExt, NotifyTaskExt}, Workspace, }; @@ -140,6 +140,7 @@ enum ListEntry { user: Arc, peer_id: Option, is_pending: bool, + role: proto::ChannelRole, }, ParticipantProject { project_id: u64, @@ -151,10 +152,6 @@ enum ListEntry { peer_id: Option, is_last: bool, }, - GuestCount { - count: usize, - has_visible_participants: bool, - }, IncomingRequest(Arc), OutgoingRequest(Arc), ChannelInvite(Arc), @@ -384,14 +381,10 @@ impl CollabPanel { if !self.collapsed_sections.contains(&Section::ActiveCall) { let room = room.read(cx); - let mut guest_count_ix = 0; - let mut guest_count = if room.read_only() { 1 } else { 0 }; - let mut non_guest_count = if room.read_only() { 0 } else { 1 }; if let Some(channel_id) = room.channel_id() { self.entries.push(ListEntry::ChannelNotes { channel_id }); self.entries.push(ListEntry::ChannelChat { channel_id }); - guest_count_ix = self.entries.len(); } // Populate the active user. @@ -410,12 +403,13 @@ impl CollabPanel { &Default::default(), executor.clone(), )); - if !matches.is_empty() && !room.read_only() { + if !matches.is_empty() { let user_id = user.id; self.entries.push(ListEntry::CallParticipant { user, peer_id: None, is_pending: false, + role: room.local_participant().role, }); let mut projects = room.local_participant().projects.iter().peekable(); while let Some(project) = projects.next() { @@ -442,12 +436,6 @@ impl CollabPanel { room.remote_participants() .iter() .filter_map(|(_, participant)| { - if participant.role == proto::ChannelRole::Guest { - guest_count += 1; - return None; - } else { - non_guest_count += 1; - } Some(StringMatchCandidate { id: participant.user.id as usize, string: participant.user.github_login.clone(), @@ -455,7 +443,7 @@ impl CollabPanel { }) }), ); - let matches = executor.block(match_strings( + let mut matches = executor.block(match_strings( &self.match_candidates, &query, true, @@ -463,6 +451,15 @@ impl CollabPanel { &Default::default(), executor.clone(), )); + matches.sort_by(|a, b| { + let a_is_guest = room.role_for_user(a.candidate_id as u64) + == Some(proto::ChannelRole::Guest); + let b_is_guest = room.role_for_user(b.candidate_id as u64) + == Some(proto::ChannelRole::Guest); + a_is_guest + .cmp(&b_is_guest) + .then_with(|| a.string.cmp(&b.string)) + }); for mat in matches { let user_id = mat.candidate_id as u64; let participant = &room.remote_participants()[&user_id]; @@ -470,6 +467,7 @@ impl CollabPanel { user: participant.user.clone(), peer_id: Some(participant.peer_id), is_pending: false, + role: participant.role, }); let mut projects = participant.projects.iter().peekable(); while let Some(project) = projects.next() { @@ -488,15 +486,6 @@ impl CollabPanel { }); } } - if guest_count > 0 { - self.entries.insert( - guest_count_ix, - ListEntry::GuestCount { - count: guest_count, - has_visible_participants: non_guest_count > 0, - }, - ); - } // Populate pending participants. self.match_candidates.clear(); @@ -521,6 +510,7 @@ impl CollabPanel { user: room.pending_participants()[mat.candidate_id].clone(), peer_id: None, is_pending: true, + role: proto::ChannelRole::Member, })); } } @@ -834,13 +824,19 @@ impl CollabPanel { user: &Arc, peer_id: Option, is_pending: bool, + role: proto::ChannelRole, is_selected: bool, cx: &mut ViewContext, ) -> ListItem { + let user_id = user.id; let is_current_user = - self.user_store.read(cx).current_user().map(|user| user.id) == Some(user.id); + self.user_store.read(cx).current_user().map(|user| user.id) == Some(user_id); let tooltip = format!("Follow {}", user.github_login); + let is_call_admin = ActiveCall::global(cx).read(cx).room().is_some_and(|room| { + room.read(cx).local_participant().role == proto::ChannelRole::Admin + }); + ListItem::new(SharedString::from(user.github_login.clone())) .start_slot(Avatar::new(user.avatar_uri.clone())) .child(Label::new(user.github_login.clone())) @@ -853,17 +849,27 @@ impl CollabPanel { .on_click(move |_, cx| Self::leave_call(cx)) .tooltip(|cx| Tooltip::text("Leave Call", cx)) .into_any_element() + } else if role == proto::ChannelRole::Guest { + Label::new("Guest").color(Color::Muted).into_any_element() } else { div().into_any_element() }) - .when_some(peer_id, |this, peer_id| { - this.tooltip(move |cx| Tooltip::text(tooltip.clone(), cx)) + .when_some(peer_id, |el, peer_id| { + if role == proto::ChannelRole::Guest { + return el; + } + el.tooltip(move |cx| Tooltip::text(tooltip.clone(), cx)) .on_click(cx.listener(move |this, _, cx| { this.workspace .update(cx, |workspace, cx| workspace.follow(peer_id, cx)) .ok(); })) }) + .when(is_call_admin && role == proto::ChannelRole::Guest, |el| { + el.on_secondary_mouse_down(cx.listener(move |this, event: &MouseDownEvent, cx| { + this.deploy_participant_context_menu(event.position, user_id, cx) + })) + }) } fn render_participant_project( @@ -986,41 +992,6 @@ impl CollabPanel { .tooltip(move |cx| Tooltip::text("Open Chat", cx)) } - fn render_guest_count( - &self, - count: usize, - has_visible_participants: bool, - is_selected: bool, - cx: &mut ViewContext, - ) -> impl IntoElement { - let manageable_channel_id = ActiveCall::global(cx).read(cx).room().and_then(|room| { - let room = room.read(cx); - if room.local_participant_is_admin() { - room.channel_id() - } else { - None - } - }); - - ListItem::new("guest_count") - .selected(is_selected) - .start_slot( - h_stack() - .gap_1() - .child(render_tree_branch(!has_visible_participants, false, cx)) - .child(""), - ) - .child(Label::new(if count == 1 { - format!("{} guest", count) - } else { - format!("{} guests", count) - })) - .when_some(manageable_channel_id, |el, channel_id| { - el.tooltip(move |cx| Tooltip::text("Manage Members", cx)) - .on_click(cx.listener(move |this, _, cx| this.manage_members(channel_id, cx))) - }) - } - fn has_subchannels(&self, ix: usize) -> bool { self.entries.get(ix).map_or(false, |entry| { if let ListEntry::Channel { has_children, .. } = entry { @@ -1031,6 +1002,47 @@ impl CollabPanel { }) } + fn deploy_participant_context_menu( + &mut self, + position: Point, + user_id: u64, + cx: &mut ViewContext, + ) { + let this = cx.view().clone(); + + let context_menu = ContextMenu::build(cx, |context_menu, cx| { + context_menu.entry( + "Allow Write Access", + None, + cx.handler_for(&this, move |_, cx| { + ActiveCall::global(cx) + .update(cx, |call, cx| { + let Some(room) = call.room() else { + return Task::ready(Ok(())); + }; + room.update(cx, |room, cx| { + room.set_participant_role(user_id, proto::ChannelRole::Member, cx) + }) + }) + .detach_and_notify_err(cx) + }), + ) + }); + + cx.focus_view(&context_menu); + let subscription = + cx.subscribe(&context_menu, |this, _, _: &DismissEvent, cx| { + if this.context_menu.as_ref().is_some_and(|context_menu| { + context_menu.0.focus_handle(cx).contains_focused(cx) + }) { + cx.focus_self(); + } + this.context_menu.take(); + cx.notify(); + }); + self.context_menu = Some((context_menu, position, subscription)); + } + fn deploy_channel_context_menu( &mut self, position: Point, @@ -1242,18 +1254,6 @@ impl CollabPanel { }); } } - ListEntry::GuestCount { .. } => { - let Some(room) = ActiveCall::global(cx).read(cx).room() else { - return; - }; - let room = room.read(cx); - let Some(channel_id) = room.channel_id() else { - return; - }; - if room.local_participant_is_admin() { - self.manage_members(channel_id, cx) - } - } ListEntry::Channel { channel, .. } => { let is_active = maybe!({ let call_channel = ActiveCall::global(cx) @@ -1788,8 +1788,9 @@ impl CollabPanel { user, peer_id, is_pending, + role, } => self - .render_call_participant(user, *peer_id, *is_pending, is_selected, cx) + .render_call_participant(user, *peer_id, *is_pending, *role, is_selected, cx) .into_any_element(), ListEntry::ParticipantProject { project_id, @@ -1809,12 +1810,6 @@ impl CollabPanel { ListEntry::ParticipantScreen { peer_id, is_last } => self .render_participant_screen(*peer_id, *is_last, is_selected, cx) .into_any_element(), - ListEntry::GuestCount { - count, - has_visible_participants, - } => self - .render_guest_count(*count, *has_visible_participants, is_selected, cx) - .into_any_element(), ListEntry::ChannelNotes { channel_id } => self .render_channel_notes(*channel_id, is_selected, cx) .into_any_element(), @@ -2621,11 +2616,6 @@ impl PartialEq for ListEntry { return true; } } - ListEntry::GuestCount { .. } => { - if let ListEntry::GuestCount { .. } = other { - return true; - } - } } false } diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index 0f71ea61a9ec347b01e601f5e3c1a2237b80e691..6ec37afb0fc2afea5d4e70d6f617c73462170a06 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -238,6 +238,10 @@ impl TestAppContext { } } + pub fn run_until_parked(&mut self) { + self.background_executor.run_until_parked() + } + pub fn dispatch_action(&mut self, window: AnyWindowHandle, action: A) where A: Action, diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index d9472e8a77afc2dc7222d003aa23f513448ed661..08210875b841cb744b2b9734d9f8c3622da86bc2 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -254,6 +254,7 @@ pub enum Event { LanguageChanged, Reparsed, DiagnosticsUpdated, + CapabilityChanged, Closed, } @@ -631,6 +632,11 @@ impl Buffer { .set_language_registry(language_registry); } + pub fn set_capability(&mut self, capability: Capability, cx: &mut ModelContext) { + self.capability = capability; + cx.emit(Event::CapabilityChanged) + } + pub fn did_save( &mut self, version: clock::Global, diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index 946e6af5ab5fd9a97439edb5acb296972068cf44..f3ecd2d25f2b3866b1f6c3f0ce68415fa7cd53ae 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -80,6 +80,7 @@ pub enum Event { Reloaded, DiffBaseChanged, LanguageChanged, + CapabilityChanged, Reparsed, Saved, FileHandleChanged, @@ -1404,7 +1405,7 @@ impl MultiBuffer { fn on_buffer_event( &mut self, - _: Model, + buffer: Model, event: &language::Event, cx: &mut ModelContext, ) { @@ -1421,6 +1422,10 @@ impl MultiBuffer { language::Event::Reparsed => Event::Reparsed, language::Event::DiagnosticsUpdated => Event::DiagnosticsUpdated, language::Event::Closed => Event::Closed, + language::Event::CapabilityChanged => { + self.capability = buffer.read(cx).capability(); + Event::CapabilityChanged + } // language::Event::Operation(_) => return, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 044b750ad9d7cecce1a00f491e0fd66199989bb7..c412fad0e10ba07ee4849273577d6b2425089820 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -799,7 +799,7 @@ impl Project { prettiers_per_worktree: HashMap::default(), prettier_instances: HashMap::default(), }; - this.set_role(role); + this.set_role(role, cx); for worktree in worktrees { let _ = this.add_worktree(&worktree, cx); } @@ -1622,14 +1622,22 @@ impl Project { cx.notify(); } - pub fn set_role(&mut self, role: proto::ChannelRole) { - if let Some(ProjectClientState::Remote { capability, .. }) = &mut self.client_state { - *capability = if role == proto::ChannelRole::Member || role == proto::ChannelRole::Admin - { + pub fn set_role(&mut self, role: proto::ChannelRole, cx: &mut ModelContext) { + let new_capability = + if role == proto::ChannelRole::Member || role == proto::ChannelRole::Admin { Capability::ReadWrite } else { Capability::ReadOnly }; + if let Some(ProjectClientState::Remote { capability, .. }) = &mut self.client_state { + if *capability == new_capability { + return; + } + + *capability = new_capability; + } + for buffer in self.opened_buffers() { + buffer.update(cx, |buffer, cx| buffer.set_capability(new_capability, cx)); } } diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index e423441a30218674c4d6b68098a8f724c8a32654..dd5d77440edaaae98876f91a4fc5fa85999dd6ed 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -180,7 +180,8 @@ message Envelope { DeleteNotification delete_notification = 152; MarkNotificationRead mark_notification_read = 153; LspExtExpandMacro lsp_ext_expand_macro = 154; - LspExtExpandMacroResponse lsp_ext_expand_macro_response = 155; // Current max + LspExtExpandMacroResponse lsp_ext_expand_macro_response = 155; + SetRoomParticipantRole set_room_participant_role = 156; // Current max } } @@ -1633,3 +1634,9 @@ message LspExtExpandMacroResponse { string name = 1; string expansion = 2; } + +message SetRoomParticipantRole { + uint64 room_id = 1; + uint64 user_id = 2; + ChannelRole role = 3; +} diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index 25b8b00dae99fe3e4c11677e480c6c48fa9f08c8..5d0a99415480c30969a9a072287841ae6dd6c689 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -283,6 +283,7 @@ messages!( (UsersResponse, Foreground), (LspExtExpandMacro, Background), (LspExtExpandMacroResponse, Background), + (SetRoomParticipantRole, Foreground), ); request_messages!( @@ -367,6 +368,7 @@ request_messages!( (UpdateProject, Ack), (UpdateWorktree, Ack), (LspExtExpandMacro, LspExtExpandMacroResponse), + (SetRoomParticipantRole, Ack), ); entity_messages!( diff --git a/crates/vim/src/test/vim_test_context.rs b/crates/vim/src/test/vim_test_context.rs index 5ed5296bff44d3e76c32f2a4b768afd760d1d121..965ac63d381a306a1fdd1c18c0c9d379292461a8 100644 --- a/crates/vim/src/test/vim_test_context.rs +++ b/crates/vim/src/test/vim_test_context.rs @@ -17,7 +17,6 @@ pub struct VimTestContext { impl VimTestContext { pub fn init(cx: &mut gpui::TestAppContext) { if cx.has_global::() { - dbg!("OOPS"); return; } cx.update(|cx| { diff --git a/crates/workspace/src/notifications.rs b/crates/workspace/src/notifications.rs index 394772b9c45b0a31bf36260389ee2e2fcc7ec717..ac091744c6a6f8f8f76f6cc916ffa19ebba400f7 100644 --- a/crates/workspace/src/notifications.rs +++ b/crates/workspace/src/notifications.rs @@ -2,7 +2,7 @@ use crate::{Toast, Workspace}; use collections::HashMap; use gpui::{ AnyView, AppContext, AsyncWindowContext, DismissEvent, Entity, EntityId, EventEmitter, Render, - View, ViewContext, VisualContext, + Task, View, ViewContext, VisualContext, WindowContext, }; use std::{any::TypeId, ops::DerefMut}; @@ -393,3 +393,18 @@ where } } } + +pub trait NotifyTaskExt { + fn detach_and_notify_err(self, cx: &mut WindowContext); +} + +impl NotifyTaskExt for Task> +where + E: std::fmt::Debug + 'static, + R: 'static, +{ + fn detach_and_notify_err(self, cx: &mut WindowContext) { + cx.spawn(|mut cx| async move { self.await.notify_async_err(&mut cx) }) + .detach(); + } +} From 8669b08161a8718c3765ad5d217e17f0fdaeed6d Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 8 Jan 2024 22:23:34 -0700 Subject: [PATCH 20/78] Failing test for unmuting microphone --- crates/call/src/room.rs | 6 ++- .../collab/src/tests/channel_guest_tests.rs | 44 ++++++++++++++----- crates/live_kit_client/src/test.rs | 8 ++++ 3 files changed, 45 insertions(+), 13 deletions(-) diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 6f8f8e171793931d64948e2972536eb83c739214..0a599e85cf4148fd08d902d611d1f3d70b336d39 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -173,7 +173,11 @@ impl Room { cx.spawn(|this, mut cx| async move { connect.await?; - if !cx.update(|cx| Self::mute_on_join(cx))? { + let is_read_only = this + .update(&mut cx, |room, _| room.read_only()) + .unwrap_or(true); + + if !cx.update(|cx| Self::mute_on_join(cx))? && !is_read_only { this.update(&mut cx, |this, cx| this.share_microphone(cx))? .await?; } diff --git a/crates/collab/src/tests/channel_guest_tests.rs b/crates/collab/src/tests/channel_guest_tests.rs index 45e9e5a1d7588b26fc314f0293423af5803d6166..8d21db678bed364cd8504c839bb3c15b339b1cd0 100644 --- a/crates/collab/src/tests/channel_guest_tests.rs +++ b/crates/collab/src/tests/channel_guest_tests.rs @@ -3,7 +3,6 @@ use call::ActiveCall; use editor::Editor; use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext}; use rpc::proto; -use workspace::Workspace; #[gpui::test] async fn test_channel_guests( @@ -44,6 +43,8 @@ async fn test_channel_guests( let active_call_b = cx_b.read(ActiveCall::global); let project_b = active_call_b.read_with(cx_b, |call, _| call.location().unwrap().upgrade().unwrap()); + let room_b = active_call_b.update(cx_b, |call, _| call.room().unwrap().clone()); + assert_eq!( project_b.read_with(cx_b, |project, _| project.remote_id()), Some(project_id), @@ -55,7 +56,8 @@ async fn test_channel_guests( project.create_entry((worktree_id, "b.txt"), false, cx) }) .await - .is_err()) + .is_err()); + assert!(room_b.read_with(cx_b, |room, _| !room.is_sharing_mic())); } #[gpui::test] @@ -64,7 +66,6 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test let client_a = server.create_client(cx_a, "user_a").await; let client_b = server.create_client(cx_b, "user_b").await; let active_call_a = cx_a.read(ActiveCall::global); - let active_call_b = cx_b.read(ActiveCall::global); let channel_id = server .make_public_channel("the-channel", &client_a, cx_a) @@ -76,7 +77,7 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test .unwrap(); // Client A shares a project in the channel - let project_id = active_call_a + active_call_a .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .await .unwrap(); @@ -88,14 +89,29 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test .unwrap(); cx_a.run_until_parked(); - let project_b = - active_call_b.read_with(cx_b, |call, _| call.location().unwrap().upgrade().unwrap()); - - // client B opens 1.txt - let (workspace, cx_b) = client_b.active_workspace(cx_b); + // client B opens 1.txt as a guest + let (workspace_b, cx_b) = client_b.active_workspace(cx_b); + let room_b = cx_b + .read(ActiveCall::global) + .update(cx_b, |call, _| call.room().unwrap().clone()); cx_b.simulate_keystrokes("cmd-p 1 enter"); - let editor_b = cx_b.update(|cx| workspace.read(cx).active_item_as::(cx).unwrap()); + let (project_b, editor_b) = workspace_b.update(cx_b, |workspace, cx| { + ( + workspace.project().clone(), + workspace.active_item_as::(cx).unwrap(), + ) + }); + assert!(project_b.read_with(cx_b, |project, _| project.is_read_only())); + assert!(editor_b.update(cx_b, |e, cx| e.read_only(cx))); + assert!(dbg!( + room_b + .update(cx_b, |room, cx| room.share_microphone(cx)) + .await + ) + .is_err()); + + // B is promoted active_call_a .update(cx_a, |call, cx| { call.room().unwrap().update(cx, |room, cx| { @@ -108,9 +124,13 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test }) .await .unwrap(); - cx_a.run_until_parked(); + // project and buffers are now editable assert!(project_b.read_with(cx_b, |project, _| !project.is_read_only())); - assert!(!editor_b.update(cx_b, |e, cx| e.read_only(cx))); + assert!(editor_b.update(cx_b, |editor, cx| !editor.read_only(cx))); + room_b + .update(cx_b, |room, cx| room.share_microphone(cx)) + .await + .unwrap() } diff --git a/crates/live_kit_client/src/test.rs b/crates/live_kit_client/src/test.rs index 1106e66f31e6d3f517fcc21b2fdffd6a7d209a09..07a12a4ab6f5e39df0b672d8fb589497a765fd04 100644 --- a/crates/live_kit_client/src/test.rs +++ b/crates/live_kit_client/src/test.rs @@ -167,6 +167,10 @@ impl TestServer { let identity = claims.sub.unwrap().to_string(); let room_name = claims.video.room.unwrap(); + if claims.video.can_publish == Some(false) { + return Err(anyhow!("user is not allowed to publish")); + } + let mut server_rooms = self.rooms.lock(); let room = server_rooms .get_mut(&*room_name) @@ -205,6 +209,10 @@ impl TestServer { let identity = claims.sub.unwrap().to_string(); let room_name = claims.video.room.unwrap(); + if claims.video.can_publish == Some(false) { + return Err(anyhow!("user is not allowed to publish")); + } + let mut server_rooms = self.rooms.lock(); let room = server_rooms .get_mut(&*room_name) From 82f7dd9bbbee58c81a27219ac9bfd320a3525ecc Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 8 Jan 2024 22:32:12 -0700 Subject: [PATCH 21/78] Prototype cursor sharing (the inefficient way) I think this will be a key user experience driver, but we do need to find a way to enable it without widening our vector clocks. --- crates/collab/src/db/queries/projects.rs | 8 +++++--- crates/collab/src/rpc.rs | 15 ++++++++++++++- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/crates/collab/src/db/queries/projects.rs b/crates/collab/src/db/queries/projects.rs index 6e1bf16309bd69315903a37ce7b57d389e749161..5b98228f112fd3a7072529dcd31dd915098e79c7 100644 --- a/crates/collab/src/db/queries/projects.rs +++ b/crates/collab/src/db/queries/projects.rs @@ -883,6 +883,7 @@ impl Database { &self, project_id: ProjectId, connection_id: ConnectionId, + requires_write: bool, ) -> Result>> { let room_id = self.room_id_for_project(project_id).await?; self.room_transaction(room_id, |tx| async move { @@ -893,9 +894,10 @@ impl Database { .await? .ok_or_else(|| anyhow!("no such room"))?; - if !current_participant - .role - .map_or(false, |role| role.can_edit_projects()) + if requires_write + && !current_participant + .role + .map_or(false, |role| role.can_edit_projects()) { Err(anyhow!("not authorized to edit projects"))?; } diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 5301ca9a23b0a3bd3d0cce44de7eaa640449347e..987c6dbffef43581112c951b3df9e45b11472c82 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -1814,11 +1814,24 @@ async fn update_buffer( let mut guest_connection_ids; let mut host_connection_id = None; + let mut requires_write_permission = false; + + for op in request.operations.iter() { + match op.variant { + None | Some(proto::operation::Variant::UpdateSelections(_)) => {} + Some(_) => requires_write_permission = true, + } + } + { let collaborators = session .db() .await - .project_collaborators_for_buffer_update(project_id, session.connection_id) + .project_collaborators_for_buffer_update( + project_id, + session.connection_id, + requires_write_permission, + ) .await?; guest_connection_ids = Vec::with_capacity(collaborators.len() - 1); for collaborator in collaborators.iter() { From 0f7b47af390eb04bd82fa24d0b285d0917411907 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 8 Jan 2024 22:45:15 -0700 Subject: [PATCH 22/78] Run migations on development server start --- crates/collab/src/lib.rs | 6 ++++++ crates/collab/src/main.rs | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/crates/collab/src/lib.rs b/crates/collab/src/lib.rs index 85216525b0018c6d051c55a5882af8445f45c7d0..68fbb4e4d7d48286564da1ab7cc6359cea819dcd 100644 --- a/crates/collab/src/lib.rs +++ b/crates/collab/src/lib.rs @@ -103,6 +103,12 @@ pub struct Config { pub zed_environment: String, } +impl Config { + pub fn is_development(&self) -> bool { + self.zed_environment == "development" + } +} + #[derive(Default, Deserialize)] pub struct MigrateConfig { pub database_url: String, diff --git a/crates/collab/src/main.rs b/crates/collab/src/main.rs index 6fbb451fee71a01c1e41c123523715d2aae61f65..87df7cac6fc77f4204d00c4df8a759d141b0345d 100644 --- a/crates/collab/src/main.rs +++ b/crates/collab/src/main.rs @@ -53,6 +53,25 @@ async fn main() -> Result<()> { let config = envy::from_env::().expect("error loading config"); init_tracing(&config); + if config.is_development() { + // sanity check database url so even if we deploy a busted ZED_ENVIRONMENT to production + // we do not run + if config.database_url != "postgres://postgres@localhost/zed" { + panic!("about to run development migrations on a non-development database?") + } + let migrations_path = Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/migrations")); + let db_options = db::ConnectOptions::new(config.database_url.clone()); + let db = Database::new(db_options, Executor::Production).await?; + + let migrations = db.migrate(&migrations_path, false).await?; + for (migration, duration) in migrations { + println!( + "Ran {} {} {:?}", + migration.version, migration.description, duration + ); + } + } + let state = AppState::new(config).await?; let listener = TcpListener::bind(&format!("0.0.0.0:{}", state.config.http_port)) From aa1d2d2f24c666536d6d57599cdabcf6238b85cd Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 9 Jan 2024 10:08:27 +0200 Subject: [PATCH 23/78] Remove dbg! usage from tests --- .../src/syntax_map/syntax_map_tests.rs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/crates/language/src/syntax_map/syntax_map_tests.rs b/crates/language/src/syntax_map/syntax_map_tests.rs index f20f481613eabfffddee791873d78d6383086ade..8b9169d1cce47f96cd9fb6a768d5f3b5397ed4d4 100644 --- a/crates/language/src/syntax_map/syntax_map_tests.rs +++ b/crates/language/src/syntax_map/syntax_map_tests.rs @@ -258,19 +258,19 @@ fn test_typing_multiple_new_injections() { let (buffer, syntax_map) = test_edit_sequence( "Rust", &[ - "fn a() { dbg }", - "fn a() { dbg«!» }", - "fn a() { dbg!«()» }", - "fn a() { dbg!(«b») }", - "fn a() { dbg!(b«.») }", - "fn a() { dbg!(b.«c») }", - "fn a() { dbg!(b.c«()») }", - "fn a() { dbg!(b.c(«vec»)) }", - "fn a() { dbg!(b.c(vec«!»)) }", - "fn a() { dbg!(b.c(vec!«[]»)) }", - "fn a() { dbg!(b.c(vec![«d»])) }", - "fn a() { dbg!(b.c(vec![d«.»])) }", - "fn a() { dbg!(b.c(vec![d.«e»])) }", + "fn a() { test_macro }", + "fn a() { test_macro«!» }", + "fn a() { test_macro!«()» }", + "fn a() { test_macro!(«b») }", + "fn a() { test_macro!(b«.») }", + "fn a() { test_macro!(b.«c») }", + "fn a() { test_macro!(b.c«()») }", + "fn a() { test_macro!(b.c(«vec»)) }", + "fn a() { test_macro!(b.c(vec«!»)) }", + "fn a() { test_macro!(b.c(vec!«[]»)) }", + "fn a() { test_macro!(b.c(vec![«d»])) }", + "fn a() { test_macro!(b.c(vec![d«.»])) }", + "fn a() { test_macro!(b.c(vec![d.«e»])) }", ], ); @@ -278,7 +278,7 @@ fn test_typing_multiple_new_injections() { &syntax_map, &buffer, &["field"], - "fn a() { dbg!(b.«c»(vec![d.«e»])) }", + "fn a() { test_macro!(b.«c»(vec![d.«e»])) }", ); } From 625c9d89802fa580ca1860975a6ddd53f13656ff Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 9 Jan 2024 10:13:40 +0200 Subject: [PATCH 24/78] Remove some todo!'s --- crates/editor/src/test.rs | 6 +- crates/gpui/docs/key_dispatch.md | 4 +- crates/gpui/src/action.rs | 1 - crates/gpui/src/app/test_context.rs | 5 +- crates/gpui/src/elements/overlay.rs | 2 +- crates/gpui/src/platform/test/display.rs | 2 +- crates/gpui/src/platform/test/platform.rs | 1 - crates/gpui/tests/action_macros.rs | 12 +-- crates/workspace/src/dock.rs | 1 - crates/workspace/src/notifications.rs | 99 ----------------------- crates/workspace/src/pane_group.rs | 3 +- crates/workspace/src/toolbar.rs | 83 ------------------- crates/workspace/src/workspace.rs | 9 +-- crates/zed/src/zed.rs | 1 - 14 files changed, 18 insertions(+), 211 deletions(-) diff --git a/crates/editor/src/test.rs b/crates/editor/src/test.rs index 4ce539ad797b2a3064a57ca15ff8e97b0f3c307e..d3337db2581b0bc7148059457cc9eef6943cc8d1 100644 --- a/crates/editor/src/test.rs +++ b/crates/editor/src/test.rs @@ -60,8 +60,7 @@ pub fn assert_text_with_selections( #[allow(dead_code)] #[cfg(any(test, feature = "test-support"))] pub(crate) fn build_editor(buffer: Model, cx: &mut ViewContext) -> Editor { - // todo!() - Editor::new(EditorMode::Full, buffer, None, /*None,*/ cx) + Editor::new(EditorMode::Full, buffer, None, cx) } pub(crate) fn build_editor_with_project( @@ -69,6 +68,5 @@ pub(crate) fn build_editor_with_project( buffer: Model, cx: &mut ViewContext, ) -> Editor { - // todo!() - Editor::new(EditorMode::Full, buffer, Some(project), /*None,*/ cx) + Editor::new(EditorMode::Full, buffer, Some(project), cx) } diff --git a/crates/gpui/docs/key_dispatch.md b/crates/gpui/docs/key_dispatch.md index daf6f820cd5c98f45415c071d38518d55b269d2f..804a0b576194b2be4f23852a49369d988b9d81cb 100644 --- a/crates/gpui/docs/key_dispatch.md +++ b/crates/gpui/docs/key_dispatch.md @@ -50,7 +50,7 @@ impl Render for Menu { .on_action(|this, move: &MoveDown, cx| { // ... }) - .children(todo!()) + .children(unimplemented!()) } } ``` @@ -68,7 +68,7 @@ impl Render for Menu { .on_action(|this, move: &MoveDown, cx| { // ... }) - .children(todo!()) + .children(unimplemented!()) } } ``` diff --git a/crates/gpui/src/action.rs b/crates/gpui/src/action.rs index e335c4255e4deb0d6c5720b03ce017952a6fa229..81b392087aeb8f80571b7bb379948b1b886fe793 100644 --- a/crates/gpui/src/action.rs +++ b/crates/gpui/src/action.rs @@ -203,7 +203,6 @@ macro_rules! __impl_action { ) } - // todo!() why is this needed in addition to name? fn debug_name() -> &'static str where Self: ::std::marker::Sized diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index 0f71ea61a9ec347b01e601f5e3c1a2237b80e691..a530aaf69b94ce65d35feee015738f306b3a79c3 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -467,12 +467,11 @@ impl View { } } - // todo!(start_waiting) - // cx.borrow().foreground_executor().start_waiting(); + cx.borrow().background_executor().start_waiting(); rx.recv() .await .expect("view dropped with pending condition"); - // cx.borrow().foreground_executor().finish_waiting(); + cx.borrow().background_executor().finish_waiting(); } }) .await diff --git a/crates/gpui/src/elements/overlay.rs b/crates/gpui/src/elements/overlay.rs index eab3ee60b41ded0f39fa548285b3d6be572e2ba1..6772baa2f9c117a834a716efe4d5e733948ce3b3 100644 --- a/crates/gpui/src/elements/overlay.rs +++ b/crates/gpui/src/elements/overlay.rs @@ -14,8 +14,8 @@ pub struct Overlay { children: SmallVec<[AnyElement; 2]>, anchor_corner: AnchorCorner, fit_mode: OverlayFitMode, - // todo!(); anchor_position: Option>, + // todo!(); // position_mode: OverlayPositionMode, } diff --git a/crates/gpui/src/platform/test/display.rs b/crates/gpui/src/platform/test/display.rs index 95f1daf8e92fc8bc620a91d3c1aa1ed12818c384..68dbb0fdf3466b9f3a2800bf68768aea2f5dd1e2 100644 --- a/crates/gpui/src/platform/test/display.rs +++ b/crates/gpui/src/platform/test/display.rs @@ -32,7 +32,7 @@ impl PlatformDisplay for TestDisplay { } fn as_any(&self) -> &dyn std::any::Any { - todo!() + unimplemented!() } fn bounds(&self) -> crate::Bounds { diff --git a/crates/gpui/src/platform/test/platform.rs b/crates/gpui/src/platform/test/platform.rs index 695323e9c46b8e2a8f4260a682d8e214f58c43f4..ec3d7a0ff008ee64c92e8ceece0a2ef32cacff14 100644 --- a/crates/gpui/src/platform/test/platform.rs +++ b/crates/gpui/src/platform/test/platform.rs @@ -103,7 +103,6 @@ impl TestPlatform { } } -// todo!("implement out what our tests needed in GPUI 1") impl Platform for TestPlatform { fn background_executor(&self) -> BackgroundExecutor { self.background_executor.clone() diff --git a/crates/gpui/tests/action_macros.rs b/crates/gpui/tests/action_macros.rs index 9e5f6dea16ca6ad0ad7a1eb7f7098b61e0fd4cea..99572a4b3c5b1c84a4aa65fb95c72b25b2aef9ec 100644 --- a/crates/gpui/tests/action_macros.rs +++ b/crates/gpui/tests/action_macros.rs @@ -18,33 +18,33 @@ fn test_action_macros() { impl gpui::Action for RegisterableAction { fn boxed_clone(&self) -> Box { - todo!() + unimplemented!() } fn as_any(&self) -> &dyn std::any::Any { - todo!() + unimplemented!() } fn partial_eq(&self, _action: &dyn gpui::Action) -> bool { - todo!() + unimplemented!() } fn name(&self) -> &str { - todo!() + unimplemented!() } fn debug_name() -> &'static str where Self: Sized, { - todo!() + unimplemented!() } fn build(_value: serde_json::Value) -> anyhow::Result> where Self: Sized, { - todo!() + unimplemented!() } } } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index c13a00b11c897b46ef5e2ac69ae10848c573ebf2..adc4fb5c80b2fd35af7ae32a620a550c977e469a 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -395,7 +395,6 @@ impl Dock { }) .ok(); } - // todo!() we do not use this event in the production code (even in zed1), remove it PanelEvent::Activate => { if let Some(ix) = this .panel_entries diff --git a/crates/workspace/src/notifications.rs b/crates/workspace/src/notifications.rs index 394772b9c45b0a31bf36260389ee2e2fcc7ec717..d70de8c13dc7b45522952474506d609e1a75788b 100644 --- a/crates/workspace/src/notifications.rs +++ b/crates/workspace/src/notifications.rs @@ -247,105 +247,6 @@ pub mod simple_message_notification { })) } } - // todo!() - // impl View for MessageNotification { - // fn ui_name() -> &'static str { - // "MessageNotification" - // } - - // fn render(&mut self, cx: &mut gpui::ViewContext) -> gpui::AnyElement { - // let theme = theme::current(cx).clone(); - // let theme = &theme.simple_message_notification; - - // enum MessageNotificationTag {} - - // let click_message = self.click_message.clone(); - // let message = match &self.message { - // NotificationMessage::Text(text) => { - // Text::new(text.to_owned(), theme.message.text.clone()).into_any() - // } - // NotificationMessage::Element(e) => e(theme.message.text.clone(), cx), - // }; - // let on_click = self.on_click.clone(); - // let has_click_action = on_click.is_some(); - - // Flex::column() - // .with_child( - // Flex::row() - // .with_child( - // message - // .contained() - // .with_style(theme.message.container) - // .aligned() - // .top() - // .left() - // .flex(1., true), - // ) - // .with_child( - // MouseEventHandler::new::(0, cx, |state, _| { - // let style = theme.dismiss_button.style_for(state); - // Svg::new("icons/x.svg") - // .with_color(style.color) - // .constrained() - // .with_width(style.icon_width) - // .aligned() - // .contained() - // .with_style(style.container) - // .constrained() - // .with_width(style.button_width) - // .with_height(style.button_width) - // }) - // .with_padding(Padding::uniform(5.)) - // .on_click(MouseButton::Left, move |_, this, cx| { - // this.dismiss(&Default::default(), cx); - // }) - // .with_cursor_style(CursorStyle::PointingHand) - // .aligned() - // .constrained() - // .with_height(cx.font_cache().line_height(theme.message.text.font_size)) - // .aligned() - // .top() - // .flex_float(), - // ), - // ) - // .with_children({ - // click_message - // .map(|click_message| { - // MouseEventHandler::new::( - // 0, - // cx, - // |state, _| { - // let style = theme.action_message.style_for(state); - - // Flex::row() - // .with_child( - // Text::new(click_message, style.text.clone()) - // .contained() - // .with_style(style.container), - // ) - // .contained() - // }, - // ) - // .on_click(MouseButton::Left, move |_, this, cx| { - // if let Some(on_click) = on_click.as_ref() { - // on_click(cx); - // this.dismiss(&Default::default(), cx); - // } - // }) - // // Since we're not using a proper overlay, we have to capture these extra events - // .on_down(MouseButton::Left, |_, _, _| {}) - // .on_up(MouseButton::Left, |_, _, _| {}) - // .with_cursor_style(if has_click_action { - // CursorStyle::PointingHand - // } else { - // CursorStyle::Arrow - // }) - // }) - // .into_iter() - // }) - // .into_any() - // } - // } } pub trait NotifyResultExt { diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 68fd3c084f591e8bd7401c66e056e38beada1278..e28d0e63deda2a97ac3c4a9cd77d4beebb802333 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -12,7 +12,7 @@ use serde::Deserialize; use std::sync::Arc; use ui::{prelude::*, Button}; -const HANDLE_HITBOX_SIZE: f32 = 10.0; //todo!(change this back to 4) +const HANDLE_HITBOX_SIZE: f32 = 4.0; const HORIZONTAL_MIN_SIZE: f32 = 80.; const VERTICAL_MIN_SIZE: f32 = 100.; @@ -707,7 +707,6 @@ mod element { proposed_current_pixel_change -= current_pixel_change; } - // todo!(schedule serialize) workspace .update(cx, |this, cx| this.schedule_serialize(cx)) .log_err(); diff --git a/crates/workspace/src/toolbar.rs b/crates/workspace/src/toolbar.rs index dc17cd3c1956b6f8475f04c6478ab86a86eb5689..cc072b08b9eac0cf13039dd45216483018d5701a 100644 --- a/crates/workspace/src/toolbar.rs +++ b/crates/workspace/src/toolbar.rs @@ -133,82 +133,6 @@ impl Render for Toolbar { } } -// todo!() -// impl View for Toolbar { -// fn ui_name() -> &'static str { -// "Toolbar" -// } - -// fn render(&mut self, cx: &mut ViewContext) -> AnyElement { -// let theme = &theme::current(cx).workspace.toolbar; - -// let mut primary_left_items = Vec::new(); -// let mut primary_right_items = Vec::new(); -// let mut secondary_item = None; -// let spacing = theme.item_spacing; -// let mut primary_items_row_count = 1; - -// for (item, position) in &self.items { -// match *position { -// ToolbarItemLocation::Hidden => {} - -// ToolbarItemLocation::PrimaryLeft { flex } => { -// primary_items_row_count = primary_items_row_count.max(item.row_count(cx)); -// let left_item = ChildView::new(item.as_any(), cx).aligned(); -// if let Some((flex, expanded)) = flex { -// primary_left_items.push(left_item.flex(flex, expanded).into_any()); -// } else { -// primary_left_items.push(left_item.into_any()); -// } -// } - -// ToolbarItemLocation::PrimaryRight { flex } => { -// primary_items_row_count = primary_items_row_count.max(item.row_count(cx)); -// let right_item = ChildView::new(item.as_any(), cx).aligned().flex_float(); -// if let Some((flex, expanded)) = flex { -// primary_right_items.push(right_item.flex(flex, expanded).into_any()); -// } else { -// primary_right_items.push(right_item.into_any()); -// } -// } - -// ToolbarItemLocation::Secondary => { -// secondary_item = Some( -// ChildView::new(item.as_any(), cx) -// .constrained() -// .with_height(theme.height * item.row_count(cx) as f32) -// .into_any(), -// ); -// } -// } -// } - -// let container_style = theme.container; -// let height = theme.height * primary_items_row_count as f32; - -// let mut primary_items = Flex::row().with_spacing(spacing); -// primary_items.extend(primary_left_items); -// primary_items.extend(primary_right_items); - -// let mut toolbar = Flex::column(); -// if !primary_items.is_empty() { -// toolbar.add_child(primary_items.constrained().with_height(height)); -// } -// if let Some(secondary_item) = secondary_item { -// toolbar.add_child(secondary_item); -// } - -// if toolbar.is_empty() { -// toolbar.into_any_named("toolbar") -// } else { -// toolbar -// .contained() -// .with_style(container_style) -// .into_any_named("toolbar") -// } -// } -// } - impl Toolbar { pub fn new() -> Self { Self { @@ -312,10 +236,3 @@ impl ToolbarItemViewHandle for View { self.read(cx).row_count(cx) } } - -// todo!() -// impl From<&dyn ToolbarItemViewHandle> for AnyViewHandle { -// fn from(val: &dyn ToolbarItemViewHandle) -> Self { -// val.as_any().clone() -// } -// } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index ad02637ae39795c0e61ee38074062c518d12a3eb..739ce88636ca32a88be1496103ad03ed42f63808 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -943,10 +943,8 @@ impl Workspace { cx: &mut ViewContext, ) -> Task> { let to_load = if let Some(pane) = pane.upgrade() { - // todo!("focus") - // cx.focus(&pane); - pane.update(cx, |pane, cx| { + pane.focus(cx); loop { // Retrieve the weak item handle from the history. let entry = pane.nav_history_mut().pop(mode, cx)?; @@ -1631,8 +1629,7 @@ impl Workspace { }); } - // todo!("focus") - // cx.focus_self(); + cx.focus_self(); cx.notify(); self.serialize_workspace(cx); } @@ -1713,6 +1710,7 @@ impl Workspace { cx.notify(); } + // todo!() // #[cfg(any(test, feature = "test-support"))] // pub fn zoomed_view(&self, cx: &AppContext) -> Option { // self.zoomed.and_then(|view| view.upgrade(cx)) @@ -2992,7 +2990,6 @@ impl Workspace { cx.notify(); } - #[allow(unused)] fn schedule_serialize(&mut self, cx: &mut ViewContext) { self._schedule_serialize = Some(cx.spawn(|this, mut cx| async move { cx.background_executor() diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 73368a988377e8edb927fb1d629c78385112a492..0b90961d2352a31e80f38dbd3eda86df4055e788 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -764,7 +764,6 @@ fn open_bundled_file( .detach_and_log_err(cx); } -// todo!() #[cfg(test)] mod tests { use super::*; From 29ed067b2651c881a99c9c3be9f1d3e523911d0c Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Tue, 9 Jan 2024 12:20:52 +0100 Subject: [PATCH 25/78] Add a missing default value to docs (#3973) Release Notes: - N/A --- crates/call/src/call_settings.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/call/src/call_settings.rs b/crates/call/src/call_settings.rs index 88c3fe84ce118e011e729e335cf8631ff273e014..441323ad5ffcf4d6d525525139905278bd15ca0b 100644 --- a/crates/call/src/call_settings.rs +++ b/crates/call/src/call_settings.rs @@ -9,9 +9,12 @@ pub struct CallSettings { pub mute_on_join: bool, } +/// Configuration of voice calls in Zed. #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] pub struct CallSettingsContent { /// Whether the microphone should be muted when joining a channel or a call. + /// + /// Default: false pub mute_on_join: Option, } From fa53353c575897b01e04eeeddb90d4e16729a700 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 9 Jan 2024 10:11:20 -0500 Subject: [PATCH 26/78] Rename `IconElement` to just `Icon` (#3974) This PR renames the `IconElement` component to just `Icon`. This better matches the rest of our components, as `IconElement` was the only one using this naming convention. The `Icon` enum has been renamed to `IconName` to free up the name. I was trying to come up with a way that would allow rendering an `Icon::Zed` directly (and thus make the `IconElement` a hidden part of the API), but I couldn't come up with a way to do this cleanly. Release Notes: - N/A --- crates/assistant/src/assistant_panel.rs | 34 +-- crates/auto_update/src/update_notification.rs | 4 +- crates/collab_ui/src/chat_panel.rs | 14 +- crates/collab_ui/src/collab_panel.rs | 62 +++--- .../src/collab_panel/channel_modal.rs | 4 +- .../src/collab_panel/contact_finder.rs | 4 +- crates/collab_ui/src/collab_titlebar_item.rs | 20 +- crates/collab_ui/src/notification_panel.rs | 12 +- crates/copilot_ui/src/copilot_button.rs | 12 +- crates/copilot_ui/src/sign_in.rs | 4 +- crates/diagnostics/src/diagnostics.rs | 12 +- crates/diagnostics/src/items.rs | 14 +- crates/diagnostics/src/toolbar_controls.rs | 4 +- crates/editor/src/editor.rs | 12 +- crates/feedback/src/deploy_feedback_button.rs | 4 +- crates/feedback/src/feedback_modal.rs | 2 +- crates/project_panel/src/project_panel.rs | 10 +- .../quick_action_bar/src/quick_action_bar.rs | 12 +- crates/search/src/buffer_search.rs | 16 +- crates/search/src/project_search.rs | 24 +-- crates/search/src/search.rs | 8 +- crates/search/src/search_bar.rs | 2 +- crates/terminal_view/src/terminal_panel.rs | 12 +- crates/terminal_view/src/terminal_view.rs | 4 +- crates/ui/src/components/button/button.rs | 10 +- .../ui/src/components/button/button_icon.rs | 12 +- .../ui/src/components/button/icon_button.rs | 10 +- crates/ui/src/components/checkbox.rs | 6 +- crates/ui/src/components/context_menu.rs | 10 +- crates/ui/src/components/disclosure.rs | 6 +- crates/ui/src/components/icon.rs | 202 +++++++++--------- crates/ui/src/components/keybinding.rs | 44 ++-- .../ui/src/components/list/list_sub_header.rs | 15 +- crates/ui/src/components/stories/button.rs | 6 +- crates/ui/src/components/stories/icon.rs | 8 +- .../ui/src/components/stories/icon_button.rs | 18 +- .../ui/src/components/stories/list_header.rs | 14 +- crates/ui/src/components/stories/list_item.rs | 8 +- crates/ui/src/components/stories/tab.rs | 2 +- crates/ui/src/components/stories/tab_bar.rs | 11 +- crates/ui/src/prelude.rs | 2 +- crates/workspace/src/dock.rs | 8 +- crates/workspace/src/notifications.rs | 4 +- crates/workspace/src/pane.rs | 18 +- crates/workspace/src/shared_screen.rs | 4 +- 45 files changed, 364 insertions(+), 360 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index f53343531af09083999e04f1d3dce92eafe34850..d4743afb714ab47be3e38b196a45174fb33c2aa5 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -933,7 +933,7 @@ impl AssistantPanel { } fn render_hamburger_button(cx: &mut ViewContext) -> impl IntoElement { - IconButton::new("hamburger_button", Icon::Menu) + IconButton::new("hamburger_button", IconName::Menu) .on_click(cx.listener(|this, _event, cx| { if this.active_editor().is_some() { this.set_active_editor_index(None, cx); @@ -957,7 +957,7 @@ impl AssistantPanel { } fn render_split_button(cx: &mut ViewContext) -> impl IntoElement { - IconButton::new("split_button", Icon::Snip) + IconButton::new("split_button", IconName::Snip) .on_click(cx.listener(|this, _event, cx| { if let Some(active_editor) = this.active_editor() { active_editor.update(cx, |editor, cx| editor.split(&Default::default(), cx)); @@ -968,7 +968,7 @@ impl AssistantPanel { } fn render_assist_button(cx: &mut ViewContext) -> impl IntoElement { - IconButton::new("assist_button", Icon::MagicWand) + IconButton::new("assist_button", IconName::MagicWand) .on_click(cx.listener(|this, _event, cx| { if let Some(active_editor) = this.active_editor() { active_editor.update(cx, |editor, cx| editor.assist(&Default::default(), cx)); @@ -979,7 +979,7 @@ impl AssistantPanel { } fn render_quote_button(cx: &mut ViewContext) -> impl IntoElement { - IconButton::new("quote_button", Icon::Quote) + IconButton::new("quote_button", IconName::Quote) .on_click(cx.listener(|this, _event, cx| { if let Some(workspace) = this.workspace.upgrade() { cx.window_context().defer(move |cx| { @@ -994,7 +994,7 @@ impl AssistantPanel { } fn render_plus_button(cx: &mut ViewContext) -> impl IntoElement { - IconButton::new("plus_button", Icon::Plus) + IconButton::new("plus_button", IconName::Plus) .on_click(cx.listener(|this, _event, cx| { this.new_conversation(cx); })) @@ -1004,12 +1004,12 @@ impl AssistantPanel { fn render_zoom_button(&self, cx: &mut ViewContext) -> impl IntoElement { let zoomed = self.zoomed; - IconButton::new("zoom_button", Icon::Maximize) + IconButton::new("zoom_button", IconName::Maximize) .on_click(cx.listener(|this, _event, cx| { this.toggle_zoom(&ToggleZoom, cx); })) .selected(zoomed) - .selected_icon(Icon::Minimize) + .selected_icon(IconName::Minimize) .icon_size(IconSize::Small) .tooltip(move |cx| { Tooltip::for_action(if zoomed { "Zoom Out" } else { "Zoom In" }, &ToggleZoom, cx) @@ -1286,8 +1286,8 @@ impl Panel for AssistantPanel { } } - fn icon(&self, cx: &WindowContext) -> Option { - Some(Icon::Ai).filter(|_| AssistantSettings::get_global(cx).button) + fn icon(&self, cx: &WindowContext) -> Option { + Some(IconName::Ai).filter(|_| AssistantSettings::get_global(cx).button) } fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> { @@ -2349,7 +2349,7 @@ impl ConversationEditor { div() .id("error") .tooltip(move |cx| Tooltip::text(error.clone(), cx)) - .child(IconElement::new(Icon::XCircle)), + .child(Icon::new(IconName::XCircle)), ) } else { None @@ -2645,7 +2645,7 @@ impl Render for InlineAssistant { .justify_center() .w(measurements.gutter_width) .child( - IconButton::new("include_conversation", Icon::Ai) + IconButton::new("include_conversation", IconName::Ai) .on_click(cx.listener(|this, _, cx| { this.toggle_include_conversation(&ToggleIncludeConversation, cx) })) @@ -2660,7 +2660,7 @@ impl Render for InlineAssistant { ) .children(if SemanticIndex::enabled(cx) { Some( - IconButton::new("retrieve_context", Icon::MagnifyingGlass) + IconButton::new("retrieve_context", IconName::MagnifyingGlass) .on_click(cx.listener(|this, _, cx| { this.toggle_retrieve_context(&ToggleRetrieveContext, cx) })) @@ -2682,7 +2682,7 @@ impl Render for InlineAssistant { div() .id("error") .tooltip(move |cx| Tooltip::text(error_message.clone(), cx)) - .child(IconElement::new(Icon::XCircle).color(Color::Error)), + .child(Icon::new(IconName::XCircle).color(Color::Error)), ) } else { None @@ -2957,7 +2957,7 @@ impl InlineAssistant { div() .id("error") .tooltip(|cx| Tooltip::text("Not Authenticated. Please ensure you have a valid 'OPENAI_API_KEY' in your environment variables.", cx)) - .child(IconElement::new(Icon::XCircle)) + .child(Icon::new(IconName::XCircle)) .into_any_element() ), @@ -2965,7 +2965,7 @@ impl InlineAssistant { div() .id("error") .tooltip(|cx| Tooltip::text("Not Indexed", cx)) - .child(IconElement::new(Icon::XCircle)) + .child(Icon::new(IconName::XCircle)) .into_any_element() ), @@ -2996,7 +2996,7 @@ impl InlineAssistant { div() .id("update") .tooltip(move |cx| Tooltip::text(status_text.clone(), cx)) - .child(IconElement::new(Icon::Update).color(Color::Info)) + .child(Icon::new(IconName::Update).color(Color::Info)) .into_any_element() ) } @@ -3005,7 +3005,7 @@ impl InlineAssistant { div() .id("check") .tooltip(|cx| Tooltip::text("Index up to date", cx)) - .child(IconElement::new(Icon::Check).color(Color::Success)) + .child(Icon::new(IconName::Check).color(Color::Success)) .into_any_element() ), } diff --git a/crates/auto_update/src/update_notification.rs b/crates/auto_update/src/update_notification.rs index f00172591ecf37221e34cf4ff3b5b330b72fba28..65f786bca4c4e1a729974459fbdf8e549a9893cc 100644 --- a/crates/auto_update/src/update_notification.rs +++ b/crates/auto_update/src/update_notification.rs @@ -4,7 +4,7 @@ use gpui::{ }; use menu::Cancel; use util::channel::ReleaseChannel; -use workspace::ui::{h_stack, v_stack, Icon, IconElement, Label, StyledExt}; +use workspace::ui::{h_stack, v_stack, Icon, IconName, Label, StyledExt}; pub struct UpdateNotification { version: SemanticVersion, @@ -30,7 +30,7 @@ impl Render for UpdateNotification { .child( div() .id("cancel") - .child(IconElement::new(Icon::Close)) + .child(Icon::new(IconName::Close)) .cursor_pointer() .on_click(cx.listener(|this, _, cx| this.dismiss(&menu::Cancel, cx))), ), diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index 4f8e12f1e8ea2be3ca2d5354a80e684e94269c22..5786ab10d4ca59b998b1b16ea7bb3c53611b4399 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -20,7 +20,7 @@ use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; use std::sync::Arc; use time::{OffsetDateTime, UtcOffset}; -use ui::{prelude::*, Avatar, Button, Icon, IconButton, Label, TabBar, Tooltip}; +use ui::{prelude::*, Avatar, Button, IconButton, IconName, Label, TabBar, Tooltip}; use util::{ResultExt, TryFutureExt}; use workspace::{ dock::{DockPosition, Panel, PanelEvent}, @@ -281,12 +281,12 @@ impl ChatPanel { )), ) .end_child( - IconButton::new("notes", Icon::File) + IconButton::new("notes", IconName::File) .on_click(cx.listener(Self::open_notes)) .tooltip(|cx| Tooltip::text("Open notes", cx)), ) .end_child( - IconButton::new("call", Icon::AudioOn) + IconButton::new("call", IconName::AudioOn) .on_click(cx.listener(Self::join_call)) .tooltip(|cx| Tooltip::text("Join call", cx)), ), @@ -395,7 +395,7 @@ impl ChatPanel { .w_8() .visible_on_hover("") .children(message_id_to_remove.map(|message_id| { - IconButton::new(("remove", message_id), Icon::XCircle).on_click( + IconButton::new(("remove", message_id), IconName::XCircle).on_click( cx.listener(move |this, _, cx| { this.remove_message(message_id, cx); }), @@ -429,7 +429,7 @@ impl ChatPanel { Button::new("sign-in", "Sign in") .style(ButtonStyle::Filled) .icon_color(Color::Muted) - .icon(Icon::Github) + .icon(IconName::Github) .icon_position(IconPosition::Start) .full_width() .on_click(cx.listener(move |this, _, cx| { @@ -622,12 +622,12 @@ impl Panel for ChatPanel { "ChatPanel" } - fn icon(&self, cx: &WindowContext) -> Option { + fn icon(&self, cx: &WindowContext) -> Option { if !is_channels_feature_enabled(cx) { return None; } - Some(ui::Icon::MessageBubbles).filter(|_| ChatPanelSettings::get_global(cx).button) + Some(ui::IconName::MessageBubbles).filter(|_| ChatPanelSettings::get_global(cx).button) } fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> { diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 86d0131d70afa9dbf0a9c51d6943dcb1f1ad2f80..df8f2a251fdf4d86ff05f0eaa8052c34317435bb 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -31,7 +31,7 @@ use smallvec::SmallVec; use std::{mem, sync::Arc}; use theme::{ActiveTheme, ThemeSettings}; use ui::{ - prelude::*, Avatar, Button, Color, ContextMenu, Icon, IconButton, IconElement, IconSize, Label, + prelude::*, Avatar, Button, Color, ContextMenu, Icon, IconButton, IconName, IconSize, Label, ListHeader, ListItem, Tooltip, }; use util::{maybe, ResultExt, TryFutureExt}; @@ -848,7 +848,7 @@ impl CollabPanel { .end_slot(if is_pending { Label::new("Calling").color(Color::Muted).into_any_element() } else if is_current_user { - IconButton::new("leave-call", Icon::Exit) + IconButton::new("leave-call", IconName::Exit) .style(ButtonStyle::Subtle) .on_click(move |_, cx| Self::leave_call(cx)) .tooltip(|cx| Tooltip::text("Leave Call", cx)) @@ -897,7 +897,7 @@ impl CollabPanel { h_stack() .gap_1() .child(render_tree_branch(is_last, false, cx)) - .child(IconButton::new(0, Icon::Folder)), + .child(IconButton::new(0, IconName::Folder)), ) .child(Label::new(project_name.clone())) .tooltip(move |cx| Tooltip::text(format!("Open {}", project_name), cx)) @@ -918,7 +918,7 @@ impl CollabPanel { h_stack() .gap_1() .child(render_tree_branch(is_last, false, cx)) - .child(IconButton::new(0, Icon::Screen)), + .child(IconButton::new(0, IconName::Screen)), ) .child(Label::new("Screen")) .when_some(peer_id, |this, _| { @@ -959,7 +959,7 @@ impl CollabPanel { h_stack() .gap_1() .child(render_tree_branch(false, true, cx)) - .child(IconButton::new(0, Icon::File)), + .child(IconButton::new(0, IconName::File)), ) .child(div().h_7().w_full().child(Label::new("notes"))) .tooltip(move |cx| Tooltip::text("Open Channel Notes", cx)) @@ -980,7 +980,7 @@ impl CollabPanel { h_stack() .gap_1() .child(render_tree_branch(false, false, cx)) - .child(IconButton::new(0, Icon::MessageBubbles)), + .child(IconButton::new(0, IconName::MessageBubbles)), ) .child(Label::new("chat")) .tooltip(move |cx| Tooltip::text("Open Chat", cx)) @@ -1724,7 +1724,7 @@ impl CollabPanel { .child( Button::new("sign_in", "Sign in") .icon_color(Color::Muted) - .icon(Icon::Github) + .icon(IconName::Github) .icon_position(IconPosition::Start) .style(ButtonStyle::Filled) .full_width() @@ -1921,7 +1921,7 @@ impl CollabPanel { let button = match section { Section::ActiveCall => channel_link.map(|channel_link| { let channel_link_copy = channel_link.clone(); - IconButton::new("channel-link", Icon::Copy) + IconButton::new("channel-link", IconName::Copy) .icon_size(IconSize::Small) .size(ButtonSize::None) .visible_on_hover("section-header") @@ -1933,13 +1933,13 @@ impl CollabPanel { .into_any_element() }), Section::Contacts => Some( - IconButton::new("add-contact", Icon::Plus) + IconButton::new("add-contact", IconName::Plus) .on_click(cx.listener(|this, _, cx| this.toggle_contact_finder(cx))) .tooltip(|cx| Tooltip::text("Search for new contact", cx)) .into_any_element(), ), Section::Channels => Some( - IconButton::new("add-channel", Icon::Plus) + IconButton::new("add-channel", IconName::Plus) .on_click(cx.listener(|this, _, cx| this.new_root_channel(cx))) .tooltip(|cx| Tooltip::text("Create a channel", cx)) .into_any_element(), @@ -2010,7 +2010,7 @@ impl CollabPanel { }) .when(!calling, |el| { el.child( - IconButton::new("remove_contact", Icon::Close) + IconButton::new("remove_contact", IconName::Close) .icon_color(Color::Muted) .visible_on_hover("") .tooltip(|cx| Tooltip::text("Remove Contact", cx)) @@ -2071,13 +2071,13 @@ impl CollabPanel { let controls = if is_incoming { vec![ - IconButton::new("decline-contact", Icon::Close) + IconButton::new("decline-contact", IconName::Close) .on_click(cx.listener(move |this, _, cx| { this.respond_to_contact_request(user_id, false, cx); })) .icon_color(color) .tooltip(|cx| Tooltip::text("Decline invite", cx)), - IconButton::new("accept-contact", Icon::Check) + IconButton::new("accept-contact", IconName::Check) .on_click(cx.listener(move |this, _, cx| { this.respond_to_contact_request(user_id, true, cx); })) @@ -2086,7 +2086,7 @@ impl CollabPanel { ] } else { let github_login = github_login.clone(); - vec![IconButton::new("remove_contact", Icon::Close) + vec![IconButton::new("remove_contact", IconName::Close) .on_click(cx.listener(move |this, _, cx| { this.remove_contact(user_id, &github_login, cx); })) @@ -2126,13 +2126,13 @@ impl CollabPanel { }; let controls = [ - IconButton::new("reject-invite", Icon::Close) + IconButton::new("reject-invite", IconName::Close) .on_click(cx.listener(move |this, _, cx| { this.respond_to_channel_invite(channel_id, false, cx); })) .icon_color(color) .tooltip(|cx| Tooltip::text("Decline invite", cx)), - IconButton::new("accept-invite", Icon::Check) + IconButton::new("accept-invite", IconName::Check) .on_click(cx.listener(move |this, _, cx| { this.respond_to_channel_invite(channel_id, true, cx); })) @@ -2150,7 +2150,7 @@ impl CollabPanel { .child(h_stack().children(controls)), ) .start_slot( - IconElement::new(Icon::Hash) + Icon::new(IconName::Hash) .size(IconSize::Small) .color(Color::Muted), ) @@ -2162,7 +2162,7 @@ impl CollabPanel { cx: &mut ViewContext, ) -> ListItem { ListItem::new("contact-placeholder") - .child(IconElement::new(Icon::Plus)) + .child(Icon::new(IconName::Plus)) .child(Label::new("Add a Contact")) .selected(is_selected) .on_click(cx.listener(|this, _, cx| this.toggle_contact_finder(cx))) @@ -2246,7 +2246,7 @@ impl CollabPanel { }; let messages_button = |cx: &mut ViewContext| { - IconButton::new("channel_chat", Icon::MessageBubbles) + IconButton::new("channel_chat", IconName::MessageBubbles) .icon_size(IconSize::Small) .icon_color(if has_messages_notification { Color::Default @@ -2258,7 +2258,7 @@ impl CollabPanel { }; let notes_button = |cx: &mut ViewContext| { - IconButton::new("channel_notes", Icon::File) + IconButton::new("channel_notes", IconName::File) .icon_size(IconSize::Small) .icon_color(if has_notes_notification { Color::Default @@ -2315,9 +2315,13 @@ impl CollabPanel { }, )) .start_slot( - IconElement::new(if is_public { Icon::Public } else { Icon::Hash }) - .size(IconSize::Small) - .color(Color::Muted), + Icon::new(if is_public { + IconName::Public + } else { + IconName::Hash + }) + .size(IconSize::Small) + .color(Color::Muted), ) .child( h_stack() @@ -2386,7 +2390,7 @@ impl CollabPanel { .indent_level(depth + 1) .indent_step_size(px(20.)) .start_slot( - IconElement::new(Icon::Hash) + Icon::new(IconName::Hash) .size(IconSize::Small) .color(Color::Muted), ); @@ -2500,10 +2504,10 @@ impl Panel for CollabPanel { cx.notify(); } - fn icon(&self, cx: &gpui::WindowContext) -> Option { + fn icon(&self, cx: &gpui::WindowContext) -> Option { CollaborationPanelSettings::get_global(cx) .button - .then(|| ui::Icon::Collab) + .then(|| ui::IconName::Collab) } fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> { @@ -2646,11 +2650,11 @@ impl Render for DraggedChannelView { .p_1() .gap_1() .child( - IconElement::new( + Icon::new( if self.channel.visibility == proto::ChannelVisibility::Public { - Icon::Public + IconName::Public } else { - Icon::Hash + IconName::Hash }, ) .size(IconSize::Small) diff --git a/crates/collab_ui/src/collab_panel/channel_modal.rs b/crates/collab_ui/src/collab_panel/channel_modal.rs index f3ae16f7939910cef5d79581725003c7bfe1c987..8020613c1ae2a4cc2ac9e0ad61293541c451f0aa 100644 --- a/crates/collab_ui/src/collab_panel/channel_modal.rs +++ b/crates/collab_ui/src/collab_panel/channel_modal.rs @@ -168,7 +168,7 @@ impl Render for ChannelModal { .w_px() .flex_1() .gap_1() - .child(IconElement::new(Icon::Hash).size(IconSize::Medium)) + .child(Icon::new(IconName::Hash).size(IconSize::Medium)) .child(Label::new(channel_name)), ) .child( @@ -406,7 +406,7 @@ impl PickerDelegate for ChannelModalDelegate { Some(ChannelRole::Guest) => Some(Label::new("Guest")), _ => None, }) - .child(IconButton::new("ellipsis", Icon::Ellipsis)) + .child(IconButton::new("ellipsis", IconName::Ellipsis)) .children( if let (Some((menu, _)), true) = (&self.context_menu, selected) { Some( diff --git a/crates/collab_ui/src/collab_panel/contact_finder.rs b/crates/collab_ui/src/collab_panel/contact_finder.rs index dbcacef7d645de6e30959b7b4d0b91b2f41aceed..b769ec7e7f394fb7a94c38f6220b507d43e77ba1 100644 --- a/crates/collab_ui/src/collab_panel/contact_finder.rs +++ b/crates/collab_ui/src/collab_panel/contact_finder.rs @@ -155,9 +155,7 @@ impl PickerDelegate for ContactFinderDelegate { .selected(selected) .start_slot(Avatar::new(user.avatar_uri.clone())) .child(Label::new(user.github_login.clone())) - .end_slot::( - icon_path.map(|icon_path| IconElement::from_path(icon_path)), - ), + .end_slot::(icon_path.map(|icon_path| Icon::from_path(icon_path))), ) } } diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 6ccad2db0d107f4ee877ecdfd38563e880a79be5..f2106b9a8f4d769801d2eadc7c0259334966b8b6 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -15,7 +15,7 @@ use std::sync::Arc; use theme::{ActiveTheme, PlayerColors}; use ui::{ h_stack, popover_menu, prelude::*, Avatar, Button, ButtonLike, ButtonStyle, ContextMenu, Icon, - IconButton, IconElement, TintColor, Tooltip, + IconButton, IconName, TintColor, Tooltip, }; use util::ResultExt; use vcs_menu::{build_branch_list, BranchList, OpenRecent as ToggleVcsMenu}; @@ -213,7 +213,7 @@ impl Render for CollabTitlebarItem { .child( div() .child( - IconButton::new("leave-call", ui::Icon::Exit) + IconButton::new("leave-call", ui::IconName::Exit) .style(ButtonStyle::Subtle) .tooltip(|cx| Tooltip::text("Leave call", cx)) .icon_size(IconSize::Small) @@ -230,9 +230,9 @@ impl Render for CollabTitlebarItem { IconButton::new( "mute-microphone", if is_muted { - ui::Icon::MicMute + ui::IconName::MicMute } else { - ui::Icon::Mic + ui::IconName::Mic }, ) .tooltip(move |cx| { @@ -256,9 +256,9 @@ impl Render for CollabTitlebarItem { IconButton::new( "mute-sound", if is_deafened { - ui::Icon::AudioOff + ui::IconName::AudioOff } else { - ui::Icon::AudioOn + ui::IconName::AudioOn }, ) .style(ButtonStyle::Subtle) @@ -281,7 +281,7 @@ impl Render for CollabTitlebarItem { ) .when(!read_only, |this| { this.child( - IconButton::new("screen-share", ui::Icon::Screen) + IconButton::new("screen-share", ui::IconName::Screen) .style(ButtonStyle::Subtle) .icon_size(IconSize::Small) .selected(is_screen_sharing) @@ -573,7 +573,7 @@ impl CollabTitlebarItem { | client::Status::ReconnectionError { .. } => Some( div() .id("disconnected") - .child(IconElement::new(Icon::Disconnected).size(IconSize::Small)) + .child(Icon::new(IconName::Disconnected).size(IconSize::Small)) .tooltip(|cx| Tooltip::text("Disconnected", cx)) .into_any_element(), ), @@ -643,7 +643,7 @@ impl CollabTitlebarItem { h_stack() .gap_0p5() .child(Avatar::new(user.avatar_uri.clone())) - .child(IconElement::new(Icon::ChevronDown).color(Color::Muted)), + .child(Icon::new(IconName::ChevronDown).color(Color::Muted)), ) .style(ButtonStyle::Subtle) .tooltip(move |cx| Tooltip::text("Toggle User Menu", cx)), @@ -665,7 +665,7 @@ impl CollabTitlebarItem { .child( h_stack() .gap_0p5() - .child(IconElement::new(Icon::ChevronDown).color(Color::Muted)), + .child(Icon::new(IconName::ChevronDown).color(Color::Muted)), ) .style(ButtonStyle::Subtle) .tooltip(move |cx| Tooltip::text("Toggle User Menu", cx)), diff --git a/crates/collab_ui/src/notification_panel.rs b/crates/collab_ui/src/notification_panel.rs index e7c94984b229165aa26a43000221446d7b56e7a5..95473044a3f4242cd497ce0087fe1e47e8865d6f 100644 --- a/crates/collab_ui/src/notification_panel.rs +++ b/crates/collab_ui/src/notification_panel.rs @@ -19,7 +19,7 @@ use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; use std::{sync::Arc, time::Duration}; use time::{OffsetDateTime, UtcOffset}; -use ui::{h_stack, prelude::*, v_stack, Avatar, Button, Icon, IconButton, IconElement, Label}; +use ui::{h_stack, prelude::*, v_stack, Avatar, Button, Icon, IconButton, IconName, Label}; use util::{ResultExt, TryFutureExt}; use workspace::{ dock::{DockPosition, Panel, PanelEvent}, @@ -553,7 +553,7 @@ impl Render for NotificationPanel { .border_b_1() .border_color(cx.theme().colors().border) .child(Label::new("Notifications")) - .child(IconElement::new(Icon::Envelope)), + .child(Icon::new(IconName::Envelope)), ) .map(|this| { if self.client.user_id().is_none() { @@ -564,7 +564,7 @@ impl Render for NotificationPanel { .child( Button::new("sign_in_prompt_button", "Sign in") .icon_color(Color::Muted) - .icon(Icon::Github) + .icon(IconName::Github) .icon_position(IconPosition::Start) .style(ButtonStyle::Filled) .full_width() @@ -655,10 +655,10 @@ impl Panel for NotificationPanel { } } - fn icon(&self, cx: &gpui::WindowContext) -> Option { + fn icon(&self, cx: &gpui::WindowContext) -> Option { (NotificationPanelSettings::get_global(cx).button && self.notification_store.read(cx).notification_count() > 0) - .then(|| Icon::Bell) + .then(|| IconName::Bell) } fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> { @@ -716,7 +716,7 @@ impl Render for NotificationToast { .children(user.map(|user| Avatar::new(user.avatar_uri.clone()))) .child(Label::new(self.text.clone())) .child( - IconButton::new("close", Icon::Close) + IconButton::new("close", IconName::Close) .on_click(cx.listener(|_, _, cx| cx.emit(DismissEvent))), ) .on_click(cx.listener(|this, _, cx| { diff --git a/crates/copilot_ui/src/copilot_button.rs b/crates/copilot_ui/src/copilot_button.rs index e55f45c29333edbecc22742fccf0d4cd10a4a0df..e5a1a942358a20c72fbb1037413796aeb84be77a 100644 --- a/crates/copilot_ui/src/copilot_button.rs +++ b/crates/copilot_ui/src/copilot_button.rs @@ -17,7 +17,9 @@ use util::{paths, ResultExt}; use workspace::{ create_and_open_local_file, item::ItemHandle, - ui::{popover_menu, ButtonCommon, Clickable, ContextMenu, Icon, IconButton, IconSize, Tooltip}, + ui::{ + popover_menu, ButtonCommon, Clickable, ContextMenu, IconButton, IconName, IconSize, Tooltip, + }, StatusItemView, Toast, Workspace, }; use zed_actions::OpenBrowser; @@ -51,15 +53,15 @@ impl Render for CopilotButton { .unwrap_or_else(|| all_language_settings.copilot_enabled(None, None)); let icon = match status { - Status::Error(_) => Icon::CopilotError, + Status::Error(_) => IconName::CopilotError, Status::Authorized => { if enabled { - Icon::Copilot + IconName::Copilot } else { - Icon::CopilotDisabled + IconName::CopilotDisabled } } - _ => Icon::CopilotInit, + _ => IconName::CopilotInit, }; if let Status::Error(e) = status { diff --git a/crates/copilot_ui/src/sign_in.rs b/crates/copilot_ui/src/sign_in.rs index aeaa35784bfabe5ab75fc2b26a59dada83ddb61a..ba6f54b634a0e2f9f14ce296423fc905d40bf744 100644 --- a/crates/copilot_ui/src/sign_in.rs +++ b/crates/copilot_ui/src/sign_in.rs @@ -4,7 +4,7 @@ use gpui::{ FocusableView, InteractiveElement, IntoElement, Model, ParentElement, Render, Styled, Subscription, ViewContext, }; -use ui::{prelude::*, Button, Icon, Label}; +use ui::{prelude::*, Button, IconName, Label}; use workspace::ModalView; const COPILOT_SIGN_UP_URL: &'static str = "https://github.com/features/copilot"; @@ -175,7 +175,7 @@ impl Render for CopilotCodeVerification { .w_32() .h_16() .flex_none() - .path(Icon::ZedXCopilot.path()) + .path(IconName::ZedXCopilot.path()) .text_color(cx.theme().colors().icon), ) .child(prompt) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 613fadf7f70caa6e35a7ecd739c8e16f6237be80..844a44c54f8bcf6eeae17ab3e629b4dee6e4a04b 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -36,7 +36,7 @@ use std::{ }; use theme::ActiveTheme; pub use toolbar_controls::ToolbarControls; -use ui::{h_stack, prelude::*, Icon, IconElement, Label}; +use ui::{h_stack, prelude::*, Icon, IconName, Label}; use util::TryFutureExt; use workspace::{ item::{BreadcrumbText, Item, ItemEvent, ItemHandle}, @@ -660,7 +660,7 @@ impl Item for ProjectDiagnosticsEditor { then.child( h_stack() .gap_1() - .child(IconElement::new(Icon::XCircle).color(Color::Error)) + .child(Icon::new(IconName::XCircle).color(Color::Error)) .child(Label::new(self.summary.error_count.to_string()).color( if selected { Color::Default @@ -674,9 +674,7 @@ impl Item for ProjectDiagnosticsEditor { then.child( h_stack() .gap_1() - .child( - IconElement::new(Icon::ExclamationTriangle).color(Color::Warning), - ) + .child(Icon::new(IconName::ExclamationTriangle).color(Color::Warning)) .child(Label::new(self.summary.warning_count.to_string()).color( if selected { Color::Default @@ -816,10 +814,10 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock { .flex_none() .map(|icon| { if diagnostic.severity == DiagnosticSeverity::ERROR { - icon.path(Icon::XCircle.path()) + icon.path(IconName::XCircle.path()) .text_color(Color::Error.color(cx)) } else { - icon.path(Icon::ExclamationTriangle.path()) + icon.path(IconName::ExclamationTriangle.path()) .text_color(Color::Warning.color(cx)) } }), diff --git a/crates/diagnostics/src/items.rs b/crates/diagnostics/src/items.rs index 0c2d673d8e68b5bd43681788bde078442ed43d9d..035b84e1020048cd7c6d2cd107577b7c79786169 100644 --- a/crates/diagnostics/src/items.rs +++ b/crates/diagnostics/src/items.rs @@ -6,7 +6,7 @@ use gpui::{ }; use language::Diagnostic; use lsp::LanguageServerId; -use ui::{h_stack, prelude::*, Button, ButtonLike, Color, Icon, IconElement, Label, Tooltip}; +use ui::{h_stack, prelude::*, Button, ButtonLike, Color, Icon, IconName, Label, Tooltip}; use workspace::{item::ItemHandle, StatusItemView, ToolbarItemEvent, Workspace}; use crate::{Deploy, ProjectDiagnosticsEditor}; @@ -25,7 +25,7 @@ impl Render for DiagnosticIndicator { let diagnostic_indicator = match (self.summary.error_count, self.summary.warning_count) { (0, 0) => h_stack().map(|this| { this.child( - IconElement::new(Icon::Check) + Icon::new(IconName::Check) .size(IconSize::Small) .color(Color::Default), ) @@ -33,7 +33,7 @@ impl Render for DiagnosticIndicator { (0, warning_count) => h_stack() .gap_1() .child( - IconElement::new(Icon::ExclamationTriangle) + Icon::new(IconName::ExclamationTriangle) .size(IconSize::Small) .color(Color::Warning), ) @@ -41,7 +41,7 @@ impl Render for DiagnosticIndicator { (error_count, 0) => h_stack() .gap_1() .child( - IconElement::new(Icon::XCircle) + Icon::new(IconName::XCircle) .size(IconSize::Small) .color(Color::Error), ) @@ -49,13 +49,13 @@ impl Render for DiagnosticIndicator { (error_count, warning_count) => h_stack() .gap_1() .child( - IconElement::new(Icon::XCircle) + Icon::new(IconName::XCircle) .size(IconSize::Small) .color(Color::Error), ) .child(Label::new(error_count.to_string()).size(LabelSize::Small)) .child( - IconElement::new(Icon::ExclamationTriangle) + Icon::new(IconName::ExclamationTriangle) .size(IconSize::Small) .color(Color::Warning), ) @@ -66,7 +66,7 @@ impl Render for DiagnosticIndicator { Some( h_stack() .gap_2() - .child(IconElement::new(Icon::ArrowCircle).size(IconSize::Small)) + .child(Icon::new(IconName::ArrowCircle).size(IconSize::Small)) .child( Label::new("Checking…") .size(LabelSize::Small) diff --git a/crates/diagnostics/src/toolbar_controls.rs b/crates/diagnostics/src/toolbar_controls.rs index 897e2ccf40f573591d7b9e712e65928a17b28413..3c09e3fad91b952a2447cd78d30645edd3a9c44a 100644 --- a/crates/diagnostics/src/toolbar_controls.rs +++ b/crates/diagnostics/src/toolbar_controls.rs @@ -1,7 +1,7 @@ use crate::ProjectDiagnosticsEditor; use gpui::{div, EventEmitter, ParentElement, Render, ViewContext, WeakView}; use ui::prelude::*; -use ui::{Icon, IconButton, Tooltip}; +use ui::{IconButton, IconName, Tooltip}; use workspace::{item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView}; pub struct ToolbarControls { @@ -24,7 +24,7 @@ impl Render for ToolbarControls { }; div().child( - IconButton::new("toggle-warnings", Icon::ExclamationTriangle) + IconButton::new("toggle-warnings", IconName::ExclamationTriangle) .tooltip(move |cx| Tooltip::text(tooltip, cx)) .on_click(cx.listener(|this, _, cx| { if let Some(editor) = this.editor.as_ref().and_then(|editor| editor.upgrade()) { diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 231f76218a44125e6c42f2a99f98027f98414ab1..9858cf8372eec5a57e4f1eeb419d06147d7ce51c 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -99,8 +99,8 @@ use sum_tree::TreeMap; use text::{OffsetUtf16, Rope}; use theme::{ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings}; use ui::{ - h_stack, prelude::*, ButtonSize, ButtonStyle, Icon, IconButton, IconSize, ListItem, Popover, - Tooltip, + h_stack, prelude::*, ButtonSize, ButtonStyle, IconButton, IconName, IconSize, ListItem, + Popover, Tooltip, }; use util::{post_inc, RangeExt, ResultExt, TryFutureExt}; use workspace::{searchable::SearchEvent, ItemNavHistory, Pane, SplitDirection, ViewId, Workspace}; @@ -4223,7 +4223,7 @@ impl Editor { ) -> Option { if self.available_code_actions.is_some() { Some( - IconButton::new("code_actions_indicator", ui::Icon::Bolt) + IconButton::new("code_actions_indicator", ui::IconName::Bolt) .icon_size(IconSize::Small) .icon_color(Color::Muted) .selected(is_active) @@ -4257,7 +4257,7 @@ impl Editor { fold_data .map(|(fold_status, buffer_row, active)| { (active || gutter_hovered || fold_status == FoldStatus::Folded).then(|| { - IconButton::new(ix as usize, ui::Icon::ChevronDown) + IconButton::new(ix as usize, ui::IconName::ChevronDown) .on_click(cx.listener(move |editor, _e, cx| match fold_status { FoldStatus::Folded => { editor.unfold_at(&UnfoldAt { buffer_row }, cx); @@ -4269,7 +4269,7 @@ impl Editor { .icon_color(ui::Color::Muted) .icon_size(ui::IconSize::Small) .selected(fold_status == FoldStatus::Folded) - .selected_icon(ui::Icon::ChevronRight) + .selected_icon(ui::IconName::ChevronRight) .size(ui::ButtonSize::None) }) }) @@ -9739,7 +9739,7 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, _is_valid: bool) -> Ren ), ) .child( - IconButton::new(("copy-block", cx.block_id), Icon::Copy) + IconButton::new(("copy-block", cx.block_id), IconName::Copy) .icon_color(Color::Muted) .size(ButtonSize::Compact) .style(ButtonStyle::Transparent) diff --git a/crates/feedback/src/deploy_feedback_button.rs b/crates/feedback/src/deploy_feedback_button.rs index a02540bc5b3339e576f1a1eae336db119bd845ed..377d4cea5c11f3c3c7581e5a2d8b8034811f39e6 100644 --- a/crates/feedback/src/deploy_feedback_button.rs +++ b/crates/feedback/src/deploy_feedback_button.rs @@ -1,5 +1,5 @@ use gpui::{Render, ViewContext, WeakView}; -use ui::{prelude::*, ButtonCommon, Icon, IconButton, Tooltip}; +use ui::{prelude::*, ButtonCommon, IconButton, IconName, Tooltip}; use workspace::{item::ItemHandle, StatusItemView, Workspace}; use crate::{feedback_modal::FeedbackModal, GiveFeedback}; @@ -27,7 +27,7 @@ impl Render for DeployFeedbackButton { }) }) .is_some(); - IconButton::new("give-feedback", Icon::Envelope) + IconButton::new("give-feedback", IconName::Envelope) .style(ui::ButtonStyle::Subtle) .icon_size(IconSize::Small) .selected(is_open) diff --git a/crates/feedback/src/feedback_modal.rs b/crates/feedback/src/feedback_modal.rs index b197d602338e052b319623e9b1ad6a1e6f7d7b53..bf7a0715604f20ec6eae1d28ea4988016a8cd2cf 100644 --- a/crates/feedback/src/feedback_modal.rs +++ b/crates/feedback/src/feedback_modal.rs @@ -488,7 +488,7 @@ impl Render for FeedbackModal { .child( Button::new("community_repository", "Community Repository") .style(ButtonStyle::Transparent) - .icon(Icon::ExternalLink) + .icon(IconName::ExternalLink) .icon_position(IconPosition::End) .icon_size(IconSize::Small) .on_click(open_community_repo), diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 727ab7e859d237c8729401e6dcee90f8529616c8..251e26ebfba004b81a49c1ce28956e01f42bbce5 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -30,7 +30,7 @@ use std::{ sync::Arc, }; use theme::ThemeSettings; -use ui::{prelude::*, v_stack, ContextMenu, IconElement, KeyBinding, Label, ListItem}; +use ui::{prelude::*, v_stack, ContextMenu, Icon, KeyBinding, Label, ListItem}; use unicase::UniCase; use util::{maybe, ResultExt, TryFutureExt}; use workspace::{ @@ -1403,7 +1403,7 @@ impl ProjectPanel { .indent_step_size(px(settings.indent_size)) .selected(is_selected) .child(if let Some(icon) = &icon { - div().child(IconElement::from_path(icon.to_string()).color(Color::Muted)) + div().child(Icon::from_path(icon.to_string()).color(Color::Muted)) } else { div().size(IconSize::default().rems()).invisible() }) @@ -1590,7 +1590,7 @@ impl Render for DraggedProjectEntryView { .indent_level(self.details.depth) .indent_step_size(px(settings.indent_size)) .child(if let Some(icon) = &self.details.icon { - div().child(IconElement::from_path(icon.to_string())) + div().child(Icon::from_path(icon.to_string())) } else { div() }) @@ -1640,8 +1640,8 @@ impl Panel for ProjectPanel { cx.notify(); } - fn icon(&self, _: &WindowContext) -> Option { - Some(ui::Icon::FileTree) + fn icon(&self, _: &WindowContext) -> Option { + Some(ui::IconName::FileTree) } fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> { diff --git a/crates/quick_action_bar/src/quick_action_bar.rs b/crates/quick_action_bar/src/quick_action_bar.rs index b40794c2fad4cfe4d02be825e1ec49be82e29ff4..cf4941bcec66cdef77f3f4453a6b3eb25d8b1321 100644 --- a/crates/quick_action_bar/src/quick_action_bar.rs +++ b/crates/quick_action_bar/src/quick_action_bar.rs @@ -6,7 +6,7 @@ use gpui::{ Subscription, View, ViewContext, WeakView, }; use search::{buffer_search, BufferSearchBar}; -use ui::{prelude::*, ButtonSize, ButtonStyle, Icon, IconButton, IconSize, Tooltip}; +use ui::{prelude::*, ButtonSize, ButtonStyle, IconButton, IconName, IconSize, Tooltip}; use workspace::{ item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, }; @@ -43,7 +43,7 @@ impl Render for QuickActionBar { let inlay_hints_button = Some(QuickActionBarButton::new( "toggle inlay hints", - Icon::InlayHint, + IconName::InlayHint, editor.read(cx).inlay_hints_enabled(), Box::new(editor::ToggleInlayHints), "Toggle Inlay Hints", @@ -60,7 +60,7 @@ impl Render for QuickActionBar { let search_button = Some(QuickActionBarButton::new( "toggle buffer search", - Icon::MagnifyingGlass, + IconName::MagnifyingGlass, !self.buffer_search_bar.read(cx).is_dismissed(), Box::new(buffer_search::Deploy { focus: false }), "Buffer Search", @@ -77,7 +77,7 @@ impl Render for QuickActionBar { let assistant_button = QuickActionBarButton::new( "toggle inline assistant", - Icon::MagicWand, + IconName::MagicWand, false, Box::new(InlineAssist), "Inline Assist", @@ -107,7 +107,7 @@ impl EventEmitter for QuickActionBar {} #[derive(IntoElement)] struct QuickActionBarButton { id: ElementId, - icon: Icon, + icon: IconName, toggled: bool, action: Box, tooltip: SharedString, @@ -117,7 +117,7 @@ struct QuickActionBarButton { impl QuickActionBarButton { fn new( id: impl Into, - icon: Icon, + icon: IconName, toggled: bool, action: Box, tooltip: impl Into, diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index c889f0a4a4c11d3f104e130e34e5b87c092565d6..9b2719936080fd14ba65c8f0b044c11455815f2c 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -21,7 +21,7 @@ use settings::Settings; use std::{any::Any, sync::Arc}; use theme::ThemeSettings; -use ui::{h_stack, prelude::*, Icon, IconButton, IconElement, ToggleButton, Tooltip}; +use ui::{h_stack, prelude::*, Icon, IconButton, IconName, ToggleButton, Tooltip}; use util::ResultExt; use workspace::{ item::ItemHandle, @@ -225,7 +225,7 @@ impl Render for BufferSearchBar { .border_color(editor_border) .min_w(rems(384. / 16.)) .rounded_lg() - .child(IconElement::new(Icon::MagnifyingGlass)) + .child(Icon::new(IconName::MagnifyingGlass)) .child(self.render_text_input(&self.query_editor, cx)) .children(supported_options.case.then(|| { self.render_search_option_button( @@ -287,7 +287,7 @@ impl Render for BufferSearchBar { this.child( IconButton::new( "buffer-search-bar-toggle-replace-button", - Icon::Replace, + IconName::Replace, ) .style(ButtonStyle::Subtle) .when(self.replace_enabled, |button| { @@ -323,7 +323,7 @@ impl Render for BufferSearchBar { ) .when(should_show_replace_input, |this| { this.child( - IconButton::new("search-replace-next", ui::Icon::ReplaceNext) + IconButton::new("search-replace-next", ui::IconName::ReplaceNext) .tooltip(move |cx| { Tooltip::for_action("Replace next", &ReplaceNext, cx) }) @@ -332,7 +332,7 @@ impl Render for BufferSearchBar { })), ) .child( - IconButton::new("search-replace-all", ui::Icon::ReplaceAll) + IconButton::new("search-replace-all", ui::IconName::ReplaceAll) .tooltip(move |cx| { Tooltip::for_action("Replace all", &ReplaceAll, cx) }) @@ -350,7 +350,7 @@ impl Render for BufferSearchBar { .gap_0p5() .flex_none() .child( - IconButton::new("select-all", ui::Icon::SelectAll) + IconButton::new("select-all", ui::IconName::SelectAll) .on_click(|_, cx| cx.dispatch_action(SelectAllMatches.boxed_clone())) .tooltip(|cx| { Tooltip::for_action("Select all matches", &SelectAllMatches, cx) @@ -358,13 +358,13 @@ impl Render for BufferSearchBar { ) .children(match_count) .child(render_nav_button( - ui::Icon::ChevronLeft, + ui::IconName::ChevronLeft, self.active_match_index.is_some(), "Select previous match", &SelectPrevMatch, )) .child(render_nav_button( - ui::Icon::ChevronRight, + ui::IconName::ChevronRight, self.active_match_index.is_some(), "Select next match", &SelectNextMatch, diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index a370cca9f69c2e89b866232ccb2c385d7726ce25..5cdf614c1b9f8e22c416866db3a863e2458251c2 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -38,7 +38,7 @@ use std::{ use theme::ThemeSettings; use ui::{ - h_stack, prelude::*, v_stack, Icon, IconButton, IconElement, Label, LabelCommon, LabelSize, + h_stack, prelude::*, v_stack, Icon, IconButton, IconName, Label, LabelCommon, LabelSize, Selectable, ToggleButton, Tooltip, }; use util::{paths::PathMatcher, ResultExt as _}; @@ -432,7 +432,7 @@ impl Item for ProjectSearchView { .unwrap_or_else(|| "Project search".into()); h_stack() .gap_2() - .child(IconElement::new(Icon::MagnifyingGlass).color(if selected { + .child(Icon::new(IconName::MagnifyingGlass).color(if selected { Color::Default } else { Color::Muted @@ -1616,12 +1616,12 @@ impl Render for ProjectSearchBar { .on_action(cx.listener(|this, action, cx| this.confirm(action, cx))) .on_action(cx.listener(|this, action, cx| this.previous_history_query(action, cx))) .on_action(cx.listener(|this, action, cx| this.next_history_query(action, cx))) - .child(IconElement::new(Icon::MagnifyingGlass)) + .child(Icon::new(IconName::MagnifyingGlass)) .child(self.render_text_input(&search.query_editor, cx)) .child( h_stack() .child( - IconButton::new("project-search-filter-button", Icon::Filter) + IconButton::new("project-search-filter-button", IconName::Filter) .tooltip(|cx| { Tooltip::for_action("Toggle filters", &ToggleFilters, cx) }) @@ -1639,7 +1639,7 @@ impl Render for ProjectSearchBar { this.child( IconButton::new( "project-search-case-sensitive", - Icon::CaseSensitive, + IconName::CaseSensitive, ) .tooltip(|cx| { Tooltip::for_action( @@ -1659,7 +1659,7 @@ impl Render for ProjectSearchBar { )), ) .child( - IconButton::new("project-search-whole-word", Icon::WholeWord) + IconButton::new("project-search-whole-word", IconName::WholeWord) .tooltip(|cx| { Tooltip::for_action( "Toggle whole word", @@ -1738,7 +1738,7 @@ impl Render for ProjectSearchBar { }), ) .child( - IconButton::new("project-search-toggle-replace", Icon::Replace) + IconButton::new("project-search-toggle-replace", IconName::Replace) .on_click(cx.listener(|this, _, cx| { this.toggle_replace(&ToggleReplace, cx); })) @@ -1755,7 +1755,7 @@ impl Render for ProjectSearchBar { .border_1() .border_color(cx.theme().colors().border) .rounded_lg() - .child(IconElement::new(Icon::Replace).size(ui::IconSize::Small)) + .child(Icon::new(IconName::Replace).size(ui::IconSize::Small)) .child(self.render_text_input(&search.replacement_editor, cx)) } else { // Fill out the space if we don't have a replacement editor. @@ -1764,7 +1764,7 @@ impl Render for ProjectSearchBar { let actions_column = h_stack() .when(search.replace_enabled, |this| { this.child( - IconButton::new("project-search-replace-next", Icon::ReplaceNext) + IconButton::new("project-search-replace-next", IconName::ReplaceNext) .on_click(cx.listener(|this, _, cx| { if let Some(search) = this.active_project_search.as_ref() { search.update(cx, |this, cx| { @@ -1775,7 +1775,7 @@ impl Render for ProjectSearchBar { .tooltip(|cx| Tooltip::for_action("Replace next match", &ReplaceNext, cx)), ) .child( - IconButton::new("project-search-replace-all", Icon::ReplaceAll) + IconButton::new("project-search-replace-all", IconName::ReplaceAll) .on_click(cx.listener(|this, _, cx| { if let Some(search) = this.active_project_search.as_ref() { search.update(cx, |this, cx| { @@ -1796,7 +1796,7 @@ impl Render for ProjectSearchBar { this }) .child( - IconButton::new("project-search-prev-match", Icon::ChevronLeft) + IconButton::new("project-search-prev-match", IconName::ChevronLeft) .disabled(search.active_match_index.is_none()) .on_click(cx.listener(|this, _, cx| { if let Some(search) = this.active_project_search.as_ref() { @@ -1810,7 +1810,7 @@ impl Render for ProjectSearchBar { }), ) .child( - IconButton::new("project-search-next-match", Icon::ChevronRight) + IconButton::new("project-search-next-match", IconName::ChevronRight) .disabled(search.active_match_index.is_none()) .on_click(cx.listener(|this, _, cx| { if let Some(search) = this.active_project_search.as_ref() { diff --git a/crates/search/src/search.rs b/crates/search/src/search.rs index f0301a5bcc7a637c17d47c59f5102f352ac3840c..1b29801e03a8b370411be0d1def77c392cccb1dd 100644 --- a/crates/search/src/search.rs +++ b/crates/search/src/search.rs @@ -60,11 +60,11 @@ impl SearchOptions { } } - pub fn icon(&self) -> ui::Icon { + pub fn icon(&self) -> ui::IconName { match *self { - SearchOptions::WHOLE_WORD => ui::Icon::WholeWord, - SearchOptions::CASE_SENSITIVE => ui::Icon::CaseSensitive, - SearchOptions::INCLUDE_IGNORED => ui::Icon::FileGit, + SearchOptions::WHOLE_WORD => ui::IconName::WholeWord, + SearchOptions::CASE_SENSITIVE => ui::IconName::CaseSensitive, + SearchOptions::INCLUDE_IGNORED => ui::IconName::FileGit, _ => panic!("{:?} is not a named SearchOption", self), } } diff --git a/crates/search/src/search_bar.rs b/crates/search/src/search_bar.rs index 628be3112ecc06e9d00e8d33361e8f17fa6efd54..0594036c25483c8dadcbfdf5988edbc1ba12a65f 100644 --- a/crates/search/src/search_bar.rs +++ b/crates/search/src/search_bar.rs @@ -3,7 +3,7 @@ use ui::IconButton; use ui::{prelude::*, Tooltip}; pub(super) fn render_nav_button( - icon: ui::Icon, + icon: ui::IconName, active: bool, tooltip: &'static str, action: &'static dyn Action, diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 99929535700e2badbe0c447ac6dfc4ee5998e7e2..d0b52f5eb217ed60cc9b62e20657e5033506f133 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -19,7 +19,7 @@ use workspace::{ dock::{DockPosition, Panel, PanelEvent}, item::Item, pane, - ui::Icon, + ui::IconName, DraggedTab, Pane, Workspace, }; @@ -71,7 +71,7 @@ impl TerminalPanel { h_stack() .gap_2() .child( - IconButton::new("plus", Icon::Plus) + IconButton::new("plus", IconName::Plus) .icon_size(IconSize::Small) .on_click(move |_, cx| { terminal_panel @@ -82,10 +82,10 @@ impl TerminalPanel { ) .child({ let zoomed = pane.is_zoomed(); - IconButton::new("toggle_zoom", Icon::Maximize) + IconButton::new("toggle_zoom", IconName::Maximize) .icon_size(IconSize::Small) .selected(zoomed) - .selected_icon(Icon::Minimize) + .selected_icon(IconName::Minimize) .on_click(cx.listener(|pane, _, cx| { pane.toggle_zoom(&workspace::ToggleZoom, cx); })) @@ -477,8 +477,8 @@ impl Panel for TerminalPanel { "TerminalPanel" } - fn icon(&self, _cx: &WindowContext) -> Option { - Some(Icon::Terminal) + fn icon(&self, _cx: &WindowContext) -> Option { + Some(IconName::Terminal) } fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> { diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index f4fb6105cb8bcf476a12d68d29aadb0d942a22dc..4d2e78f0daddacb0bc33673b8a14c29f54d3fdb4 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -20,7 +20,7 @@ use terminal::{ Clear, Copy, Event, MaybeNavigationTarget, Paste, ShowCharacterPalette, Terminal, }; use terminal_element::TerminalElement; -use ui::{h_stack, prelude::*, ContextMenu, Icon, IconElement, Label}; +use ui::{h_stack, prelude::*, ContextMenu, Icon, IconName, Label}; use util::{paths::PathLikeWithPosition, ResultExt}; use workspace::{ item::{BreadcrumbText, Item, ItemEvent}, @@ -690,7 +690,7 @@ impl Item for TerminalView { let title = self.terminal().read(cx).title(true); h_stack() .gap_2() - .child(IconElement::new(Icon::Terminal)) + .child(Icon::new(IconName::Terminal)) .child(Label::new(title).color(if selected { Color::Default } else { diff --git a/crates/ui/src/components/button/button.rs b/crates/ui/src/components/button/button.rs index 1e60aae03b11e84eda1a2041565c6b75a5fae79f..398f8f10e27831a07bdca21f837e1425f396c5e9 100644 --- a/crates/ui/src/components/button/button.rs +++ b/crates/ui/src/components/button/button.rs @@ -2,7 +2,7 @@ use gpui::{AnyView, DefiniteLength}; use crate::{prelude::*, IconPosition, KeyBinding}; use crate::{ - ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Icon, IconSize, Label, LineHeightStyle, + ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, IconName, IconSize, Label, LineHeightStyle, }; use super::button_icon::ButtonIcon; @@ -14,11 +14,11 @@ pub struct Button { label_color: Option, label_size: Option, selected_label: Option, - icon: Option, + icon: Option, icon_position: Option, icon_size: Option, icon_color: Option, - selected_icon: Option, + selected_icon: Option, key_binding: Option, } @@ -54,7 +54,7 @@ impl Button { self } - pub fn icon(mut self, icon: impl Into>) -> Self { + pub fn icon(mut self, icon: impl Into>) -> Self { self.icon = icon.into(); self } @@ -74,7 +74,7 @@ impl Button { self } - pub fn selected_icon(mut self, icon: impl Into>) -> Self { + pub fn selected_icon(mut self, icon: impl Into>) -> Self { self.selected_icon = icon.into(); self } diff --git a/crates/ui/src/components/button/button_icon.rs b/crates/ui/src/components/button/button_icon.rs index 15538bb24d79b9f17c3fcef8c4de37466e84fc66..b8f5427d30aaa6dfb21b802bdd49922de9e17433 100644 --- a/crates/ui/src/components/button/button_icon.rs +++ b/crates/ui/src/components/button/button_icon.rs @@ -1,4 +1,4 @@ -use crate::{prelude::*, Icon, IconElement, IconSize}; +use crate::{prelude::*, Icon, IconName, IconSize}; /// An icon that appears within a button. /// @@ -6,17 +6,17 @@ use crate::{prelude::*, Icon, IconElement, IconSize}; /// or as a standalone icon, like in [`IconButton`](crate::IconButton). #[derive(IntoElement)] pub(super) struct ButtonIcon { - icon: Icon, + icon: IconName, size: IconSize, color: Color, disabled: bool, selected: bool, - selected_icon: Option, + selected_icon: Option, selected_style: Option, } impl ButtonIcon { - pub fn new(icon: Icon) -> Self { + pub fn new(icon: IconName) -> Self { Self { icon, size: IconSize::default(), @@ -44,7 +44,7 @@ impl ButtonIcon { self } - pub fn selected_icon(mut self, icon: impl Into>) -> Self { + pub fn selected_icon(mut self, icon: impl Into>) -> Self { self.selected_icon = icon.into(); self } @@ -88,6 +88,6 @@ impl RenderOnce for ButtonIcon { self.color }; - IconElement::new(icon).size(self.size).color(icon_color) + Icon::new(icon).size(self.size).color(icon_color) } } diff --git a/crates/ui/src/components/button/icon_button.rs b/crates/ui/src/components/button/icon_button.rs index d9ed6ccb5d86a2c5122ef6cd2fd364be235566bc..7c5313497c77ff6532838966001d2cbd4a3688e0 100644 --- a/crates/ui/src/components/button/icon_button.rs +++ b/crates/ui/src/components/button/icon_button.rs @@ -1,21 +1,21 @@ use gpui::{AnyView, DefiniteLength}; use crate::{prelude::*, SelectableButton}; -use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Icon, IconSize}; +use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, IconName, IconSize}; use super::button_icon::ButtonIcon; #[derive(IntoElement)] pub struct IconButton { base: ButtonLike, - icon: Icon, + icon: IconName, icon_size: IconSize, icon_color: Color, - selected_icon: Option, + selected_icon: Option, } impl IconButton { - pub fn new(id: impl Into, icon: Icon) -> Self { + pub fn new(id: impl Into, icon: IconName) -> Self { Self { base: ButtonLike::new(id), icon, @@ -35,7 +35,7 @@ impl IconButton { self } - pub fn selected_icon(mut self, icon: impl Into>) -> Self { + pub fn selected_icon(mut self, icon: impl Into>) -> Self { self.selected_icon = icon.into(); self } diff --git a/crates/ui/src/components/checkbox.rs b/crates/ui/src/components/checkbox.rs index 3b778420291980ff70e3e8cebaa5b3c4d411033f..08c95f2d939f0fc76be90d2186fab9a6788c8f8f 100644 --- a/crates/ui/src/components/checkbox.rs +++ b/crates/ui/src/components/checkbox.rs @@ -1,7 +1,7 @@ use gpui::{div, prelude::*, ElementId, IntoElement, Styled, WindowContext}; use crate::prelude::*; -use crate::{Color, Icon, IconElement, Selection}; +use crate::{Color, Icon, IconName, Selection}; pub type CheckHandler = Box; @@ -47,7 +47,7 @@ impl RenderOnce for Checkbox { let group_id = format!("checkbox_group_{:?}", self.id); let icon = match self.checked { - Selection::Selected => Some(IconElement::new(Icon::Check).size(IconSize::Small).color( + Selection::Selected => Some(Icon::new(IconName::Check).size(IconSize::Small).color( if self.disabled { Color::Disabled } else { @@ -55,7 +55,7 @@ impl RenderOnce for Checkbox { }, )), Selection::Indeterminate => Some( - IconElement::new(Icon::Dash) + Icon::new(IconName::Dash) .size(IconSize::Small) .color(if self.disabled { Color::Disabled diff --git a/crates/ui/src/components/context_menu.rs b/crates/ui/src/components/context_menu.rs index 8666ec65651d6f00cbe225f7b065521673326037..098c54f33cb98d831c0806a2b4ec9f5b0a18ad07 100644 --- a/crates/ui/src/components/context_menu.rs +++ b/crates/ui/src/components/context_menu.rs @@ -1,6 +1,6 @@ use crate::{ - h_stack, prelude::*, v_stack, Icon, IconElement, KeyBinding, Label, List, ListItem, - ListSeparator, ListSubHeader, + h_stack, prelude::*, v_stack, Icon, IconName, KeyBinding, Label, List, ListItem, ListSeparator, + ListSubHeader, }; use gpui::{ px, Action, AnyElement, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, @@ -14,7 +14,7 @@ enum ContextMenuItem { Header(SharedString), Entry { label: SharedString, - icon: Option, + icon: Option, handler: Rc, action: Option>, }, @@ -117,7 +117,7 @@ impl ContextMenu { label: label.into(), action: Some(action.boxed_clone()), handler: Rc::new(move |cx| cx.dispatch_action(action.boxed_clone())), - icon: Some(Icon::Link), + icon: Some(IconName::Link), }); self } @@ -280,7 +280,7 @@ impl Render for ContextMenu { h_stack() .gap_1() .child(Label::new(label.clone())) - .child(IconElement::new(*icon)) + .child(Icon::new(*icon)) .into_any_element() } else { Label::new(label.clone()).into_any_element() diff --git a/crates/ui/src/components/disclosure.rs b/crates/ui/src/components/disclosure.rs index d4349f61a0f53b7f70af3255576aaa4c715796ae..59651ddb0b5ec9154c0180ca89e2331007cd3404 100644 --- a/crates/ui/src/components/disclosure.rs +++ b/crates/ui/src/components/disclosure.rs @@ -1,6 +1,6 @@ use gpui::ClickEvent; -use crate::{prelude::*, Color, Icon, IconButton, IconSize}; +use crate::{prelude::*, Color, IconButton, IconName, IconSize}; #[derive(IntoElement)] pub struct Disclosure { @@ -32,8 +32,8 @@ impl RenderOnce for Disclosure { IconButton::new( self.id, match self.is_open { - true => Icon::ChevronDown, - false => Icon::ChevronRight, + true => IconName::ChevronDown, + false => IconName::ChevronRight, }, ) .icon_color(Color::Muted) diff --git a/crates/ui/src/components/icon.rs b/crates/ui/src/components/icon.rs index 4c6e48c0fc034dbcfbd4454c920b2d4a994b92b0..908e76ef918b56aefff6949e86ea6473a272253d 100644 --- a/crates/ui/src/components/icon.rs +++ b/crates/ui/src/components/icon.rs @@ -22,7 +22,7 @@ impl IconSize { } #[derive(Debug, PartialEq, Copy, Clone, EnumIter)] -pub enum Icon { +pub enum IconName { Ai, ArrowDown, ArrowLeft, @@ -111,118 +111,108 @@ pub enum Icon { ZedXCopilot, } -impl Icon { +impl IconName { pub fn path(self) -> &'static str { match self { - Icon::Ai => "icons/ai.svg", - Icon::ArrowDown => "icons/arrow_down.svg", - Icon::ArrowLeft => "icons/arrow_left.svg", - Icon::ArrowRight => "icons/arrow_right.svg", - Icon::ArrowUp => "icons/arrow_up.svg", - Icon::ArrowUpRight => "icons/arrow_up_right.svg", - Icon::ArrowCircle => "icons/arrow_circle.svg", - Icon::AtSign => "icons/at_sign.svg", - Icon::AudioOff => "icons/speaker_off.svg", - Icon::AudioOn => "icons/speaker_loud.svg", - Icon::Backspace => "icons/backspace.svg", - Icon::Bell => "icons/bell.svg", - Icon::BellOff => "icons/bell_off.svg", - Icon::BellRing => "icons/bell_ring.svg", - Icon::Bolt => "icons/bolt.svg", - Icon::CaseSensitive => "icons/case_insensitive.svg", - Icon::Check => "icons/check.svg", - Icon::ChevronDown => "icons/chevron_down.svg", - Icon::ChevronLeft => "icons/chevron_left.svg", - Icon::ChevronRight => "icons/chevron_right.svg", - Icon::ChevronUp => "icons/chevron_up.svg", - Icon::Close => "icons/x.svg", - Icon::Collab => "icons/user_group_16.svg", - Icon::Command => "icons/command.svg", - Icon::Control => "icons/control.svg", - Icon::Copilot => "icons/copilot.svg", - Icon::CopilotDisabled => "icons/copilot_disabled.svg", - Icon::CopilotError => "icons/copilot_error.svg", - Icon::CopilotInit => "icons/copilot_init.svg", - Icon::Copy => "icons/copy.svg", - Icon::Dash => "icons/dash.svg", - Icon::Delete => "icons/delete.svg", - Icon::Disconnected => "icons/disconnected.svg", - Icon::Ellipsis => "icons/ellipsis.svg", - Icon::Envelope => "icons/feedback.svg", - Icon::Escape => "icons/escape.svg", - Icon::ExclamationTriangle => "icons/warning.svg", - Icon::Exit => "icons/exit.svg", - Icon::ExternalLink => "icons/external_link.svg", - Icon::File => "icons/file.svg", - Icon::FileDoc => "icons/file_icons/book.svg", - Icon::FileGeneric => "icons/file_icons/file.svg", - Icon::FileGit => "icons/file_icons/git.svg", - Icon::FileLock => "icons/file_icons/lock.svg", - Icon::FileRust => "icons/file_icons/rust.svg", - Icon::FileToml => "icons/file_icons/toml.svg", - Icon::FileTree => "icons/project.svg", - Icon::Filter => "icons/filter.svg", - Icon::Folder => "icons/file_icons/folder.svg", - Icon::FolderOpen => "icons/file_icons/folder_open.svg", - Icon::FolderX => "icons/stop_sharing.svg", - Icon::Github => "icons/github.svg", - Icon::Hash => "icons/hash.svg", - Icon::InlayHint => "icons/inlay_hint.svg", - Icon::Link => "icons/link.svg", - Icon::MagicWand => "icons/magic_wand.svg", - Icon::MagnifyingGlass => "icons/magnifying_glass.svg", - Icon::MailOpen => "icons/mail_open.svg", - Icon::Maximize => "icons/maximize.svg", - Icon::Menu => "icons/menu.svg", - Icon::MessageBubbles => "icons/conversations.svg", - Icon::Mic => "icons/mic.svg", - Icon::MicMute => "icons/mic_mute.svg", - Icon::Minimize => "icons/minimize.svg", - Icon::Option => "icons/option.svg", - Icon::PageDown => "icons/page_down.svg", - Icon::PageUp => "icons/page_up.svg", - Icon::Plus => "icons/plus.svg", - Icon::Public => "icons/public.svg", - Icon::Quote => "icons/quote.svg", - Icon::Replace => "icons/replace.svg", - Icon::ReplaceAll => "icons/replace_all.svg", - Icon::ReplaceNext => "icons/replace_next.svg", - Icon::Return => "icons/return.svg", - Icon::Screen => "icons/desktop.svg", - Icon::SelectAll => "icons/select_all.svg", - Icon::Shift => "icons/shift.svg", - Icon::Snip => "icons/snip.svg", - Icon::Space => "icons/space.svg", - Icon::Split => "icons/split.svg", - Icon::Tab => "icons/tab.svg", - Icon::Terminal => "icons/terminal.svg", - Icon::Update => "icons/update.svg", - Icon::WholeWord => "icons/word_search.svg", - Icon::XCircle => "icons/error.svg", - Icon::ZedXCopilot => "icons/zed_x_copilot.svg", + IconName::Ai => "icons/ai.svg", + IconName::ArrowDown => "icons/arrow_down.svg", + IconName::ArrowLeft => "icons/arrow_left.svg", + IconName::ArrowRight => "icons/arrow_right.svg", + IconName::ArrowUp => "icons/arrow_up.svg", + IconName::ArrowUpRight => "icons/arrow_up_right.svg", + IconName::ArrowCircle => "icons/arrow_circle.svg", + IconName::AtSign => "icons/at_sign.svg", + IconName::AudioOff => "icons/speaker_off.svg", + IconName::AudioOn => "icons/speaker_loud.svg", + IconName::Backspace => "icons/backspace.svg", + IconName::Bell => "icons/bell.svg", + IconName::BellOff => "icons/bell_off.svg", + IconName::BellRing => "icons/bell_ring.svg", + IconName::Bolt => "icons/bolt.svg", + IconName::CaseSensitive => "icons/case_insensitive.svg", + IconName::Check => "icons/check.svg", + IconName::ChevronDown => "icons/chevron_down.svg", + IconName::ChevronLeft => "icons/chevron_left.svg", + IconName::ChevronRight => "icons/chevron_right.svg", + IconName::ChevronUp => "icons/chevron_up.svg", + IconName::Close => "icons/x.svg", + IconName::Collab => "icons/user_group_16.svg", + IconName::Command => "icons/command.svg", + IconName::Control => "icons/control.svg", + IconName::Copilot => "icons/copilot.svg", + IconName::CopilotDisabled => "icons/copilot_disabled.svg", + IconName::CopilotError => "icons/copilot_error.svg", + IconName::CopilotInit => "icons/copilot_init.svg", + IconName::Copy => "icons/copy.svg", + IconName::Dash => "icons/dash.svg", + IconName::Delete => "icons/delete.svg", + IconName::Disconnected => "icons/disconnected.svg", + IconName::Ellipsis => "icons/ellipsis.svg", + IconName::Envelope => "icons/feedback.svg", + IconName::Escape => "icons/escape.svg", + IconName::ExclamationTriangle => "icons/warning.svg", + IconName::Exit => "icons/exit.svg", + IconName::ExternalLink => "icons/external_link.svg", + IconName::File => "icons/file.svg", + IconName::FileDoc => "icons/file_icons/book.svg", + IconName::FileGeneric => "icons/file_icons/file.svg", + IconName::FileGit => "icons/file_icons/git.svg", + IconName::FileLock => "icons/file_icons/lock.svg", + IconName::FileRust => "icons/file_icons/rust.svg", + IconName::FileToml => "icons/file_icons/toml.svg", + IconName::FileTree => "icons/project.svg", + IconName::Filter => "icons/filter.svg", + IconName::Folder => "icons/file_icons/folder.svg", + IconName::FolderOpen => "icons/file_icons/folder_open.svg", + IconName::FolderX => "icons/stop_sharing.svg", + IconName::Github => "icons/github.svg", + IconName::Hash => "icons/hash.svg", + IconName::InlayHint => "icons/inlay_hint.svg", + IconName::Link => "icons/link.svg", + IconName::MagicWand => "icons/magic_wand.svg", + IconName::MagnifyingGlass => "icons/magnifying_glass.svg", + IconName::MailOpen => "icons/mail_open.svg", + IconName::Maximize => "icons/maximize.svg", + IconName::Menu => "icons/menu.svg", + IconName::MessageBubbles => "icons/conversations.svg", + IconName::Mic => "icons/mic.svg", + IconName::MicMute => "icons/mic_mute.svg", + IconName::Minimize => "icons/minimize.svg", + IconName::Option => "icons/option.svg", + IconName::PageDown => "icons/page_down.svg", + IconName::PageUp => "icons/page_up.svg", + IconName::Plus => "icons/plus.svg", + IconName::Public => "icons/public.svg", + IconName::Quote => "icons/quote.svg", + IconName::Replace => "icons/replace.svg", + IconName::ReplaceAll => "icons/replace_all.svg", + IconName::ReplaceNext => "icons/replace_next.svg", + IconName::Return => "icons/return.svg", + IconName::Screen => "icons/desktop.svg", + IconName::SelectAll => "icons/select_all.svg", + IconName::Shift => "icons/shift.svg", + IconName::Snip => "icons/snip.svg", + IconName::Space => "icons/space.svg", + IconName::Split => "icons/split.svg", + IconName::Tab => "icons/tab.svg", + IconName::Terminal => "icons/terminal.svg", + IconName::Update => "icons/update.svg", + IconName::WholeWord => "icons/word_search.svg", + IconName::XCircle => "icons/error.svg", + IconName::ZedXCopilot => "icons/zed_x_copilot.svg", } } } #[derive(IntoElement)] -pub struct IconElement { +pub struct Icon { path: SharedString, color: Color, size: IconSize, } -impl RenderOnce for IconElement { - fn render(self, cx: &mut WindowContext) -> impl IntoElement { - svg() - .size(self.size.rems()) - .flex_none() - .path(self.path) - .text_color(self.color.color(cx)) - } -} - -impl IconElement { - pub fn new(icon: Icon) -> Self { +impl Icon { + pub fn new(icon: IconName) -> Self { Self { path: icon.path().into(), color: Color::default(), @@ -248,3 +238,13 @@ impl IconElement { self } } + +impl RenderOnce for Icon { + fn render(self, cx: &mut WindowContext) -> impl IntoElement { + svg() + .size(self.size.rems()) + .flex_none() + .path(self.path) + .text_color(self.color.color(cx)) + } +} diff --git a/crates/ui/src/components/keybinding.rs b/crates/ui/src/components/keybinding.rs index 671f9810831649f8f83dac2e25f88f2223febc40..e0e0583b7cb25e4966c183ae54d9f4742c66935d 100644 --- a/crates/ui/src/components/keybinding.rs +++ b/crates/ui/src/components/keybinding.rs @@ -1,4 +1,4 @@ -use crate::{h_stack, prelude::*, Icon, IconElement, IconSize}; +use crate::{h_stack, prelude::*, Icon, IconName, IconSize}; use gpui::{relative, rems, Action, FocusHandle, IntoElement, Keystroke}; #[derive(IntoElement, Clone)] @@ -26,16 +26,16 @@ impl RenderOnce for KeyBinding { .text_color(cx.theme().colors().text_muted) .when(keystroke.modifiers.function, |el| el.child(Key::new("fn"))) .when(keystroke.modifiers.control, |el| { - el.child(KeyIcon::new(Icon::Control)) + el.child(KeyIcon::new(IconName::Control)) }) .when(keystroke.modifiers.alt, |el| { - el.child(KeyIcon::new(Icon::Option)) + el.child(KeyIcon::new(IconName::Option)) }) .when(keystroke.modifiers.command, |el| { - el.child(KeyIcon::new(Icon::Command)) + el.child(KeyIcon::new(IconName::Command)) }) .when(keystroke.modifiers.shift, |el| { - el.child(KeyIcon::new(Icon::Shift)) + el.child(KeyIcon::new(IconName::Shift)) }) .when_some(key_icon, |el, icon| el.child(KeyIcon::new(icon))) .when(key_icon.is_none(), |el| { @@ -62,21 +62,21 @@ impl KeyBinding { Some(Self::new(key_binding)) } - fn icon_for_key(keystroke: &Keystroke) -> Option { + fn icon_for_key(keystroke: &Keystroke) -> Option { match keystroke.key.as_str() { - "left" => Some(Icon::ArrowLeft), - "right" => Some(Icon::ArrowRight), - "up" => Some(Icon::ArrowUp), - "down" => Some(Icon::ArrowDown), - "backspace" => Some(Icon::Backspace), - "delete" => Some(Icon::Delete), - "return" => Some(Icon::Return), - "enter" => Some(Icon::Return), - "tab" => Some(Icon::Tab), - "space" => Some(Icon::Space), - "escape" => Some(Icon::Escape), - "pagedown" => Some(Icon::PageDown), - "pageup" => Some(Icon::PageUp), + "left" => Some(IconName::ArrowLeft), + "right" => Some(IconName::ArrowRight), + "up" => Some(IconName::ArrowUp), + "down" => Some(IconName::ArrowDown), + "backspace" => Some(IconName::Backspace), + "delete" => Some(IconName::Delete), + "return" => Some(IconName::Return), + "enter" => Some(IconName::Return), + "tab" => Some(IconName::Tab), + "space" => Some(IconName::Space), + "escape" => Some(IconName::Escape), + "pagedown" => Some(IconName::PageDown), + "pageup" => Some(IconName::PageUp), _ => None, } } @@ -120,13 +120,13 @@ impl Key { #[derive(IntoElement)] pub struct KeyIcon { - icon: Icon, + icon: IconName, } impl RenderOnce for KeyIcon { fn render(self, _cx: &mut WindowContext) -> impl IntoElement { div().w(rems(14. / 16.)).child( - IconElement::new(self.icon) + Icon::new(self.icon) .size(IconSize::Small) .color(Color::Muted), ) @@ -134,7 +134,7 @@ impl RenderOnce for KeyIcon { } impl KeyIcon { - pub fn new(icon: Icon) -> Self { + pub fn new(icon: IconName) -> Self { Self { icon } } } diff --git a/crates/ui/src/components/list/list_sub_header.rs b/crates/ui/src/components/list/list_sub_header.rs index 2e976b35178a2132cf1f0a6f2c154f03de249389..fc9f35e175c0d42a1517fc1227a4befc7dfdb2da 100644 --- a/crates/ui/src/components/list/list_sub_header.rs +++ b/crates/ui/src/components/list/list_sub_header.rs @@ -1,10 +1,10 @@ use crate::prelude::*; -use crate::{h_stack, Icon, IconElement, IconSize, Label}; +use crate::{h_stack, Icon, IconName, IconSize, Label}; #[derive(IntoElement)] pub struct ListSubHeader { label: SharedString, - start_slot: Option, + start_slot: Option, inset: bool, } @@ -17,7 +17,7 @@ impl ListSubHeader { } } - pub fn left_icon(mut self, left_icon: Option) -> Self { + pub fn left_icon(mut self, left_icon: Option) -> Self { self.start_slot = left_icon; self } @@ -40,11 +40,10 @@ impl RenderOnce for ListSubHeader { .flex() .gap_1() .items_center() - .children(self.start_slot.map(|i| { - IconElement::new(i) - .color(Color::Muted) - .size(IconSize::Small) - })) + .children( + self.start_slot + .map(|i| Icon::new(i).color(Color::Muted).size(IconSize::Small)), + ) .child(Label::new(self.label.clone()).color(Color::Muted)), ), ) diff --git a/crates/ui/src/components/stories/button.rs b/crates/ui/src/components/stories/button.rs index 7240812fa5a3a0d964a811795f925439def97670..c3fcdc5ae913974dcd86519715ae18ba016084fa 100644 --- a/crates/ui/src/components/stories/button.rs +++ b/crates/ui/src/components/stories/button.rs @@ -1,7 +1,7 @@ use gpui::Render; use story::Story; -use crate::{prelude::*, Icon}; +use crate::{prelude::*, IconName}; use crate::{Button, ButtonStyle}; pub struct ButtonStory; @@ -23,12 +23,12 @@ impl Render for ButtonStory { .child(Story::label("With `label_color`")) .child(Button::new("filled_with_label_color", "Click me").color(Color::Created)) .child(Story::label("With `icon`")) - .child(Button::new("filled_with_icon", "Click me").icon(Icon::FileGit)) + .child(Button::new("filled_with_icon", "Click me").icon(IconName::FileGit)) .child(Story::label("Selected with `icon`")) .child( Button::new("filled_and_selected_with_icon", "Click me") .selected(true) - .icon(Icon::FileGit), + .icon(IconName::FileGit), ) .child(Story::label("Default (Subtle)")) .child(Button::new("default_subtle", "Click me").style(ButtonStyle::Subtle)) diff --git a/crates/ui/src/components/stories/icon.rs b/crates/ui/src/components/stories/icon.rs index 83fc5980dd731bfbaa61770d58c446c47624f888..f6e750de2add47059ecc630bbc85780cfa273872 100644 --- a/crates/ui/src/components/stories/icon.rs +++ b/crates/ui/src/components/stories/icon.rs @@ -3,17 +3,17 @@ use story::Story; use strum::IntoEnumIterator; use crate::prelude::*; -use crate::{Icon, IconElement}; +use crate::{Icon, IconName}; pub struct IconStory; impl Render for IconStory { fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { - let icons = Icon::iter(); + let icons = IconName::iter(); Story::container() - .child(Story::title_for::()) + .child(Story::title_for::()) .child(Story::label("All Icons")) - .child(div().flex().gap_3().children(icons.map(IconElement::new))) + .child(div().flex().gap_3().children(icons.map(Icon::new))) } } diff --git a/crates/ui/src/components/stories/icon_button.rs b/crates/ui/src/components/stories/icon_button.rs index 66fc4affb3b0b63daa8dabe4461b9ec806c9416c..6a67183e97c73b3795fc14a71c11a16b6012f549 100644 --- a/crates/ui/src/components/stories/icon_button.rs +++ b/crates/ui/src/components/stories/icon_button.rs @@ -2,7 +2,7 @@ use gpui::Render; use story::{StoryContainer, StoryItem, StorySection}; use crate::{prelude::*, Tooltip}; -use crate::{Icon, IconButton}; +use crate::{IconButton, IconName}; pub struct IconButtonStory; @@ -10,7 +10,7 @@ impl Render for IconButtonStory { fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { let default_button = StoryItem::new( "Default", - IconButton::new("default_icon_button", Icon::Hash), + IconButton::new("default_icon_button", IconName::Hash), ) .description("Displays an icon button.") .usage( @@ -21,7 +21,7 @@ impl Render for IconButtonStory { let selected_button = StoryItem::new( "Selected", - IconButton::new("selected_icon_button", Icon::Hash).selected(true), + IconButton::new("selected_icon_button", IconName::Hash).selected(true), ) .description("Displays an icon button that is selected.") .usage( @@ -32,9 +32,9 @@ impl Render for IconButtonStory { let selected_with_selected_icon = StoryItem::new( "Selected with `selected_icon`", - IconButton::new("selected_with_selected_icon_button", Icon::AudioOn) + IconButton::new("selected_with_selected_icon_button", IconName::AudioOn) .selected(true) - .selected_icon(Icon::AudioOff), + .selected_icon(IconName::AudioOff), ) .description( "Displays an icon button that is selected and shows a different icon when selected.", @@ -49,7 +49,7 @@ impl Render for IconButtonStory { let disabled_button = StoryItem::new( "Disabled", - IconButton::new("disabled_icon_button", Icon::Hash).disabled(true), + IconButton::new("disabled_icon_button", IconName::Hash).disabled(true), ) .description("Displays an icon button that is disabled.") .usage( @@ -60,7 +60,7 @@ impl Render for IconButtonStory { let with_on_click_button = StoryItem::new( "With `on_click`", - IconButton::new("with_on_click_button", Icon::Ai).on_click(|_event, _cx| { + IconButton::new("with_on_click_button", IconName::Ai).on_click(|_event, _cx| { println!("Clicked!"); }), ) @@ -75,7 +75,7 @@ impl Render for IconButtonStory { let with_tooltip_button = StoryItem::new( "With `tooltip`", - IconButton::new("with_tooltip_button", Icon::MessageBubbles) + IconButton::new("with_tooltip_button", IconName::MessageBubbles) .tooltip(|cx| Tooltip::text("Open messages", cx)), ) .description("Displays an icon button that has a tooltip when hovered.") @@ -88,7 +88,7 @@ impl Render for IconButtonStory { let selected_with_tooltip_button = StoryItem::new( "Selected with `tooltip`", - IconButton::new("selected_with_tooltip_button", Icon::InlayHint) + IconButton::new("selected_with_tooltip_button", IconName::InlayHint) .selected(true) .tooltip(|cx| Tooltip::text("Toggle inlay hints", cx)), ) diff --git a/crates/ui/src/components/stories/list_header.rs b/crates/ui/src/components/stories/list_header.rs index ffbf7157f5b17271633ad72b4ee54023272a831a..358dc26a875c6735373bd22c50edc904ea635597 100644 --- a/crates/ui/src/components/stories/list_header.rs +++ b/crates/ui/src/components/stories/list_header.rs @@ -2,7 +2,7 @@ use gpui::Render; use story::Story; use crate::{prelude::*, IconButton}; -use crate::{Icon, ListHeader}; +use crate::{IconName, ListHeader}; pub struct ListHeaderStory; @@ -13,19 +13,19 @@ impl Render for ListHeaderStory { .child(Story::label("Default")) .child(ListHeader::new("Section 1")) .child(Story::label("With left icon")) - .child(ListHeader::new("Section 2").start_slot(IconElement::new(Icon::Bell))) + .child(ListHeader::new("Section 2").start_slot(Icon::new(IconName::Bell))) .child(Story::label("With left icon and meta")) .child( ListHeader::new("Section 3") - .start_slot(IconElement::new(Icon::BellOff)) - .end_slot(IconButton::new("action_1", Icon::Bolt)), + .start_slot(Icon::new(IconName::BellOff)) + .end_slot(IconButton::new("action_1", IconName::Bolt)), ) .child(Story::label("With multiple meta")) .child( ListHeader::new("Section 4") - .end_slot(IconButton::new("action_1", Icon::Bolt)) - .end_slot(IconButton::new("action_2", Icon::ExclamationTriangle)) - .end_slot(IconButton::new("action_3", Icon::Plus)), + .end_slot(IconButton::new("action_1", IconName::Bolt)) + .end_slot(IconButton::new("action_2", IconName::ExclamationTriangle)) + .end_slot(IconButton::new("action_3", IconName::Plus)), ) } } diff --git a/crates/ui/src/components/stories/list_item.rs b/crates/ui/src/components/stories/list_item.rs index b3ff096d9dfe9d435e68dc7e818b661603267ca2..487f14279178cbaf3d6d4baaab5e5c2f2609eb7a 100644 --- a/crates/ui/src/components/stories/list_item.rs +++ b/crates/ui/src/components/stories/list_item.rs @@ -2,7 +2,7 @@ use gpui::Render; use story::Story; use crate::{prelude::*, Avatar}; -use crate::{Icon, ListItem}; +use crate::{IconName, ListItem}; pub struct ListItemStory; @@ -18,13 +18,13 @@ impl Render for ListItemStory { ListItem::new("inset_list_item") .inset(true) .start_slot( - IconElement::new(Icon::Bell) + Icon::new(IconName::Bell) .size(IconSize::Small) .color(Color::Muted), ) .child("Hello, world!") .end_slot( - IconElement::new(Icon::Bell) + Icon::new(IconName::Bell) .size(IconSize::Small) .color(Color::Muted), ), @@ -34,7 +34,7 @@ impl Render for ListItemStory { ListItem::new("with start slot_icon") .child("Hello, world!") .start_slot( - IconElement::new(Icon::Bell) + Icon::new(IconName::Bell) .size(IconSize::Small) .color(Color::Muted), ), diff --git a/crates/ui/src/components/stories/tab.rs b/crates/ui/src/components/stories/tab.rs index 4c63e593aaa9eab582351b62b618c7e6cae3978c..bd7b602620938775b32be3d46a41b519f2639f63 100644 --- a/crates/ui/src/components/stories/tab.rs +++ b/crates/ui/src/components/stories/tab.rs @@ -27,7 +27,7 @@ impl Render for TabStory { h_stack().child( Tab::new("tab_1") .end_slot( - IconButton::new("close_button", Icon::Close) + IconButton::new("close_button", IconName::Close) .icon_color(Color::Muted) .size(ButtonSize::None) .icon_size(IconSize::XSmall), diff --git a/crates/ui/src/components/stories/tab_bar.rs b/crates/ui/src/components/stories/tab_bar.rs index 805725315c672a34feac758f52991d0cb3d03f01..289ceff9a6f576739daacd02b57e260b295a7ae8 100644 --- a/crates/ui/src/components/stories/tab_bar.rs +++ b/crates/ui/src/components/stories/tab_bar.rs @@ -38,16 +38,19 @@ impl Render for TabBarStory { h_stack().child( TabBar::new("tab_bar_1") .start_child( - IconButton::new("navigate_backward", Icon::ArrowLeft) + IconButton::new("navigate_backward", IconName::ArrowLeft) .icon_size(IconSize::Small), ) .start_child( - IconButton::new("navigate_forward", Icon::ArrowRight) + IconButton::new("navigate_forward", IconName::ArrowRight) .icon_size(IconSize::Small), ) - .end_child(IconButton::new("new", Icon::Plus).icon_size(IconSize::Small)) .end_child( - IconButton::new("split_pane", Icon::Split).icon_size(IconSize::Small), + IconButton::new("new", IconName::Plus).icon_size(IconSize::Small), + ) + .end_child( + IconButton::new("split_pane", IconName::Split) + .icon_size(IconSize::Small), ) .children(tabs), ), diff --git a/crates/ui/src/prelude.rs b/crates/ui/src/prelude.rs index 48536f59b312b5389e9892b6593919ed0adc7bd0..0a86b99f9f232927d9e92615adcf987997df7919 100644 --- a/crates/ui/src/prelude.rs +++ b/crates/ui/src/prelude.rs @@ -15,6 +15,6 @@ pub use crate::{h_stack, v_stack}; pub use crate::{Button, ButtonSize, ButtonStyle, IconButton, SelectableButton}; pub use crate::{ButtonCommon, Color, StyledExt}; pub use crate::{Headline, HeadlineSize}; -pub use crate::{Icon, IconElement, IconPosition, IconSize}; +pub use crate::{Icon, IconName, IconPosition, IconSize}; pub use crate::{Label, LabelCommon, LabelSize, LineHeightStyle}; pub use theme::ActiveTheme; diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index adc4fb5c80b2fd35af7ae32a620a550c977e469a..ed03695c5f2c234f3e7a633cb341da6002d344cd 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -28,7 +28,7 @@ pub trait Panel: FocusableView + EventEmitter { fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext); fn size(&self, cx: &WindowContext) -> Pixels; fn set_size(&mut self, size: Option, cx: &mut ViewContext); - fn icon(&self, cx: &WindowContext) -> Option; + fn icon(&self, cx: &WindowContext) -> Option; fn icon_tooltip(&self, cx: &WindowContext) -> Option<&'static str>; fn toggle_action(&self) -> Box; fn icon_label(&self, _: &WindowContext) -> Option { @@ -52,7 +52,7 @@ pub trait PanelHandle: Send + Sync { fn set_active(&self, active: bool, cx: &mut WindowContext); fn size(&self, cx: &WindowContext) -> Pixels; fn set_size(&self, size: Option, cx: &mut WindowContext); - fn icon(&self, cx: &WindowContext) -> Option; + fn icon(&self, cx: &WindowContext) -> Option; fn icon_tooltip(&self, cx: &WindowContext) -> Option<&'static str>; fn toggle_action(&self, cx: &WindowContext) -> Box; fn icon_label(&self, cx: &WindowContext) -> Option; @@ -104,7 +104,7 @@ where self.update(cx, |this, cx| this.set_size(size, cx)) } - fn icon(&self, cx: &WindowContext) -> Option { + fn icon(&self, cx: &WindowContext) -> Option { self.read(cx).icon(cx) } @@ -774,7 +774,7 @@ pub mod test { self.size = size.unwrap_or(px(300.)); } - fn icon(&self, _: &WindowContext) -> Option { + fn icon(&self, _: &WindowContext) -> Option { None } diff --git a/crates/workspace/src/notifications.rs b/crates/workspace/src/notifications.rs index d70de8c13dc7b45522952474506d609e1a75788b..36628290bb83ed6f640206f20f77933a577f58e3 100644 --- a/crates/workspace/src/notifications.rs +++ b/crates/workspace/src/notifications.rs @@ -175,7 +175,7 @@ pub mod simple_message_notification { }; use std::sync::Arc; use ui::prelude::*; - use ui::{h_stack, v_stack, Button, Icon, IconElement, Label, StyledExt}; + use ui::{h_stack, v_stack, Button, Icon, IconName, Label, StyledExt}; pub struct MessageNotification { message: SharedString, @@ -230,7 +230,7 @@ pub mod simple_message_notification { .child( div() .id("cancel") - .child(IconElement::new(Icon::Close)) + .child(Icon::new(IconName::Close)) .cursor_pointer() .on_click(cx.listener(|this, _, cx| this.dismiss(cx))), ), diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 04a51fc655be0d7b5d2b890479bef484b5cbc14a..2a434b32d928bcd7d59de0bca03dad5c8d79d031 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -31,8 +31,8 @@ use std::{ use theme::ThemeSettings; use ui::{ - prelude::*, right_click_menu, ButtonSize, Color, Icon, IconButton, IconSize, Indicator, Label, - Tab, TabBar, TabPosition, Tooltip, + prelude::*, right_click_menu, ButtonSize, Color, IconButton, IconName, IconSize, Indicator, + Label, Tab, TabBar, TabPosition, Tooltip, }; use ui::{v_stack, ContextMenu}; use util::{maybe, truncate_and_remove_front, ResultExt}; @@ -384,7 +384,7 @@ impl Pane { h_stack() .gap_2() .child( - IconButton::new("plus", Icon::Plus) + IconButton::new("plus", IconName::Plus) .icon_size(IconSize::Small) .icon_color(Color::Muted) .on_click(cx.listener(|pane, _, cx| { @@ -406,7 +406,7 @@ impl Pane { el.child(Self::render_menu_overlay(new_item_menu)) }) .child( - IconButton::new("split", Icon::Split) + IconButton::new("split", IconName::Split) .icon_size(IconSize::Small) .icon_color(Color::Muted) .on_click(cx.listener(|pane, _, cx| { @@ -427,11 +427,11 @@ impl Pane { ) .child({ let zoomed = pane.is_zoomed(); - IconButton::new("toggle_zoom", Icon::Maximize) + IconButton::new("toggle_zoom", IconName::Maximize) .icon_size(IconSize::Small) .icon_color(Color::Muted) .selected(zoomed) - .selected_icon(Icon::Minimize) + .selected_icon(IconName::Minimize) .on_click(cx.listener(|pane, _, cx| { pane.toggle_zoom(&crate::ToggleZoom, cx); })) @@ -1570,7 +1570,7 @@ impl Pane { }) .start_slot::(indicator) .end_slot( - IconButton::new("close tab", Icon::Close) + IconButton::new("close tab", IconName::Close) .icon_color(Color::Muted) .size(ButtonSize::None) .icon_size(IconSize::XSmall) @@ -1676,7 +1676,7 @@ impl Pane { h_stack() .gap_2() .child( - IconButton::new("navigate_backward", Icon::ArrowLeft) + IconButton::new("navigate_backward", IconName::ArrowLeft) .icon_size(IconSize::Small) .on_click({ let view = cx.view().clone(); @@ -1686,7 +1686,7 @@ impl Pane { .tooltip(|cx| Tooltip::for_action("Go Back", &GoBack, cx)), ) .child( - IconButton::new("navigate_forward", Icon::ArrowRight) + IconButton::new("navigate_forward", IconName::ArrowRight) .icon_size(IconSize::Small) .on_click({ let view = cx.view().clone(); diff --git a/crates/workspace/src/shared_screen.rs b/crates/workspace/src/shared_screen.rs index edfabed60d3a03e2290fb94dc9c8482a7e9b4a5e..5b1ca6477ee99b47ad7abc08e64e0421444c5dfd 100644 --- a/crates/workspace/src/shared_screen.rs +++ b/crates/workspace/src/shared_screen.rs @@ -12,7 +12,7 @@ use gpui::{ WindowContext, }; use std::sync::{Arc, Weak}; -use ui::{h_stack, prelude::*, Icon, IconElement, Label}; +use ui::{h_stack, prelude::*, Icon, IconName, Label}; pub enum Event { Close, @@ -100,7 +100,7 @@ impl Item for SharedScreen { ) -> gpui::AnyElement { h_stack() .gap_1() - .child(IconElement::new(Icon::Screen)) + .child(Icon::new(IconName::Screen)) .child( Label::new(format!("{}'s screen", self.user.github_login)).color(if selected { Color::Default From f0ef63bfa0a6cab1676d27ae75e839c2e7666e3c Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 9 Jan 2024 10:55:49 -0500 Subject: [PATCH 27/78] gpui: Add `SharedUrl` type (#3975) This PR adds a `SharedUrl` type to GPUI. It's just like a `SharedString`, but for denoting that the contained value is a URL. Mainlined from @nathansobo's GPUI blog post: https://github.com/zed-industries/zed/pull/3968/files#diff-7ee75937e2daf7dd53f71b17698d8bd6d46993d06928d411781b9bd739b5f231R9-R12 Release Notes: - N/A --- crates/client/src/user.rs | 4 +-- .../src/notifications/collab_notification.rs | 6 ++--- crates/gpui/src/elements/img.rs | 8 +++--- crates/gpui/src/gpui.rs | 2 ++ crates/gpui/src/image_cache.rs | 6 ++--- crates/gpui/src/shared_url.rs | 25 +++++++++++++++++++ crates/ui/src/components/stories/list_item.rs | 18 ++++++------- 7 files changed, 48 insertions(+), 21 deletions(-) create mode 100644 crates/gpui/src/shared_url.rs diff --git a/crates/client/src/user.rs b/crates/client/src/user.rs index 1c288c875db39e3d3d7aed81ed836c1a69b41f5e..4453bb40eaaf34291cfa5b7f9cc917cd8606706f 100644 --- a/crates/client/src/user.rs +++ b/crates/client/src/user.rs @@ -3,7 +3,7 @@ use anyhow::{anyhow, Context, Result}; use collections::{hash_map::Entry, HashMap, HashSet}; use feature_flags::FeatureFlagAppExt; use futures::{channel::mpsc, Future, StreamExt}; -use gpui::{AsyncAppContext, EventEmitter, Model, ModelContext, SharedString, Task}; +use gpui::{AsyncAppContext, EventEmitter, Model, ModelContext, SharedUrl, Task}; use postage::{sink::Sink, watch}; use rpc::proto::{RequestMessage, UsersResponse}; use std::sync::{Arc, Weak}; @@ -19,7 +19,7 @@ pub struct ParticipantIndex(pub u32); pub struct User { pub id: UserId, pub github_login: String, - pub avatar_uri: SharedString, + pub avatar_uri: SharedUrl, } #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/crates/collab_ui/src/notifications/collab_notification.rs b/crates/collab_ui/src/notifications/collab_notification.rs index e2ef06f9a58f8b131f166ab2a83250839f076ca2..fa0b0a1b14782b8bbe586348487228d75df743f7 100644 --- a/crates/collab_ui/src/notifications/collab_notification.rs +++ b/crates/collab_ui/src/notifications/collab_notification.rs @@ -1,10 +1,10 @@ -use gpui::{img, prelude::*, AnyElement}; +use gpui::{img, prelude::*, AnyElement, SharedUrl}; use smallvec::SmallVec; use ui::prelude::*; #[derive(IntoElement)] pub struct CollabNotification { - avatar_uri: SharedString, + avatar_uri: SharedUrl, accept_button: Button, dismiss_button: Button, children: SmallVec<[AnyElement; 2]>, @@ -12,7 +12,7 @@ pub struct CollabNotification { impl CollabNotification { pub fn new( - avatar_uri: impl Into, + avatar_uri: impl Into, accept_button: Button, dismiss_button: Button, ) -> Self { diff --git a/crates/gpui/src/elements/img.rs b/crates/gpui/src/elements/img.rs index 650b5b666bc821e15873c53f2cdc56c0546b6a20..71a51351fdb24ccf4b275b42e2541a87569014dd 100644 --- a/crates/gpui/src/elements/img.rs +++ b/crates/gpui/src/elements/img.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use crate::{ point, size, BorrowWindow, Bounds, DevicePixels, Element, ImageData, InteractiveElement, - InteractiveElementState, Interactivity, IntoElement, LayoutId, Pixels, SharedString, Size, + InteractiveElementState, Interactivity, IntoElement, LayoutId, Pixels, SharedUrl, Size, StyleRefinement, Styled, WindowContext, }; use futures::FutureExt; @@ -12,13 +12,13 @@ use util::ResultExt; #[derive(Clone, Debug)] pub enum ImageSource { /// Image content will be loaded from provided URI at render time. - Uri(SharedString), + Uri(SharedUrl), Data(Arc), Surface(CVImageBuffer), } -impl From for ImageSource { - fn from(value: SharedString) -> Self { +impl From for ImageSource { + fn from(value: SharedUrl) -> Self { Self::Uri(value) } } diff --git a/crates/gpui/src/gpui.rs b/crates/gpui/src/gpui.rs index d5236d8f08c7f2fa4cb3551ee5a58695634f7e8a..6f5e30149d9691b3c364d62ab2e3ce6ec7da1b4c 100644 --- a/crates/gpui/src/gpui.rs +++ b/crates/gpui/src/gpui.rs @@ -18,6 +18,7 @@ mod platform; pub mod prelude; mod scene; mod shared_string; +mod shared_url; mod style; mod styled; mod subscription; @@ -67,6 +68,7 @@ pub use refineable::*; pub use scene::*; use seal::Sealed; pub use shared_string::*; +pub use shared_url::*; pub use smol::Timer; pub use style::*; pub use styled::*; diff --git a/crates/gpui/src/image_cache.rs b/crates/gpui/src/image_cache.rs index f80b0f0c2f71a60fa91dbf87a13ffa3b86f43abf..0d6ec81557aa7b21165a628cf707f0d1caded9d0 100644 --- a/crates/gpui/src/image_cache.rs +++ b/crates/gpui/src/image_cache.rs @@ -1,4 +1,4 @@ -use crate::{ImageData, ImageId, SharedString}; +use crate::{ImageData, ImageId, SharedUrl}; use collections::HashMap; use futures::{ future::{BoxFuture, Shared}, @@ -44,7 +44,7 @@ impl From for Error { pub struct ImageCache { client: Arc, - images: Arc>>, + images: Arc>>, } type FetchImageFuture = Shared, Error>>>; @@ -59,7 +59,7 @@ impl ImageCache { pub fn get( &self, - uri: impl Into, + uri: impl Into, ) -> Shared, Error>>> { let uri = uri.into(); let mut images = self.images.lock(); diff --git a/crates/gpui/src/shared_url.rs b/crates/gpui/src/shared_url.rs new file mode 100644 index 0000000000000000000000000000000000000000..8fb901894367d3fa4d87e78d8b95e1a47df0ce10 --- /dev/null +++ b/crates/gpui/src/shared_url.rs @@ -0,0 +1,25 @@ +use derive_more::{Deref, DerefMut}; + +use crate::SharedString; + +/// A [`SharedString`] containing a URL. +#[derive(Deref, DerefMut, Default, PartialEq, Eq, Hash, Clone)] +pub struct SharedUrl(SharedString); + +impl std::fmt::Debug for SharedUrl { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +impl std::fmt::Display for SharedUrl { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0.as_ref()) + } +} + +impl> From for SharedUrl { + fn from(value: T) -> Self { + Self(value.into()) + } +} diff --git a/crates/ui/src/components/stories/list_item.rs b/crates/ui/src/components/stories/list_item.rs index 487f14279178cbaf3d6d4baaab5e5c2f2609eb7a..a25b07df849aeb67a185e3984ab11ec341b07045 100644 --- a/crates/ui/src/components/stories/list_item.rs +++ b/crates/ui/src/components/stories/list_item.rs @@ -1,4 +1,4 @@ -use gpui::Render; +use gpui::{Render, SharedUrl}; use story::Story; use crate::{prelude::*, Avatar}; @@ -43,7 +43,7 @@ impl Render for ListItemStory { .child( ListItem::new("with_start slot avatar") .child("Hello, world!") - .start_slot(Avatar::new(SharedString::from( + .start_slot(Avatar::new(SharedUrl::from( "https://avatars.githubusercontent.com/u/1714999?v=4", ))), ) @@ -51,7 +51,7 @@ impl Render for ListItemStory { .child( ListItem::new("with_left_avatar") .child("Hello, world!") - .end_slot(Avatar::new(SharedString::from( + .end_slot(Avatar::new(SharedUrl::from( "https://avatars.githubusercontent.com/u/1714999?v=4", ))), ) @@ -62,23 +62,23 @@ impl Render for ListItemStory { .end_slot( h_stack() .gap_2() - .child(Avatar::new(SharedString::from( + .child(Avatar::new(SharedUrl::from( "https://avatars.githubusercontent.com/u/1789?v=4", ))) - .child(Avatar::new(SharedString::from( + .child(Avatar::new(SharedUrl::from( "https://avatars.githubusercontent.com/u/1789?v=4", ))) - .child(Avatar::new(SharedString::from( + .child(Avatar::new(SharedUrl::from( "https://avatars.githubusercontent.com/u/1789?v=4", ))) - .child(Avatar::new(SharedString::from( + .child(Avatar::new(SharedUrl::from( "https://avatars.githubusercontent.com/u/1789?v=4", ))) - .child(Avatar::new(SharedString::from( + .child(Avatar::new(SharedUrl::from( "https://avatars.githubusercontent.com/u/1789?v=4", ))), ) - .end_hover_slot(Avatar::new(SharedString::from( + .end_hover_slot(Avatar::new(SharedUrl::from( "https://avatars.githubusercontent.com/u/1714999?v=4", ))), ) From d3749531808f4626b4e790a1eec47d3b4860400b Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Tue, 9 Jan 2024 17:16:25 +0100 Subject: [PATCH 28/78] search: Remove newlines from query used for tab_content. (#3976) Fixes https://github.com/zed-industries/community/issues/2388 Release Notes: - Fixed tab content of project search overflowing the tab for queries with newlines. --- crates/search/src/project_search.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 5cdf614c1b9f8e22c416866db3a863e2458251c2..6fd66b5bad2c1c9ab1036a445f1e2061b791f206 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -424,7 +424,8 @@ impl Item for ProjectSearchView { .current() .as_ref() .map(|query| { - let query_text = util::truncate_and_trailoff(query, MAX_TAB_TITLE_LEN); + let query = query.replace('\n', ""); + let query_text = util::truncate_and_trailoff(&query, MAX_TAB_TITLE_LEN); query_text.into() }); let tab_name = last_query From 824d06e2b2d7f06b70c18e56206579263adbbc04 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 9 Jan 2024 11:31:19 -0500 Subject: [PATCH 29/78] Remove `Default` impl for `StatusColors` (#3977) This PR removes the `Default` impl for `StatusColors`. Since we need default light and dark variants for `StatusColors`, we can't use a single `Default` impl. Release Notes: - N/A --- crates/editor/src/editor.rs | 20 +++++++++++++++++++- crates/editor/src/hover_popover.rs | 24 +++++++++++++----------- crates/theme/src/styles/colors.rs | 4 +++- crates/theme/src/styles/status.rs | 9 --------- 4 files changed, 35 insertions(+), 22 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 9858cf8372eec5a57e4f1eeb419d06147d7ce51c..71fe6ccfcbc743dc8121362e11f0f9431e5ce05d 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -507,7 +507,7 @@ pub enum SoftWrap { Column(u32), } -#[derive(Clone, Default)] +#[derive(Clone)] pub struct EditorStyle { pub background: Hsla, pub local_player: PlayerColor, @@ -519,6 +519,24 @@ pub struct EditorStyle { pub suggestions_style: HighlightStyle, } +impl Default for EditorStyle { + fn default() -> Self { + Self { + background: Hsla::default(), + local_player: PlayerColor::default(), + text: TextStyle::default(), + scrollbar_width: Pixels::default(), + syntax: Default::default(), + // HACK: Status colors don't have a real default. + // We should look into removing the status colors from the editor + // style and retrieve them directly from the theme. + status: StatusColors::dark(), + inlays_style: HighlightStyle::default(), + suggestions_style: HighlightStyle::default(), + } + } +} + type CompletionId = usize; // type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor; diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 22c58056f01d660bce7a031782577904881214c4..26ce3e5cf708e0193eef2c87fbf72c27f4da806a 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -16,7 +16,7 @@ use lsp::DiagnosticSeverity; use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project}; use settings::Settings; use std::{ops::Range, sync::Arc, time::Duration}; -use ui::{StyledExt, Tooltip}; +use ui::{prelude::*, Tooltip}; use util::TryFutureExt; use workspace::Workspace; @@ -514,6 +514,8 @@ impl DiagnosticPopover { None => self.local_diagnostic.diagnostic.message.clone(), }; + let status_colors = cx.theme().status(); + struct DiagnosticColors { pub background: Hsla, pub border: Hsla, @@ -521,24 +523,24 @@ impl DiagnosticPopover { let diagnostic_colors = match self.local_diagnostic.diagnostic.severity { DiagnosticSeverity::ERROR => DiagnosticColors { - background: style.status.error_background, - border: style.status.error_border, + background: status_colors.error_background, + border: status_colors.error_border, }, DiagnosticSeverity::WARNING => DiagnosticColors { - background: style.status.warning_background, - border: style.status.warning_border, + background: status_colors.warning_background, + border: status_colors.warning_border, }, DiagnosticSeverity::INFORMATION => DiagnosticColors { - background: style.status.info_background, - border: style.status.info_border, + background: status_colors.info_background, + border: status_colors.info_border, }, DiagnosticSeverity::HINT => DiagnosticColors { - background: style.status.hint_background, - border: style.status.hint_border, + background: status_colors.hint_background, + border: status_colors.hint_border, }, _ => DiagnosticColors { - background: style.status.ignored_background, - border: style.status.ignored_border, + background: status_colors.ignored_background, + border: status_colors.ignored_border, }, }; diff --git a/crates/theme/src/styles/colors.rs b/crates/theme/src/styles/colors.rs index 5f2295866535deefbe5ff29f51662847b241692e..eb68b6c219f2090a57af9db2c9d1f67ff6dfacb9 100644 --- a/crates/theme/src/styles/colors.rs +++ b/crates/theme/src/styles/colors.rs @@ -2,7 +2,7 @@ use gpui::Hsla; use refineable::Refineable; use std::sync::Arc; -use crate::{PlayerColors, StatusColors, SyntaxTheme, SystemColors}; +use crate::{PlayerColors, StatusColors, StatusColorsRefinement, SyntaxTheme, SystemColors}; #[derive(Refineable, Clone, Debug)] #[refineable(Debug, serde::Deserialize)] @@ -219,6 +219,8 @@ pub struct ThemeStyles { #[refineable] pub colors: ThemeColors, + + #[refineable] pub status: StatusColors, pub player: PlayerColors, pub syntax: Arc, diff --git a/crates/theme/src/styles/status.rs b/crates/theme/src/styles/status.rs index 0ce166deb59b75a8abd10ea105b5efddd66fa33e..854b876ac20b33702f9f4cce467c162e610ad125 100644 --- a/crates/theme/src/styles/status.rs +++ b/crates/theme/src/styles/status.rs @@ -78,15 +78,6 @@ pub struct StatusColors { pub warning_border: Hsla, } -impl Default for StatusColors { - /// Don't use this! - /// We have to have a default to be `[refineable::Refinable]`. - /// todo!("Find a way to not need this for Refinable") - fn default() -> Self { - Self::dark() - } -} - pub struct DiagnosticColors { pub error: Hsla, pub warning: Hsla, From b801e0a46eb58889f652ad13ab614be80f1ef39c Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 9 Jan 2024 09:44:22 -0700 Subject: [PATCH 30/78] Fix merge conflict --- crates/collab/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/collab/src/lib.rs b/crates/collab/src/lib.rs index 87a423bea90df1d2b21a6d85c39f43fbe08a9142..aba9bd75d1f0aa9cc1849309dcb8f8db5b2ed9e3 100644 --- a/crates/collab/src/lib.rs +++ b/crates/collab/src/lib.rs @@ -105,7 +105,7 @@ pub struct Config { impl Config { pub fn is_development(&self) -> bool { - self.zed_environment == "development" + self.zed_environment == "development".into() } } From ab6cd1d93aa7c454f97a3c4ea0275468caa47212 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Tue, 9 Jan 2024 18:00:54 +0100 Subject: [PATCH 31/78] chore: cfg(test) a test in rpc. (#3978) The test in particular should be included in our release binary at present. :/ Release Notes: - N/A --- Cargo.lock | 23 ++++++++++---- crates/rpc/src/notification.rs | 55 ++++++++++++++++++---------------- 2 files changed, 47 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c6e7ecebc1300628462750ed714b9103e6a001c6..49af5ec2d47a9b795bb5ae0a4cf2b95c8b78a298 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1515,7 +1515,7 @@ dependencies = [ "tonic", "tower", "tracing", - "tracing-log", + "tracing-log 0.1.3", "tracing-subscriber", "unindent", "util", @@ -6930,9 +6930,9 @@ dependencies = [ [[package]] name = "sharded-slab" -version = "0.1.4" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ "lazy_static", ] @@ -8316,6 +8316,17 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + [[package]] name = "tracing-serde" version = "0.1.3" @@ -8328,9 +8339,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "matchers", "nu-ansi-term", @@ -8343,7 +8354,7 @@ dependencies = [ "thread_local", "tracing", "tracing-core", - "tracing-log", + "tracing-log 0.2.0", "tracing-serde", ] diff --git a/crates/rpc/src/notification.rs b/crates/rpc/src/notification.rs index c5476469be90112e6ae294665c2bc53319cf38be..57131d3421787a71b4df0edefd59a3d7cfef865e 100644 --- a/crates/rpc/src/notification.rs +++ b/crates/rpc/src/notification.rs @@ -76,30 +76,35 @@ impl Notification { } } -#[test] -fn test_notification() { - // Notifications can be serialized and deserialized. - for notification in [ - Notification::ContactRequest { sender_id: 1 }, - Notification::ContactRequestAccepted { responder_id: 2 }, - Notification::ChannelInvitation { - channel_id: 100, - channel_name: "the-channel".into(), - inviter_id: 50, - }, - Notification::ChannelMessageMention { - sender_id: 200, - channel_id: 30, - message_id: 1, - }, - ] { - let message = notification.to_proto(); - let deserialized = Notification::from_proto(&message).unwrap(); - assert_eq!(deserialized, notification); - } +#[cfg(test)] +mod tests { + use crate::Notification; + + #[test] + fn test_notification() { + // Notifications can be serialized and deserialized. + for notification in [ + Notification::ContactRequest { sender_id: 1 }, + Notification::ContactRequestAccepted { responder_id: 2 }, + Notification::ChannelInvitation { + channel_id: 100, + channel_name: "the-channel".into(), + inviter_id: 50, + }, + Notification::ChannelMessageMention { + sender_id: 200, + channel_id: 30, + message_id: 1, + }, + ] { + let message = notification.to_proto(); + let deserialized = Notification::from_proto(&message).unwrap(); + assert_eq!(deserialized, notification); + } - // When notifications are serialized, the `kind` and `actor_id` fields are - // stored separately, and do not appear redundantly in the JSON. - let notification = Notification::ContactRequest { sender_id: 1 }; - assert_eq!(notification.to_proto().content, "{}"); + // When notifications are serialized, the `kind` and `actor_id` fields are + // stored separately, and do not appear redundantly in the JSON. + let notification = Notification::ContactRequest { sender_id: 1 }; + assert_eq!(notification.to_proto().content, "{}"); + } } From bcbfa7d0361b18218e3aefce25d072dbc75fbbf9 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Tue, 9 Jan 2024 12:49:56 -0500 Subject: [PATCH 32/78] Start documenting Theme crate Co-Authored-By: Antonio Scandurra --- Cargo.lock | 23 ++++----------- crates/theme/src/scale.rs | 49 +++++++++++++------------------ crates/theme/src/settings.rs | 2 +- crates/theme/src/styles/colors.rs | 1 + crates/theme/src/theme.rs | 8 +++++ crates/theme/theme.md | 15 ++++++++++ 6 files changed, 51 insertions(+), 47 deletions(-) create mode 100644 crates/theme/theme.md diff --git a/Cargo.lock b/Cargo.lock index 49af5ec2d47a9b795bb5ae0a4cf2b95c8b78a298..c6e7ecebc1300628462750ed714b9103e6a001c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1515,7 +1515,7 @@ dependencies = [ "tonic", "tower", "tracing", - "tracing-log 0.1.3", + "tracing-log", "tracing-subscriber", "unindent", "util", @@ -6930,9 +6930,9 @@ dependencies = [ [[package]] name = "sharded-slab" -version = "0.1.7" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" dependencies = [ "lazy_static", ] @@ -8316,17 +8316,6 @@ dependencies = [ "tracing-core", ] -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - [[package]] name = "tracing-serde" version = "0.1.3" @@ -8339,9 +8328,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" dependencies = [ "matchers", "nu-ansi-term", @@ -8354,7 +8343,7 @@ dependencies = [ "thread_local", "tracing", "tracing-core", - "tracing-log 0.2.0", + "tracing-log", "tracing-serde", ] diff --git a/crates/theme/src/scale.rs b/crates/theme/src/scale.rs index c1c2ff924c866473fb90f89842f0d32432b9da27..8bfb2999cda97f2da6d4dac816a85f51f3b564e6 100644 --- a/crates/theme/src/scale.rs +++ b/crates/theme/src/scale.rs @@ -2,41 +2,31 @@ use gpui::{AppContext, Hsla, SharedString}; use crate::{ActiveTheme, Appearance}; -/// A one-based step in a [`ColorScale`]. +/// A collection of colors that are used to style the UI. +/// +/// Each step has a semantic meaning, and is used to style different parts of the UI. #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] pub struct ColorScaleStep(usize); impl ColorScaleStep { - pub const ONE: Self = Self(1); - pub const TWO: Self = Self(2); - pub const THREE: Self = Self(3); - pub const FOUR: Self = Self(4); - pub const FIVE: Self = Self(5); - pub const SIX: Self = Self(6); - pub const SEVEN: Self = Self(7); - pub const EIGHT: Self = Self(8); - pub const NINE: Self = Self(9); - pub const TEN: Self = Self(10); - pub const ELEVEN: Self = Self(11); - pub const TWELVE: Self = Self(12); - - /// All of the steps in a [`ColorScale`]. - pub const ALL: [ColorScaleStep; 12] = [ - Self::ONE, - Self::TWO, - Self::THREE, - Self::FOUR, - Self::FIVE, - Self::SIX, - Self::SEVEN, - Self::EIGHT, - Self::NINE, - Self::TEN, - Self::ELEVEN, - Self::TWELVE, - ]; + const ONE: Self = Self(1); + const TWO: Self = Self(2); + const THREE: Self = Self(3); + const FOUR: Self = Self(4); + const FIVE: Self = Self(5); + const SIX: Self = Self(6); + const SEVEN: Self = Self(7); + const EIGHT: Self = Self(8); + const NINE: Self = Self(9); + const TEN: Self = Self(10); + const ELEVEN: Self = Self(11); + const TWELVE: Self = Self(12); } +/// A scale of colors for a given [ColorScaleSet]. +/// +/// Each [ColorScale] contains exactly 12 colors. Refer to +/// [ColorScaleStep] for a reference of what each step is used for. pub struct ColorScale(Vec); impl FromIterator for ColorScale { @@ -229,6 +219,7 @@ impl IntoIterator for ColorScales { } } +/// Provides groups of [ColorScale]s for light and dark themes, as well as transparent versions of each scale. pub struct ColorScaleSet { name: SharedString, light: ColorScale, diff --git a/crates/theme/src/settings.rs b/crates/theme/src/settings.rs index 624b14fe33fe194e2f8b7af3211ff4c9a144c100..3ecf1935a47eeb7fb4c8e4edbce9d99f33a2b3f3 100644 --- a/crates/theme/src/settings.rs +++ b/crates/theme/src/settings.rs @@ -27,7 +27,7 @@ pub struct ThemeSettings { } #[derive(Default)] -pub struct AdjustedBufferFontSize(Pixels); +pub(crate) struct AdjustedBufferFontSize(Pixels); #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct ThemeSettingsContent { diff --git a/crates/theme/src/styles/colors.rs b/crates/theme/src/styles/colors.rs index eb68b6c219f2090a57af9db2c9d1f67ff6dfacb9..c207aa6bb413321d38d8f6b2787296afee5b7e59 100644 --- a/crates/theme/src/styles/colors.rs +++ b/crates/theme/src/styles/colors.rs @@ -7,6 +7,7 @@ use crate::{PlayerColors, StatusColors, StatusColorsRefinement, SyntaxTheme, Sys #[derive(Refineable, Clone, Debug)] #[refineable(Debug, serde::Deserialize)] pub struct ThemeColors { + /// Border color. Used for most borders, is usually a high contrast color. pub border: Hsla, /// Border color. Used for deemphasized borders, like a visual divider between two sections pub border_variant: Hsla, diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 93253b95e7358962707ef0fc22fbe01a01f7cd56..f8d90b7bdc823b0b52348fe94908454002616347 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -1,3 +1,11 @@ +//! # Theme +//! +//! This crate provides the theme system for Zed. +//! +//! ## Overview +//! +//! A theme is a collection of colors used to build a consistent appearance for UI components across the application. + mod default_colors; mod default_theme; mod one_themes; diff --git a/crates/theme/theme.md b/crates/theme/theme.md new file mode 100644 index 0000000000000000000000000000000000000000..f9a7a581786eff51b53d1209e6a8518ae60403a5 --- /dev/null +++ b/crates/theme/theme.md @@ -0,0 +1,15 @@ + # Theme + + This crate provides the theme system for Zed. + + ## Overview + + A theme is a collection of colors used to build a consistent appearance for UI components across the application. + To produce a theme in Zed, + + A theme is made of of two parts: A [ThemeFamily] and one or more [Theme]s. + +// + A [ThemeFamily] contains metadata like theme name, author, and theme-specific [ColorScales] as well as a series of themes. + + - [ThemeColors] - A set of colors that are used to style the UI. Refer to the [ThemeColors] documentation for more information. From 9747c10ce8bc0be962c87e10777ce38414068571 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Tue, 9 Jan 2024 12:57:12 -0500 Subject: [PATCH 33/78] Document UI modules Co-Authored-By: Antonio Scandurra --- crates/ui/src/prelude.rs | 2 ++ crates/ui/src/ui.rs | 2 -- crates/ui/src/utils.rs | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/ui/src/prelude.rs b/crates/ui/src/prelude.rs index 0a86b99f9f232927d9e92615adcf987997df7919..ee635e4d6b944d807034087ff1e4de2788505847 100644 --- a/crates/ui/src/prelude.rs +++ b/crates/ui/src/prelude.rs @@ -1,3 +1,5 @@ +//! The prelude of this crate. When building UI in zed you almost always want to import this. + pub use gpui::prelude::*; pub use gpui::{ div, px, relative, rems, AbsoluteLength, DefiniteLength, Div, Element, ElementId, diff --git a/crates/ui/src/ui.rs b/crates/ui/src/ui.rs index b074f10dad50b9e61d0ed69ca29f39687e54973e..e8ee51818ea963657aafc2cf1dfdd5f8dbd4668f 100644 --- a/crates/ui/src/ui.rs +++ b/crates/ui/src/ui.rs @@ -3,8 +3,6 @@ //! This crate provides a set of UI primitives and components that are used to build all of the elements in Zed's UI. //! -#![doc = include_str!("../docs/building-ui.md")] - mod clickable; mod components; mod disableable; diff --git a/crates/ui/src/utils.rs b/crates/ui/src/utils.rs index 573a1333efcfa0169a84f52ce27e72838492c696..ed1fec690fc738be2bee5d621c7fc53f0c0f6b9e 100644 --- a/crates/ui/src/utils.rs +++ b/crates/ui/src/utils.rs @@ -1,3 +1,5 @@ +//! UI-related utilities (e.g. converting dates to a human-readable form). + mod format_distance; pub use format_distance::*; From 59f41acb8236aa1f9956404d699ac79036d1210a Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 9 Jan 2024 11:02:57 -0700 Subject: [PATCH 34/78] Add a bunch of docs Co-Authored-By: Conrad Co-Authored-By: Mikayla --- crates/gpui/src/app/entity_map.rs | 5 +- crates/gpui/src/app/test_context.rs | 25 ++++++ crates/gpui/src/executor.rs | 42 +++++++++- crates/gpui/src/test.rs | 28 +++++++ crates/gpui/src/window.rs | 123 ++++++++++++++++++++++++++-- 5 files changed, 214 insertions(+), 9 deletions(-) diff --git a/crates/gpui/src/app/entity_map.rs b/crates/gpui/src/app/entity_map.rs index 17f6e47ddfec85de1c82b1fc0b8cd6ffc285aa32..0b213b20f769975e22761f8294251051e39e2753 100644 --- a/crates/gpui/src/app/entity_map.rs +++ b/crates/gpui/src/app/entity_map.rs @@ -19,7 +19,10 @@ use std::{ #[cfg(any(test, feature = "test-support"))] use collections::HashMap; -slotmap::new_key_type! { pub struct EntityId; } +slotmap::new_key_type! { + /// A unique identifier for a model or view across the application. + pub struct EntityId; +} impl EntityId { pub fn as_u64(self) -> u64 { diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index a530aaf69b94ce65d35feee015738f306b3a79c3..587d3f3fec4b5f6d278b4bd1e7272e5b6bf3d356 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -9,6 +9,8 @@ use anyhow::{anyhow, bail}; use futures::{Stream, StreamExt}; use std::{future::Future, ops::Deref, rc::Rc, sync::Arc, time::Duration}; +/// A TestAppContext is provided to tests created with `#[gpui::test]`, it provides +/// an implementation of `Context` with additional methods that are useful in tests. #[derive(Clone)] pub struct TestAppContext { pub app: Rc, @@ -95,38 +97,47 @@ impl TestAppContext { } } + /// returns a new `TestAppContext` re-using the same executors to interleave tasks. pub fn new_app(&self) -> TestAppContext { Self::new(self.dispatcher.clone()) } + /// Simulates quitting the app. pub fn quit(&self) { self.app.borrow_mut().shutdown(); } + /// Schedules all windows to be redrawn on the next effect cycle. pub fn refresh(&mut self) -> Result<()> { let mut app = self.app.borrow_mut(); app.refresh(); Ok(()) } + /// Returns an executor (for running tasks in the background) pub fn executor(&self) -> BackgroundExecutor { self.background_executor.clone() } + /// Returns an executor (for running tasks on the main thread) pub fn foreground_executor(&self) -> &ForegroundExecutor { &self.foreground_executor } + /// Gives you an `&mut AppContext` for the duration of the closure pub fn update(&self, f: impl FnOnce(&mut AppContext) -> R) -> R { let mut cx = self.app.borrow_mut(); cx.update(f) } + /// Gives you an `&AppContext` for the duration of the closure pub fn read(&self, f: impl FnOnce(&AppContext) -> R) -> R { let cx = self.app.borrow(); f(&*cx) } + // Adds a new window. The Window will always be backed by a `TestWindow` which + // can be retrieved with `self.test_window(handle)` pub fn add_window(&mut self, build_window: F) -> WindowHandle where F: FnOnce(&mut ViewContext) -> V, @@ -136,12 +147,16 @@ impl TestAppContext { cx.open_window(WindowOptions::default(), |cx| cx.new_view(build_window)) } + // Adds a new window with no content. pub fn add_empty_window(&mut self) -> AnyWindowHandle { let mut cx = self.app.borrow_mut(); cx.open_window(WindowOptions::default(), |cx| cx.new_view(|_| EmptyView {})) .any_handle } + /// Adds a new window, and returns its root view and a `VisualTestContext` which can be used + /// as a `WindowContext` for the rest of the test. Typically you would shadow this context with + /// the returned one. `let (view, cx) = cx.add_window_view(...);` pub fn add_window_view(&mut self, build_window: F) -> (View, &mut VisualTestContext) where F: FnOnce(&mut ViewContext) -> V, @@ -156,18 +171,23 @@ impl TestAppContext { (view, Box::leak(cx)) } + /// returns the TextSystem pub fn text_system(&self) -> &Arc { &self.text_system } + /// Simulates writing to the platform clipboard pub fn write_to_clipboard(&self, item: ClipboardItem) { self.test_platform.write_to_clipboard(item) } + /// Simulates reading from the platform clipboard. + /// This will return the most recent value from `write_to_clipboard`. pub fn read_from_clipboard(&self) -> Option { self.test_platform.read_from_clipboard() } + /// Simulates choosing a File in the platform's "Open" dialog. pub fn simulate_new_path_selection( &self, select_path: impl FnOnce(&std::path::Path) -> Option, @@ -175,22 +195,27 @@ impl TestAppContext { self.test_platform.simulate_new_path_selection(select_path); } + /// Simulates clicking a button in an platform-level alert dialog. pub fn simulate_prompt_answer(&self, button_ix: usize) { self.test_platform.simulate_prompt_answer(button_ix); } + /// Returns true if there's an alert dialog open. pub fn has_pending_prompt(&self) -> bool { self.test_platform.has_pending_prompt() } + /// Simulates the user resizing the window to the new size. pub fn simulate_window_resize(&self, window_handle: AnyWindowHandle, size: Size) { self.test_window(window_handle).simulate_resize(size); } + /// Returns all windows open in the test. pub fn windows(&self) -> Vec { self.app.borrow().windows().clone() } + /// Run the given task on the main thread. pub fn spawn(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task where Fut: Future + 'static, diff --git a/crates/gpui/src/executor.rs b/crates/gpui/src/executor.rs index 589493b4bb431586ed004e88e59d0d8f329334fc..4be1ffbf0fdf970353d6f2402eda02ab2b0faac8 100644 --- a/crates/gpui/src/executor.rs +++ b/crates/gpui/src/executor.rs @@ -32,6 +32,10 @@ pub struct ForegroundExecutor { not_send: PhantomData>, } +/// Task is a primitive that allows work to happen in the background. +/// It implements Future so you can `.await` on it. +/// If you drop a task it will be cancelled immediately. Calling `.detach()` allows +/// the task to continue running in the background, but with no way to return a value. #[must_use] #[derive(Debug)] pub enum Task { @@ -40,10 +44,12 @@ pub enum Task { } impl Task { + /// Create a new task that will resolve with the value pub fn ready(val: T) -> Self { Task::Ready(Some(val)) } + /// Detaching a task runs it to completion in the background pub fn detach(self) { match self { Task::Ready(_) => {} @@ -57,6 +63,8 @@ where T: 'static, E: 'static + Debug, { + /// Run the task to completion in the background and log any + /// errors that occur. #[track_caller] pub fn detach_and_log_err(self, cx: &mut AppContext) { let location = core::panic::Location::caller(); @@ -97,6 +105,10 @@ type AnyLocalFuture = Pin>>; type AnyFuture = Pin>>; +/// BackgroundExecutor lets you run things on background threads. +/// In production this is a thread pool with no ordering guarantees. +/// In tests this is simalated by running tasks one by one in a deterministic +/// (but arbitrary) order controlled by the `SEED` environment variable. impl BackgroundExecutor { pub fn new(dispatcher: Arc) -> Self { Self { dispatcher } @@ -135,6 +147,7 @@ impl BackgroundExecutor { Task::Spawned(task) } + /// Used by the test harness to run an async test in a syncronous fashion. #[cfg(any(test, feature = "test-support"))] #[track_caller] pub fn block_test(&self, future: impl Future) -> R { @@ -145,6 +158,8 @@ impl BackgroundExecutor { } } + /// Block the current thread until the given future resolves. + /// Consider using `block_with_timeout` instead. pub fn block(&self, future: impl Future) -> R { if let Ok(value) = self.block_internal(true, future, usize::MAX) { value @@ -206,6 +221,8 @@ impl BackgroundExecutor { } } + /// Block the current thread until the given future resolves + /// or `duration` has elapsed. pub fn block_with_timeout( &self, duration: Duration, @@ -238,6 +255,8 @@ impl BackgroundExecutor { } } + /// Scoped lets you start a number of tasks and waits + /// for all of them to complete before returning. pub async fn scoped<'scope, F>(&self, scheduler: F) where F: FnOnce(&mut Scope<'scope>), @@ -253,6 +272,9 @@ impl BackgroundExecutor { } } + /// Returns a task that will complete after the given duration. + /// Depending on other concurrent tasks the elapsed duration may be longer + /// than reqested. pub fn timer(&self, duration: Duration) -> Task<()> { let (runnable, task) = async_task::spawn(async move {}, { let dispatcher = self.dispatcher.clone(); @@ -262,65 +284,81 @@ impl BackgroundExecutor { Task::Spawned(task) } + /// in tests, start_waiting lets you indicate which task is waiting (for debugging only) #[cfg(any(test, feature = "test-support"))] pub fn start_waiting(&self) { self.dispatcher.as_test().unwrap().start_waiting(); } + /// in tests, removes the debugging data added by start_waiting #[cfg(any(test, feature = "test-support"))] pub fn finish_waiting(&self) { self.dispatcher.as_test().unwrap().finish_waiting(); } + /// in tests, run an arbitrary number of tasks (determined by the SEED environment variable) #[cfg(any(test, feature = "test-support"))] pub fn simulate_random_delay(&self) -> impl Future { self.dispatcher.as_test().unwrap().simulate_random_delay() } + /// in tests, indicate that a given task from `spawn_labeled` should run after everything else #[cfg(any(test, feature = "test-support"))] pub fn deprioritize(&self, task_label: TaskLabel) { self.dispatcher.as_test().unwrap().deprioritize(task_label) } + /// in tests, move time forward. This does not run any tasks, but does make `timer`s ready. #[cfg(any(test, feature = "test-support"))] pub fn advance_clock(&self, duration: Duration) { self.dispatcher.as_test().unwrap().advance_clock(duration) } + /// in tests, run one task. #[cfg(any(test, feature = "test-support"))] pub fn tick(&self) -> bool { self.dispatcher.as_test().unwrap().tick(false) } + /// in tests, run all tasks that are ready to run. If after doing so + /// the test still has outstanding tasks, this will panic. (See also `allow_parking`) #[cfg(any(test, feature = "test-support"))] pub fn run_until_parked(&self) { self.dispatcher.as_test().unwrap().run_until_parked() } + /// in tests, prevents `run_until_parked` from panicking if there are outstanding tasks. + /// This is useful when you are integrating other (non-GPUI) futures, like disk access, that + /// do take real async time to run. #[cfg(any(test, feature = "test-support"))] pub fn allow_parking(&self) { self.dispatcher.as_test().unwrap().allow_parking(); } + /// in tests, returns the rng used by the dispatcher and seeded by the `SEED` environment variable #[cfg(any(test, feature = "test-support"))] pub fn rng(&self) -> StdRng { self.dispatcher.as_test().unwrap().rng() } + /// How many CPUs are available to the dispatcher pub fn num_cpus(&self) -> usize { num_cpus::get() } + /// Whether we're on the main thread. pub fn is_main_thread(&self) -> bool { self.dispatcher.is_main_thread() } #[cfg(any(test, feature = "test-support"))] + /// in tests, control the number of ticks that `block_with_timeout` will run before timing out. pub fn set_block_on_ticks(&self, range: std::ops::RangeInclusive) { self.dispatcher.as_test().unwrap().set_block_on_ticks(range); } } +/// ForegroundExecutor runs things on the main thread. impl ForegroundExecutor { pub fn new(dispatcher: Arc) -> Self { Self { @@ -329,8 +367,7 @@ impl ForegroundExecutor { } } - /// Enqueues the given closure to be run on any thread. The closure returns - /// a future which will be run to completion on any available thread. + /// Enqueues the given Task to run on the main thread at some point in the future. pub fn spawn(&self, future: impl Future + 'static) -> Task where R: 'static, @@ -350,6 +387,7 @@ impl ForegroundExecutor { } } +/// Scope manages a set of tasks that are enqueued and waited on together. See `BackgroundExecutor#scoped` pub struct Scope<'a> { executor: BackgroundExecutor, futures: Vec + Send + 'static>>>, diff --git a/crates/gpui/src/test.rs b/crates/gpui/src/test.rs index 5a21576fb26178ff67c750ca7ee63652690f1700..1771f29c67b13639fbb7b87029840c435b6a681d 100644 --- a/crates/gpui/src/test.rs +++ b/crates/gpui/src/test.rs @@ -1,3 +1,30 @@ +//! Test support for GPUI. +//! +//! GPUI provides first-class support for testing, which includes a macro to run test that rely on having a context, +//! and a test implementation of the `ForegroundExecutor` and `BackgroundExecutor` which ensure that your tests run +//! deterministically even in the face of arbitrary parallelism. +//! +//! The output of the `gpui::test` macro is understood by other rust test runners, so you can use it with `cargo test` +//! or `cargo-nextest`, or another runner of your choice. +//! +//! To make it possible to test collaborative user interfaces (like Zed) you can ask for as many different contexts +//! as you need. +//! +//! ## Example +//! +//! ``` +//! use gpui; +//! +//! #[gpui::test] +//! async fn test_example(cx: &TestAppContext) { +//! assert!(true) +//! } +//! +//! #[gpui::test] +//! async fn test_collaboration_example(cx_a: &TestAppContext, cx_b: &TestAppContext) { +//! assert!(true) +//! } +//! ``` use crate::{Entity, Subscription, TestAppContext, TestDispatcher}; use futures::StreamExt as _; use rand::prelude::*; @@ -68,6 +95,7 @@ impl futures::Stream for Observation { } } +/// observe returns a stream of the change events from the given `View` or `Model` pub fn observe(entity: &impl Entity, cx: &mut TestAppContext) -> Observation<()> { let (tx, rx) = smol::channel::unbounded(); let _subscription = cx.update(|cx| { diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 7e4c5f93f95e6ea770d404a63a3e6795d9a4be7d..dd567a83f6baee9ae65b57def9d7576b84bad89f 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -1,3 +1,5 @@ +#![deny(missing_docs)] + use crate::{ px, size, transparent_black, Action, AnyDrag, AnyView, AppContext, Arena, ArenaBox, ArenaRef, AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle, @@ -85,10 +87,12 @@ pub enum DispatchPhase { } impl DispatchPhase { + /// Returns true if this represents the "bubble" phase. pub fn bubble(self) -> bool { self == DispatchPhase::Bubble } + /// Returns true if this represents the "capture" phase. pub fn capture(self) -> bool { self == DispatchPhase::Capture } @@ -103,7 +107,10 @@ struct FocusEvent { current_focus_path: SmallVec<[FocusId; 8]>, } -slotmap::new_key_type! { pub struct FocusId; } +slotmap::new_key_type! { + /// A globally unique identifier for a focusable element. + pub struct FocusId; +} thread_local! { pub(crate) static ELEMENT_ARENA: RefCell = RefCell::new(Arena::new(4 * 1024 * 1024)); @@ -240,6 +247,7 @@ pub trait ManagedView: FocusableView + EventEmitter {} impl> ManagedView for M {} +/// Emitted by implementers of [ManagedView] to indicate the view should be dismissed, such as when a view is presented as a modal. pub struct DismissEvent; // Holds the state for a specific window. @@ -525,11 +533,13 @@ impl<'a> WindowContext<'a> { self.notify(); } + /// Blur the window and don't allow anything in it to be focused again. pub fn disable_focus(&mut self) { self.blur(); self.window.focus_enabled = false; } + /// Dispatch the given action on the currently focused element. pub fn dispatch_action(&mut self, action: Box) { let focus_handle = self.focused(); @@ -591,6 +601,9 @@ impl<'a> WindowContext<'a> { }); } + /// Subscribe to events emitted by a model or view. + /// The entity to which you're subscribing must implement the [EventEmitter] trait. + /// The callback will be invoked a handle to the emitting entity (either a [View] or [Model]), the event, and a window context for the current window. pub fn subscribe( &mut self, entity: &E, @@ -754,6 +767,9 @@ impl<'a> WindowContext<'a> { .request_measured_layout(style, rem_size, measure) } + /// Compute the layout for the given id within the given available space. + /// This method is called for its side effect, typically by the framework prior to painting. + /// After calling it, you can request the bounds of the given layout node id or any descendant. pub fn compute_layout(&mut self, layout_id: LayoutId, available_space: Size) { let mut layout_engine = self.window.layout_engine.take().unwrap(); layout_engine.compute_layout(layout_id, available_space, self); @@ -788,30 +804,37 @@ impl<'a> WindowContext<'a> { .retain(&(), |callback| callback(self)); } + /// Returns the bounds of the current window in the global coordinate space, which could span across multiple displays. pub fn window_bounds(&self) -> WindowBounds { self.window.bounds } + /// Returns the size of the drawable area within the window. pub fn viewport_size(&self) -> Size { self.window.viewport_size } + /// Returns whether this window is focused by the operating system (receiving key events). pub fn is_window_active(&self) -> bool { self.window.active } + /// Toggle zoom on the window. pub fn zoom_window(&self) { self.window.platform_window.zoom(); } + /// Update the window's title at the platform level. pub fn set_window_title(&mut self, title: &str) { self.window.platform_window.set_title(title); } + /// Mark the window as dirty at the platform level. pub fn set_window_edited(&mut self, edited: bool) { self.window.platform_window.set_edited(edited); } + /// Determine the display on which the window is visible. pub fn display(&self) -> Option> { self.platform .displays() @@ -819,6 +842,7 @@ impl<'a> WindowContext<'a> { .find(|display| display.id() == self.window.display_id) } + /// Show the platform character palette. pub fn show_character_palette(&self) { self.window.platform_window.show_character_palette(); } @@ -936,6 +960,7 @@ impl<'a> WindowContext<'a> { .on_action(action_type, ArenaRef::from(listener)); } + /// Determine whether the given action is available along the dispatch path to the currently focused element. pub fn is_action_available(&self, action: &dyn Action) -> bool { let target = self .focused() @@ -962,6 +987,7 @@ impl<'a> WindowContext<'a> { self.window.modifiers } + /// Update the cursor style at the platform level. pub fn set_cursor_style(&mut self, style: CursorStyle) { self.window.requested_cursor_style = Some(style) } @@ -991,7 +1017,7 @@ impl<'a> WindowContext<'a> { true } - pub fn was_top_layer_under_active_drag( + pub(crate) fn was_top_layer_under_active_drag( &self, point: &Point, level: &StackingOrder, @@ -1649,6 +1675,7 @@ impl<'a> WindowContext<'a> { self.dispatch_keystroke_observers(event, None); } + /// Determine whether a potential multi-stroke key binding is in progress on this window. pub fn has_pending_keystrokes(&self) -> bool { self.window .rendered_frame @@ -1715,27 +1742,34 @@ impl<'a> WindowContext<'a> { subscription } + /// Focus the current window and bring it to the foreground at the platform level. pub fn activate_window(&self) { self.window.platform_window.activate(); } + /// Minimize the current window at the platform level. pub fn minimize_window(&self) { self.window.platform_window.minimize(); } + /// Toggle full screen status on the current window at the platform level. pub fn toggle_full_screen(&self) { self.window.platform_window.toggle_full_screen(); } + /// Present a platform dialog. + /// The provided message will be presented, along with buttons for each answer. + /// When a button is clicked, the returned Receiver will receive the index of the clicked button. pub fn prompt( &self, level: PromptLevel, - msg: &str, + message: &str, answers: &[&str], ) -> oneshot::Receiver { - self.window.platform_window.prompt(level, msg, answers) + self.window.platform_window.prompt(level, message, answers) } + /// Returns all available actions for the focused element. pub fn available_actions(&self) -> Vec> { let node_id = self .window @@ -1754,6 +1788,7 @@ impl<'a> WindowContext<'a> { .available_actions(node_id) } + /// Returns any key bindings that invoke the given action. pub fn bindings_for_action(&self, action: &dyn Action) -> Vec { self.window .rendered_frame @@ -2279,6 +2314,10 @@ impl BorrowMut for WindowContext<'_> { impl BorrowWindow for T where T: BorrowMut + BorrowMut {} +/// Provides access to application state that is specialized for a particular [View]. +/// Allows you to interact with focus, emit events, etc. +/// ViewContext also derefs to [WindowContext], giving you access to all of its methods as well. +/// When you call [View::::update], you're passed a `&mut V` and an `&mut ViewContext`. pub struct ViewContext<'a, V> { window_cx: WindowContext<'a>, view: &'a View, @@ -2316,14 +2355,17 @@ impl<'a, V: 'static> ViewContext<'a, V> { } } + /// Get the entity_id of this view. pub fn entity_id(&self) -> EntityId { self.view.entity_id() } + /// Get the view pointer underlying this context. pub fn view(&self) -> &View { self.view } + /// Get the model underlying this view. pub fn model(&self) -> &Model { &self.view.model } @@ -2333,6 +2375,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { &mut self.window_cx } + /// Set a given callback to be run on the next frame. pub fn on_next_frame(&mut self, f: impl FnOnce(&mut V, &mut ViewContext) + 'static) where V: 'static, @@ -2350,6 +2393,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { }); } + /// Observe another model or view for changes to it's state, as tracked by the [AppContext::notify] pub fn observe( &mut self, entity: &E, @@ -2383,6 +2427,9 @@ impl<'a, V: 'static> ViewContext<'a, V> { subscription } + /// Subscribe to events emitted by another model or view. + /// The entity to which you're subscribing must implement the [EventEmitter] trait. + /// The callback will be invoked with a reference to the current view, a handle to the emitting entity (either a [View] or [Model]), the event, and a view context for the current view. pub fn subscribe( &mut self, entity: &E, @@ -2440,6 +2487,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { subscription } + /// Register a callback to be invoked when the given Model or View is released. pub fn observe_release( &mut self, entity: &E, @@ -2466,6 +2514,8 @@ impl<'a, V: 'static> ViewContext<'a, V> { subscription } + /// Indicate that this view has changed, which will invoke any observers and also mark the window as dirty. + /// If this view or any of its ancestors are *cached*, notifying it will cause it or its ancestors to be redrawn. pub fn notify(&mut self) { if !self.window.drawing { self.window_cx.notify(); @@ -2475,6 +2525,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { } } + /// Register a callback to be invoked when the window is resized. pub fn observe_window_bounds( &mut self, mut callback: impl FnMut(&mut V, &mut ViewContext) + 'static, @@ -2488,6 +2539,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { subscription } + /// Register a callback to be invoked when the window is activated or deactivated. pub fn observe_window_activation( &mut self, mut callback: impl FnMut(&mut V, &mut ViewContext) + 'static, @@ -2620,6 +2672,10 @@ impl<'a, V: 'static> ViewContext<'a, V> { subscription } + /// Schedule a future to be run asynchronously. + /// The given callback is invoked with a [WeakView] to avoid leaking the view for a long-running process. + /// It's also given an `AsyncWindowContext`, which can be used to access the state of the view across await points. + /// The returned future will be polled on the main thread. pub fn spawn( &mut self, f: impl FnOnce(WeakView, AsyncWindowContext) -> Fut, @@ -2632,6 +2688,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { self.window_cx.spawn(|cx| f(view, cx)) } + /// Update the global state of the given type. pub fn update_global(&mut self, f: impl FnOnce(&mut G, &mut Self) -> R) -> R where G: 'static, @@ -2642,6 +2699,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { result } + /// Register a callback to be invoked when the given global state changes. pub fn observe_global( &mut self, mut f: impl FnMut(&mut V, &mut ViewContext<'_, V>) + 'static, @@ -2660,6 +2718,9 @@ impl<'a, V: 'static> ViewContext<'a, V> { subscription } + /// Add a listener for any mouse event that occurs in the window. + /// This is a fairly low level method. + /// Typically, you'll want to use methods on UI elements, which perform bounds checking etc. pub fn on_mouse_event( &mut self, handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext) + 'static, @@ -2672,6 +2733,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { }); } + /// Register a callback to be invoked when the given Key Event is dispatched to the window. pub fn on_key_event( &mut self, handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext) + 'static, @@ -2684,6 +2746,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { }); } + /// Register a callback to be invoked when the given Action type is dispatched to the window. pub fn on_action( &mut self, action_type: TypeId, @@ -2698,6 +2761,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { }); } + /// Emit an event to be handled any other views that have subscribed via [ViewContext::subscribe]. pub fn emit(&mut self, event: Evt) where Evt: 'static, @@ -2711,6 +2775,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { }); } + /// Move focus to the current view, assuming it implements [FocusableView]. pub fn focus_self(&mut self) where V: FocusableView, @@ -2718,6 +2783,11 @@ impl<'a, V: 'static> ViewContext<'a, V> { self.defer(|view, cx| view.focus_handle(cx).focus(cx)) } + /// Convenience method for accessing view state in an event callback. + /// + /// Many GPUI callbacks take the form of `Fn(&E, &mut WindowContext)`, + /// but it's often useful to be able to access view state in these + /// callbacks. This method provides a convenient way to do so. pub fn listener( &self, f: impl Fn(&mut V, &E, &mut ViewContext) + 'static, @@ -2827,14 +2897,20 @@ impl<'a, V> std::ops::DerefMut for ViewContext<'a, V> { } // #[derive(Clone, Copy, Eq, PartialEq, Hash)] -slotmap::new_key_type! { pub struct WindowId; } +slotmap::new_key_type! { + /// A unique identifier for a window. + pub struct WindowId; +} impl WindowId { + /// Converts this window ID to a `u64`. pub fn as_u64(&self) -> u64 { self.0.as_ffi() } } +/// A handle to a window with a specific root view type. +/// Note that this does not keep the window alive on its own. #[derive(Deref, DerefMut)] pub struct WindowHandle { #[deref] @@ -2844,6 +2920,8 @@ pub struct WindowHandle { } impl WindowHandle { + /// Create a new handle from a window ID. + /// This does not check if the root type of the window is `V`. pub fn new(id: WindowId) -> Self { WindowHandle { any_handle: AnyWindowHandle { @@ -2854,6 +2932,9 @@ impl WindowHandle { } } + /// Get the root view out of this window. + /// + /// This will fail if the window is closed or if the root view's type does not match `V`. pub fn root(&self, cx: &mut C) -> Result> where C: Context, @@ -2865,6 +2946,9 @@ impl WindowHandle { })) } + /// Update the root view of this window. + /// + /// This will fail if the window has been closed or if the root view's type does not match pub fn update( &self, cx: &mut C, @@ -2881,6 +2965,9 @@ impl WindowHandle { })? } + /// Read the root view out of this window. + /// + /// This will fail if the window is closed or if the root view's type does not match `V`. pub fn read<'a>(&self, cx: &'a AppContext) -> Result<&'a V> { let x = cx .windows @@ -2897,6 +2984,9 @@ impl WindowHandle { Ok(x.read(cx)) } + /// Read the root view out of this window, with a callback + /// + /// This will fail if the window is closed or if the root view's type does not match `V`. pub fn read_with(&self, cx: &C, read_with: impl FnOnce(&V, &AppContext) -> R) -> Result where C: Context, @@ -2904,6 +2994,9 @@ impl WindowHandle { cx.read_window(self, |root_view, cx| read_with(root_view.read(cx), cx)) } + /// Read the root view pointer off of this window. + /// + /// This will fail if the window is closed or if the root view's type does not match `V`. pub fn root_view(&self, cx: &C) -> Result> where C: Context, @@ -2911,6 +3004,9 @@ impl WindowHandle { cx.read_window(self, |root_view, _cx| root_view.clone()) } + /// Check if this window is 'active'. + /// + /// Will return `None` if the window is closed. pub fn is_active(&self, cx: &AppContext) -> Option { cx.windows .get(self.id) @@ -2946,6 +3042,7 @@ impl From> for AnyWindowHandle { } } +/// A handle to a window with any root view type, which can be downcast to a window with a specific root view type. #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub struct AnyWindowHandle { pub(crate) id: WindowId, @@ -2953,10 +3050,13 @@ pub struct AnyWindowHandle { } impl AnyWindowHandle { + /// Get the ID of this window. pub fn window_id(&self) -> WindowId { self.id } + /// Attempt to convert this handle to a window handle with a specific root view type. + /// If the types do not match, this will return `None`. pub fn downcast(&self) -> Option> { if TypeId::of::() == self.state_type { Some(WindowHandle { @@ -2968,6 +3068,9 @@ impl AnyWindowHandle { } } + /// Update the state of the root view of this window. + /// + /// This will fail if the window has been closed. pub fn update( self, cx: &mut C, @@ -2979,6 +3082,9 @@ impl AnyWindowHandle { cx.update_window(self, update) } + /// Read the state of the root view of this window. + /// + /// This will fail if the window has been closed. pub fn read(self, cx: &C, read: impl FnOnce(View, &AppContext) -> R) -> Result where C: Context, @@ -2999,6 +3105,10 @@ impl AnyWindowHandle { // } // } +/// An identifier for an [Element]. +/// +/// Can be constructed with a string, a number, or both, as well +/// as other internal representations. #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub enum ElementId { View(EntityId), @@ -3074,7 +3184,8 @@ impl From<(&'static str, u64)> for ElementId { } } -/// A rectangle, to be rendered on the screen by GPUI at the given position and size. +/// A rectangle to be rendered in the window at the given position and size. +/// Passed as an argument [WindowContext::paint_quad]. #[derive(Clone)] pub struct PaintQuad { bounds: Bounds, From 53597620510d76d4070cd9f87ccb69b58e34b8a0 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Tue, 9 Jan 2024 13:04:55 -0500 Subject: [PATCH 35/78] Document components/avatar Co-Authored-By: Antonio Scandurra --- crates/ui/src/components/avatar.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/crates/ui/src/components/avatar.rs b/crates/ui/src/components/avatar.rs index b1dba69520c4d02ac735885f69d84db5d653236b..8e13bc9faa8f2e48227ab51757c847785043b51d 100644 --- a/crates/ui/src/components/avatar.rs +++ b/crates/ui/src/components/avatar.rs @@ -8,6 +8,16 @@ pub enum Shape { RoundedRectangle, } +/// An element that renders a user avatar with customizable appearance options. +/// +/// # Examples +/// +/// ``` +/// Avatar::new("path/to/image.png") +/// .shape(Shape::Circle) +/// .grayscale(true) +/// .border_color(cx.theme().colors().border) +/// ``` #[derive(IntoElement)] pub struct Avatar { image: Img, @@ -66,6 +76,16 @@ impl Avatar { } } + /// Sets the shape of the avatar image. + /// + /// This method allows the shape of the avatar to be specified using the [Shape] enum. + /// It modifies the corner radius of the image to match the specified shape. + /// + /// # Examples + /// + /// ``` + /// Avatar::new("path/to/image.png").shape(Shape::Circle); + /// ``` pub fn shape(mut self, shape: Shape) -> Self { self.image = match shape { Shape::Circle => self.image.rounded_full(), @@ -74,6 +94,13 @@ impl Avatar { self } + /// Applies a grayscale filter to the avatar image. + /// + /// # Examples + /// + /// ``` + /// let avatar = Avatar::new("path/to/image.png").grayscale(true); + /// ``` pub fn grayscale(mut self, grayscale: bool) -> Self { self.image = self.image.grayscale(grayscale); self From 458c672a720cdb106548afbd3423e234c0ec60a8 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 9 Jan 2024 11:16:34 -0700 Subject: [PATCH 36/78] Add more API docs Co-Authored-By: Conrad --- crates/gpui/src/app/test_context.rs | 35 +++++++++++++++++++++++++++-- crates/gpui/src/window.rs | 23 +++++++++++++++++-- 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index 587d3f3fec4b5f6d278b4bd1e7272e5b6bf3d356..7c95f75d96cf1a1456971b122f9807783a80785c 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -1,3 +1,5 @@ +#![deny(missing_docs)] + use crate::{ div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, BackgroundExecutor, ClipboardItem, Context, Entity, EventEmitter, ForegroundExecutor, @@ -255,6 +257,8 @@ impl TestAppContext { lock.update_global(update) } + /// Returns an `AsyncAppContext` which can be used to run tasks that expect to be on a background + /// thread on the current thread in tests. pub fn to_async(&self) -> AsyncAppContext { AsyncAppContext { app: Rc::downgrade(&self.app), @@ -263,6 +267,7 @@ impl TestAppContext { } } + /// Simulate dispatching an action to the currently focused node in the window. pub fn dispatch_action(&mut self, window: AnyWindowHandle, action: A) where A: Action, @@ -276,7 +281,8 @@ impl TestAppContext { /// simulate_keystrokes takes a space-separated list of keys to type. /// cx.simulate_keystrokes("cmd-shift-p b k s p enter") - /// will run backspace on the current editor through the command palette. + /// in Zed, this will run backspace on the current editor through the command palette. + /// This will also run the background executor until it's parked. pub fn simulate_keystrokes(&mut self, window: AnyWindowHandle, keystrokes: &str) { for keystroke in keystrokes .split(" ") @@ -291,7 +297,8 @@ impl TestAppContext { /// simulate_input takes a string of text to type. /// cx.simulate_input("abc") - /// will type abc into your current editor. + /// will type abc into your current editor + /// This will also run the background executor until it's parked. pub fn simulate_input(&mut self, window: AnyWindowHandle, input: &str) { for keystroke in input.split("").map(Keystroke::parse).map(Result::unwrap) { self.dispatch_keystroke(window, keystroke.into(), false); @@ -300,6 +307,7 @@ impl TestAppContext { self.background_executor.run_until_parked() } + /// dispatches a single Keystroke (see also `simulate_keystrokes` and `simulate_input`) pub fn dispatch_keystroke( &mut self, window: AnyWindowHandle, @@ -310,6 +318,7 @@ impl TestAppContext { .simulate_keystroke(keystroke, is_held) } + /// Returns the `TestWindow` backing the given handle. pub fn test_window(&self, window: AnyWindowHandle) -> TestWindow { self.app .borrow_mut() @@ -324,6 +333,7 @@ impl TestAppContext { .clone() } + /// Returns a stream of notifications whenever the View or Model is updated. pub fn notifications(&mut self, entity: &impl Entity) -> impl Stream { let (tx, rx) = futures::channel::mpsc::unbounded(); self.update(|cx| { @@ -340,6 +350,7 @@ impl TestAppContext { rx } + /// Retuens a stream of events emitted by the given Model. pub fn events>( &mut self, entity: &Model, @@ -358,6 +369,8 @@ impl TestAppContext { rx } + /// Runs until the given condition becomes true. (Prefer `run_until_parked` if you + /// don't need to jump in at a specific time). pub async fn condition( &mut self, model: &Model, @@ -387,6 +400,7 @@ impl TestAppContext { } impl Model { + /// Block until the next event is emitted by the model, then return it. pub fn next_event(&self, cx: &mut TestAppContext) -> Evt where Evt: Send + Clone + 'static, @@ -416,6 +430,7 @@ impl Model { } impl View { + /// Returns a future that resolves when the view is next updated. pub fn next_notification(&self, cx: &TestAppContext) -> impl Future { use postage::prelude::{Sink as _, Stream as _}; @@ -442,6 +457,7 @@ impl View { } impl View { + /// Returns a future that resolves when the condition becomes true. pub fn condition( &self, cx: &TestAppContext, @@ -506,6 +522,8 @@ impl View { } } +/// A VisualTestContext is the test-equivalent of a `WindowContext`. It allows you to +/// run window-specific test code. use derive_more::{Deref, DerefMut}; #[derive(Deref, DerefMut, Clone)] pub struct VisualTestContext { @@ -520,6 +538,9 @@ impl<'a> VisualTestContext { self.cx.update_window(self.window, |_, cx| f(cx)).unwrap() } + /// Create a new VisualTestContext. You would typically shadow the passed in + /// TestAppContext with this, as this is typically more useful. + /// `let cx = VisualTestContext::from_window(window, cx);` pub fn from_window(window: AnyWindowHandle, cx: &TestAppContext) -> Self { Self { cx: cx.clone(), @@ -527,10 +548,12 @@ impl<'a> VisualTestContext { } } + /// Wait until there are no more pending tasks. pub fn run_until_parked(&self) { self.cx.background_executor.run_until_parked(); } + /// Dispatch the action to the currently focused node. pub fn dispatch_action(&mut self, action: A) where A: Action, @@ -538,24 +561,32 @@ impl<'a> VisualTestContext { self.cx.dispatch_action(self.window, action) } + /// Read the title off the window (set by `WindowContext#set_window_title`) pub fn window_title(&mut self) -> Option { self.cx.test_window(self.window).0.lock().title.clone() } + /// Simulate a sequence of keystrokes `cx.simulate_keystrokes("cmd-p escape")` + /// Automatically runs until parked. pub fn simulate_keystrokes(&mut self, keystrokes: &str) { self.cx.simulate_keystrokes(self.window, keystrokes) } + /// Simulate typing text `cx.simulate_input("hello")` + /// Automatically runs until parked. pub fn simulate_input(&mut self, input: &str) { self.cx.simulate_input(self.window, input) } + /// Simulates the user blurring the window. pub fn deactivate_window(&mut self) { if Some(self.window) == self.test_platform.active_window() { self.test_platform.set_active_window(None) } self.background_executor.run_until_parked(); } + + /// Simulates the user closing the window. /// Returns true if the window was closed. pub fn simulate_close(&mut self) -> bool { let handler = self diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index dd567a83f6baee9ae65b57def9d7576b84bad89f..9b657a3f71c8fe226008d4ed56b787f17bc65a8c 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -238,6 +238,7 @@ impl Drop for FocusHandle { /// FocusableView allows users of your view to easily /// focus it (using cx.focus_view(view)) pub trait FocusableView: 'static + Render { + /// Returns the focus handle associated with this view. fn focus_handle(&self, cx: &AppContext) -> FocusHandle; } @@ -251,6 +252,7 @@ impl> ManagedView for M {} pub struct DismissEvent; // Holds the state for a specific window. +#[doc(hidden)] pub struct Window { pub(crate) handle: AnyWindowHandle, pub(crate) removed: bool, @@ -442,6 +444,7 @@ impl Window { #[derive(Clone, Debug, Default, PartialEq, Eq)] #[repr(C)] pub struct ContentMask { + /// The bounds pub bounds: Bounds

, } @@ -1788,7 +1791,7 @@ impl<'a> WindowContext<'a> { .available_actions(node_id) } - /// Returns any key bindings that invoke the given action. + /// Returns key bindings that invoke the given action on the currently focused element. pub fn bindings_for_action(&self, action: &dyn Action) -> Vec { self.window .rendered_frame @@ -1799,6 +1802,7 @@ impl<'a> WindowContext<'a> { ) } + /// Returns any bindings that would invoke the given action on the given focus handle if it were focused. pub fn bindings_for_action_in( &self, action: &dyn Action, @@ -1817,6 +1821,7 @@ impl<'a> WindowContext<'a> { dispatch_tree.bindings_for_action(action, &context_stack) } + /// Returns a generic event listener that invokes the given listener with the view and context associated with the given view handle. pub fn listener_for( &self, view: &View, @@ -1828,6 +1833,7 @@ impl<'a> WindowContext<'a> { } } + /// Returns a generic handler that invokes the given handler with the view and context associated with the given view handle. pub fn handler_for( &self, view: &View, @@ -1839,7 +1845,8 @@ impl<'a> WindowContext<'a> { } } - //========== ELEMENT RELATED FUNCTIONS =========== + /// Invoke the given function with the given focus handle present on the key dispatch stack. + /// If you want an element to participate in key dispatch, use this method to push its key context and focus handle into the stack during paint. pub fn with_key_dispatch( &mut self, context: Option, @@ -1878,6 +1885,8 @@ impl<'a> WindowContext<'a> { } } + /// Register a callback that can interrupt the closing of the current window based the returned boolean. + /// If the callback returns false, the window won't be closed. pub fn on_window_should_close(&mut self, f: impl Fn(&mut WindowContext) -> bool + 'static) { let mut this = self.to_async(); self.window @@ -2052,19 +2061,24 @@ impl<'a> BorrowMut for WindowContext<'a> { } } +/// This trait contains functionality that is shared across [ViewContext] and [WindowContext] pub trait BorrowWindow: BorrowMut + BorrowMut { + #[doc(hidden)] fn app_mut(&mut self) -> &mut AppContext { self.borrow_mut() } + #[doc(hidden)] fn app(&self) -> &AppContext { self.borrow() } + #[doc(hidden)] fn window(&self) -> &Window { self.borrow() } + #[doc(hidden)] fn window_mut(&mut self) -> &mut Window { self.borrow_mut() } @@ -3111,10 +3125,15 @@ impl AnyWindowHandle { /// as other internal representations. #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub enum ElementId { + /// The id of a View element View(EntityId), + /// An integer id Integer(usize), + /// A string based id Name(SharedString), + /// An id that's equated with a focus handle FocusHandle(FocusId), + /// A combination of a name and an integer NamedInteger(SharedString, usize), } From 324fd24709b3b004d4fe3c821f0ad4b1a4723393 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Tue, 9 Jan 2024 13:39:15 -0500 Subject: [PATCH 37/78] Initial button documentation Co-Authored-By: Antonio Scandurra --- crates/ui/src/components/button/button.rs | 169 ++++++++++++++++++++++ 1 file changed, 169 insertions(+) diff --git a/crates/ui/src/components/button/button.rs b/crates/ui/src/components/button/button.rs index 398f8f10e27831a07bdca21f837e1425f396c5e9..d3d3326544046eebf6040a222100097f3cf4758f 100644 --- a/crates/ui/src/components/button/button.rs +++ b/crates/ui/src/components/button/button.rs @@ -7,6 +7,63 @@ use crate::{ use super::button_icon::ButtonIcon; +/// [Button] can be use to create a button with a label and an optional icon. +/// +/// Common buttons: +/// - Label, Icon + Label: [Button] (this component) +/// - Icon only: [IconButton] +/// - Custom: [ButtonLike] +/// +/// To create a more complex button than what the [Button] or [IconButton] components provide, use +/// [ButtonLike] directly. +/// +/// # Examples +/// +/// A button with a label only: +/// +/// ``` +/// Button::new("button_id", "Click me!") +/// .on_click(|event, cx| { +/// // Handle click event +/// }); +/// ``` +/// +/// **A toggleable button**, is typically used in scenarios such as a toolbar, +/// where the button's state indicates whether a feature is enabled or not, or +/// a trigger for a popover menu, where clicking the button toggles the visibility of the menu. +/// +/// ``` +/// Button::new("button_id", "Click me!") +/// .icon(IconName::Check) +/// .selected(some_bool) +/// .on_click(|event, cx| { +/// // Handle click event +/// }); +/// ``` +/// +/// To change the style of the button when it is selected use the [selected_style][Button::selected_style] method. +/// +/// ``` +/// Button::new("button_id", "Click me!") +/// .selected(some_bool) +/// .selected_style(ButtonStyle::Tinted(TintColor::Accent)) +/// .on_click(|event, cx| { +/// // Handle click event +/// }); +/// ``` +/// This will create a button with a blue tinted background when selected. +/// +/// **A full-width button**, is typically used in scenarios such as the bottom of a modal or form, where it occupies the entire width of its container. +/// The button's content, including text and icons, is centered by default. +/// +/// ``` +/// let button = Button::new("button_id", "Click me!") +/// .full_width() +/// .on_click(|event, cx| { +/// // Handle click event +/// }); +/// ``` +/// #[derive(IntoElement)] pub struct Button { base: ButtonLike, @@ -23,6 +80,12 @@ pub struct Button { } impl Button { + /// Creates a new [Button] with a specified identifier and label. + /// + /// This is the primary constructor for a `Button` component. It initializes + /// the button with the provided identifier and label text, setting all other + /// properties to their default values, which can be customized using the + /// builder pattern methods provided by this struct. pub fn new(id: impl Into, label: impl Into) -> Self { Self { base: ButtonLike::new(id), @@ -39,46 +102,55 @@ impl Button { } } + /// Sets the color of the button's label. pub fn color(mut self, label_color: impl Into>) -> Self { self.label_color = label_color.into(); self } + /// Defines the size of the button's label. pub fn label_size(mut self, label_size: impl Into>) -> Self { self.label_size = label_size.into(); self } + /// Sets the label used when the button is in a selected state. pub fn selected_label>(mut self, label: impl Into>) -> Self { self.selected_label = label.into().map(Into::into); self } + /// Assigns an icon to the button. pub fn icon(mut self, icon: impl Into>) -> Self { self.icon = icon.into(); self } + /// Sets the position of the icon relative to the label. pub fn icon_position(mut self, icon_position: impl Into>) -> Self { self.icon_position = icon_position.into(); self } + /// Specifies the size of the button's icon. pub fn icon_size(mut self, icon_size: impl Into>) -> Self { self.icon_size = icon_size.into(); self } + /// Sets the color of the button's icon. pub fn icon_color(mut self, icon_color: impl Into>) -> Self { self.icon_color = icon_color.into(); self } + /// Chooses an icon to display when the button is in a selected state. pub fn selected_icon(mut self, icon: impl Into>) -> Self { self.selected_icon = icon.into(); self } + /// Binds a key combination to the button for keyboard shortcuts. pub fn key_binding(mut self, key_binding: impl Into>) -> Self { self.key_binding = key_binding.into(); self @@ -86,6 +158,22 @@ impl Button { } impl Selectable for Button { + /// Sets the selected state of the button. + /// + /// This method allows the selection state of the button to be specified. + /// It modifies the button's appearance to reflect its selected state. + /// + /// # Examples + /// + /// ``` + /// Button::new("button_id", "Click me!") + /// .selected(true) + /// .on_click(|event, cx| { + /// // Handle click event + /// }); + /// ``` + /// + /// Use [selected_style](Button::selected_style) to change the style of the button when it is selected. fn selected(mut self, selected: bool) -> Self { self.base = self.base.selected(selected); self @@ -93,6 +181,19 @@ impl Selectable for Button { } impl SelectableButton for Button { + /// Sets the style for the button when selected. + /// + /// # Examples + /// + /// ``` + /// Button::new("button_id", "Click me!") + /// .selected(true) + /// .selected_style(ButtonStyle::Tinted(TintColor::Accent)) + /// .on_click(|event, cx| { + /// // Handle click event + /// }); + /// ``` + /// This results in a button with a blue tinted background when selected. fn selected_style(mut self, style: ButtonStyle) -> Self { self.base = self.base.selected_style(style); self @@ -100,6 +201,22 @@ impl SelectableButton for Button { } impl Disableable for Button { + /// Disables the button. + /// + /// This method allows the button to be disabled. When a button is disabled, + /// it doesn't react to user interactions and its appearance is updated to reflect this. + /// + /// # Examples + /// + /// ``` + /// Button::new("button_id", "Click me!") + /// .disabled(true) + /// .on_click(|event, cx| { + /// // Handle click event + /// }); + /// ``` + /// + /// This results in a button that is disabled and does not respond to click events. fn disabled(mut self, disabled: bool) -> Self { self.base = self.base.disabled(disabled); self @@ -107,6 +224,7 @@ impl Disableable for Button { } impl Clickable for Button { + /// Sets the click event handler for the button. fn on_click( mut self, handler: impl Fn(&gpui::ClickEvent, &mut WindowContext) + 'static, @@ -117,11 +235,40 @@ impl Clickable for Button { } impl FixedWidth for Button { + /// Sets a fixed width for the button. + /// + /// This function allows a button to have a fixed width instead of automatically growing or shrinking. + /// Sets a fixed width for the button. + /// + /// # Examples + /// + /// ``` + /// Button::new("button_id", "Click me!") + /// .width(DefiniteLength::Pixels(100)) + /// .on_click(|event, cx| { + /// // Handle click event + /// }); + /// ``` + /// + /// This sets the button's width to be exactly 100 pixels. fn width(mut self, width: DefiniteLength) -> Self { self.base = self.base.width(width); self } + /// Sets the button to occupy the full width of its container. + /// + /// # Examples + /// + /// ``` + /// Button::new("button_id", "Click me!") + /// .full_width() + /// .on_click(|event, cx| { + /// // Handle click event + /// }); + /// ``` + /// + /// This stretches the button to the full width of its container. fn full_width(mut self) -> Self { self.base = self.base.full_width(); self @@ -129,20 +276,42 @@ impl FixedWidth for Button { } impl ButtonCommon for Button { + /// Sets the button's id. fn id(&self) -> &ElementId { self.base.id() } + /// Sets the visual style of the button using a [ButtonStyle]. fn style(mut self, style: ButtonStyle) -> Self { self.base = self.base.style(style); self } + /// Sets the button's size using a [ButtonSize]. fn size(mut self, size: ButtonSize) -> Self { self.base = self.base.size(size); self } + /// Sets a tooltip for the button. + /// + /// This method allows a tooltip to be set for the button. The tooltip is a function that + /// takes a mutable reference to a [WindowContext] and returns an [AnyView]. The tooltip + /// is displayed when the user hovers over the button. + /// + /// # Examples + /// + /// ``` + /// Button::new("button_id", "Click me!") + /// .tooltip(|cx| { + /// Text::new("This is a tooltip").into() + /// }) + /// .on_click(|event, cx| { + /// // Handle click event + /// }); + /// ``` + /// + /// This will create a button with a tooltip that displays "This is a tooltip" when hovered over. fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self { self.base = self.base.tooltip(tooltip); self From 7dbe0519ece2b472ef3ebeee348bb2bd268300a9 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Tue, 9 Jan 2024 13:49:27 -0500 Subject: [PATCH 38/78] Add label docs --- crates/ui/src/components/button/button.rs | 5 +- crates/ui/src/components/label/label.rs | 58 ++++++++++++++++++++++- 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/crates/ui/src/components/button/button.rs b/crates/ui/src/components/button/button.rs index d3d3326544046eebf6040a222100097f3cf4758f..b65bf050eb87c3055c1c4eb923ff2c9f2b9c4ee9 100644 --- a/crates/ui/src/components/button/button.rs +++ b/crates/ui/src/components/button/button.rs @@ -7,7 +7,7 @@ use crate::{ use super::button_icon::ButtonIcon; -/// [Button] can be use to create a button with a label and an optional icon. +/// An element that creates a button with a label and an optional icon. /// /// Common buttons: /// - Label, Icon + Label: [Button] (this component) @@ -19,7 +19,8 @@ use super::button_icon::ButtonIcon; /// /// # Examples /// -/// A button with a label only: +/// **A button with a label**, is typically used in scenarios such as a form, where the button's label +/// indicates what action will be performed when the button is clicked. /// /// ``` /// Button::new("button_id", "Click me!") diff --git a/crates/ui/src/components/label/label.rs b/crates/ui/src/components/label/label.rs index 61f463a531b85dc9a2780a334c8a771ab5c9f40b..7f092883ac6ae48f9eb0724783a3434ad16b14e0 100644 --- a/crates/ui/src/components/label/label.rs +++ b/crates/ui/src/components/label/label.rs @@ -2,6 +2,28 @@ use gpui::WindowContext; use crate::{prelude::*, LabelCommon, LabelLike, LabelSize, LineHeightStyle}; +/// A struct representing a label element in the UI. +/// +/// The `Label` struct stores the label text and common properties for a label element. +/// It provides methods for modifying these properties. +/// +/// # Examples +/// +/// ``` +/// Label::new("Hello, World!") +/// ``` +/// +/// **A colored label**, for example labeling a dangerous action: +/// +/// ``` +/// let my_label = Label::new("Delete").color(Color::Error); +/// ``` +/// +/// **A label with a strikethrough**, for example labeling something that has been deleted: +/// +/// ``` +/// let my_label = Label::new("Deleted").strikethrough(true); +/// ``` #[derive(IntoElement)] pub struct Label { base: LabelLike, @@ -9,6 +31,13 @@ pub struct Label { } impl Label { + /// Create a new `Label` with the given text. + /// + /// # Examples + /// + /// ``` + /// let my_label = Label::new("Hello, World!"); + /// ``` pub fn new(label: impl Into) -> Self { Self { base: LabelLike::new(), @@ -18,26 +47,53 @@ impl Label { } impl LabelCommon for Label { + /// Sets the size of the label using a [LabelSize]. + /// + /// # Examples + /// + /// ``` + /// let my_label = Label::new("Hello, World!").size(LabelSize::Large); + /// ``` fn size(mut self, size: LabelSize) -> Self { self.base = self.base.size(size); self } + /// Sets the line height style of the label using a [LineHeightStyle]. + /// + /// # Examples + /// + /// ``` + /// let my_label = Label::new("Hello, World!").line_height_style(LineHeightStyle::Normal); + /// ``` fn line_height_style(mut self, line_height_style: LineHeightStyle) -> Self { self.base = self.base.line_height_style(line_height_style); self } + /// Sets the color of the label using a [Color]. + /// + /// # Examples + /// + /// ``` + /// let my_label = Label::new("Hello, World!").color(Color::Primary); + /// ``` fn color(mut self, color: Color) -> Self { self.base = self.base.color(color); self } + /// Sets the strikethrough property of the label. + /// + /// # Examples + /// + /// ``` + /// let my_label = Label::new("Hello, World!").strikethrough(true); + /// ``` fn strikethrough(mut self, strikethrough: bool) -> Self { self.base = self.base.strikethrough(strikethrough); self } -} impl RenderOnce for Label { fn render(self, _cx: &mut WindowContext) -> impl IntoElement { From 46065c26210c19f9213edee8b736045c41df2142 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Tue, 9 Jan 2024 13:52:11 -0500 Subject: [PATCH 39/78] Fix unclosed delimiter --- crates/ui/src/components/label/label.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/ui/src/components/label/label.rs b/crates/ui/src/components/label/label.rs index 7f092883ac6ae48f9eb0724783a3434ad16b14e0..8cc43bc99374432188a456e5fb2b6af12d2eb092 100644 --- a/crates/ui/src/components/label/label.rs +++ b/crates/ui/src/components/label/label.rs @@ -94,6 +94,7 @@ impl LabelCommon for Label { self.base = self.base.strikethrough(strikethrough); self } +} impl RenderOnce for Label { fn render(self, _cx: &mut WindowContext) -> impl IntoElement { From 42cbd103fb91f80a66fdbee1166f72295541b0e1 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 9 Jan 2024 11:46:43 -0700 Subject: [PATCH 40/78] Even more docs Co-authored-by: Conrad --- crates/editor/src/display_map.rs | 1 - crates/gpui/src/app.rs | 37 ++++++++++++++++------- crates/gpui/src/app/async_context.rs | 6 ++++ crates/gpui/src/app/test_context.rs | 26 ++++++++++++---- crates/gpui/src/platform/test/platform.rs | 1 + crates/gpui_macros/src/gpui_macros.rs | 30 ++++++++++++++++++ 6 files changed, 83 insertions(+), 18 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 4511ffe407849162b603c4b3a44d51e8d552c1c9..4f2d5179dbd08fedd38f46ea23f919d6c30147c8 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -1015,7 +1015,6 @@ pub mod tests { .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) .unwrap_or(10); - let _test_platform = &cx.test_platform; let mut tab_size = rng.gen_range(1..=4); let buffer_start_excerpt_header_height = rng.gen_range(1..=5); let excerpt_header_height = rng.gen_range(1..=5); diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 638396abc51e97f58a93fabec816bd8d48a50135..f84ae809d34b14712495de95043e68ae28fe88a3 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -45,11 +45,13 @@ use util::{ /// Temporary(?) wrapper around [`RefCell`] to help us debug any double borrows. /// Strongly consider removing after stabilization. +#[doc(hidden)] pub struct AppCell { app: RefCell, } impl AppCell { + #[doc(hidden)] #[track_caller] pub fn borrow(&self) -> AppRef { if option_env!("TRACK_THREAD_BORROWS").is_some() { @@ -59,6 +61,7 @@ impl AppCell { AppRef(self.app.borrow()) } + #[doc(hidden)] #[track_caller] pub fn borrow_mut(&self) -> AppRefMut { if option_env!("TRACK_THREAD_BORROWS").is_some() { @@ -69,6 +72,7 @@ impl AppCell { } } +#[doc(hidden)] #[derive(Deref, DerefMut)] pub struct AppRef<'a>(Ref<'a, AppContext>); @@ -81,6 +85,7 @@ impl<'a> Drop for AppRef<'a> { } } +#[doc(hidden)] #[derive(Deref, DerefMut)] pub struct AppRefMut<'a>(RefMut<'a, AppContext>); @@ -93,6 +98,8 @@ impl<'a> Drop for AppRefMut<'a> { } } +/// A reference to a GPUI application, typically constructed in the `main` function of your app. +/// You won't interact with this type much outside of initial configuration and startup. pub struct App(Rc); /// Represents an application before it is fully launched. Once your app is @@ -136,6 +143,8 @@ impl App { self } + /// Invokes a handler when an already-running application is launched. + /// On macOS, this can occur when the application icon is double-clicked or the app is launched via the dock. pub fn on_reopen(&self, mut callback: F) -> &Self where F: 'static + FnMut(&mut AppContext), @@ -149,18 +158,22 @@ impl App { self } + /// Returns metadata associated with the application pub fn metadata(&self) -> AppMetadata { self.0.borrow().app_metadata.clone() } + /// Returns a handle to the [BackgroundExecutor] associated with this app, which can be used to spawn futures in the background. pub fn background_executor(&self) -> BackgroundExecutor { self.0.borrow().background_executor.clone() } + /// Returns a handle to the [ForegroundExecutor] associated with this app, which can be used to spawn futures in the foreground. pub fn foreground_executor(&self) -> ForegroundExecutor { self.0.borrow().foreground_executor.clone() } + /// Returns a reference to the [TextSystem] associated with this app. pub fn text_system(&self) -> Arc { self.0.borrow().text_system.clone() } @@ -174,12 +187,6 @@ type QuitHandler = Box LocalBoxFuture<'static, () type ReleaseListener = Box; type NewViewListener = Box; -// struct FrameConsumer { -// next_frame_callbacks: Vec, -// task: Task<()>, -// display_linker -// } - pub struct AppContext { pub(crate) this: Weak, pub(crate) platform: Rc, @@ -314,10 +321,12 @@ impl AppContext { } } + /// Gracefully quit the application via the platform's standard routine. pub fn quit(&mut self) { self.platform.quit(); } + /// Get metadata about the app and platform. pub fn app_metadata(&self) -> AppMetadata { self.app_metadata.clone() } @@ -340,6 +349,7 @@ impl AppContext { result } + /// Arrange a callback to be invoked when the given model or view calls `notify` on its respective context. pub fn observe( &mut self, entity: &E, @@ -355,7 +365,7 @@ impl AppContext { }) } - pub fn observe_internal( + pub(crate) fn observe_internal( &mut self, entity: &E, mut on_notify: impl FnMut(E, &mut AppContext) -> bool + 'static, @@ -380,15 +390,17 @@ impl AppContext { subscription } - pub fn subscribe( + /// Arrange for the given callback to be invoked whenever the given model or view emits an event of a given type. + /// The callback is provided a handle to the emitting entity and a reference to the emitted event. + pub fn subscribe( &mut self, entity: &E, - mut on_event: impl FnMut(E, &Evt, &mut AppContext) + 'static, + mut on_event: impl FnMut(E, &Event, &mut AppContext) + 'static, ) -> Subscription where - T: 'static + EventEmitter, + T: 'static + EventEmitter, E: Entity, - Evt: 'static, + Event: 'static, { self.subscribe_internal(entity, move |entity, event, cx| { on_event(entity, event, cx); @@ -426,6 +438,9 @@ impl AppContext { subscription } + /// Returns handles to all open windows in the application. + /// Each handle could be downcast to a handle typed for the root view of that window. + /// To find all windows of a given type, you could filter on pub fn windows(&self) -> Vec { self.windows .values() diff --git a/crates/gpui/src/app/async_context.rs b/crates/gpui/src/app/async_context.rs index 475ef76ef25cec97cc75b30953f9ad30ce87a5cd..6afb356e5e63c6431fe0858b4e1c4fd97159c0b2 100644 --- a/crates/gpui/src/app/async_context.rs +++ b/crates/gpui/src/app/async_context.rs @@ -82,6 +82,7 @@ impl Context for AsyncAppContext { } impl AsyncAppContext { + /// Schedules all windows in the application to be redrawn. pub fn refresh(&mut self) -> Result<()> { let app = self .app @@ -92,14 +93,17 @@ impl AsyncAppContext { Ok(()) } + /// Get an executor which can be used to spawn futures in the background. pub fn background_executor(&self) -> &BackgroundExecutor { &self.background_executor } + /// Get an executor which can be used to spawn futures in the foreground. pub fn foreground_executor(&self) -> &ForegroundExecutor { &self.foreground_executor } + /// Invoke the given function in the context of the app, then flush any effects produced during its invocation. pub fn update(&self, f: impl FnOnce(&mut AppContext) -> R) -> Result { let app = self .app @@ -109,6 +113,7 @@ impl AsyncAppContext { Ok(f(&mut lock)) } + /// Open a window with the given options based on the root view returned by the given function. pub fn open_window( &self, options: crate::WindowOptions, @@ -125,6 +130,7 @@ impl AsyncAppContext { Ok(lock.open_window(options, build_root_view)) } + /// Schedule a future to be polled in the background. pub fn spawn(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task where Fut: Future + 'static, diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index 7c95f75d96cf1a1456971b122f9807783a80785c..de31339b8d79bb3b4a80d4e8c9bc95834ffef92c 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -15,11 +15,15 @@ use std::{future::Future, ops::Deref, rc::Rc, sync::Arc, time::Duration}; /// an implementation of `Context` with additional methods that are useful in tests. #[derive(Clone)] pub struct TestAppContext { + #[doc(hidden)] pub app: Rc, + #[doc(hidden)] pub background_executor: BackgroundExecutor, + #[doc(hidden)] pub foreground_executor: ForegroundExecutor, + #[doc(hidden)] pub dispatcher: TestDispatcher, - pub test_platform: Rc, + test_platform: Rc, text_system: Arc, } @@ -80,6 +84,7 @@ impl Context for TestAppContext { } impl TestAppContext { + /// Creates a new `TestAppContext`. Usually you can rely on `#[gpui::test]` to do this for you. pub fn new(dispatcher: TestDispatcher) -> Self { let arc_dispatcher = Arc::new(dispatcher.clone()); let background_executor = BackgroundExecutor::new(arc_dispatcher.clone()); @@ -138,8 +143,8 @@ impl TestAppContext { f(&*cx) } - // Adds a new window. The Window will always be backed by a `TestWindow` which - // can be retrieved with `self.test_window(handle)` + /// Adds a new window. The Window will always be backed by a `TestWindow` which + /// can be retrieved with `self.test_window(handle)` pub fn add_window(&mut self, build_window: F) -> WindowHandle where F: FnOnce(&mut ViewContext) -> V, @@ -149,7 +154,7 @@ impl TestAppContext { cx.open_window(WindowOptions::default(), |cx| cx.new_view(build_window)) } - // Adds a new window with no content. + /// Adds a new window with no content. pub fn add_empty_window(&mut self) -> AnyWindowHandle { let mut cx = self.app.borrow_mut(); cx.open_window(WindowOptions::default(), |cx| cx.new_view(|_| EmptyView {})) @@ -226,16 +231,20 @@ impl TestAppContext { self.foreground_executor.spawn(f(self.to_async())) } + /// true if the given global is defined pub fn has_global(&self) -> bool { let app = self.app.borrow(); app.has_global::() } + /// runs the given closure with a reference to the global + /// panics if `has_global` would return false. pub fn read_global(&self, read: impl FnOnce(&G, &AppContext) -> R) -> R { let app = self.app.borrow(); read(app.global(), &app) } + /// runs the given closure with a reference to the global (if set) pub fn try_read_global( &self, read: impl FnOnce(&G, &AppContext) -> R, @@ -244,11 +253,13 @@ impl TestAppContext { Some(read(lock.try_global()?, &lock)) } + /// sets the global in this context. pub fn set_global(&mut self, global: G) { let mut lock = self.app.borrow_mut(); lock.set_global(global); } + /// updates the global in this context. (panics if `has_global` would return false) pub fn update_global( &mut self, update: impl FnOnce(&mut G, &mut AppContext) -> R, @@ -522,10 +533,10 @@ impl View { } } -/// A VisualTestContext is the test-equivalent of a `WindowContext`. It allows you to -/// run window-specific test code. use derive_more::{Deref, DerefMut}; #[derive(Deref, DerefMut, Clone)] +/// A VisualTestContext is the test-equivalent of a `WindowContext`. It allows you to +/// run window-specific test code. pub struct VisualTestContext { #[deref] #[deref_mut] @@ -534,6 +545,7 @@ pub struct VisualTestContext { } impl<'a> VisualTestContext { + /// Provides the `WindowContext` for the duration of the closure. pub fn update(&mut self, f: impl FnOnce(&mut WindowContext) -> R) -> R { self.cx.update_window(self.window, |_, cx| f(cx)).unwrap() } @@ -723,6 +735,7 @@ impl VisualContext for VisualTestContext { } impl AnyWindowHandle { + /// Creates the given view in this window. pub fn build_view( &self, cx: &mut TestAppContext, @@ -732,6 +745,7 @@ impl AnyWindowHandle { } } +/// An EmptyView for testing. pub struct EmptyView {} impl Render for EmptyView { diff --git a/crates/gpui/src/platform/test/platform.rs b/crates/gpui/src/platform/test/platform.rs index ec3d7a0ff008ee64c92e8ceece0a2ef32cacff14..c4452a593a3747fd10534b75c7f0622479585075 100644 --- a/crates/gpui/src/platform/test/platform.rs +++ b/crates/gpui/src/platform/test/platform.rs @@ -15,6 +15,7 @@ use std::{ time::Duration, }; +/// TestPlatform implements the Platform trait for use in tests. pub struct TestPlatform { background_executor: BackgroundExecutor, foreground_executor: ForegroundExecutor, diff --git a/crates/gpui_macros/src/gpui_macros.rs b/crates/gpui_macros/src/gpui_macros.rs index f0cd59908dc6967d3b00e1b0991d2390b61a59e1..1187d96ca320abbc51b66b24b0639b1211b451dc 100644 --- a/crates/gpui_macros/src/gpui_macros.rs +++ b/crates/gpui_macros/src/gpui_macros.rs @@ -7,26 +7,56 @@ mod test; use proc_macro::TokenStream; #[proc_macro] +/// register_action! can be used to register an action with the GPUI runtime. +/// You should typically use `gpui::actions!` or `gpui::impl_actions!` instead, +/// but this can be used for fine grained customization. pub fn register_action(ident: TokenStream) -> TokenStream { register_action::register_action_macro(ident) } #[proc_macro_derive(IntoElement)] +// #[derive(IntoElement)] is used to create a Component out of anything that implements +// the `RenderOnce` trait. pub fn derive_into_element(input: TokenStream) -> TokenStream { derive_into_element::derive_into_element(input) } #[proc_macro_derive(Render)] +#[doc(hidden)] pub fn derive_render(input: TokenStream) -> TokenStream { derive_render::derive_render(input) } +// Used by gpui to generate the style helpers. #[proc_macro] +#[doc(hidden)] pub fn style_helpers(input: TokenStream) -> TokenStream { style_helpers::style_helpers(input) } #[proc_macro_attribute] +/// #[gpui::test] can be used to annotate test functions that run with GPUI support. +/// it supports both synchronous and asynchronous tests, and can provide you with +/// as many `TestAppContext` instances as you need. +/// The output contains a `#[test]` annotation so this can be used with any existing +/// test harness (`cargo test` or `cargo-nextest`). +/// +/// ``` +/// #[gpui::test] +/// async fn test_foo(mut cx: &TestAppContext) { } +/// ``` +/// +/// In addition to passing a TestAppContext, you can also ask for a `StdRnd` instance. +/// this will be seeded with the `SEED` environment variable and is used internally by +/// the ForegroundExecutor and BackgroundExecutor to run tasks deterministically in tests. +/// Using the same `StdRng` for behaviour in your test will allow you to exercise a wide +/// variety of scenarios and interleavings just by changing the seed. +/// +/// #[gpui::test] also takes three different arguments: +/// - `#[gpui::test(interations=10)]` will run the test ten times with a different initial SEED. +/// - `#[gpui::test(retries=3)]` will run the test up to four times if it fails to try and make it pass. +/// - `#[gpui::test(on_failure="crate::test::report_failure")]` will call the specified function after the +/// tests fail so that you can write out more detail about the failure. pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { test::test(args, function) } From e020d7ca110cd361af30a59225bde5f4b10be3d8 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Tue, 9 Jan 2024 14:15:25 -0500 Subject: [PATCH 41/78] Document ui crate traits --- crates/ui/src/clickable.rs | 2 +- crates/ui/src/components/button/button_like.rs | 2 ++ crates/ui/src/components/label/label_like.rs | 1 + crates/ui/src/disableable.rs | 2 +- crates/ui/src/fixed.rs | 2 +- crates/ui/src/selectable.rs | 9 ++++++++- crates/ui/src/styled_ext.rs | 8 +++++++- 7 files changed, 21 insertions(+), 5 deletions(-) diff --git a/crates/ui/src/clickable.rs b/crates/ui/src/clickable.rs index 44f40b4cd4ccb98244a369b70639e3a768f4ddfa..462d2c60998a993527fc5dab24d1b0a2ed260d86 100644 --- a/crates/ui/src/clickable.rs +++ b/crates/ui/src/clickable.rs @@ -1,6 +1,6 @@ use gpui::{ClickEvent, WindowContext}; -/// A trait for elements that can be clicked. +/// A trait for elements that can be clicked. Enables the use of the `on_click` method. pub trait Clickable { /// Sets the click handler that will fire whenever the element is clicked. fn on_click(self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self; diff --git a/crates/ui/src/components/button/button_like.rs b/crates/ui/src/components/button/button_like.rs index 431286073fb33b263fd08c27ba1c07e44fe82c01..d6ecad41fc657860ba31622810e8459cb4a72072 100644 --- a/crates/ui/src/components/button/button_like.rs +++ b/crates/ui/src/components/button/button_like.rs @@ -4,10 +4,12 @@ use smallvec::SmallVec; use crate::prelude::*; +/// A trait for buttons that can be Selected. Enables setting the [ButtonStyle] of a button when it is selected. pub trait SelectableButton: Selectable { fn selected_style(self, style: ButtonStyle) -> Self; } +/// A common set of traits all buttons must implement. pub trait ButtonCommon: Clickable + Disableable { /// A unique element ID to identify the button. fn id(&self) -> &ElementId; diff --git a/crates/ui/src/components/label/label_like.rs b/crates/ui/src/components/label/label_like.rs index 436461fddeaffa9b6d87af0de11b11f48cafec68..d7bd30187d6d816b01e0192a10845fa53401fe62 100644 --- a/crates/ui/src/components/label/label_like.rs +++ b/crates/ui/src/components/label/label_like.rs @@ -19,6 +19,7 @@ pub enum LineHeightStyle { UiLabel, } +/// A common set of traits all labels must implement. pub trait LabelCommon { fn size(self, size: LabelSize) -> Self; fn line_height_style(self, line_height_style: LineHeightStyle) -> Self; diff --git a/crates/ui/src/disableable.rs b/crates/ui/src/disableable.rs index f9b4e5ba910be1e65b6f2b106e9a45b707f20744..ebd34fba1cf8289a830b56516e027c7eabaf8f12 100644 --- a/crates/ui/src/disableable.rs +++ b/crates/ui/src/disableable.rs @@ -1,4 +1,4 @@ -/// A trait for elements that can be disabled. +/// A trait for elements that can be disabled. Generally used to implement disabling an element's interactivity and changing it's appearance to reflect that it is disabled. pub trait Disableable { /// Sets whether the element is disabled. fn disabled(self, disabled: bool) -> Self; diff --git a/crates/ui/src/fixed.rs b/crates/ui/src/fixed.rs index a2c3ed3edc819ed9a4bc4d4a33ff3b9494c11075..9ba64da0905310d2e3409e0c13d110cb81cc5e41 100644 --- a/crates/ui/src/fixed.rs +++ b/crates/ui/src/fixed.rs @@ -1,6 +1,6 @@ use gpui::DefiniteLength; -/// A trait for elements that have a fixed with. +/// A trait for elements that can have a fixed with. Enables the use of the `width` and `full_width` methods. pub trait FixedWidth { /// Sets the width of the element. fn width(self, width: DefiniteLength) -> Self; diff --git a/crates/ui/src/selectable.rs b/crates/ui/src/selectable.rs index 34c66ab1fa4a4a961ee972588b9e3c7cf211308b..e5e60008b9194d129fb03523ead8e3d58c804681 100644 --- a/crates/ui/src/selectable.rs +++ b/crates/ui/src/selectable.rs @@ -1,18 +1,25 @@ -/// A trait for elements that can be selected. +/// A trait for elements that can be selected. Generally used to enable "toggle" or "active" behavior and styles on an element through the [Selection] status. pub trait Selectable { /// Sets whether the element is selected. fn selected(self, selected: bool) -> Self; } +/// Represents the selection status of an element. #[derive(Debug, Default, PartialEq, Eq, Hash, Clone, Copy)] pub enum Selection { + /// The element is not selected. #[default] Unselected, + /// The selection state of the element is indeterminate. Indeterminate, + /// The element is selected. Selected, } impl Selection { + /// Returns the inverse of the current selection status. + /// + /// Indeterminate states become selected if inverted. pub fn inverse(&self) -> Self { match self { Self::Unselected | Self::Indeterminate => Self::Selected, diff --git a/crates/ui/src/styled_ext.rs b/crates/ui/src/styled_ext.rs index a6381e604f5f672553a2b502a6e4814eee5f384d..8c5da763f360621a5a4a1d77ada46c56a6a05992 100644 --- a/crates/ui/src/styled_ext.rs +++ b/crates/ui/src/styled_ext.rs @@ -14,7 +14,7 @@ fn elevated(this: E, cx: &mut WindowContext, index: ElevationIndex) - .shadow(index.shadow()) } -/// Extends [`Styled`](gpui::Styled) with Zed specific styling methods. +/// Extends [gpui::Styled] with Zed specific styling methods. pub trait StyledExt: Styled + Sized { /// Horizontally stacks elements. /// @@ -119,26 +119,32 @@ pub trait StyledExt: Styled + Sized { self.border_color(cx.theme().colors().border_variant) } + /// Sets the background color to red for debugging when building UI. fn debug_bg_red(self) -> Self { self.bg(hsla(0. / 360., 1., 0.5, 1.)) } + /// Sets the background color to green for debugging when building UI. fn debug_bg_green(self) -> Self { self.bg(hsla(120. / 360., 1., 0.5, 1.)) } + /// Sets the background color to blue for debugging when building UI. fn debug_bg_blue(self) -> Self { self.bg(hsla(240. / 360., 1., 0.5, 1.)) } + /// Sets the background color to yellow for debugging when building UI. fn debug_bg_yellow(self) -> Self { self.bg(hsla(60. / 360., 1., 0.5, 1.)) } + /// Sets the background color to cyan for debugging when building UI. fn debug_bg_cyan(self) -> Self { self.bg(hsla(160. / 360., 1., 0.5, 1.)) } + /// Sets the background color to magenta for debugging when building UI. fn debug_bg_magenta(self) -> Self { self.bg(hsla(300. / 360., 1., 0.5, 1.)) } From cb24062e79fe18b8a1f18f6e15e16956c2360d04 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Tue, 9 Jan 2024 14:16:50 -0500 Subject: [PATCH 42/78] Restore removed ColorScaleStep ALL that was breaking the build --- crates/theme/src/scale.rs | 40 +++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/crates/theme/src/scale.rs b/crates/theme/src/scale.rs index 8bfb2999cda97f2da6d4dac816a85f51f3b564e6..1f562f16f06dd4f86be7267ef6ceca030c4dc65c 100644 --- a/crates/theme/src/scale.rs +++ b/crates/theme/src/scale.rs @@ -9,18 +9,34 @@ use crate::{ActiveTheme, Appearance}; pub struct ColorScaleStep(usize); impl ColorScaleStep { - const ONE: Self = Self(1); - const TWO: Self = Self(2); - const THREE: Self = Self(3); - const FOUR: Self = Self(4); - const FIVE: Self = Self(5); - const SIX: Self = Self(6); - const SEVEN: Self = Self(7); - const EIGHT: Self = Self(8); - const NINE: Self = Self(9); - const TEN: Self = Self(10); - const ELEVEN: Self = Self(11); - const TWELVE: Self = Self(12); + pub const ONE: Self = Self(1); + pub const TWO: Self = Self(2); + pub const THREE: Self = Self(3); + pub const FOUR: Self = Self(4); + pub const FIVE: Self = Self(5); + pub const SIX: Self = Self(6); + pub const SEVEN: Self = Self(7); + pub const EIGHT: Self = Self(8); + pub const NINE: Self = Self(9); + pub const TEN: Self = Self(10); + pub const ELEVEN: Self = Self(11); + pub const TWELVE: Self = Self(12); + + /// All of the steps in a [`ColorScale`]. + pub const ALL: [ColorScaleStep; 12] = [ + Self::ONE, + Self::TWO, + Self::THREE, + Self::FOUR, + Self::FIVE, + Self::SIX, + Self::SEVEN, + Self::EIGHT, + Self::NINE, + Self::TEN, + Self::ELEVEN, + Self::TWELVE, + ]; } /// A scale of colors for a given [ColorScaleSet]. From c19551d9746522019e5364ea5a5894fbfe4cf388 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Tue, 9 Jan 2024 14:21:29 -0500 Subject: [PATCH 43/78] Document free functions --- crates/ui/src/components/popover_menu.rs | 1 + crates/ui/src/components/right_click_menu.rs | 1 + crates/ui/src/components/stack.rs | 8 ++------ 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/ui/src/components/popover_menu.rs b/crates/ui/src/components/popover_menu.rs index fb823b05dba3d18cea7992948abb50f827167e1c..6bfc88bbec3ef292d1163717b0f1aaad7ec2c2fb 100644 --- a/crates/ui/src/components/popover_menu.rs +++ b/crates/ui/src/components/popover_menu.rs @@ -108,6 +108,7 @@ impl PopoverMenu { } } +/// Creates a [PopoverMenu] pub fn popover_menu(id: impl Into) -> PopoverMenu { PopoverMenu { id: id.into(), diff --git a/crates/ui/src/components/right_click_menu.rs b/crates/ui/src/components/right_click_menu.rs index 8bf40f61a8264ec3b00eec3ec844efa247876da7..282884c948b757b9ba305e8ee5532e7d5732796e 100644 --- a/crates/ui/src/components/right_click_menu.rs +++ b/crates/ui/src/components/right_click_menu.rs @@ -39,6 +39,7 @@ impl RightClickMenu { } } +/// Creates a [RightClickMenu] pub fn right_click_menu(id: impl Into) -> RightClickMenu { RightClickMenu { id: id.into(), diff --git a/crates/ui/src/components/stack.rs b/crates/ui/src/components/stack.rs index a6321b93d7c9e15afa1d3053fa61fb467acdbeb0..76f08de911e8e1e60a67c1d9311355f62d55914b 100644 --- a/crates/ui/src/components/stack.rs +++ b/crates/ui/src/components/stack.rs @@ -2,17 +2,13 @@ use gpui::{div, Div}; use crate::StyledExt; -/// Horizontally stacks elements. -/// -/// Sets `flex()`, `flex_row()`, `items_center()` +/// Horizontally stacks elements. Sets `flex()`, `flex_row()`, `items_center()` #[track_caller] pub fn h_stack() -> Div { div().h_flex() } -/// Vertically stacks elements. -/// -/// Sets `flex()`, `flex_col()` +/// Vertically stacks elements. Sets `flex()`, `flex_col()` #[track_caller] pub fn v_stack() -> Div { div().v_flex() From 463270ed36e4d24ce8ded0a30a06b889ee128939 Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 9 Jan 2024 14:32:43 -0500 Subject: [PATCH 44/78] Take into account multiple scroll deltas within a single frame Co-Authored-By: Antonio Scandurra --- crates/editor/src/element.rs | 101 ++++++++++++++++++--------------- crates/gpui/src/interactive.rs | 14 +++++ 2 files changed, 69 insertions(+), 46 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index c7fbb658a3cf106e1ee7cc6212cf67a18481c84c..fc3c53f34343684ed5d39305d1c30d4fe7872881 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -28,7 +28,7 @@ use gpui::{ AnchorCorner, AnyElement, AvailableSpace, BorrowWindow, Bounds, ContentMask, Corners, CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Hsla, InteractiveBounds, InteractiveElement, IntoElement, ModifiersChangedEvent, MouseButton, MouseDownEvent, - MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, ScrollWheelEvent, ShapedLine, + MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size, StackingOrder, StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, View, ViewContext, WindowContext, }; @@ -581,41 +581,6 @@ impl EditorElement { } } - fn scroll( - editor: &mut Editor, - event: &ScrollWheelEvent, - position_map: &PositionMap, - bounds: &InteractiveBounds, - cx: &mut ViewContext, - ) { - if !bounds.visibly_contains(&event.position, cx) { - return; - } - - let line_height = position_map.line_height; - let max_glyph_width = position_map.em_width; - let (delta, axis) = match event.delta { - gpui::ScrollDelta::Pixels(mut pixels) => { - //Trackpad - let axis = position_map.snapshot.ongoing_scroll.filter(&mut pixels); - (pixels, axis) - } - - gpui::ScrollDelta::Lines(lines) => { - //Not trackpad - let pixels = point(lines.x * max_glyph_width, lines.y * line_height); - (pixels, None) - } - }; - - let scroll_position = position_map.snapshot.scroll_position(); - let x = f32::from((scroll_position.x * max_glyph_width - delta.x) / max_glyph_width); - let y = f32::from((scroll_position.y * line_height - delta.y) / line_height); - let scroll_position = point(x, y).clamp(&point(0., 0.), &position_map.scroll_max); - editor.scroll(scroll_position, axis, cx); - cx.stop_propagation(); - } - fn paint_background( &self, gutter_bounds: Bounds, @@ -2450,34 +2415,78 @@ impl EditorElement { ) } - fn paint_mouse_listeners( + fn paint_scroll_wheel_listener( &mut self, - bounds: Bounds, - gutter_bounds: Bounds, - text_bounds: Bounds, + interactive_bounds: &InteractiveBounds, layout: &LayoutState, cx: &mut WindowContext, ) { - let interactive_bounds = InteractiveBounds { - bounds: bounds.intersect(&cx.content_mask().bounds), - stacking_order: cx.stacking_order().clone(), - }; - cx.on_mouse_event({ let position_map = layout.position_map.clone(); let editor = self.editor.clone(); let interactive_bounds = interactive_bounds.clone(); + let mut delta = ScrollDelta::default(); move |event: &ScrollWheelEvent, phase, cx| { if phase == DispatchPhase::Bubble && interactive_bounds.visibly_contains(&event.position, cx) { + delta = delta.coalesce(event.delta); editor.update(cx, |editor, cx| { - Self::scroll(editor, event, &position_map, &interactive_bounds, cx) + let position = event.position; + let position_map: &PositionMap = &position_map; + let bounds = &interactive_bounds; + if !bounds.visibly_contains(&position, cx) { + return; + } + + let line_height = position_map.line_height; + let max_glyph_width = position_map.em_width; + let (delta, axis) = match delta { + gpui::ScrollDelta::Pixels(mut pixels) => { + //Trackpad + let axis = position_map.snapshot.ongoing_scroll.filter(&mut pixels); + (pixels, axis) + } + + gpui::ScrollDelta::Lines(lines) => { + //Not trackpad + let pixels = + point(lines.x * max_glyph_width, lines.y * line_height); + (pixels, None) + } + }; + + let scroll_position = position_map.snapshot.scroll_position(); + let x = f32::from( + (scroll_position.x * max_glyph_width - delta.x) / max_glyph_width, + ); + let y = + f32::from((scroll_position.y * line_height - delta.y) / line_height); + let scroll_position = + point(x, y).clamp(&point(0., 0.), &position_map.scroll_max); + editor.scroll(scroll_position, axis, cx); + cx.stop_propagation(); }); } } }); + } + + fn paint_mouse_listeners( + &mut self, + bounds: Bounds, + gutter_bounds: Bounds, + text_bounds: Bounds, + layout: &LayoutState, + cx: &mut WindowContext, + ) { + let interactive_bounds = InteractiveBounds { + bounds: bounds.intersect(&cx.content_mask().bounds), + stacking_order: cx.stacking_order().clone(), + }; + + self.paint_scroll_wheel_listener(&interactive_bounds, layout, cx); cx.on_mouse_event({ let position_map = layout.position_map.clone(); diff --git a/crates/gpui/src/interactive.rs b/crates/gpui/src/interactive.rs index 6f396d31aa481571d3331e816f30dbf788d47816..dfccfc35307f1eb2e75e2d7e8fe8eb73b2c4b7ef 100644 --- a/crates/gpui/src/interactive.rs +++ b/crates/gpui/src/interactive.rs @@ -178,6 +178,20 @@ impl ScrollDelta { ScrollDelta::Lines(delta) => point(line_height * delta.x, line_height * delta.y), } } + + pub fn coalesce(self, other: ScrollDelta) -> ScrollDelta { + match (self, other) { + (ScrollDelta::Pixels(px_a), ScrollDelta::Pixels(px_b)) => { + ScrollDelta::Pixels(px_a + px_b) + } + + (ScrollDelta::Lines(lines_a), ScrollDelta::Lines(lines_b)) => { + ScrollDelta::Lines(lines_a + lines_b) + } + + _ => other, + } + } } #[derive(Clone, Debug, Default)] From 74dadd68d251e2b9cd93158bad2d1c4c726ac311 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 9 Jan 2024 14:48:48 -0500 Subject: [PATCH 45/78] Clean up references in doc comments (#3983) This PR cleans up a number of references in doc comments so that `rustdoc` will link and display them correctly. Release Notes: - N/A --- crates/gpui/src/app.rs | 10 ++--- crates/gpui/src/element.rs | 10 ++--- crates/gpui/src/executor.rs | 8 ++-- crates/gpui/src/input.rs | 2 +- crates/gpui/src/platform/mac/display.rs | 4 +- crates/gpui/src/subscription.rs | 6 +-- crates/gpui/src/window.rs | 50 ++++++++++++------------- crates/rope/src/rope.rs | 6 +-- crates/text/src/anchor.rs | 2 +- 9 files changed, 50 insertions(+), 48 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index f84ae809d34b14712495de95043e68ae28fe88a3..108ad28d24a16191b6a419d09b9d56bf212c4050 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -163,17 +163,17 @@ impl App { self.0.borrow().app_metadata.clone() } - /// Returns a handle to the [BackgroundExecutor] associated with this app, which can be used to spawn futures in the background. + /// Returns a handle to the [`BackgroundExecutor`] associated with this app, which can be used to spawn futures in the background. pub fn background_executor(&self) -> BackgroundExecutor { self.0.borrow().background_executor.clone() } - /// Returns a handle to the [ForegroundExecutor] associated with this app, which can be used to spawn futures in the foreground. + /// Returns a handle to the [`ForegroundExecutor`] associated with this app, which can be used to spawn futures in the foreground. pub fn foreground_executor(&self) -> ForegroundExecutor { self.0.borrow().foreground_executor.clone() } - /// Returns a reference to the [TextSystem] associated with this app. + /// Returns a reference to the [`TextSystem`] associated with this app. pub fn text_system(&self) -> Arc { self.0.borrow().text_system.clone() } @@ -299,7 +299,7 @@ impl AppContext { app } - /// Quit the application gracefully. Handlers registered with `ModelContext::on_app_quit` + /// Quit the application gracefully. Handlers registered with [`ModelContext::on_app_quit`] /// will be given 100ms to complete before exiting. pub fn shutdown(&mut self) { let mut futures = Vec::new(); @@ -580,7 +580,7 @@ impl AppContext { self.pending_effects.push_back(effect); } - /// Called at the end of AppContext::update to complete any side effects + /// Called at the end of [`AppContext::update`] to complete any side effects /// such as notifying observers, emitting events, etc. Effects can themselves /// cause effects, so we continue looping until all effects are processed. fn flush_effects(&mut self) { diff --git a/crates/gpui/src/element.rs b/crates/gpui/src/element.rs index 987b91b791a93b9fb72672a4608c1b6665fc20e2..179c2cb1e25db449556e92cfbf9710278dbd2b73 100644 --- a/crates/gpui/src/element.rs +++ b/crates/gpui/src/element.rs @@ -31,14 +31,14 @@ pub trait IntoElement: Sized { /// The specific type of element into which the implementing type is converted. type Element: Element; - /// The [ElementId] of self once converted into an [Element]. + /// The [`ElementId`] of self once converted into an [`Element`]. /// If present, the resulting element's state will be carried across frames. fn element_id(&self) -> Option; - /// Convert self into a type that implements [Element]. + /// Convert self into a type that implements [`Element`]. fn into_element(self) -> Self::Element; - /// Convert self into a dynamically-typed [AnyElement]. + /// Convert self into a dynamically-typed [`AnyElement`]. fn into_any_element(self) -> AnyElement { self.into_element().into_any() } @@ -115,7 +115,7 @@ pub trait Render: 'static + Sized { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement; } -/// You can derive [IntoElement] on any type that implements this trait. +/// You can derive [`IntoElement`] on any type that implements this trait. /// It is used to allow views to be expressed in terms of abstract data. pub trait RenderOnce: 'static { fn render(self, cx: &mut WindowContext) -> impl IntoElement; @@ -224,7 +224,7 @@ enum ElementDrawPhase { }, } -/// A wrapper around an implementer of [Element] that allows it to be drawn in a window. +/// A wrapper around an implementer of [`Element`] that allows it to be drawn in a window. impl DrawableElement { fn new(element: E) -> Self { DrawableElement { diff --git a/crates/gpui/src/executor.rs b/crates/gpui/src/executor.rs index 4be1ffbf0fdf970353d6f2402eda02ab2b0faac8..fc60cb1ec6afcd1c79f5b561a436eac4635c47bc 100644 --- a/crates/gpui/src/executor.rs +++ b/crates/gpui/src/executor.rs @@ -33,8 +33,10 @@ pub struct ForegroundExecutor { } /// Task is a primitive that allows work to happen in the background. -/// It implements Future so you can `.await` on it. -/// If you drop a task it will be cancelled immediately. Calling `.detach()` allows +/// +/// It implements [`Future`] so you can `.await` on it. +/// +/// If you drop a task it will be cancelled immediately. Calling [`Task::detach`] allows /// the task to continue running in the background, but with no way to return a value. #[must_use] #[derive(Debug)] @@ -387,7 +389,7 @@ impl ForegroundExecutor { } } -/// Scope manages a set of tasks that are enqueued and waited on together. See `BackgroundExecutor#scoped` +/// Scope manages a set of tasks that are enqueued and waited on together. See [`BackgroundExecutor::scoped`]. pub struct Scope<'a> { executor: BackgroundExecutor, futures: Vec + Send + 'static>>>, diff --git a/crates/gpui/src/input.rs b/crates/gpui/src/input.rs index da240a77a8b633683d11d96356451dd889e17818..7290b48abd7157cdbf11ffa74e6eed294bcc271e 100644 --- a/crates/gpui/src/input.rs +++ b/crates/gpui/src/input.rs @@ -34,7 +34,7 @@ pub trait InputHandler: 'static + Sized { ) -> Option>; } -/// The canonical implementation of `PlatformInputHandler`. Call `WindowContext::handle_input` +/// The canonical implementation of [`PlatformInputHandler`]. Call [`WindowContext::handle_input`] /// with an instance during your element's paint. pub struct ElementInputHandler { view: View, diff --git a/crates/gpui/src/platform/mac/display.rs b/crates/gpui/src/platform/mac/display.rs index 123cbf8159be02b0496bf8e3656f837e63b03bd4..25e0921fee28efd25c0c1ddf88d6df84318a19aa 100644 --- a/crates/gpui/src/platform/mac/display.rs +++ b/crates/gpui/src/platform/mac/display.rs @@ -14,12 +14,12 @@ pub struct MacDisplay(pub(crate) CGDirectDisplayID); unsafe impl Send for MacDisplay {} impl MacDisplay { - /// Get the screen with the given [DisplayId]. + /// Get the screen with the given [`DisplayId`]. pub fn find_by_id(id: DisplayId) -> Option { Self::all().find(|screen| screen.id() == id) } - /// Get the screen with the given persistent [Uuid]. + /// Get the screen with the given persistent [`Uuid`]. pub fn find_by_uuid(uuid: Uuid) -> Option { Self::all().find(|screen| screen.uuid().ok() == Some(uuid)) } diff --git a/crates/gpui/src/subscription.rs b/crates/gpui/src/subscription.rs index b56c9a1ccdbe800d5e24e8a4a353ef4ff08c784c..887283d09486ae139e219f5910206eda317b741b 100644 --- a/crates/gpui/src/subscription.rs +++ b/crates/gpui/src/subscription.rs @@ -37,10 +37,10 @@ where }))) } - /// Inserts a new `[Subscription]` for the given `emitter_key`. By default, subscriptions + /// Inserts a new [`Subscription`] for the given `emitter_key`. By default, subscriptions /// are inert, meaning that they won't be listed when calling `[SubscriberSet::remove]` or `[SubscriberSet::retain]`. - /// This method returns a tuple of a `[Subscription]` and an `impl FnOnce`, and you can use the latter - /// to activate the `[Subscription]`. + /// This method returns a tuple of a [`Subscription`] and an `impl FnOnce`, and you can use the latter + /// to activate the [`Subscription`]. #[must_use] pub fn insert( &self, diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 9b657a3f71c8fe226008d4ed56b787f17bc65a8c..ec4713639e930cd759a8af5cb4435ea588c5ec30 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -248,7 +248,7 @@ pub trait ManagedView: FocusableView + EventEmitter {} impl> ManagedView for M {} -/// Emitted by implementers of [ManagedView] to indicate the view should be dismissed, such as when a view is presented as a modal. +/// Emitted by implementers of [`ManagedView`] to indicate the view should be dismissed, such as when a view is presented as a modal. pub struct DismissEvent; // Holds the state for a specific window. @@ -464,8 +464,8 @@ impl ContentMask { } /// Provides access to application state in the context of a single window. Derefs -/// to an `AppContext`, so you can also pass a `WindowContext` to any method that takes -/// an `AppContext` and call any `AppContext` methods. +/// to an [`AppContext`], so you can also pass a [`WindowContext`] to any method that takes +/// an [`AppContext`] and call any [`AppContext`] methods. pub struct WindowContext<'a> { pub(crate) app: &'a mut AppContext, pub(crate) window: &'a mut Window, @@ -493,20 +493,20 @@ impl<'a> WindowContext<'a> { self.window.removed = true; } - /// Obtain a new `FocusHandle`, which allows you to track and manipulate the keyboard focus + /// Obtain a new [`FocusHandle`], which allows you to track and manipulate the keyboard focus /// for elements rendered within this window. pub fn focus_handle(&mut self) -> FocusHandle { FocusHandle::new(&self.window.focus_handles) } - /// Obtain the currently focused `FocusHandle`. If no elements are focused, returns `None`. + /// Obtain the currently focused [`FocusHandle`]. If no elements are focused, returns `None`. pub fn focused(&self) -> Option { self.window .focus .and_then(|id| FocusHandle::for_id(id, &self.window.focus_handles)) } - /// Move focus to the element associated with the given `FocusHandle`. + /// Move focus to the element associated with the given [`FocusHandle`]. pub fn focus(&mut self, handle: &FocusHandle) { if !self.window.focus_enabled || self.window.focus == Some(handle.id) { return; @@ -605,8 +605,8 @@ impl<'a> WindowContext<'a> { } /// Subscribe to events emitted by a model or view. - /// The entity to which you're subscribing must implement the [EventEmitter] trait. - /// The callback will be invoked a handle to the emitting entity (either a [View] or [Model]), the event, and a window context for the current window. + /// The entity to which you're subscribing must implement the [`EventEmitter`] trait. + /// The callback will be invoked a handle to the emitting entity (either a [`View`] or [`Model`]), the event, and a window context for the current window. pub fn subscribe( &mut self, entity: &E, @@ -2061,7 +2061,7 @@ impl<'a> BorrowMut for WindowContext<'a> { } } -/// This trait contains functionality that is shared across [ViewContext] and [WindowContext] +/// This trait contains functionality that is shared across [`ViewContext`] and [`WindowContext`] pub trait BorrowWindow: BorrowMut + BorrowMut { #[doc(hidden)] fn app_mut(&mut self) -> &mut AppContext { @@ -2328,10 +2328,10 @@ impl BorrowMut for WindowContext<'_> { impl BorrowWindow for T where T: BorrowMut + BorrowMut {} -/// Provides access to application state that is specialized for a particular [View]. +/// Provides access to application state that is specialized for a particular [`View`]. /// Allows you to interact with focus, emit events, etc. -/// ViewContext also derefs to [WindowContext], giving you access to all of its methods as well. -/// When you call [View::::update], you're passed a `&mut V` and an `&mut ViewContext`. +/// ViewContext also derefs to [`WindowContext`], giving you access to all of its methods as well. +/// When you call [`View::update`], you're passed a `&mut V` and an `&mut ViewContext`. pub struct ViewContext<'a, V> { window_cx: WindowContext<'a>, view: &'a View, @@ -2407,7 +2407,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { }); } - /// Observe another model or view for changes to it's state, as tracked by the [AppContext::notify] + /// Observe another model or view for changes to its state, as tracked by [`ModelContext::notify`]. pub fn observe( &mut self, entity: &E, @@ -2442,8 +2442,8 @@ impl<'a, V: 'static> ViewContext<'a, V> { } /// Subscribe to events emitted by another model or view. - /// The entity to which you're subscribing must implement the [EventEmitter] trait. - /// The callback will be invoked with a reference to the current view, a handle to the emitting entity (either a [View] or [Model]), the event, and a view context for the current view. + /// The entity to which you're subscribing must implement the [`EventEmitter`] trait. + /// The callback will be invoked with a reference to the current view, a handle to the emitting entity (either a [`View`] or [`Model`]), the event, and a view context for the current view. pub fn subscribe( &mut self, entity: &E, @@ -2687,8 +2687,8 @@ impl<'a, V: 'static> ViewContext<'a, V> { } /// Schedule a future to be run asynchronously. - /// The given callback is invoked with a [WeakView] to avoid leaking the view for a long-running process. - /// It's also given an `AsyncWindowContext`, which can be used to access the state of the view across await points. + /// The given callback is invoked with a [`WeakView`] to avoid leaking the view for a long-running process. + /// It's also given an [`AsyncWindowContext`], which can be used to access the state of the view across await points. /// The returned future will be polled on the main thread. pub fn spawn( &mut self, @@ -2789,7 +2789,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { }); } - /// Move focus to the current view, assuming it implements [FocusableView]. + /// Move focus to the current view, assuming it implements [`FocusableView`]. pub fn focus_self(&mut self) where V: FocusableView, @@ -3119,21 +3119,21 @@ impl AnyWindowHandle { // } // } -/// An identifier for an [Element]. +/// An identifier for an [`Element`](crate::Element). /// /// Can be constructed with a string, a number, or both, as well /// as other internal representations. #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub enum ElementId { - /// The id of a View element + /// The ID of a View element View(EntityId), - /// An integer id + /// An integer ID. Integer(usize), - /// A string based id + /// A string based ID. Name(SharedString), - /// An id that's equated with a focus handle + /// An ID that's equated with a focus handle. FocusHandle(FocusId), - /// A combination of a name and an integer + /// A combination of a name and an integer. NamedInteger(SharedString, usize), } @@ -3204,7 +3204,7 @@ impl From<(&'static str, u64)> for ElementId { } /// A rectangle to be rendered in the window at the given position and size. -/// Passed as an argument [WindowContext::paint_quad]. +/// Passed as an argument [`WindowContext::paint_quad`]. #[derive(Clone)] pub struct PaintQuad { bounds: Bounds, diff --git a/crates/rope/src/rope.rs b/crates/rope/src/rope.rs index adfcd19a6c5420f12fab5495731cea7b9e70397a..05873818c7a5601f494ccc0cd87d3f5d25cf94cd 100644 --- a/crates/rope/src/rope.rs +++ b/crates/rope/src/rope.rs @@ -25,10 +25,10 @@ const CHUNK_BASE: usize = 6; #[cfg(not(test))] const CHUNK_BASE: usize = 16; -/// Type alias to [HashMatrix], an implementation of a homomorphic hash function. Two [Rope] instances +/// Type alias to [`HashMatrix`], an implementation of a homomorphic hash function. Two [`Rope`] instances /// containing the same text will produce the same fingerprint. This hash function is special in that -/// it allows us to hash individual chunks and aggregate them up the [Rope]'s tree, with the resulting -/// hash being equivalent to hashing all the text contained in the [Rope] at once. +/// it allows us to hash individual chunks and aggregate them up the [`Rope`]'s tree, with the resulting +/// hash being equivalent to hashing all the text contained in the [`Rope`] at once. pub type RopeFingerprint = HashMatrix; #[derive(Clone, Default)] diff --git a/crates/text/src/anchor.rs b/crates/text/src/anchor.rs index 084be0e336e5ddbdaa2e5bd683aac763e5ca1735..a65e3753d49ae99285931646ffaa3dd7906818cf 100644 --- a/crates/text/src/anchor.rs +++ b/crates/text/src/anchor.rs @@ -90,7 +90,7 @@ impl Anchor { content.summary_for_anchor(self) } - /// Returns true when the [Anchor] is located inside a visible fragment. + /// Returns true when the [`Anchor`] is located inside a visible fragment. pub fn is_valid(&self, buffer: &BufferSnapshot) -> bool { if *self == Anchor::MIN || *self == Anchor::MAX { true From 51988f63d5eabc0f4461b5aa2a71495452d54ab1 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Tue, 9 Jan 2024 14:50:04 -0500 Subject: [PATCH 46/78] Document more enums --- crates/ui/src/components/button/button_like.rs | 4 ++-- crates/ui/src/components/divider.rs | 1 + crates/ui/src/styled_ext.rs | 7 ++++--- crates/ui/src/styles/color.rs | 1 + crates/ui/src/styles/elevation.rs | 1 + crates/ui/src/styles/typography.rs | 1 + 6 files changed, 10 insertions(+), 5 deletions(-) diff --git a/crates/ui/src/components/button/button_like.rs b/crates/ui/src/components/button/button_like.rs index d6ecad41fc657860ba31622810e8459cb4a72072..2c292595c751c136b4c83b4832f5b49dda81c17a 100644 --- a/crates/ui/src/components/button/button_like.rs +++ b/crates/ui/src/components/button/button_like.rs @@ -95,6 +95,7 @@ impl From for Color { } } +/// Sets the visual appearance of a button. #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)] pub enum ButtonStyle { /// A filled button with a solid background color. Provides emphasis versus @@ -262,8 +263,7 @@ impl ButtonStyle { } } -/// ButtonSize can also be used to help build non-button elements -/// that are consistently sized with buttons. +/// Sets the height of a button. Can also be used to size non-button elements to align with [Button]s. #[derive(Default, PartialEq, Clone, Copy)] pub enum ButtonSize { Large, diff --git a/crates/ui/src/components/divider.rs b/crates/ui/src/components/divider.rs index 2567c3fc3476c72819f0d30f5eae2a3e74f68059..dfe31b677ba6fb8b1d4a2878b97a9ad947856cac 100644 --- a/crates/ui/src/components/divider.rs +++ b/crates/ui/src/components/divider.rs @@ -7,6 +7,7 @@ enum DividerDirection { Vertical, } +/// Sets the color of a [Divider]. #[derive(Default)] pub enum DividerColor { Border, diff --git a/crates/ui/src/styled_ext.rs b/crates/ui/src/styled_ext.rs index 8c5da763f360621a5a4a1d77ada46c56a6a05992..1f47fe4f140cfc0e0766ecf5a028c382a8d22b73 100644 --- a/crates/ui/src/styled_ext.rs +++ b/crates/ui/src/styled_ext.rs @@ -30,6 +30,7 @@ pub trait StyledExt: Styled + Sized { self.flex().flex_col() } + /// Sets the text size using a [UiTextSize]. fn text_ui_size(self, size: UiTextSize) -> Self { self.text_size(size.rems()) } @@ -40,7 +41,7 @@ pub trait StyledExt: Styled + Sized { /// /// Note: The absolute size of this text will change based on a user's `ui_scale` setting. /// - /// Use [`text_ui_sm`] for regular-sized text. + /// Use `text_ui_sm` for smaller text. fn text_ui(self) -> Self { self.text_size(UiTextSize::default().rems()) } @@ -51,7 +52,7 @@ pub trait StyledExt: Styled + Sized { /// /// Note: The absolute size of this text will change based on a user's `ui_scale` setting. /// - /// Use [`text_ui`] for regular-sized text. + /// Use `text_ui` for regular-sized text. fn text_ui_sm(self) -> Self { self.text_size(UiTextSize::Small.rems()) } @@ -62,7 +63,7 @@ pub trait StyledExt: Styled + Sized { /// /// Note: The absolute size of this text will change based on a user's `ui_scale` setting. /// - /// Use [`text_ui`] for regular-sized text. + /// Use `text_ui` for regular-sized text. fn text_ui_xs(self) -> Self { self.text_size(UiTextSize::XSmall.rems()) } diff --git a/crates/ui/src/styles/color.rs b/crates/ui/src/styles/color.rs index 977a26dedce61d20bb27ee6b65bcc9f3f8738096..434183e5606135cdcb7e420c023c1108d0aa0a42 100644 --- a/crates/ui/src/styles/color.rs +++ b/crates/ui/src/styles/color.rs @@ -1,6 +1,7 @@ use gpui::{Hsla, WindowContext}; use theme::ActiveTheme; +/// Sets a color that has a consistent meaning across all themes. #[derive(Debug, Default, PartialEq, Copy, Clone)] pub enum Color { #[default] diff --git a/crates/ui/src/styles/elevation.rs b/crates/ui/src/styles/elevation.rs index 7b3835c2e54b3e60528ab212311d245dfd7223d3..055a419e072afa959e12fe2d2f5b4942b34679a1 100644 --- a/crates/ui/src/styles/elevation.rs +++ b/crates/ui/src/styles/elevation.rs @@ -85,6 +85,7 @@ impl LayerIndex { } } +/// Sets ann appropriate z-index for the given layer based on it's intended useage. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ElementIndex { Effect, diff --git a/crates/ui/src/styles/typography.rs b/crates/ui/src/styles/typography.rs index 39937ebff1701f2195c4112235c919368b97b364..20f5e5f48f3815559d3ba48cdbe27ea6d242ebfb 100644 --- a/crates/ui/src/styles/typography.rs +++ b/crates/ui/src/styles/typography.rs @@ -38,6 +38,7 @@ impl UiTextSize { } } +/// Sets the size of a [Headline] element #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)] pub enum HeadlineSize { XSmall, From 1728c4eacca01fe6750869d89bb65c24f999a6e0 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 9 Jan 2024 11:52:03 -0800 Subject: [PATCH 47/78] Fixed test --- crates/gpui/src/key_dispatch.rs | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/crates/gpui/src/key_dispatch.rs b/crates/gpui/src/key_dispatch.rs index c65b169596c8b8627521e029d61ae59e24b3845b..cc4af6d7e30d4bcc546ba264d1999cdbf5bec26c 100644 --- a/crates/gpui/src/key_dispatch.rs +++ b/crates/gpui/src/key_dispatch.rs @@ -192,9 +192,8 @@ impl DispatchTree { keymap .bindings_for_action(action) .filter(|binding| { - for i in 1..context_stack.len() { - dbg!(i); - let context = &context_stack[0..i]; + for i in 0..context_stack.len() { + let context = &context_stack[0..=i]; if keymap.binding_enabled(binding, context) { return true; } @@ -333,36 +332,26 @@ mod tests { #[test] fn test_keybinding_for_action_bounds() { - dbg!("got here"); - let keymap = Keymap::new(vec![KeyBinding::new( "cmd-n", TestAction, Some("ProjectPanel"), )]); - dbg!("got here"); let mut registry = ActionRegistry::default(); - dbg!("got here"); registry.load_action::(); - dbg!("got here"); - let keymap = Arc::new(Mutex::new(keymap)); - dbg!("got here"); let tree = DispatchTree::new(keymap, Rc::new(registry)); - dbg!("got here"); - let keybinding = tree.bindings_for_action( - &TestAction, - &vec![ - KeyContext::parse(",").unwrap(), - KeyContext::parse("Workspace").unwrap(), - KeyContext::parse("ProjectPanel").unwrap(), - ], - ); + let contexts = vec![ + KeyContext::parse("Workspace").unwrap(), + KeyContext::parse("ProjectPanel").unwrap(), + ]; + + let keybinding = tree.bindings_for_action(&TestAction, &contexts); assert!(keybinding[0].action.partial_eq(&TestAction)) } From 128a8ff0b91dfd4db2657ffa0a780d87d33eb216 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 9 Jan 2024 12:02:29 -0800 Subject: [PATCH 48/78] Remove unnecessary mutexes from livekit client types Co-authored-by: Conrad --- crates/live_kit_client/src/prod.rs | 60 ++++++++++++++---------------- 1 file changed, 27 insertions(+), 33 deletions(-) diff --git a/crates/live_kit_client/src/prod.rs b/crates/live_kit_client/src/prod.rs index b2b83e95fcf33543aed6d548b21a5f07d6f6b400..74cf8423bc57f638d5dc3602f765e4f656fe9df0 100644 --- a/crates/live_kit_client/src/prod.rs +++ b/crates/live_kit_client/src/prod.rs @@ -164,29 +164,26 @@ pub enum ConnectionState { } pub struct Room { - native_room: Mutex, + native_room: swift::Room, connection: Mutex<( watch::Sender, watch::Receiver, )>, remote_audio_track_subscribers: Mutex>>, remote_video_track_subscribers: Mutex>>, - _delegate: Mutex, + _delegate: RoomDelegate, } -trait AssertSendSync: Send {} -impl AssertSendSync for Room {} - impl Room { pub fn new() -> Arc { Arc::new_cyclic(|weak_room| { let delegate = RoomDelegate::new(weak_room.clone()); Self { - native_room: Mutex::new(unsafe { LKRoomCreate(delegate.native_delegate) }), + native_room: unsafe { LKRoomCreate(delegate.native_delegate) }, connection: Mutex::new(watch::channel_with(ConnectionState::Disconnected)), remote_audio_track_subscribers: Default::default(), remote_video_track_subscribers: Default::default(), - _delegate: Mutex::new(delegate), + _delegate: delegate, } }) } @@ -201,7 +198,7 @@ impl Room { let (did_connect, tx, rx) = Self::build_done_callback(); unsafe { LKRoomConnect( - *self.native_room.lock(), + self.native_room, url.as_concrete_TypeRef(), token.as_concrete_TypeRef(), did_connect, @@ -271,7 +268,7 @@ impl Room { } unsafe { LKRoomPublishVideoTrack( - *self.native_room.lock(), + self.native_room, track.0, callback, Box::into_raw(Box::new(tx)) as *mut c_void, @@ -301,7 +298,7 @@ impl Room { } unsafe { LKRoomPublishAudioTrack( - *self.native_room.lock(), + self.native_room, track.0, callback, Box::into_raw(Box::new(tx)) as *mut c_void, @@ -312,14 +309,14 @@ impl Room { pub fn unpublish_track(&self, publication: LocalTrackPublication) { unsafe { - LKRoomUnpublishTrack(*self.native_room.lock(), publication.0); + LKRoomUnpublishTrack(self.native_room, publication.0); } } pub fn remote_video_tracks(&self, participant_id: &str) -> Vec> { unsafe { let tracks = LKRoomVideoTracksForRemoteParticipant( - *self.native_room.lock(), + self.native_room, CFString::new(participant_id).as_concrete_TypeRef(), ); @@ -348,7 +345,7 @@ impl Room { pub fn remote_audio_tracks(&self, participant_id: &str) -> Vec> { unsafe { let tracks = LKRoomAudioTracksForRemoteParticipant( - *self.native_room.lock(), + self.native_room, CFString::new(participant_id).as_concrete_TypeRef(), ); @@ -380,7 +377,7 @@ impl Room { ) -> Vec> { unsafe { let tracks = LKRoomAudioTrackPublicationsForRemoteParticipant( - *self.native_room.lock(), + self.native_room, CFString::new(participant_id).as_concrete_TypeRef(), ); @@ -508,9 +505,8 @@ impl Room { impl Drop for Room { fn drop(&mut self) { unsafe { - let native_room = &*self.native_room.lock(); - LKRoomDisconnect(*native_room); - CFRelease(native_room.0); + LKRoomDisconnect(self.native_room); + CFRelease(self.native_room.0); } } } @@ -726,7 +722,7 @@ impl Drop for LocalTrackPublication { } pub struct RemoteTrackPublication { - native_publication: Mutex, + native_publication: swift::RemoteTrackPublication, } impl RemoteTrackPublication { @@ -735,21 +731,19 @@ impl RemoteTrackPublication { CFRetain(native_track_publication.0); } Self { - native_publication: Mutex::new(native_track_publication), + native_publication: native_track_publication, } } pub fn sid(&self) -> String { unsafe { - CFString::wrap_under_get_rule(LKRemoteTrackPublicationGetSid( - *self.native_publication.lock(), - )) - .to_string() + CFString::wrap_under_get_rule(LKRemoteTrackPublicationGetSid(self.native_publication)) + .to_string() } } pub fn is_muted(&self) -> bool { - unsafe { LKRemoteTrackPublicationIsMuted(*self.native_publication.lock()) } + unsafe { LKRemoteTrackPublicationIsMuted(self.native_publication) } } pub fn set_enabled(&self, enabled: bool) -> impl Future> { @@ -767,7 +761,7 @@ impl RemoteTrackPublication { unsafe { LKRemoteTrackPublicationSetEnabled( - *self.native_publication.lock(), + self.native_publication, enabled, complete_callback, Box::into_raw(Box::new(tx)) as *mut c_void, @@ -780,13 +774,13 @@ impl RemoteTrackPublication { impl Drop for RemoteTrackPublication { fn drop(&mut self) { - unsafe { CFRelease((*self.native_publication.lock()).0) } + unsafe { CFRelease(self.native_publication.0) } } } #[derive(Debug)] pub struct RemoteAudioTrack { - native_track: Mutex, + native_track: swift::RemoteAudioTrack, sid: Sid, publisher_id: String, } @@ -797,7 +791,7 @@ impl RemoteAudioTrack { CFRetain(native_track.0); } Self { - native_track: Mutex::new(native_track), + native_track, sid, publisher_id, } @@ -822,13 +816,13 @@ impl RemoteAudioTrack { impl Drop for RemoteAudioTrack { fn drop(&mut self) { - unsafe { CFRelease(self.native_track.lock().0) } + unsafe { CFRelease(self.native_track.0) } } } #[derive(Debug)] pub struct RemoteVideoTrack { - native_track: Mutex, + native_track: swift::RemoteVideoTrack, sid: Sid, publisher_id: String, } @@ -839,7 +833,7 @@ impl RemoteVideoTrack { CFRetain(native_track.0); } Self { - native_track: Mutex::new(native_track), + native_track, sid, publisher_id, } @@ -888,7 +882,7 @@ impl RemoteVideoTrack { on_frame, on_drop, ); - LKVideoTrackAddRenderer(*self.native_track.lock(), renderer); + LKVideoTrackAddRenderer(self.native_track, renderer); rx } } @@ -896,7 +890,7 @@ impl RemoteVideoTrack { impl Drop for RemoteVideoTrack { fn drop(&mut self) { - unsafe { CFRelease(self.native_track.lock().0) } + unsafe { CFRelease(self.native_track.0) } } } From 145f3f55e9bc61d470c767b487a675df69a56c63 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 9 Jan 2024 12:20:17 -0800 Subject: [PATCH 49/78] Remove unused API --- crates/activity_indicator/src/activity_indicator.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/crates/activity_indicator/src/activity_indicator.rs b/crates/activity_indicator/src/activity_indicator.rs index 4b990fa430cf5c652457ee42a1dc57915a7a1b8c..d04a5ab319f4c116a319a0a6f4d7b51a1de51184 100644 --- a/crates/activity_indicator/src/activity_indicator.rs +++ b/crates/activity_indicator/src/activity_indicator.rs @@ -77,9 +77,6 @@ impl ActivityIndicator { cx.observe(auto_updater, |_, _, cx| cx.notify()).detach(); } - // cx.observe_active_labeled_tasks(|_, cx| cx.notify()) - // .detach(); - Self { statuses: Default::default(), project: project.clone(), @@ -288,15 +285,6 @@ impl ActivityIndicator { }; } - // todo!(show active tasks) - // if let Some(most_recent_active_task) = cx.active_labeled_tasks().last() { - // return Content { - // icon: None, - // message: most_recent_active_task.to_string(), - // on_click: None, - // }; - // } - Default::default() } } From 7ed3f5f392fcdf1e20669d24817b49d9006225d5 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 9 Jan 2024 15:22:36 -0500 Subject: [PATCH 50/78] Clean up references in doc comments in `ui` and `theme` crates (#3985) This PR cleans up a number of references in doc comments in the `ui` and `theme` crates so that `rustdoc` will link and display them correctly. Release Notes: - N/A --- crates/theme/src/scale.rs | 8 +++---- crates/ui/src/components/avatar.rs | 2 +- crates/ui/src/components/button/button.rs | 24 +++++++++---------- .../ui/src/components/button/button_like.rs | 8 ++++--- crates/ui/src/components/divider.rs | 2 +- crates/ui/src/components/label/label.rs | 6 ++--- crates/ui/src/components/popover_menu.rs | 2 +- crates/ui/src/components/right_click_menu.rs | 2 +- crates/ui/src/prelude.rs | 2 +- crates/ui/src/selectable.rs | 4 +++- crates/ui/src/styled_ext.rs | 10 ++++---- crates/ui/src/styles/elevation.rs | 2 +- crates/ui/src/styles/typography.rs | 2 +- crates/ui/src/utils/format_distance.rs | 20 ++++++++-------- 14 files changed, 49 insertions(+), 45 deletions(-) diff --git a/crates/theme/src/scale.rs b/crates/theme/src/scale.rs index 1f562f16f06dd4f86be7267ef6ceca030c4dc65c..1146090edcc1e7fbef1f8cc09e43377db4183862 100644 --- a/crates/theme/src/scale.rs +++ b/crates/theme/src/scale.rs @@ -39,10 +39,10 @@ impl ColorScaleStep { ]; } -/// A scale of colors for a given [ColorScaleSet]. +/// A scale of colors for a given [`ColorScaleSet`]. /// -/// Each [ColorScale] contains exactly 12 colors. Refer to -/// [ColorScaleStep] for a reference of what each step is used for. +/// Each [`ColorScale`] contains exactly 12 colors. Refer to +/// [`ColorScaleStep`] for a reference of what each step is used for. pub struct ColorScale(Vec); impl FromIterator for ColorScale { @@ -235,7 +235,7 @@ impl IntoIterator for ColorScales { } } -/// Provides groups of [ColorScale]s for light and dark themes, as well as transparent versions of each scale. +/// Provides groups of [`ColorScale`]s for light and dark themes, as well as transparent versions of each scale. pub struct ColorScaleSet { name: SharedString, light: ColorScale, diff --git a/crates/ui/src/components/avatar.rs b/crates/ui/src/components/avatar.rs index 8e13bc9faa8f2e48227ab51757c847785043b51d..98a48de6863af8cfd171dd67cee2604b395261f2 100644 --- a/crates/ui/src/components/avatar.rs +++ b/crates/ui/src/components/avatar.rs @@ -78,7 +78,7 @@ impl Avatar { /// Sets the shape of the avatar image. /// - /// This method allows the shape of the avatar to be specified using the [Shape] enum. + /// This method allows the shape of the avatar to be specified using a [`Shape`]. /// It modifies the corner radius of the image to match the specified shape. /// /// # Examples diff --git a/crates/ui/src/components/button/button.rs b/crates/ui/src/components/button/button.rs index b65bf050eb87c3055c1c4eb923ff2c9f2b9c4ee9..b241b519cdab78c08168eeb1174ab24cc067fc5e 100644 --- a/crates/ui/src/components/button/button.rs +++ b/crates/ui/src/components/button/button.rs @@ -10,12 +10,12 @@ use super::button_icon::ButtonIcon; /// An element that creates a button with a label and an optional icon. /// /// Common buttons: -/// - Label, Icon + Label: [Button] (this component) -/// - Icon only: [IconButton] -/// - Custom: [ButtonLike] +/// - Label, Icon + Label: [`Button`] (this component) +/// - Icon only: [`IconButton`] +/// - Custom: [`ButtonLike`] /// -/// To create a more complex button than what the [Button] or [IconButton] components provide, use -/// [ButtonLike] directly. +/// To create a more complex button than what the [`Button`] or [`IconButton`] components provide, use +/// [`ButtonLike`] directly. /// /// # Examples /// @@ -42,7 +42,7 @@ use super::button_icon::ButtonIcon; /// }); /// ``` /// -/// To change the style of the button when it is selected use the [selected_style][Button::selected_style] method. +/// To change the style of the button when it is selected use the [`selected_style`][Button::selected_style] method. /// /// ``` /// Button::new("button_id", "Click me!") @@ -81,9 +81,9 @@ pub struct Button { } impl Button { - /// Creates a new [Button] with a specified identifier and label. + /// Creates a new [`Button`] with a specified identifier and label. /// - /// This is the primary constructor for a `Button` component. It initializes + /// This is the primary constructor for a [`Button`] component. It initializes /// the button with the provided identifier and label text, setting all other /// properties to their default values, which can be customized using the /// builder pattern methods provided by this struct. @@ -174,7 +174,7 @@ impl Selectable for Button { /// }); /// ``` /// - /// Use [selected_style](Button::selected_style) to change the style of the button when it is selected. + /// Use [`selected_style`](Button::selected_style) to change the style of the button when it is selected. fn selected(mut self, selected: bool) -> Self { self.base = self.base.selected(selected); self @@ -282,13 +282,13 @@ impl ButtonCommon for Button { self.base.id() } - /// Sets the visual style of the button using a [ButtonStyle]. + /// Sets the visual style of the button using a [`ButtonStyle`]. fn style(mut self, style: ButtonStyle) -> Self { self.base = self.base.style(style); self } - /// Sets the button's size using a [ButtonSize]. + /// Sets the button's size using a [`ButtonSize`]. fn size(mut self, size: ButtonSize) -> Self { self.base = self.base.size(size); self @@ -297,7 +297,7 @@ impl ButtonCommon for Button { /// Sets a tooltip for the button. /// /// This method allows a tooltip to be set for the button. The tooltip is a function that - /// takes a mutable reference to a [WindowContext] and returns an [AnyView]. The tooltip + /// takes a mutable reference to a [`WindowContext`] and returns an [`AnyView`]. The tooltip /// is displayed when the user hovers over the button. /// /// # Examples diff --git a/crates/ui/src/components/button/button_like.rs b/crates/ui/src/components/button/button_like.rs index 2c292595c751c136b4c83b4832f5b49dda81c17a..3e4b478a9a237c44b95e5de09158e35e40e55449 100644 --- a/crates/ui/src/components/button/button_like.rs +++ b/crates/ui/src/components/button/button_like.rs @@ -4,7 +4,7 @@ use smallvec::SmallVec; use crate::prelude::*; -/// A trait for buttons that can be Selected. Enables setting the [ButtonStyle] of a button when it is selected. +/// A trait for buttons that can be Selected. Enables setting the [`ButtonStyle`] of a button when it is selected. pub trait SelectableButton: Selectable { fn selected_style(self, style: ButtonStyle) -> Self; } @@ -95,7 +95,7 @@ impl From for Color { } } -/// Sets the visual appearance of a button. +/// The visual appearance of a button. #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)] pub enum ButtonStyle { /// A filled button with a solid background color. Provides emphasis versus @@ -263,7 +263,9 @@ impl ButtonStyle { } } -/// Sets the height of a button. Can also be used to size non-button elements to align with [Button]s. +/// The height of a button. +/// +/// Can also be used to size non-button elements to align with [`Button`]s. #[derive(Default, PartialEq, Clone, Copy)] pub enum ButtonSize { Large, diff --git a/crates/ui/src/components/divider.rs b/crates/ui/src/components/divider.rs index dfe31b677ba6fb8b1d4a2878b97a9ad947856cac..772fc1a81a34b816649efeae19d36fc7405c66b2 100644 --- a/crates/ui/src/components/divider.rs +++ b/crates/ui/src/components/divider.rs @@ -7,7 +7,7 @@ enum DividerDirection { Vertical, } -/// Sets the color of a [Divider]. +/// The color of a [`Divider`]. #[derive(Default)] pub enum DividerColor { Border, diff --git a/crates/ui/src/components/label/label.rs b/crates/ui/src/components/label/label.rs index 8cc43bc99374432188a456e5fb2b6af12d2eb092..403053baf4227fb945df3eccacc6e1a32e95c914 100644 --- a/crates/ui/src/components/label/label.rs +++ b/crates/ui/src/components/label/label.rs @@ -47,7 +47,7 @@ impl Label { } impl LabelCommon for Label { - /// Sets the size of the label using a [LabelSize]. + /// Sets the size of the label using a [`LabelSize`]. /// /// # Examples /// @@ -59,7 +59,7 @@ impl LabelCommon for Label { self } - /// Sets the line height style of the label using a [LineHeightStyle]. + /// Sets the line height style of the label using a [`LineHeightStyle`]. /// /// # Examples /// @@ -71,7 +71,7 @@ impl LabelCommon for Label { self } - /// Sets the color of the label using a [Color]. + /// Sets the color of the label using a [`Color`]. /// /// # Examples /// diff --git a/crates/ui/src/components/popover_menu.rs b/crates/ui/src/components/popover_menu.rs index 6bfc88bbec3ef292d1163717b0f1aaad7ec2c2fb..52c907fab51f5cf07e92e42aaedfe055de995247 100644 --- a/crates/ui/src/components/popover_menu.rs +++ b/crates/ui/src/components/popover_menu.rs @@ -108,7 +108,7 @@ impl PopoverMenu { } } -/// Creates a [PopoverMenu] +/// Creates a [`PopoverMenu`] pub fn popover_menu(id: impl Into) -> PopoverMenu { PopoverMenu { id: id.into(), diff --git a/crates/ui/src/components/right_click_menu.rs b/crates/ui/src/components/right_click_menu.rs index 282884c948b757b9ba305e8ee5532e7d5732796e..cbc924ff59936d4904695cda12d253a28610af36 100644 --- a/crates/ui/src/components/right_click_menu.rs +++ b/crates/ui/src/components/right_click_menu.rs @@ -39,7 +39,7 @@ impl RightClickMenu { } } -/// Creates a [RightClickMenu] +/// Creates a [`RightClickMenu`] pub fn right_click_menu(id: impl Into) -> RightClickMenu { RightClickMenu { id: id.into(), diff --git a/crates/ui/src/prelude.rs b/crates/ui/src/prelude.rs index ee635e4d6b944d807034087ff1e4de2788505847..69d1d0583d70e8176f577ce7d7fa651845df82f6 100644 --- a/crates/ui/src/prelude.rs +++ b/crates/ui/src/prelude.rs @@ -1,4 +1,4 @@ -//! The prelude of this crate. When building UI in zed you almost always want to import this. +//! The prelude of this crate. When building UI in Zed you almost always want to import this. pub use gpui::prelude::*; pub use gpui::{ diff --git a/crates/ui/src/selectable.rs b/crates/ui/src/selectable.rs index e5e60008b9194d129fb03523ead8e3d58c804681..54da86d09492f03a106cb1be265c0a46851000f6 100644 --- a/crates/ui/src/selectable.rs +++ b/crates/ui/src/selectable.rs @@ -1,4 +1,6 @@ -/// A trait for elements that can be selected. Generally used to enable "toggle" or "active" behavior and styles on an element through the [Selection] status. +/// A trait for elements that can be selected. +/// +/// Generally used to enable "toggle" or "active" behavior and styles on an element through the [`Selection`] status. pub trait Selectable { /// Sets whether the element is selected. fn selected(self, selected: bool) -> Self; diff --git a/crates/ui/src/styled_ext.rs b/crates/ui/src/styled_ext.rs index 1f47fe4f140cfc0e0766ecf5a028c382a8d22b73..b2eaf75ed913b753574be6f449133eb536bd0a72 100644 --- a/crates/ui/src/styled_ext.rs +++ b/crates/ui/src/styled_ext.rs @@ -14,7 +14,7 @@ fn elevated(this: E, cx: &mut WindowContext, index: ElevationIndex) - .shadow(index.shadow()) } -/// Extends [gpui::Styled] with Zed specific styling methods. +/// Extends [`gpui::Styled`] with Zed-specific styling methods. pub trait StyledExt: Styled + Sized { /// Horizontally stacks elements. /// @@ -30,7 +30,7 @@ pub trait StyledExt: Styled + Sized { self.flex().flex_col() } - /// Sets the text size using a [UiTextSize]. + /// Sets the text size using a [`UiTextSize`]. fn text_ui_size(self, size: UiTextSize) -> Self { self.text_size(size.rems()) } @@ -79,7 +79,7 @@ pub trait StyledExt: Styled + Sized { self.text_size(settings.buffer_font_size(cx)) } - /// The [`Surface`](ui::ElevationIndex::Surface) elevation level, located above the app background, is the standard level for all elements + /// The [`Surface`](ElevationIndex::Surface) elevation level, located above the app background, is the standard level for all elements /// /// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()` /// @@ -88,7 +88,7 @@ pub trait StyledExt: Styled + Sized { elevated(self, cx, ElevationIndex::Surface) } - /// Non-Modal Elevated Surfaces appear above the [`Surface`](ui::ElevationIndex::Surface) layer and is used for things that should appear above most UI elements like an editor or panel, but not elements like popovers, context menus, modals, etc. + /// Non-Modal Elevated Surfaces appear above the [`Surface`](ElevationIndex::Surface) layer and is used for things that should appear above most UI elements like an editor or panel, but not elements like popovers, context menus, modals, etc. /// /// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()` /// @@ -101,7 +101,7 @@ pub trait StyledExt: Styled + Sized { /// /// Elements rendered at this layer should have an enforced behavior: Any interaction outside of the modal will either dismiss the modal or prompt an action (Save your progress, etc) then dismiss the modal. /// - /// If the element does not have this behavior, it should be rendered at the [`Elevated Surface`](ui::ElevationIndex::ElevatedSurface) layer. + /// If the element does not have this behavior, it should be rendered at the [`Elevated Surface`](ElevationIndex::ElevatedSurface) layer. /// /// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()` /// diff --git a/crates/ui/src/styles/elevation.rs b/crates/ui/src/styles/elevation.rs index 055a419e072afa959e12fe2d2f5b4942b34679a1..ec1848ca6093606aea9abcc926f242b20e0e727c 100644 --- a/crates/ui/src/styles/elevation.rs +++ b/crates/ui/src/styles/elevation.rs @@ -85,7 +85,7 @@ impl LayerIndex { } } -/// Sets ann appropriate z-index for the given layer based on it's intended useage. +/// An appropriate z-index for the given layer based on its intended useage. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ElementIndex { Effect, diff --git a/crates/ui/src/styles/typography.rs b/crates/ui/src/styles/typography.rs index 20f5e5f48f3815559d3ba48cdbe27ea6d242ebfb..70cd797d5162b534a9ae42a0124f81c34342716e 100644 --- a/crates/ui/src/styles/typography.rs +++ b/crates/ui/src/styles/typography.rs @@ -38,7 +38,7 @@ impl UiTextSize { } } -/// Sets the size of a [Headline] element +/// The size of a [`Headline`] element #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)] pub enum HeadlineSize { XSmall, diff --git a/crates/ui/src/utils/format_distance.rs b/crates/ui/src/utils/format_distance.rs index 17f6b7a6549b793b6fe58c7a00c083d2dbe53c42..3582d87f70ad37dc3bd710ef433dc2242bc26d32 100644 --- a/crates/ui/src/utils/format_distance.rs +++ b/crates/ui/src/utils/format_distance.rs @@ -7,10 +7,10 @@ pub enum DateTimeType { } impl DateTimeType { - /// Converts the DateTimeType to a NaiveDateTime. + /// Converts the [`DateTimeType`] to a [`NaiveDateTime`]. /// - /// If the DateTimeType is already a NaiveDateTime, it will be returned as is. - /// If the DateTimeType is a DateTime, it will be converted to a NaiveDateTime. + /// If the [`DateTimeType`] is already a [`NaiveDateTime`], it will be returned as is. + /// If the [`DateTimeType`] is a [`DateTime`], it will be converted to a [`NaiveDateTime`]. pub fn to_naive(&self) -> NaiveDateTime { match self { DateTimeType::Naive(naive) => *naive, @@ -68,13 +68,13 @@ impl FormatDistance { } } -/// Calculates the distance in seconds between two NaiveDateTime objects. +/// Calculates the distance in seconds between two [`NaiveDateTime`] objects. /// It returns a signed integer denoting the difference. If `date` is earlier than `base_date`, the returned value will be negative. /// /// ## Arguments /// -/// * `date` - A NaiveDateTime object representing the date of interest -/// * `base_date` - A NaiveDateTime object representing the base date against which the comparison is made +/// * `date` - A [NaiveDateTime`] object representing the date of interest +/// * `base_date` - A [NaiveDateTime`] object representing the base date against which the comparison is made fn distance_in_seconds(date: NaiveDateTime, base_date: NaiveDateTime) -> i64 { let duration = date.signed_duration_since(base_date); -duration.num_seconds() @@ -233,12 +233,12 @@ fn distance_string( /// /// For example, "less than a minute ago", "about 2 hours ago", "3 months from now", etc. /// -/// Use [naive_format_distance_from_now] to compare a NaiveDateTime against now. +/// Use [`format_distance_from_now`] to compare a NaiveDateTime against now. /// /// # Arguments /// -/// * `date` - The NaiveDateTime to compare. -/// * `base_date` - The NaiveDateTime to compare against. +/// * `date` - The [`NaiveDateTime`] to compare. +/// * `base_date` - The [`NaiveDateTime`] to compare against. /// * `include_seconds` - A boolean. If true, distances less than a minute are more detailed /// * `add_suffix` - A boolean. If true, result indicates if the time is in the past or future /// @@ -274,7 +274,7 @@ pub fn format_distance( /// /// # Arguments /// -/// * `datetime` - The NaiveDateTime to compare with the current time. +/// * `datetime` - The [`NaiveDateTime`] to compare with the current time. /// * `include_seconds` - A boolean. If true, distances less than a minute are more detailed /// * `add_suffix` - A boolean. If true, result indicates if the time is in the past or future /// From 356f9fc3b64a375d0af98c33ebe53bad24bcd539 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 9 Jan 2024 12:50:00 -0800 Subject: [PATCH 51/78] Store a raw Room pointer on RoomDelegate --- crates/live_kit_client/src/prod.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/live_kit_client/src/prod.rs b/crates/live_kit_client/src/prod.rs index 74cf8423bc57f638d5dc3602f765e4f656fe9df0..df33cf72f0239ef4e2464c664ddf641cd299305f 100644 --- a/crates/live_kit_client/src/prod.rs +++ b/crates/live_kit_client/src/prod.rs @@ -513,14 +513,15 @@ impl Drop for Room { struct RoomDelegate { native_delegate: swift::RoomDelegate, - _weak_room: Weak, + weak_room: *mut c_void, } impl RoomDelegate { fn new(weak_room: Weak) -> Self { + let weak_room = weak_room.into_raw() as *mut c_void; let native_delegate = unsafe { LKRoomDelegateCreate( - weak_room.as_ptr() as *mut c_void, + weak_room, Self::on_did_disconnect, Self::on_did_subscribe_to_remote_audio_track, Self::on_did_unsubscribe_from_remote_audio_track, @@ -532,7 +533,7 @@ impl RoomDelegate { }; Self { native_delegate, - _weak_room: weak_room, + weak_room, } } @@ -647,6 +648,7 @@ impl Drop for RoomDelegate { fn drop(&mut self) { unsafe { CFRelease(self.native_delegate.0); + let _ = Weak::from_raw(self.weak_room); } } } From e3c603f41bc04eb364d2822e7dbc7c3178be347b Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 9 Jan 2024 12:54:51 -0800 Subject: [PATCH 52/78] Make RemoteTrackPublication a tuple struct again --- crates/live_kit_client/src/prod.rs | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/crates/live_kit_client/src/prod.rs b/crates/live_kit_client/src/prod.rs index df33cf72f0239ef4e2464c664ddf641cd299305f..5d8ef9bf134a61111247d3fac5b94fd87a50caf8 100644 --- a/crates/live_kit_client/src/prod.rs +++ b/crates/live_kit_client/src/prod.rs @@ -723,29 +723,22 @@ impl Drop for LocalTrackPublication { } } -pub struct RemoteTrackPublication { - native_publication: swift::RemoteTrackPublication, -} +pub struct RemoteTrackPublication(swift::RemoteTrackPublication); impl RemoteTrackPublication { pub fn new(native_track_publication: swift::RemoteTrackPublication) -> Self { unsafe { CFRetain(native_track_publication.0); } - Self { - native_publication: native_track_publication, - } + Self(native_track_publication) } pub fn sid(&self) -> String { - unsafe { - CFString::wrap_under_get_rule(LKRemoteTrackPublicationGetSid(self.native_publication)) - .to_string() - } + unsafe { CFString::wrap_under_get_rule(LKRemoteTrackPublicationGetSid(self.0)).to_string() } } pub fn is_muted(&self) -> bool { - unsafe { LKRemoteTrackPublicationIsMuted(self.native_publication) } + unsafe { LKRemoteTrackPublicationIsMuted(self.0) } } pub fn set_enabled(&self, enabled: bool) -> impl Future> { @@ -763,7 +756,7 @@ impl RemoteTrackPublication { unsafe { LKRemoteTrackPublicationSetEnabled( - self.native_publication, + self.0, enabled, complete_callback, Box::into_raw(Box::new(tx)) as *mut c_void, @@ -776,7 +769,7 @@ impl RemoteTrackPublication { impl Drop for RemoteTrackPublication { fn drop(&mut self) { - unsafe { CFRelease(self.native_publication.0) } + unsafe { CFRelease(self.0 .0) } } } From a46947d5d76d2eddb437d37fc66fc25f766ebc32 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 9 Jan 2024 14:19:48 -0700 Subject: [PATCH 53/78] Fix panic in set_scroll_anchor_remote --- crates/editor/src/scroll.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/editor/src/scroll.rs b/crates/editor/src/scroll.rs index 0798870f76cb37131b86295b57e5f3f01ad22705..bc5fe4bddd1b38d9445f69bd481145a2f3c884f7 100644 --- a/crates/editor/src/scroll.rs +++ b/crates/editor/src/scroll.rs @@ -384,10 +384,12 @@ impl Editor { ) { hide_hover(self, cx); let workspace_id = self.workspace.as_ref().map(|workspace| workspace.1); - let top_row = scroll_anchor - .anchor - .to_point(&self.buffer().read(cx).snapshot(cx)) - .row; + let snapshot = &self.buffer().read(cx).snapshot(cx); + if !scroll_anchor.anchor.is_valid(snapshot) { + log::warn!("Invalid scroll anchor: {:?}", scroll_anchor); + return; + } + let top_row = scroll_anchor.anchor.to_point(snapshot).row; self.scroll_manager .set_anchor(scroll_anchor, top_row, false, false, workspace_id, cx); } From a579ef17d7cb5b50da80535293683d273a9acc9d Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 9 Jan 2024 16:21:27 -0500 Subject: [PATCH 54/78] Rename `Shape` to `AvatarShape` (#3986) This PR renames the `Shape` enum to `AvatarShape`, since it seems pretty specific to `Avatar`s. Release Notes: - N/A --- crates/ui/src/components/avatar.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/crates/ui/src/components/avatar.rs b/crates/ui/src/components/avatar.rs index 98a48de6863af8cfd171dd67cee2604b395261f2..cd85d179951339371faa389b76494ff593b88070 100644 --- a/crates/ui/src/components/avatar.rs +++ b/crates/ui/src/components/avatar.rs @@ -1,10 +1,13 @@ use crate::prelude::*; use gpui::{img, Hsla, ImageSource, Img, IntoElement, Styled}; +/// The shape of an [`Avatar`]. #[derive(Debug, Default, PartialEq, Clone)] -pub enum Shape { +pub enum AvatarShape { + /// The avatar is shown in a circle. #[default] Circle, + /// The avatar is shown in a rectangle with rounded corners. RoundedRectangle, } @@ -14,7 +17,7 @@ pub enum Shape { /// /// ``` /// Avatar::new("path/to/image.png") -/// .shape(Shape::Circle) +/// .shape(AvatarShape::Circle) /// .grayscale(true) /// .border_color(cx.theme().colors().border) /// ``` @@ -28,7 +31,7 @@ pub struct Avatar { impl RenderOnce for Avatar { fn render(mut self, cx: &mut WindowContext) -> impl IntoElement { if self.image.style().corner_radii.top_left.is_none() { - self = self.shape(Shape::Circle); + self = self.shape(AvatarShape::Circle); } let size = cx.rem_size(); @@ -84,12 +87,12 @@ impl Avatar { /// # Examples /// /// ``` - /// Avatar::new("path/to/image.png").shape(Shape::Circle); + /// Avatar::new("path/to/image.png").shape(AvatarShape::Circle); /// ``` - pub fn shape(mut self, shape: Shape) -> Self { + pub fn shape(mut self, shape: AvatarShape) -> Self { self.image = match shape { - Shape::Circle => self.image.rounded_full(), - Shape::RoundedRectangle => self.image.rounded_md(), + AvatarShape::Circle => self.image.rounded_full(), + AvatarShape::RoundedRectangle => self.image.rounded_md(), }; self } From 8b71b1d07b2bfc9b7cf2dfaf66e16bb7d15e4e06 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 9 Jan 2024 23:27:54 +0200 Subject: [PATCH 55/78] Do not dismiss buffer search when any modal is present Co-authored-by: Piotr Osiewicz --- crates/search/src/buffer_search.rs | 13 +++++++++---- crates/workspace/src/modal_layer.rs | 4 ++++ crates/workspace/src/workspace.rs | 4 ++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 9b2719936080fd14ba65c8f0b044c11455815f2c..7dd4b85231695d8ece48dbd6075cd030c2081dfc 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -43,7 +43,7 @@ pub enum Event { } pub fn init(cx: &mut AppContext) { - cx.observe_new_views(|editor: &mut Workspace, _| BufferSearchBar::register(editor)) + cx.observe_new_views(|workspace: &mut Workspace, _| BufferSearchBar::register(workspace)) .detach(); } @@ -479,6 +479,11 @@ impl SearchActionsRegistrar for Workspace { callback: fn(&mut BufferSearchBar, &A, &mut ViewContext), ) { self.register_action(move |workspace, action: &A, cx| { + if workspace.has_active_modal(cx) { + cx.propagate(); + return; + } + let pane = workspace.active_pane(); pane.update(cx, move |this, cx| { this.toolbar().update(cx, move |this, cx| { @@ -539,11 +544,11 @@ impl BufferSearchBar { this.select_all_matches(action, cx); }); registrar.register_handler(|this, _: &editor::Cancel, cx| { - if !this.dismissed { + if this.dismissed { + cx.propagate(); + } else { this.dismiss(&Dismiss, cx); - return; } - cx.propagate(); }); registrar.register_handler(|this, deploy, cx| { this.deploy(deploy, cx); diff --git a/crates/workspace/src/modal_layer.rs b/crates/workspace/src/modal_layer.rs index ae105345cd360eb072e4a1064efbcca339bfb954..627581c4760c0209de3379d5a2cbf0ead7cbbc62 100644 --- a/crates/workspace/src/modal_layer.rs +++ b/crates/workspace/src/modal_layer.rs @@ -101,6 +101,10 @@ impl ModalLayer { let active_modal = self.active_modal.as_ref()?; active_modal.modal.view().downcast::().ok() } + + pub fn has_active_modal(&self) -> bool { + self.active_modal.is_some() + } } impl Render for ModalLayer { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 739ce88636ca32a88be1496103ad03ed42f63808..09e0a1378d440e0bbf2eb2a7dc21ec88557fa320 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -3383,6 +3383,10 @@ impl Workspace { div } + pub fn has_active_modal(&self, cx: &WindowContext<'_>) -> bool { + self.modal_layer.read(cx).has_active_modal() + } + pub fn active_modal( &mut self, cx: &ViewContext, From 684bd530f0a2d78b9b5c37c72b83fba09e02f451 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 9 Jan 2024 16:56:28 -0500 Subject: [PATCH 56/78] ui: Fix doc tests (#3989) There were a ton of doc tests that weren't compiling in the `ui` crate, so this PR fixes them. Release Notes: - N/A --- crates/ui/src/components/avatar.rs | 8 +++- crates/ui/src/components/button/button.rs | 33 ++++++++++++--- crates/ui/src/components/label/label.rs | 26 +++++++++--- crates/ui/src/components/label/label_like.rs | 7 ++++ crates/ui/src/utils/format_distance.rs | 42 -------------------- 5 files changed, 63 insertions(+), 53 deletions(-) diff --git a/crates/ui/src/components/avatar.rs b/crates/ui/src/components/avatar.rs index cd85d179951339371faa389b76494ff593b88070..9e64e1223c346f4d153fb7d2955b606ff53736ae 100644 --- a/crates/ui/src/components/avatar.rs +++ b/crates/ui/src/components/avatar.rs @@ -16,10 +16,12 @@ pub enum AvatarShape { /// # Examples /// /// ``` +/// use ui::{Avatar, AvatarShape}; +/// /// Avatar::new("path/to/image.png") /// .shape(AvatarShape::Circle) /// .grayscale(true) -/// .border_color(cx.theme().colors().border) +/// .border_color(gpui::red()); /// ``` #[derive(IntoElement)] pub struct Avatar { @@ -87,6 +89,8 @@ impl Avatar { /// # Examples /// /// ``` + /// use ui::{Avatar, AvatarShape}; + /// /// Avatar::new("path/to/image.png").shape(AvatarShape::Circle); /// ``` pub fn shape(mut self, shape: AvatarShape) -> Self { @@ -102,6 +106,8 @@ impl Avatar { /// # Examples /// /// ``` + /// use ui::{Avatar, AvatarShape}; + /// /// let avatar = Avatar::new("path/to/image.png").grayscale(true); /// ``` pub fn grayscale(mut self, grayscale: bool) -> Self { diff --git a/crates/ui/src/components/button/button.rs b/crates/ui/src/components/button/button.rs index b241b519cdab78c08168eeb1174ab24cc067fc5e..fcc30e633815fd764909c32bb7bbc34b8c5e0624 100644 --- a/crates/ui/src/components/button/button.rs +++ b/crates/ui/src/components/button/button.rs @@ -23,6 +23,8 @@ use super::button_icon::ButtonIcon; /// indicates what action will be performed when the button is clicked. /// /// ``` +/// use ui::prelude::*; +/// /// Button::new("button_id", "Click me!") /// .on_click(|event, cx| { /// // Handle click event @@ -34,9 +36,11 @@ use super::button_icon::ButtonIcon; /// a trigger for a popover menu, where clicking the button toggles the visibility of the menu. /// /// ``` +/// use ui::prelude::*; +/// /// Button::new("button_id", "Click me!") /// .icon(IconName::Check) -/// .selected(some_bool) +/// .selected(true) /// .on_click(|event, cx| { /// // Handle click event /// }); @@ -45,8 +49,11 @@ use super::button_icon::ButtonIcon; /// To change the style of the button when it is selected use the [`selected_style`][Button::selected_style] method. /// /// ``` +/// use ui::prelude::*; +/// use ui::TintColor; +/// /// Button::new("button_id", "Click me!") -/// .selected(some_bool) +/// .selected(true) /// .selected_style(ButtonStyle::Tinted(TintColor::Accent)) /// .on_click(|event, cx| { /// // Handle click event @@ -58,6 +65,8 @@ use super::button_icon::ButtonIcon; /// The button's content, including text and icons, is centered by default. /// /// ``` +/// use ui::prelude::*; +/// /// let button = Button::new("button_id", "Click me!") /// .full_width() /// .on_click(|event, cx| { @@ -167,6 +176,8 @@ impl Selectable for Button { /// # Examples /// /// ``` + /// use ui::prelude::*; + /// /// Button::new("button_id", "Click me!") /// .selected(true) /// .on_click(|event, cx| { @@ -187,6 +198,9 @@ impl SelectableButton for Button { /// # Examples /// /// ``` + /// use ui::prelude::*; + /// use ui::TintColor; + /// /// Button::new("button_id", "Click me!") /// .selected(true) /// .selected_style(ButtonStyle::Tinted(TintColor::Accent)) @@ -210,6 +224,8 @@ impl Disableable for Button { /// # Examples /// /// ``` + /// use ui::prelude::*; + /// /// Button::new("button_id", "Click me!") /// .disabled(true) /// .on_click(|event, cx| { @@ -244,8 +260,10 @@ impl FixedWidth for Button { /// # Examples /// /// ``` + /// use ui::prelude::*; + /// /// Button::new("button_id", "Click me!") - /// .width(DefiniteLength::Pixels(100)) + /// .width(px(100.).into()) /// .on_click(|event, cx| { /// // Handle click event /// }); @@ -262,6 +280,8 @@ impl FixedWidth for Button { /// # Examples /// /// ``` + /// use ui::prelude::*; + /// /// Button::new("button_id", "Click me!") /// .full_width() /// .on_click(|event, cx| { @@ -303,9 +323,12 @@ impl ButtonCommon for Button { /// # Examples /// /// ``` + /// use ui::prelude::*; + /// use ui::Tooltip; + /// /// Button::new("button_id", "Click me!") - /// .tooltip(|cx| { - /// Text::new("This is a tooltip").into() + /// .tooltip(move |cx| { + /// Tooltip::text("This is a tooltip", cx) /// }) /// .on_click(|event, cx| { /// // Handle click event diff --git a/crates/ui/src/components/label/label.rs b/crates/ui/src/components/label/label.rs index 403053baf4227fb945df3eccacc6e1a32e95c914..0ba67286a22ccaeec1505612ec34cbc3b1b5883e 100644 --- a/crates/ui/src/components/label/label.rs +++ b/crates/ui/src/components/label/label.rs @@ -10,18 +10,24 @@ use crate::{prelude::*, LabelCommon, LabelLike, LabelSize, LineHeightStyle}; /// # Examples /// /// ``` -/// Label::new("Hello, World!") +/// use ui::prelude::*; +/// +/// Label::new("Hello, World!"); /// ``` /// /// **A colored label**, for example labeling a dangerous action: /// /// ``` +/// use ui::prelude::*; +/// /// let my_label = Label::new("Delete").color(Color::Error); /// ``` /// /// **A label with a strikethrough**, for example labeling something that has been deleted: /// /// ``` +/// use ui::prelude::*; +/// /// let my_label = Label::new("Deleted").strikethrough(true); /// ``` #[derive(IntoElement)] @@ -31,11 +37,13 @@ pub struct Label { } impl Label { - /// Create a new `Label` with the given text. + /// Create a new [`Label`] with the given text. /// /// # Examples /// /// ``` + /// use ui::prelude::*; + /// /// let my_label = Label::new("Hello, World!"); /// ``` pub fn new(label: impl Into) -> Self { @@ -52,7 +60,9 @@ impl LabelCommon for Label { /// # Examples /// /// ``` - /// let my_label = Label::new("Hello, World!").size(LabelSize::Large); + /// use ui::prelude::*; + /// + /// let my_label = Label::new("Hello, World!").size(LabelSize::Small); /// ``` fn size(mut self, size: LabelSize) -> Self { self.base = self.base.size(size); @@ -64,7 +74,9 @@ impl LabelCommon for Label { /// # Examples /// /// ``` - /// let my_label = Label::new("Hello, World!").line_height_style(LineHeightStyle::Normal); + /// use ui::prelude::*; + /// + /// let my_label = Label::new("Hello, World!").line_height_style(LineHeightStyle::UiLabel); /// ``` fn line_height_style(mut self, line_height_style: LineHeightStyle) -> Self { self.base = self.base.line_height_style(line_height_style); @@ -76,7 +88,9 @@ impl LabelCommon for Label { /// # Examples /// /// ``` - /// let my_label = Label::new("Hello, World!").color(Color::Primary); + /// use ui::prelude::*; + /// + /// let my_label = Label::new("Hello, World!").color(Color::Accent); /// ``` fn color(mut self, color: Color) -> Self { self.base = self.base.color(color); @@ -88,6 +102,8 @@ impl LabelCommon for Label { /// # Examples /// /// ``` + /// use ui::prelude::*; + /// /// let my_label = Label::new("Hello, World!").strikethrough(true); /// ``` fn strikethrough(mut self, strikethrough: bool) -> Self { diff --git a/crates/ui/src/components/label/label_like.rs b/crates/ui/src/components/label/label_like.rs index d7bd30187d6d816b01e0192a10845fa53401fe62..6da07d81a39c8a51355b6d922e19c302c1c683ad 100644 --- a/crates/ui/src/components/label/label_like.rs +++ b/crates/ui/src/components/label/label_like.rs @@ -21,9 +21,16 @@ pub enum LineHeightStyle { /// A common set of traits all labels must implement. pub trait LabelCommon { + /// Sets the size of the label using a [`LabelSize`]. fn size(self, size: LabelSize) -> Self; + + /// Sets the line height style of the label using a [`LineHeightStyle`]. fn line_height_style(self, line_height_style: LineHeightStyle) -> Self; + + /// Sets the color of the label using a [`Color`]. fn color(self, color: Color) -> Self; + + /// Sets the strikethrough property of the label. fn strikethrough(self, strikethrough: bool) -> Self; } diff --git a/crates/ui/src/utils/format_distance.rs b/crates/ui/src/utils/format_distance.rs index 3582d87f70ad37dc3bd710ef433dc2242bc26d32..03a0de3adb75c656272397a1f851b8c12ed9eee6 100644 --- a/crates/ui/src/utils/format_distance.rs +++ b/crates/ui/src/utils/format_distance.rs @@ -234,28 +234,6 @@ fn distance_string( /// For example, "less than a minute ago", "about 2 hours ago", "3 months from now", etc. /// /// Use [`format_distance_from_now`] to compare a NaiveDateTime against now. -/// -/// # Arguments -/// -/// * `date` - The [`NaiveDateTime`] to compare. -/// * `base_date` - The [`NaiveDateTime`] to compare against. -/// * `include_seconds` - A boolean. If true, distances less than a minute are more detailed -/// * `add_suffix` - A boolean. If true, result indicates if the time is in the past or future -/// -/// # Example -/// -/// ```rust -/// use chrono::DateTime; -/// use ui::utils::format_distance; -/// -/// fn time_between_moon_landings() -> String { -/// let date = DateTime::parse_from_rfc3339("1969-07-20T00:00:00Z").unwrap().naive_local(); -/// let base_date = DateTime::parse_from_rfc3339("1972-12-14T00:00:00Z").unwrap().naive_local(); -/// format!("There was {} between the first and last crewed moon landings.", naive_format_distance(date, base_date, false, false)) -/// } -/// ``` -/// -/// Output: `"There was about 3 years between the first and last crewed moon landings."` pub fn format_distance( date: DateTimeType, base_date: NaiveDateTime, @@ -271,26 +249,6 @@ pub fn format_distance( /// Get the time difference between a date and now as relative human readable string. /// /// For example, "less than a minute ago", "about 2 hours ago", "3 months from now", etc. -/// -/// # Arguments -/// -/// * `datetime` - The [`NaiveDateTime`] to compare with the current time. -/// * `include_seconds` - A boolean. If true, distances less than a minute are more detailed -/// * `add_suffix` - A boolean. If true, result indicates if the time is in the past or future -/// -/// # Example -/// -/// ```rust -/// use chrono::DateTime; -/// use ui::utils::naive_format_distance_from_now; -/// -/// fn time_since_first_moon_landing() -> String { -/// let date = DateTime::parse_from_rfc3339("1969-07-20T00:00:00Z").unwrap().naive_local(); -/// format!("It's been {} since Apollo 11 first landed on the moon.", naive_format_distance_from_now(date, false, false)) -/// } -/// ``` -/// -/// Output: `It's been over 54 years since Apollo 11 first landed on the moon.` pub fn format_distance_from_now( datetime: DateTimeType, include_seconds: bool, From 80790d921dcd1bea77eabfdd329f9920b40a6b81 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 9 Jan 2024 14:16:46 -0800 Subject: [PATCH 57/78] Fix / remove small todos --- .../collab/src/tests/channel_message_tests.rs | 1 - crates/collab_ui/src/collab_titlebar_item.rs | 6 --- .../incoming_call_notification.rs | 1 - .../project_shared_notification.rs | 2 - crates/editor/src/editor.rs | 4 -- crates/editor/src/editor_tests.rs | 9 ---- crates/editor/src/movement.rs | 2 +- crates/editor/src/scroll/actions.rs | 7 ++- crates/gpui/src/app/test_context.rs | 2 +- crates/gpui/src/elements/overlay.rs | 50 +++++++++++++++++-- crates/text/src/selection.rs | 2 +- 11 files changed, 52 insertions(+), 34 deletions(-) diff --git a/crates/collab/src/tests/channel_message_tests.rs b/crates/collab/src/tests/channel_message_tests.rs index f5da0e3ee6fc85016dafee4b0e6d01c3ec738520..5870bd193842620a2953e577ab0005b48237bcde 100644 --- a/crates/collab/src/tests/channel_message_tests.rs +++ b/crates/collab/src/tests/channel_message_tests.rs @@ -262,7 +262,6 @@ async fn test_remove_channel_message( #[track_caller] fn assert_messages(chat: &Model, messages: &[&str], cx: &mut TestAppContext) { - // todo!(don't directly borrow here) assert_eq!( chat.read_with(cx, |chat, _| { chat.messages() diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index f2106b9a8f4d769801d2eadc7c0259334966b8b6..03dfd450704153b31c52ec3d0baad0d215389190 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -41,12 +41,6 @@ pub fn init(cx: &mut AppContext) { workspace.set_titlebar_item(titlebar_item.into(), cx) }) .detach(); - // todo!() - // cx.add_action(CollabTitlebarItem::share_project); - // cx.add_action(CollabTitlebarItem::unshare_project); - // cx.add_action(CollabTitlebarItem::toggle_user_menu); - // cx.add_action(CollabTitlebarItem::toggle_vcs_menu); - // cx.add_action(CollabTitlebarItem::toggle_project_menu); } pub struct CollabTitlebarItem { diff --git a/crates/collab_ui/src/notifications/incoming_call_notification.rs b/crates/collab_ui/src/notifications/incoming_call_notification.rs index 223415119fca00cca478d56423b20f9b44417c2e..93df9a4be5445bd67072affaaf2093e7fd2a32a1 100644 --- a/crates/collab_ui/src/notifications/incoming_call_notification.rs +++ b/crates/collab_ui/src/notifications/incoming_call_notification.rs @@ -19,7 +19,6 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { for window in notification_windows.drain(..) { window .update(&mut cx, |_, cx| { - // todo!() cx.remove_window(); }) .log_err(); diff --git a/crates/collab_ui/src/notifications/project_shared_notification.rs b/crates/collab_ui/src/notifications/project_shared_notification.rs index 79adc69a801dbe5ed30c7b9afb8d1df19bba6c5e..88fe540c397b65c8ddc3ad0230c47210b8bc0e7e 100644 --- a/crates/collab_ui/src/notifications/project_shared_notification.rs +++ b/crates/collab_ui/src/notifications/project_shared_notification.rs @@ -51,7 +51,6 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { for window in windows { window .update(cx, |_, cx| { - // todo!() cx.remove_window(); }) .ok(); @@ -64,7 +63,6 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { for window in windows { window .update(cx, |_, cx| { - // todo!() cx.remove_window(); }) .ok(); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 71fe6ccfcbc743dc8121362e11f0f9431e5ce05d..95aec1a2007e19df4eb3099bff0f104bd4754a80 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1829,10 +1829,6 @@ impl Editor { this.end_selection(cx); this.scroll_manager.show_scrollbar(cx); - // todo!("use a different mechanism") - // let editor_created_event = EditorCreated(cx.handle()); - // cx.emit_global(editor_created_event); - if mode == EditorMode::Full { let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars(); cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars)); diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 66f28db3e463d39d14cff2ab3ab1890e6e98995f..520c3714d3d529dbcd2df4d4cc4d750db2a7a53c 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -539,7 +539,6 @@ fn test_clone(cx: &mut TestAppContext) { ); } -//todo!(editor navigate) #[gpui::test] async fn test_navigation_history(cx: &mut TestAppContext) { init_test(cx, |_| {}); @@ -993,7 +992,6 @@ fn test_move_cursor_multibyte(cx: &mut TestAppContext) { }); } -//todo!(finish editor tests) #[gpui::test] fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) { init_test(cx, |_| {}); @@ -1259,7 +1257,6 @@ fn test_prev_next_word_boundary(cx: &mut TestAppContext) { }); } -//todo!(finish editor tests) #[gpui::test] fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) { init_test(cx, |_| {}); @@ -1318,7 +1315,6 @@ fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) { }); } -//todo!(simulate_resize) #[gpui::test] async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); @@ -2546,7 +2542,6 @@ fn test_delete_line(cx: &mut TestAppContext) { }); } -//todo!(select_anchor_ranges) #[gpui::test] fn test_join_lines_with_single_selection(cx: &mut TestAppContext) { init_test(cx, |_| {}); @@ -3114,7 +3109,6 @@ fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) { }); } -//todo!(test_transpose) #[gpui::test] fn test_transpose(cx: &mut TestAppContext) { init_test(cx, |_| {}); @@ -4860,7 +4854,6 @@ async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) { }); } -// todo!(select_anchor_ranges) #[gpui::test] async fn test_snippets(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); @@ -6455,7 +6448,6 @@ fn test_highlighted_ranges(cx: &mut TestAppContext) { }); } -// todo!(following) #[gpui::test] async fn test_following(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); @@ -7094,7 +7086,6 @@ async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) { ); } -// todo!(completions) #[gpui::test(iterations = 10)] async fn test_copilot(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) { // flaky diff --git a/crates/editor/src/movement.rs b/crates/editor/src/movement.rs index 0b13e25d5dd621f9d62fcf05e7d75657a3902656..72441974c3788d659084c19503ffc22740bfd005 100644 --- a/crates/editor/src/movement.rs +++ b/crates/editor/src/movement.rs @@ -95,7 +95,7 @@ pub fn up_by_rows( text_layout_details: &TextLayoutDetails, ) -> (DisplayPoint, SelectionGoal) { let mut goal_x = match goal { - SelectionGoal::HorizontalPosition(x) => x.into(), // todo!("Can the fields in SelectionGoal by Pixels? We should extract a geometry crate and depend on that.") + SelectionGoal::HorizontalPosition(x) => x.into(), SelectionGoal::WrappedHorizontalPosition((_, x)) => x.into(), SelectionGoal::HorizontalRange { end, .. } => end.into(), _ => map.x_for_display_point(start, text_layout_details), diff --git a/crates/editor/src/scroll/actions.rs b/crates/editor/src/scroll/actions.rs index 21a4258f6f186700128e081b1ab5d36c3a5938c7..436a0291d020f00c3c685d72e5e7c50934158b27 100644 --- a/crates/editor/src/scroll/actions.rs +++ b/crates/editor/src/scroll/actions.rs @@ -11,10 +11,9 @@ impl Editor { return; } - // todo!() - // if self.mouse_context_menu.read(cx).visible() { - // return None; - // } + if self.mouse_context_menu.is_some() { + return; + } if matches!(self.mode, EditorMode::SingleLine) { cx.propagate(); diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index de31339b8d79bb3b4a80d4e8c9bc95834ffef92c..032d0a7963bc8d6bd2e78a156e97b792cac39b8c 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -481,7 +481,7 @@ impl View { use postage::prelude::{Sink as _, Stream as _}; let (tx, mut rx) = postage::mpsc::channel(1024); - let timeout_duration = Duration::from_millis(100); //todo!() cx.condition_duration(); + let timeout_duration = Duration::from_millis(100); let mut cx = cx.app.borrow_mut(); let subscriptions = ( diff --git a/crates/gpui/src/elements/overlay.rs b/crates/gpui/src/elements/overlay.rs index 6772baa2f9c117a834a716efe4d5e733948ce3b3..019436493d937bdc5898db695622f53ff9d8f997 100644 --- a/crates/gpui/src/elements/overlay.rs +++ b/crates/gpui/src/elements/overlay.rs @@ -15,8 +15,7 @@ pub struct Overlay { anchor_corner: AnchorCorner, fit_mode: OverlayFitMode, anchor_position: Option>, - // todo!(); - // position_mode: OverlayPositionMode, + position_mode: OverlayPositionMode, } /// overlay gives you a floating element that will avoid overflowing the window bounds. @@ -27,6 +26,7 @@ pub fn overlay() -> Overlay { anchor_corner: AnchorCorner::TopLeft, fit_mode: OverlayFitMode::SwitchAnchor, anchor_position: None, + position_mode: OverlayPositionMode::Window, } } @@ -44,6 +44,14 @@ impl Overlay { self } + /// Sets the position mode for this overlay. Local will have this + /// interpret it's [Overlay::position] as relative to the parent element. + /// While Window will have it interpret the position as relative to the window. + pub fn position_mode(mut self, mode: OverlayPositionMode) -> Self { + self.position_mode = mode; + self + } + /// Snap to window edge instead of switching anchor corner when an overflow would occur. pub fn snap_to_window(mut self) -> Self { self.fit_mode = OverlayFitMode::SnapToWindow; @@ -100,9 +108,14 @@ impl Element for Overlay { child_max = child_max.max(&child_bounds.lower_right()); } let size: Size = (child_max - child_min).into(); - let origin = self.anchor_position.unwrap_or(bounds.origin); - let mut desired = self.anchor_corner.get_bounds(origin, size); + let (origin, mut desired) = self.position_mode.get_position_and_bounds( + self.anchor_position, + self.anchor_corner, + size, + bounds, + ); + let limits = Bounds { origin: Point::default(), size: cx.viewport_size(), @@ -184,6 +197,35 @@ pub enum OverlayFitMode { SwitchAnchor, } +#[derive(Copy, Clone, PartialEq)] +pub enum OverlayPositionMode { + Window, + Local, +} + +impl OverlayPositionMode { + fn get_position_and_bounds( + &self, + anchor_position: Option>, + anchor_corner: AnchorCorner, + size: Size, + bounds: Bounds, + ) -> (Point, Bounds) { + match self { + OverlayPositionMode::Window => { + let anchor_position = anchor_position.unwrap_or_else(|| bounds.origin); + let bounds = anchor_corner.get_bounds(anchor_position, size); + (anchor_position, bounds) + } + OverlayPositionMode::Local => { + let anchor_position = anchor_position.unwrap_or_default(); + let bounds = anchor_corner.get_bounds(bounds.origin + anchor_position, size); + (anchor_position, bounds) + } + } + } +} + #[derive(Clone, Copy, PartialEq, Eq)] pub enum AnchorCorner { TopLeft, diff --git a/crates/text/src/selection.rs b/crates/text/src/selection.rs index 4f1f9a29223b927cd993d53a9df27488c9b37ad0..480cb99d747783b7c7bfc100af8b57401781a984 100644 --- a/crates/text/src/selection.rs +++ b/crates/text/src/selection.rs @@ -5,7 +5,7 @@ use std::ops::Range; #[derive(Copy, Clone, Debug, PartialEq)] pub enum SelectionGoal { None, - HorizontalPosition(f32), // todo!("Can we use pixels here without adding a runtime gpui dependency?") + HorizontalPosition(f32), HorizontalRange { start: f32, end: f32 }, WrappedHorizontalPosition((u32, f32)), } From ed263a7b5cccd8e715125fe8e3cc03ce5c9ce485 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 9 Jan 2024 15:08:05 -0800 Subject: [PATCH 58/78] Resolve more todos --- crates/gpui/src/app/test_context.rs | 11 ++++++++-- crates/gpui/src/elements/div.rs | 17 +++------------ crates/gpui/src/elements/text.rs | 5 ++++- crates/gpui/src/style.rs | 3 ++- crates/gpui/src/test.rs | 1 - crates/gpui/src/text_system.rs | 24 ++++++++++++++++++--- crates/gpui/src/text_system/line_wrapper.rs | 2 +- crates/gpui_macros/src/test.rs | 13 +++++------ 8 files changed, 47 insertions(+), 29 deletions(-) diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index 032d0a7963bc8d6bd2e78a156e97b792cac39b8c..ddf88537579809f9c5c2d22995d8b1379812c789 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -25,6 +25,7 @@ pub struct TestAppContext { pub dispatcher: TestDispatcher, test_platform: Rc, text_system: Arc, + fn_name: Option<&'static str>, } impl Context for TestAppContext { @@ -85,7 +86,7 @@ impl Context for TestAppContext { impl TestAppContext { /// Creates a new `TestAppContext`. Usually you can rely on `#[gpui::test]` to do this for you. - pub fn new(dispatcher: TestDispatcher) -> Self { + pub fn new(dispatcher: TestDispatcher, fn_name: Option<&'static str>) -> Self { let arc_dispatcher = Arc::new(dispatcher.clone()); let background_executor = BackgroundExecutor::new(arc_dispatcher.clone()); let foreground_executor = ForegroundExecutor::new(arc_dispatcher); @@ -101,12 +102,18 @@ impl TestAppContext { dispatcher: dispatcher.clone(), test_platform: platform, text_system, + fn_name, } } + /// The name of the test function that created this `TestAppContext` + pub fn test_function_name(&self) -> Option<&'static str> { + self.fn_name + } + /// returns a new `TestAppContext` re-using the same executors to interleave tasks. pub fn new_app(&self) -> TestAppContext { - Self::new(self.dispatcher.clone()) + Self::new(self.dispatcher.clone(), self.fn_name) } /// Simulates quitting the app. diff --git a/crates/gpui/src/elements/div.rs b/crates/gpui/src/elements/div.rs index 45097411d13875c7b1a8fecc4888711dbc0d9dd3..627a2ac339d631c666926d4fc8e1354396cb36f7 100644 --- a/crates/gpui/src/elements/div.rs +++ b/crates/gpui/src/elements/div.rs @@ -1003,7 +1003,7 @@ impl Interactivity { if let Some(text) = cx .text_system() .shape_text( - &element_id, + element_id.into(), FONT_SIZE, &[cx.text_style().to_run(str_len)], None, @@ -1055,22 +1055,11 @@ impl Interactivity { }; eprintln!( - "This element is created at:\n{}:{}:{}", - location.file(), + "This element was created at:\n{}:{}:{}", + dir.join(location.file()).to_string_lossy(), location.line(), location.column() ); - - std::process::Command::new("zed") - .arg(format!( - "{}/{}:{}:{}", - dir.to_string_lossy(), - location.file(), - location.line(), - location.column() - )) - .spawn() - .ok(); } } }); diff --git a/crates/gpui/src/elements/text.rs b/crates/gpui/src/elements/text.rs index 29c93fd19e91518a96a5f663352b92519264aca9..4e5c6721472398233a457573a56d27d43881c071 100644 --- a/crates/gpui/src/elements/text.rs +++ b/crates/gpui/src/elements/text.rs @@ -202,7 +202,10 @@ impl TextState { let Some(lines) = cx .text_system() .shape_text( - &text, font_size, &runs, wrap_width, // Wrap if we know the width. + text.clone(), + font_size, + &runs, + wrap_width, // Wrap if we know the width. ) .log_err() else { diff --git a/crates/gpui/src/style.rs b/crates/gpui/src/style.rs index 244ccebf2498fb9ff275d0818215a9ba658ffc02..a21957611d09feb25f59d4a842ab9b998e83b4d4 100644 --- a/crates/gpui/src/style.rs +++ b/crates/gpui/src/style.rs @@ -165,7 +165,8 @@ impl Default for TextStyle { fn default() -> Self { TextStyle { color: black(), - font_family: "Helvetica".into(), // todo!("Get a font we know exists on the system") + // Helvetica is a web safe font, so it should be available + font_family: "Helvetica".into(), font_features: FontFeatures::default(), font_size: rems(1.).into(), line_height: phi(), diff --git a/crates/gpui/src/test.rs b/crates/gpui/src/test.rs index 1771f29c67b13639fbb7b87029840c435b6a681d..f53d19fdc8b1028bdddba0957719e05b3af4d69d 100644 --- a/crates/gpui/src/test.rs +++ b/crates/gpui/src/test.rs @@ -39,7 +39,6 @@ pub fn run_test( max_retries: usize, test_fn: &mut (dyn RefUnwindSafe + Fn(TestDispatcher, u64)), on_fail_fn: Option, - _fn_name: String, // todo!("re-enable fn_name") ) { let starting_seed = env::var("SEED") .map(|seed| seed.parse().expect("invalid SEED variable")) diff --git a/crates/gpui/src/text_system.rs b/crates/gpui/src/text_system.rs index 0969560e95d62e6d74dc82e88eb4b13958a77480..47073bcde0ef77b7b6674d0c0a24b7dfa107e948 100644 --- a/crates/gpui/src/text_system.rs +++ b/crates/gpui/src/text_system.rs @@ -258,7 +258,7 @@ impl TextSystem { pub fn shape_text( &self, - text: &str, // todo!("pass a SharedString and preserve it when passed a single line?") + text: SharedString, font_size: Pixels, runs: &[TextRun], wrap_width: Option, @@ -268,8 +268,8 @@ impl TextSystem { let mut lines = SmallVec::new(); let mut line_start = 0; - for line_text in text.split('\n') { - let line_text = SharedString::from(line_text.to_string()); + + let mut process_line = |line_text: SharedString| { let line_end = line_start + line_text.len(); let mut last_font: Option = None; @@ -335,6 +335,24 @@ impl TextSystem { } font_runs.clear(); + }; + + let mut split_lines = text.split('\n'); + let mut processed = false; + + if let Some(first_line) = split_lines.next() { + if let Some(second_line) = split_lines.next() { + processed = true; + process_line(first_line.to_string().into()); + process_line(second_line.to_string().into()); + for line_text in split_lines { + process_line(line_text.to_string().into()); + } + } + } + + if !processed { + process_line(text); } self.font_runs_pool.lock().push(font_runs); diff --git a/crates/gpui/src/text_system/line_wrapper.rs b/crates/gpui/src/text_system/line_wrapper.rs index e2f0a8a5fdbce9c0e3f81c9fb018bdf517b1db7c..79013adbb2c83cd0e8e816ca5ef7bb88618b5ae8 100644 --- a/crates/gpui/src/text_system/line_wrapper.rs +++ b/crates/gpui/src/text_system/line_wrapper.rs @@ -143,7 +143,7 @@ mod tests { #[test] fn test_wrap_line() { let dispatcher = TestDispatcher::new(StdRng::seed_from_u64(0)); - let cx = TestAppContext::new(dispatcher); + let cx = TestAppContext::new(dispatcher, None); cx.update(|cx| { let text_system = cx.text_system().clone(); diff --git a/crates/gpui_macros/src/test.rs b/crates/gpui_macros/src/test.rs index 70c6da22d5a7ab888f586d87f2d8ad261089d1bf..ee3f8f713701cfc334a1343902edf29fddd781d2 100644 --- a/crates/gpui_macros/src/test.rs +++ b/crates/gpui_macros/src/test.rs @@ -106,7 +106,8 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { let cx_varname = format_ident!("cx_{}", ix); cx_vars.extend(quote!( let mut #cx_varname = gpui::TestAppContext::new( - dispatcher.clone() + dispatcher.clone(), + Some(stringify!(#outer_fn_name)), ); )); cx_teardowns.extend(quote!( @@ -140,8 +141,7 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { executor.block_test(#inner_fn_name(#inner_fn_args)); #cx_teardowns }, - #on_failure_fn_name, - stringify!(#outer_fn_name).to_string(), + #on_failure_fn_name ); } } @@ -169,7 +169,8 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { let cx_varname_lock = format_ident!("cx_{}_lock", ix); cx_vars.extend(quote!( let mut #cx_varname = gpui::TestAppContext::new( - dispatcher.clone() + dispatcher.clone(), + Some(stringify!(#outer_fn_name)) ); let mut #cx_varname_lock = #cx_varname.app.borrow_mut(); )); @@ -186,7 +187,8 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { let cx_varname = format_ident!("cx_{}", ix); cx_vars.extend(quote!( let mut #cx_varname = gpui::TestAppContext::new( - dispatcher.clone() + dispatcher.clone(), + Some(stringify!(#outer_fn_name)) ); )); cx_teardowns.extend(quote!( @@ -222,7 +224,6 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { #cx_teardowns }, #on_failure_fn_name, - stringify!(#outer_fn_name).to_string(), ); } } From 4da9d61a4264604dd22427fe0e1a40050b85558b Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 9 Jan 2024 16:10:12 -0700 Subject: [PATCH 59/78] Implement live kit promotion/demotion --- crates/call/src/room.rs | 30 +++++++++ crates/collab/src/db/ids.rs | 2 +- crates/collab/src/db/queries/projects.rs | 2 +- crates/collab/src/rpc.rs | 45 ++++++++++--- .../collab/src/tests/channel_guest_tests.rs | 27 +++++++- crates/collab_ui/src/collab_panel.rs | 67 ++++++++++++++----- crates/live_kit_client/src/test.rs | 61 ++++++++++++++--- crates/live_kit_server/src/api.rs | 29 ++++++++ crates/live_kit_server/src/live_kit_server.rs | 2 +- 9 files changed, 223 insertions(+), 42 deletions(-) diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 0a599e85cf4148fd08d902d611d1f3d70b336d39..3d1f1e70c74ab481864884e37a33e6db03ba5b05 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -754,6 +754,18 @@ impl Room { if this.local_participant.role != role { this.local_participant.role = role; + if role == proto::ChannelRole::Guest { + for project in mem::take(&mut this.shared_projects) { + if let Some(project) = project.upgrade() { + this.unshare_project(project, cx).log_err(); + } + } + this.local_participant.projects.clear(); + if let Some(live_kit_room) = &mut this.live_kit { + live_kit_room.stop_publishing(cx); + } + } + this.joined_projects.retain(|project| { if let Some(project) = project.upgrade() { project.update(cx, |project, cx| project.set_role(role, cx)); @@ -1632,6 +1644,24 @@ impl LiveKitRoom { Ok((result, old_muted)) } + + fn stop_publishing(&mut self, cx: &mut ModelContext) { + if let LocalTrack::Published { + track_publication, .. + } = mem::replace(&mut self.microphone_track, LocalTrack::None) + { + self.room.unpublish_track(track_publication); + cx.notify(); + } + + if let LocalTrack::Published { + track_publication, .. + } = mem::replace(&mut self.screen_track, LocalTrack::None) + { + self.room.unpublish_track(track_publication); + cx.notify(); + } + } } enum LocalTrack { diff --git a/crates/collab/src/db/ids.rs b/crates/collab/src/db/ids.rs index 9f77225fb704c1d4d53051cb7ee29d77c3536f80..9dbbfaff8f8d0e94ea56b02eda37485ddc695fa8 100644 --- a/crates/collab/src/db/ids.rs +++ b/crates/collab/src/db/ids.rs @@ -133,7 +133,7 @@ impl ChannelRole { } } - pub fn can_share_projects(&self) -> bool { + pub fn can_publish_to_rooms(&self) -> bool { use ChannelRole::*; match self { Admin | Member => true, diff --git a/crates/collab/src/db/queries/projects.rs b/crates/collab/src/db/queries/projects.rs index 6e1bf16309bd69315903a37ce7b57d389e749161..a55345dc16b331a124736962c456b1114e089b8e 100644 --- a/crates/collab/src/db/queries/projects.rs +++ b/crates/collab/src/db/queries/projects.rs @@ -49,7 +49,7 @@ impl Database { if !participant .role .unwrap_or(ChannelRole::Member) - .can_share_projects() + .can_publish_to_rooms() { return Err(anyhow!("guests cannot share projects"))?; } diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 766cf4bcc0d7344c0780c0a34b62ac4feeb5ea84..b933ce7c9a73a4e801acf349902125f150da2ebe 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -1264,18 +1264,41 @@ async fn set_room_participant_role( response: Response, session: Session, ) -> Result<()> { - let room = session - .db() - .await - .set_room_participant_role( - session.user_id, - RoomId::from_proto(request.room_id), - UserId::from_proto(request.user_id), - ChannelRole::from(request.role()), - ) - .await?; + let (live_kit_room, can_publish) = { + let room = session + .db() + .await + .set_room_participant_role( + session.user_id, + RoomId::from_proto(request.room_id), + UserId::from_proto(request.user_id), + ChannelRole::from(request.role()), + ) + .await?; + + let live_kit_room = room.live_kit_room.clone(); + let can_publish = ChannelRole::from(request.role()).can_publish_to_rooms(); + room_updated(&room, &session.peer); + (live_kit_room, can_publish) + }; + + if let Some(live_kit) = session.live_kit_client.as_ref() { + live_kit + .update_participant( + live_kit_room.clone(), + request.user_id.to_string(), + live_kit_server::proto::ParticipantPermission { + can_subscribe: true, + can_publish, + can_publish_data: can_publish, + hidden: false, + recorder: false, + }, + ) + .await + .trace_err(); + } - room_updated(&room, &session.peer); response.send(proto::Ack {})?; Ok(()) } diff --git a/crates/collab/src/tests/channel_guest_tests.rs b/crates/collab/src/tests/channel_guest_tests.rs index 8d21db678bed364cd8504c839bb3c15b339b1cd0..9b68ce3922ab24726130c356636dae7d6899ef35 100644 --- a/crates/collab/src/tests/channel_guest_tests.rs +++ b/crates/collab/src/tests/channel_guest_tests.rs @@ -1,7 +1,7 @@ use crate::tests::TestServer; use call::ActiveCall; use editor::Editor; -use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext}; +use gpui::{BackgroundExecutor, TestAppContext}; use rpc::proto; #[gpui::test] @@ -132,5 +132,28 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test room_b .update(cx_b, |room, cx| room.share_microphone(cx)) .await - .unwrap() + .unwrap(); + + // B is demoted + active_call_a + .update(cx_a, |call, cx| { + call.room().unwrap().update(cx, |room, cx| { + room.set_participant_role( + client_b.user_id().unwrap(), + proto::ChannelRole::Guest, + cx, + ) + }) + }) + .await + .unwrap(); + cx_a.run_until_parked(); + + // project and buffers are no longer editable + assert!(project_b.read_with(cx_b, |project, _| project.is_read_only())); + assert!(editor_b.update(cx_b, |editor, cx| editor.read_only(cx))); + assert!(room_b + .update(cx_b, |room, cx| room.share_microphone(cx)) + .await + .is_err()); } diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index e25ad0d225680e8454ae69ce063a1fb5d67d1b2a..687c9e45b6c4ce6d41a40725c52b5bc6c0f4efba 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -865,9 +865,9 @@ impl CollabPanel { .ok(); })) }) - .when(is_call_admin && role == proto::ChannelRole::Guest, |el| { + .when(is_call_admin, |el| { el.on_secondary_mouse_down(cx.listener(move |this, event: &MouseDownEvent, cx| { - this.deploy_participant_context_menu(event.position, user_id, cx) + this.deploy_participant_context_menu(event.position, user_id, role, cx) })) }) } @@ -1006,27 +1006,60 @@ impl CollabPanel { &mut self, position: Point, user_id: u64, + role: proto::ChannelRole, cx: &mut ViewContext, ) { let this = cx.view().clone(); + if !(role == proto::ChannelRole::Guest || role == proto::ChannelRole::Member) { + return; + } let context_menu = ContextMenu::build(cx, |context_menu, cx| { - context_menu.entry( - "Allow Write Access", - None, - cx.handler_for(&this, move |_, cx| { - ActiveCall::global(cx) - .update(cx, |call, cx| { - let Some(room) = call.room() else { - return Task::ready(Ok(())); - }; - room.update(cx, |room, cx| { - room.set_participant_role(user_id, proto::ChannelRole::Member, cx) + if role == proto::ChannelRole::Guest { + context_menu.entry( + "Grant Write Access", + None, + cx.handler_for(&this, move |_, cx| { + ActiveCall::global(cx) + .update(cx, |call, cx| { + let Some(room) = call.room() else { + return Task::ready(Ok(())); + }; + room.update(cx, |room, cx| { + room.set_participant_role( + user_id, + proto::ChannelRole::Member, + cx, + ) + }) }) - }) - .detach_and_notify_err(cx) - }), - ) + .detach_and_notify_err(cx) + }), + ) + } else if role == proto::ChannelRole::Member { + context_menu.entry( + "Revoke Write Access", + None, + cx.handler_for(&this, move |_, cx| { + ActiveCall::global(cx) + .update(cx, |call, cx| { + let Some(room) = call.room() else { + return Task::ready(Ok(())); + }; + room.update(cx, |room, cx| { + room.set_participant_role( + user_id, + proto::ChannelRole::Guest, + cx, + ) + }) + }) + .detach_and_notify_err(cx) + }), + ) + } else { + unreachable!() + } }); cx.focus_view(&context_menu); diff --git a/crates/live_kit_client/src/test.rs b/crates/live_kit_client/src/test.rs index 07a12a4ab6f5e39df0b672d8fb589497a765fd04..4575fdd2c1845c53b29876d83f1fb6a8498717dc 100644 --- a/crates/live_kit_client/src/test.rs +++ b/crates/live_kit_client/src/test.rs @@ -3,7 +3,7 @@ use async_trait::async_trait; use collections::{BTreeMap, HashMap}; use futures::Stream; use gpui::BackgroundExecutor; -use live_kit_server::token; +use live_kit_server::{proto, token}; use media::core_video::CVImageBuffer; use parking_lot::Mutex; use postage::watch; @@ -151,6 +151,21 @@ impl TestServer { Ok(()) } + async fn update_participant( + &self, + room_name: String, + identity: String, + permission: proto::ParticipantPermission, + ) -> Result<()> { + self.executor.simulate_random_delay().await; + let mut server_rooms = self.rooms.lock(); + let room = server_rooms + .get_mut(&room_name) + .ok_or_else(|| anyhow!("room {} does not exist", room_name))?; + room.participant_permissions.insert(identity, permission); + Ok(()) + } + pub async fn disconnect_client(&self, client_identity: String) { self.executor.simulate_random_delay().await; let mut server_rooms = self.rooms.lock(); @@ -167,15 +182,22 @@ impl TestServer { let identity = claims.sub.unwrap().to_string(); let room_name = claims.video.room.unwrap(); - if claims.video.can_publish == Some(false) { - return Err(anyhow!("user is not allowed to publish")); - } - let mut server_rooms = self.rooms.lock(); let room = server_rooms .get_mut(&*room_name) .ok_or_else(|| anyhow!("room {} does not exist", room_name))?; + let can_publish = room + .participant_permissions + .get(&identity) + .map(|permission| permission.can_publish) + .or(claims.video.can_publish) + .unwrap_or(true); + + if !can_publish { + return Err(anyhow!("user is not allowed to publish")); + } + let track = Arc::new(RemoteVideoTrack { sid: nanoid::nanoid!(17), publisher_id: identity.clone(), @@ -209,15 +231,22 @@ impl TestServer { let identity = claims.sub.unwrap().to_string(); let room_name = claims.video.room.unwrap(); - if claims.video.can_publish == Some(false) { - return Err(anyhow!("user is not allowed to publish")); - } - let mut server_rooms = self.rooms.lock(); let room = server_rooms .get_mut(&*room_name) .ok_or_else(|| anyhow!("room {} does not exist", room_name))?; + let can_publish = room + .participant_permissions + .get(&identity) + .map(|permission| permission.can_publish) + .or(claims.video.can_publish) + .unwrap_or(true); + + if !can_publish { + return Err(anyhow!("user is not allowed to publish")); + } + let track = Arc::new(RemoteAudioTrack { sid: nanoid::nanoid!(17), publisher_id: identity.clone(), @@ -273,6 +302,7 @@ struct TestServerRoom { client_rooms: HashMap>, video_tracks: Vec>, audio_tracks: Vec>, + participant_permissions: HashMap, } impl TestServerRoom {} @@ -305,6 +335,19 @@ impl live_kit_server::api::Client for TestApiClient { Ok(()) } + async fn update_participant( + &self, + room: String, + identity: String, + permission: live_kit_server::proto::ParticipantPermission, + ) -> Result<()> { + let server = TestServer::get(&self.url)?; + server + .update_participant(room, identity, permission) + .await?; + Ok(()) + } + fn room_token(&self, room: &str, identity: &str) -> Result { let server = TestServer::get(&self.url)?; token::create( diff --git a/crates/live_kit_server/src/api.rs b/crates/live_kit_server/src/api.rs index 2c1e174fb41551d22b498b75d9ce36011ca5a5a9..e7e933c9c39d51d9611da135edd3749030444263 100644 --- a/crates/live_kit_server/src/api.rs +++ b/crates/live_kit_server/src/api.rs @@ -11,10 +11,18 @@ pub trait Client: Send + Sync { async fn create_room(&self, name: String) -> Result<()>; async fn delete_room(&self, name: String) -> Result<()>; async fn remove_participant(&self, room: String, identity: String) -> Result<()>; + async fn update_participant( + &self, + room: String, + identity: String, + permission: proto::ParticipantPermission, + ) -> Result<()>; fn room_token(&self, room: &str, identity: &str) -> Result; fn guest_token(&self, room: &str, identity: &str) -> Result; } +pub struct LiveKitParticipantUpdate {} + #[derive(Clone)] pub struct LiveKitClient { http: reqwest::Client, @@ -131,6 +139,27 @@ impl Client for LiveKitClient { Ok(()) } + async fn update_participant( + &self, + room: String, + identity: String, + permission: proto::ParticipantPermission, + ) -> Result<()> { + let _: proto::ParticipantInfo = self + .request( + "twirp/livekit.RoomService/UpdateParticipant", + token::VideoGrant::to_admin(&room), + proto::UpdateParticipantRequest { + room: room.clone(), + identity, + metadata: "".to_string(), + permission: Some(permission), + }, + ) + .await?; + Ok(()) + } + fn room_token(&self, room: &str, identity: &str) -> Result { token::create( &self.key, diff --git a/crates/live_kit_server/src/live_kit_server.rs b/crates/live_kit_server/src/live_kit_server.rs index 7471a96ec418a5ddeeb25d527b98865c31e75686..aa7c1f2fd0179e9062a5165bf4e332e74bf30ea7 100644 --- a/crates/live_kit_server/src/live_kit_server.rs +++ b/crates/live_kit_server/src/live_kit_server.rs @@ -1,3 +1,3 @@ pub mod api; -mod proto; +pub mod proto; pub mod token; From 8cb291baa446a9fcf4d7fcfec16d3dc7d416277e Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 9 Jan 2024 18:39:29 -0500 Subject: [PATCH 60/78] Take a different approach to rendering channel buttons (#3991) This PR changes the approach we're using to render the channel buttons to use a more straightforward (and less hacky) approach. ### Motivation Even with the variety of hacks that were employed to make the current approach work, there are still a number of issues with the current solution: - Hovering in the empty space to the left of a channel doesn't correctly apply the hover background to the container for the channel buttons - Hovering to the very right of the collab panel (right on top of the drag handle) causes the channel button container to apply its hover background without applying it to the rest of the row - The buttons would still get pushed off to the right by the indent space in the channel tree, resulting in jagged indicators at small sizes Additionally, the rectangular background placed behind the channel buttons still didn't look great when it overlapped with the channel names. ### Explanation For these reasons, I decided to explore a simpler approach that addresses these issues, albeit with some tradeoffs that I think are acceptable. We now render the absolutely-positioned button container as a sibling element of the `ListItem`. This is to avoid issues with the container getting pushed around based on the contents of the `ListItem` rather than staying absolutely positioned at the end of the row. We also have gotten rid of the background for the button container, and now rely on the background of the individual `IconButton`s to occlude the channel name behind them when the two are overlapping. Here are some examples of the new UI in various configurations: #### When the channel entry is hovered Screenshot 2024-01-09 at 6 15 24 PM #### Overlapping with the channel name Screenshot 2024-01-09 at 6 15 43 PM #### Narrow collab panel Screenshot 2024-01-09 at 6 16 07 PM ### Tradeoffs The new approach comes with the following tradeoffs that I am currently aware of: The occlusion can look a little weird when the icons are in the middle of a channel name (as opposed to fully occluding the end of the channel name): Screenshot 2024-01-09 at 6 24 32 PM Hovering one of the icons causes the icon to be hovered instead of the row: Screenshot 2024-01-09 at 6 31 38 PM Release Notes: - Improved the way channel buttons are displayed. --- crates/collab_ui/src/collab_panel.rs | 139 ++++++++++----------------- 1 file changed, 49 insertions(+), 90 deletions(-) diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index df8f2a251fdf4d86ff05f0eaa8052c34317435bb..b9946d6cac97c2c03c8315bc6d59b710516040b4 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -2228,47 +2228,6 @@ impl CollabPanel { None }; - let button_container = |cx: &mut ViewContext| { - h_stack() - .absolute() - // We're using a negative coordinate for the right anchor to - // counteract the padding of the `ListItem`. - // - // This prevents a gap from showing up between the background - // of this element and the edge of the collab panel. - .right(rems(-0.5)) - // HACK: Without this the channel name clips on top of the icons, but I'm not sure why. - .z_index(10) - .bg(cx.theme().colors().panel_background) - .when(is_selected || is_active, |this| { - this.bg(cx.theme().colors().ghost_element_selected) - }) - }; - - let messages_button = |cx: &mut ViewContext| { - IconButton::new("channel_chat", IconName::MessageBubbles) - .icon_size(IconSize::Small) - .icon_color(if has_messages_notification { - Color::Default - } else { - Color::Muted - }) - .on_click(cx.listener(move |this, _, cx| this.join_channel_chat(channel_id, cx))) - .tooltip(|cx| Tooltip::text("Open channel chat", cx)) - }; - - let notes_button = |cx: &mut ViewContext| { - IconButton::new("channel_notes", IconName::File) - .icon_size(IconSize::Small) - .icon_color(if has_notes_notification { - Color::Default - } else { - Color::Muted - }) - .on_click(cx.listener(move |this, _, cx| this.open_channel_notes(channel_id, cx))) - .tooltip(|cx| Tooltip::text("Open channel notes", cx)) - }; - let width = self.width.unwrap_or(px(240.)); div() @@ -2326,58 +2285,58 @@ impl CollabPanel { .child( h_stack() .id(channel_id as usize) - // HACK: This is a dirty hack to help with the positioning of the button container. - // - // We're using a pixel width for the elements but then allowing the contents to - // overflow. This means that the label and facepile will be shown, but will not - // push the button container off the edge of the panel. - .w_px() .child(Label::new(channel.name.clone())) .children(face_pile.map(|face_pile| face_pile.render(cx))), - ) - .end_slot::

( - // If we have a notification for either button, we want to show the corresponding - // button(s) as indicators. - if has_messages_notification || has_notes_notification { - Some( - button_container(cx).child( - h_stack() - .px_1() - .children( - // We only want to render the messages button if there are unseen messages. - // This way we don't take up any space that might overlap the channel name - // when there are no notifications. - has_messages_notification.then(|| messages_button(cx)), - ) - .child( - // We always want the notes button to take up space to prevent layout - // shift when hovering over the channel. - // However, if there are is no notes notification we just show an empty slot. - notes_button(cx) - .when(!has_notes_notification, |this| { - this.visible_on_hover("") - }), - ), - ), + ), + ) + .child( + h_stack() + .absolute() + .right(rems(0.)) + .h_full() + // HACK: Without this the channel name clips on top of the icons, but I'm not sure why. + .z_index(10) + .child( + h_stack() + .h_full() + .gap_1() + .px_1() + .child( + IconButton::new("channel_chat", IconName::MessageBubbles) + .style(ButtonStyle::Filled) + .size(ButtonSize::Compact) + .icon_size(IconSize::Small) + .icon_color(if has_messages_notification { + Color::Default + } else { + Color::Muted + }) + .on_click(cx.listener(move |this, _, cx| { + this.join_channel_chat(channel_id, cx) + })) + .tooltip(|cx| Tooltip::text("Open channel chat", cx)) + .when(!has_messages_notification, |this| { + this.visible_on_hover("") + }), ) - } else { - None - }, - ) - .end_hover_slot( - // When we hover the channel entry we want to always show both buttons. - button_container(cx).child( - h_stack() - .px_1() - // The element hover background has a slight transparency to it, so we - // need to apply it to the inner element so that it blends with the solid - // background color of the absolutely-positioned element. - .group_hover("", |style| { - style.bg(cx.theme().colors().ghost_element_hover) - }) - .child(messages_button(cx)) - .child(notes_button(cx)), - ), + .child( + IconButton::new("channel_notes", IconName::File) + .style(ButtonStyle::Filled) + .size(ButtonSize::Compact) + .icon_size(IconSize::Small) + .icon_color(if has_notes_notification { + Color::Default + } else { + Color::Muted + }) + .on_click(cx.listener(move |this, _, cx| { + this.open_channel_notes(channel_id, cx) + })) + .tooltip(|cx| Tooltip::text("Open channel notes", cx)) + .when(!has_notes_notification, |this| { + this.visible_on_hover("") + }), + ), ), ) .tooltip(|cx| Tooltip::text("Join channel", cx)) From 9ce7ef89497985c98341b6a452e4e8b9d2fc801e Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 9 Jan 2024 16:14:54 -0800 Subject: [PATCH 61/78] Remove the last of the major todos --- crates/gpui/src/app/test_context.rs | 5 + crates/gpui/src/platform/test/platform.rs | 4 + crates/gpui/src/window.rs | 2 - crates/live_kit_client/examples/test_app.rs | 19 ++- crates/project_symbols/src/project_symbols.rs | 1 - crates/terminal_view/src/terminal_element.rs | 17 --- crates/terminal_view/src/terminal_view.rs | 6 +- crates/theme/src/styles/colors.rs | 1 + crates/theme/src/styles/players.rs | 2 - crates/vim/src/test/vim_test_context.rs | 8 +- crates/workspace/src/dock.rs | 26 ---- crates/workspace/src/notifications.rs | 2 - crates/workspace/src/pane.rs | 94 -------------- crates/workspace/src/searchable.rs | 13 +- crates/workspace/src/workspace.rs | 23 ---- crates/zed/src/zed.rs | 115 ++++++------------ script/bundle | 2 +- 17 files changed, 73 insertions(+), 267 deletions(-) diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index ddf88537579809f9c5c2d22995d8b1379812c789..7db4b32002c03e8a28e667b710b6ad76f78598b2 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -111,6 +111,11 @@ impl TestAppContext { self.fn_name } + /// Checks whether there have been any new path prompts received by the platform. + pub fn did_prompt_for_new_path(&self) -> bool { + self.test_platform.did_prompt_for_new_path() + } + /// returns a new `TestAppContext` re-using the same executors to interleave tasks. pub fn new_app(&self) -> TestAppContext { Self::new(self.dispatcher.clone(), self.fn_name) diff --git a/crates/gpui/src/platform/test/platform.rs b/crates/gpui/src/platform/test/platform.rs index c4452a593a3747fd10534b75c7f0622479585075..38f23b94f20d43cc9b491f4269558240577fea4d 100644 --- a/crates/gpui/src/platform/test/platform.rs +++ b/crates/gpui/src/platform/test/platform.rs @@ -102,6 +102,10 @@ impl TestPlatform { }) .detach(); } + + pub(crate) fn did_prompt_for_new_path(&self) -> bool { + self.prompts.borrow().new_path.len() > 0 + } } impl Platform for TestPlatform { diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index ec4713639e930cd759a8af5cb4435ea588c5ec30..5b09b6ebd8ad78b5a78d6596aa84bb485d07ae97 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -1474,9 +1474,7 @@ impl<'a> WindowContext<'a> { InputEvent::MouseUp(mouse_up) } InputEvent::MouseExited(mouse_exited) => { - // todo!("Should we record that the mouse is outside of the window somehow? Or are these global pixels?") self.window.modifiers = mouse_exited.modifiers; - InputEvent::MouseExited(mouse_exited) } InputEvent::ModifiersChanged(modifiers_changed) => { diff --git a/crates/live_kit_client/examples/test_app.rs b/crates/live_kit_client/examples/test_app.rs index 96407497aec4b2c94925a7a86dc914e99043169a..68a8a84209b1df63f81f972918a7f1dc63e77a74 100644 --- a/crates/live_kit_client/examples/test_app.rs +++ b/crates/live_kit_client/examples/test_app.rs @@ -1,7 +1,7 @@ use std::{sync::Arc, time::Duration}; use futures::StreamExt; -use gpui::{actions, KeyBinding}; +use gpui::{actions, KeyBinding, Menu, MenuItem}; use live_kit_client::{ LocalAudioTrack, LocalVideoTrack, RemoteAudioTrackUpdate, RemoteVideoTrackUpdate, Room, }; @@ -26,15 +26,14 @@ fn main() { cx.on_action(quit); cx.bind_keys([KeyBinding::new("cmd-q", Quit, None)]); - // todo!() - // cx.set_menus(vec![Menu { - // name: "Zed", - // items: vec![MenuItem::Action { - // name: "Quit", - // action: Box::new(Quit), - // os_action: None, - // }], - // }]); + cx.set_menus(vec![Menu { + name: "Zed", + items: vec![MenuItem::Action { + name: "Quit", + action: Box::new(Quit), + os_action: None, + }], + }]); let live_kit_url = std::env::var("LIVE_KIT_URL").unwrap_or("http://localhost:7880".into()); let live_kit_key = std::env::var("LIVE_KIT_KEY").unwrap_or("devkey".into()); diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index ed31ebd94997bd01d53b56df4f62dfff0518f737..23dfd21b8256574bf122215cd6d34e36d8059f1f 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/crates/project_symbols/src/project_symbols.rs @@ -242,7 +242,6 @@ impl PickerDelegate for ProjectSymbolsDelegate { .spacing(ListItemSpacing::Sparse) .selected(selected) .child( - // todo!() combine_syntax_and_fuzzy_match_highlights() v_stack() .child( LabelLike::new().child( diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index bcaf147af239f9e36111a0a50980561ac4befe33..c52dbcb3d8e453729bd0d1ab57dc8da94e131f58 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -424,7 +424,6 @@ impl TerminalElement { let line_height = font_pixels * line_height.to_pixels(rem_size); let font_id = cx.text_system().resolve_font(&text_style.font()); - // todo!(do we need to keep this unwrap?) let cell_width = text_system .advance(font_id, font_pixels, 'm') .unwrap() @@ -524,7 +523,6 @@ impl TerminalElement { underline: Default::default(), }], ) - //todo!(do we need to keep this unwrap?) .unwrap() }; @@ -664,21 +662,6 @@ impl TerminalElement { }, ), ); - self.interactivity.on_click({ - let terminal = terminal.clone(); - move |e, cx| { - if e.down.button == MouseButton::Right { - let mouse_mode = terminal.update(cx, |terminal, _cx| { - terminal.mouse_mode(e.down.modifiers.shift) - }); - - if !mouse_mode { - //todo!(context menu) - // view.deploy_context_menu(e.position, cx); - } - } - } - }); self.interactivity.on_scroll_wheel({ let terminal = terminal.clone(); move |e, cx| { diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 4d2e78f0daddacb0bc33673b8a14c29f54d3fdb4..b4a273dd0bc9085c4a6b3b069589ef1d9d640c5d 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -651,8 +651,10 @@ impl Render for TerminalView { .on_mouse_down( MouseButton::Right, cx.listener(|this, event: &MouseDownEvent, cx| { - this.deploy_context_menu(event.position, cx); - cx.notify(); + if !this.terminal.read(cx).mouse_mode(event.modifiers.shift) { + this.deploy_context_menu(event.position, cx); + cx.notify(); + } }), ) .child( diff --git a/crates/theme/src/styles/colors.rs b/crates/theme/src/styles/colors.rs index eb68b6c219f2090a57af9db2c9d1f67ff6dfacb9..b4d20c593067bd420974234012ee6b18908eec9f 100644 --- a/crates/theme/src/styles/colors.rs +++ b/crates/theme/src/styles/colors.rs @@ -222,6 +222,7 @@ pub struct ThemeStyles { #[refineable] pub status: StatusColors, + pub player: PlayerColors, pub syntax: Arc, } diff --git a/crates/theme/src/styles/players.rs b/crates/theme/src/styles/players.rs index d4d27e71237b0968d4642a92399dca754dada77e..b2b797db08b270513b77809437184f9b782af77d 100644 --- a/crates/theme/src/styles/players.rs +++ b/crates/theme/src/styles/players.rs @@ -122,12 +122,10 @@ impl PlayerColors { impl PlayerColors { pub fn local(&self) -> PlayerColor { - // todo!("use a valid color"); *self.0.first().unwrap() } pub fn absent(&self) -> PlayerColor { - // todo!("use a valid color"); *self.0.last().unwrap() } diff --git a/crates/vim/src/test/vim_test_context.rs b/crates/vim/src/test/vim_test_context.rs index 5ed5296bff44d3e76c32f2a4b768afd760d1d121..cf6d35044b2abb216a0494f7816e81a7b9663262 100644 --- a/crates/vim/src/test/vim_test_context.rs +++ b/crates/vim/src/test/vim_test_context.rs @@ -6,7 +6,7 @@ use editor::test::{ use futures::Future; use gpui::{Context, View, VisualContext}; use lsp::request; -use search::BufferSearchBar; +use search::{project_search::ProjectSearchBar, BufferSearchBar}; use crate::{state::Operator, *}; @@ -59,9 +59,9 @@ impl VimTestContext { pane.toolbar().update(cx, |toolbar, cx| { let buffer_search_bar = cx.new_view(BufferSearchBar::new); toolbar.add_item(buffer_search_bar, cx); - // todo!(); - // let project_search_bar = cx.add_view(|_| ProjectSearchBar::new()); - // toolbar.add_item(project_search_bar, cx); + + let project_search_bar = cx.new_view(|_| ProjectSearchBar::new()); + toolbar.add_item(project_search_bar, cx); }) }); workspace.status_bar().update(cx, |status_bar, cx| { diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index ed03695c5f2c234f3e7a633cb341da6002d344cd..0c752597262ca1f64a996d09795db289941a30b2 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -167,15 +167,6 @@ impl DockPosition { } } - // todo!() - // fn to_resize_handle_side(self) -> HandleSide { - // match self { - // Self::Left => HandleSide::Right, - // Self::Bottom => HandleSide::Top, - // Self::Right => HandleSide::Left, - // } - // } - pub fn axis(&self) -> Axis { match self { Self::Left | Self::Right => Axis::Horizontal, @@ -186,8 +177,6 @@ impl DockPosition { struct PanelEntry { panel: Arc, - // todo!() - // context_menu: View, _subscriptions: [Subscription; 2], } @@ -265,12 +254,6 @@ impl Dock { self.is_open } - // todo!() - // pub fn has_focus(&self, cx: &WindowContext) -> bool { - // self.visible_panel() - // .map_or(false, |panel| panel.has_focus(cx)) - // } - pub fn panel(&self) -> Option> { self.panel_entries .iter() @@ -417,16 +400,8 @@ impl Dock { }), ]; - // todo!() - // let dock_view_id = cx.view_id(); self.panel_entries.push(PanelEntry { panel: Arc::new(panel), - // todo!() - // context_menu: cx.add_view(|cx| { - // let mut menu = ContextMenu::new(dock_view_id, cx); - // menu.set_position_mode(OverlayPositionMode::Local); - // menu - // }), _subscriptions: subscriptions, }); cx.notify() @@ -618,7 +593,6 @@ impl PanelButtons { impl Render for PanelButtons { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - // todo!() let dock = self.dock.read(cx); let active_index = dock.active_panel_index; let is_open = dock.is_open; diff --git a/crates/workspace/src/notifications.rs b/crates/workspace/src/notifications.rs index 36628290bb83ed6f640206f20f77933a577f58e3..6d46626d3631481a900f23286c8eb7dd219b42bd 100644 --- a/crates/workspace/src/notifications.rs +++ b/crates/workspace/src/notifications.rs @@ -8,8 +8,6 @@ use std::{any::TypeId, ops::DerefMut}; pub fn init(cx: &mut AppContext) { cx.set_global(NotificationTracker::new()); - // todo!() - // simple_message_notification::init(cx); } pub trait Notification: EventEmitter + Render {} diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 2a434b32d928bcd7d59de0bca03dad5c8d79d031..dad7b50ca6c307aef780910ee03249633ccfbfda 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -242,87 +242,6 @@ pub struct DraggedTab { pub is_active: bool, } -// pub struct DraggedItem { -// pub handle: Box, -// pub pane: WeakView, -// } - -// pub enum ReorderBehavior { -// None, -// MoveAfterActive, -// MoveToIndex(usize), -// } - -// #[derive(Debug, Clone, Copy, PartialEq, Eq)] -// enum TabBarContextMenuKind { -// New, -// Split, -// } - -// struct TabBarContextMenu { -// kind: TabBarContextMenuKind, -// handle: View, -// } - -// impl TabBarContextMenu { -// fn handle_if_kind(&self, kind: TabBarContextMenuKind) -> Option> { -// if self.kind == kind { -// return Some(self.handle.clone()); -// } -// None -// } -// } - -// #[allow(clippy::too_many_arguments)] -// fn nav_button)>( -// svg_path: &'static str, -// style: theme::Interactive, -// nav_button_height: f32, -// tooltip_style: TooltipStyle, -// enabled: bool, -// on_click: F, -// tooltip_action: A, -// action_name: &str, -// cx: &mut ViewContext, -// ) -> AnyElement { -// MouseEventHandler::new::(0, cx, |state, _| { -// let style = if enabled { -// style.style_for(state) -// } else { -// style.disabled_style() -// }; -// Svg::new(svg_path) -// .with_color(style.color) -// .constrained() -// .with_width(style.icon_width) -// .aligned() -// .contained() -// .with_style(style.container) -// .constrained() -// .with_width(style.button_width) -// .with_height(nav_button_height) -// .aligned() -// .top() -// }) -// .with_cursor_style(if enabled { -// CursorStyle::PointingHand -// } else { -// CursorStyle::default() -// }) -// .on_click(MouseButton::Left, move |_, toolbar, cx| { -// on_click(toolbar, cx) -// }) -// .with_tooltip::( -// 0, -// action_name.to_string(), -// Some(Box::new(tooltip_action)), -// tooltip_style, -// cx, -// ) -// .contained() -// .into_any_named("nav button") -// } - impl EventEmitter for Pane {} impl Pane { @@ -333,13 +252,6 @@ impl Pane { can_drop_predicate: Option bool + 'static>>, cx: &mut ViewContext, ) -> Self { - // todo!("context menu") - // let pane_view_id = cx.view_id(); - // let context_menu = cx.build_view(|cx| ContextMenu::new(pane_view_id, cx)); - // context_menu.update(cx, |menu, _| { - // menu.set_position_mode(OverlayPositionMode::Local) - // }); - // let focus_handle = cx.focus_handle(); let subscriptions = vec![ @@ -370,11 +282,6 @@ impl Pane { split_item_menu: None, tab_bar_scroll_handle: ScrollHandle::new(), drag_split_direction: None, - // tab_bar_context_menu: TabBarContextMenu { - // kind: TabBarContextMenuKind::New, - // handle: context_menu, - // }, - // tab_context_menu: cx.build_view(|_| ContextMenu::new(pane_view_id, cx)), workspace, project, can_drop_predicate, @@ -450,7 +357,6 @@ impl Pane { } pub fn has_focus(&self, cx: &WindowContext) -> bool { - // todo!(); // inline this manually self.focus_handle.contains_focused(cx) } diff --git a/crates/workspace/src/searchable.rs b/crates/workspace/src/searchable.rs index 59202cbbaf53e5e4f62d77be693e3b13916b555b..e1f93f31cb22cc7fe7c7b23158de6bb034424526 100644 --- a/crates/workspace/src/searchable.rs +++ b/crates/workspace/src/searchable.rs @@ -1,8 +1,8 @@ use std::{any::Any, sync::Arc}; use gpui::{ - AnyView, AppContext, EventEmitter, Subscription, Task, View, ViewContext, WeakView, - WindowContext, + AnyView, AnyWeakView, AppContext, EventEmitter, Subscription, Task, View, ViewContext, + WeakView, WindowContext, }; use project::search::SearchQuery; @@ -127,7 +127,6 @@ pub trait SearchableItemHandle: ItemHandle { ) -> Option; } -// todo!("here is where we need to use AnyWeakView"); impl SearchableItemHandle for View { fn downgrade(&self) -> Box { Box::new(self.downgrade()) @@ -249,7 +248,7 @@ impl Eq for Box {} pub trait WeakSearchableItemHandle: WeakItemHandle { fn upgrade(&self, cx: &AppContext) -> Option>; - // fn into_any(self) -> AnyWeakView; + fn into_any(self) -> AnyWeakView; } impl WeakSearchableItemHandle for WeakView { @@ -257,9 +256,9 @@ impl WeakSearchableItemHandle for WeakView { Some(Box::new(self.upgrade()?)) } - // fn into_any(self) -> AnyView { - // self.into_any() - // } + fn into_any(self) -> AnyWeakView { + self.into() + } } impl PartialEq for Box { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 739ce88636ca32a88be1496103ad03ed42f63808..2bff02b4f6b14ead673d359e7c579e9f00f007e5 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1143,7 +1143,6 @@ impl Workspace { quitting: bool, cx: &mut ViewContext, ) -> Task> { - //todo!(saveing) let active_call = self.active_call().cloned(); let window = cx.window_handle(); @@ -1694,28 +1693,6 @@ impl Workspace { None } - // todo!("implement zoom") - #[allow(unused)] - fn zoom_out(&mut self, cx: &mut ViewContext) { - for pane in &self.panes { - pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); - } - - self.left_dock.update(cx, |dock, cx| dock.zoom_out(cx)); - self.bottom_dock.update(cx, |dock, cx| dock.zoom_out(cx)); - self.right_dock.update(cx, |dock, cx| dock.zoom_out(cx)); - self.zoomed = None; - self.zoomed_position = None; - - cx.notify(); - } - - // todo!() - // #[cfg(any(test, feature = "test-support"))] - // pub fn zoomed_view(&self, cx: &AppContext) -> Option { - // self.zoomed.and_then(|view| view.upgrade(cx)) - // } - fn dismiss_zoomed_items_to_reveal( &mut self, dock_to_reveal: Option, diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 0b90961d2352a31e80f38dbd3eda86df4055e788..38e0bec14e30418bee589450bc21cccc31364daf 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -23,7 +23,7 @@ use quick_action_bar::QuickActionBar; use search::project_search::ProjectSearchBar; use settings::{initial_local_settings_content, KeymapFile, Settings, SettingsStore}; use std::{borrow::Cow, ops::Deref, sync::Arc}; -use terminal_view::terminal_panel::TerminalPanel; +use terminal_view::terminal_panel::{self, TerminalPanel}; use util::{ asset_str, channel::{AppCommitSha, ReleaseChannel}, @@ -299,79 +299,42 @@ pub fn initialize_workspace(app_state: Arc, cx: &mut AppContext) { ); }, ) - //todo!() - // cx.add_action({ - // move |workspace: &mut Workspace, _: &DebugElements, cx: &mut ViewContext| { - // let app_state = workspace.app_state().clone(); - // let markdown = app_state.languages.language_for_name("JSON"); - // let window = cx.window(); - // cx.spawn(|workspace, mut cx| async move { - // let markdown = markdown.await.log_err(); - // let content = to_string_pretty(&window.debug_elements(&cx).ok_or_else(|| { - // anyhow!("could not debug elements for window {}", window.id()) - // })?) - // .unwrap(); - // workspace - // .update(&mut cx, |workspace, cx| { - // workspace.with_local_workspace(cx, move |workspace, cx| { - // let project = workspace.project().clone(); - // let buffer = project - // .update(cx, |project, cx| { - // project.create_buffer(&content, markdown, cx) - // }) - // .expect("creating buffers on a local workspace always succeeds"); - // let buffer = cx.add_model(|cx| { - // MultiBuffer::singleton(buffer, cx) - // .with_title("Debug Elements".into()) - // }); - // workspace.add_item( - // Box::new(cx.add_view(|cx| { - // Editor::for_multibuffer(buffer, Some(project.clone()), cx) - // })), - // cx, - // ); - // }) - // })? - // .await - // }) - // .detach_and_log_err(cx); - // } - // }); - // .register_action( - // |workspace: &mut Workspace, - // _: &project_panel::ToggleFocus, - // cx: &mut ViewContext| { - // workspace.toggle_panel_focus::(cx); - // }, - // ); - // cx.add_action( - // |workspace: &mut Workspace, - // _: &collab_ui::collab_panel::ToggleFocus, - // cx: &mut ViewContext| { - // workspace.toggle_panel_focus::(cx); - // }, - // ); - // cx.add_action( - // |workspace: &mut Workspace, - // _: &collab_ui::chat_panel::ToggleFocus, - // cx: &mut ViewContext| { - // workspace.toggle_panel_focus::(cx); - // }, - // ); - // cx.add_action( - // |workspace: &mut Workspace, - // _: &collab_ui::notification_panel::ToggleFocus, - // cx: &mut ViewContext| { - // workspace.toggle_panel_focus::(cx); - // }, - // ); - // cx.add_action( - // |workspace: &mut Workspace, - // _: &terminal_panel::ToggleFocus, - // cx: &mut ViewContext| { - // workspace.toggle_panel_focus::(cx); - // }, - // ); + .register_action( + |workspace: &mut Workspace, + _: &project_panel::ToggleFocus, + cx: &mut ViewContext| { + workspace.toggle_panel_focus::(cx); + }, + ) + .register_action( + |workspace: &mut Workspace, + _: &collab_ui::collab_panel::ToggleFocus, + cx: &mut ViewContext| { + workspace.toggle_panel_focus::(cx); + }, + ) + .register_action( + |workspace: &mut Workspace, + _: &collab_ui::chat_panel::ToggleFocus, + cx: &mut ViewContext| { + workspace.toggle_panel_focus::(cx); + }, + ) + .register_action( + |workspace: &mut Workspace, + _: &collab_ui::notification_panel::ToggleFocus, + cx: &mut ViewContext| { + workspace + .toggle_panel_focus::(cx); + }, + ) + .register_action( + |workspace: &mut Workspace, + _: &terminal_panel::ToggleFocus, + cx: &mut ViewContext| { + workspace.toggle_panel_focus::(cx); + }, + ) .register_action({ let app_state = Arc::downgrade(&app_state); move |_, _: &NewWindow, cx| { @@ -1658,8 +1621,8 @@ mod tests { }) .unwrap(); save_task.await.unwrap(); - // todo!() po - //assert!(!cx.did_prompt_for_new_path()); + + assert!(!cx.did_prompt_for_new_path()); window .update(cx, |_, cx| { editor.update(cx, |editor, cx| { diff --git a/script/bundle b/script/bundle index 462706679905cbd1b4250f2b455120dfe3e9fcf1..9c0dddbac49bf71dc9ca55534284afadacef9b52 100755 --- a/script/bundle +++ b/script/bundle @@ -132,7 +132,7 @@ else cp -R target/${target_dir}/WebRTC.framework "${app_path}/Contents/Frameworks/" fi -#todo!(The app identifier has been set to 'Dev', but the channel is nightly, RATIONALIZE ALL OF THIS MESS) +# Note: The app identifier for our development builds is the same as the app identifier for nightly. cp crates/${zed_crate}/contents/$channel/embedded.provisionprofile "${app_path}/Contents/" if [[ -n $MACOS_CERTIFICATE && -n $MACOS_CERTIFICATE_PASSWORD && -n $APPLE_NOTARIZATION_USERNAME && -n $APPLE_NOTARIZATION_PASSWORD ]]; then From e786e221212bcce55c47cd6d7e553ded137e2daa Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 9 Jan 2024 16:31:12 -0800 Subject: [PATCH 62/78] And a few more todos --- crates/collab/src/tests/editor_tests.rs | 11 +++++++---- crates/gpui/src/platform/test/platform.rs | 3 +-- crates/vcs_menu/src/lib.rs | 1 - crates/workspace/src/pane_group.rs | 11 +---------- crates/workspace/src/workspace.rs | 6 +++++- 5 files changed, 14 insertions(+), 18 deletions(-) diff --git a/crates/collab/src/tests/editor_tests.rs b/crates/collab/src/tests/editor_tests.rs index 6f06e9f10faadf77d44eecbf43f6026c2799adca..0c3601b07531bf5c77459fd5530a31ba8ef68717 100644 --- a/crates/collab/src/tests/editor_tests.rs +++ b/crates/collab/src/tests/editor_tests.rs @@ -71,6 +71,7 @@ async fn test_host_disconnect( let workspace_b = cx_b.add_window(|cx| Workspace::new(0, project_b.clone(), client_b.app_state.clone(), cx)); let cx_b = &mut VisualTestContext::from_window(*workspace_b, cx_b); + let workspace_b_view = workspace_b.root_view(cx_b).unwrap(); let editor_b = workspace_b .update(cx_b, |workspace, cx| { @@ -85,8 +86,10 @@ async fn test_host_disconnect( //TODO: focus assert!(cx_b.update_view(&editor_b, |editor, cx| editor.is_focused(cx))); editor_b.update(cx_b, |editor, cx| editor.insert("X", cx)); - //todo(is_edited) - // assert!(workspace_b.is_edited(cx_b)); + + cx_b.update(|cx| { + assert!(workspace_b_view.read(cx).is_edited()); + }); // Drop client A's connection. Collaborators should disappear and the project should not be shown as shared. server.forbid_connections(); @@ -105,11 +108,11 @@ async fn test_host_disconnect( // Ensure client B's edited state is reset and that the whole window is blurred. workspace_b - .update(cx_b, |_, cx| { + .update(cx_b, |workspace, cx| { assert_eq!(cx.focused(), None); + assert!(!workspace.is_edited()) }) .unwrap(); - // assert!(!workspace_b.is_edited(cx_b)); // Ensure client B is not prompted to save edits when closing window after disconnecting. let can_close = workspace_b diff --git a/crates/gpui/src/platform/test/platform.rs b/crates/gpui/src/platform/test/platform.rs index 38f23b94f20d43cc9b491f4269558240577fea4d..a7dc6d48419cc2e5d54dc132617752ae030e561e 100644 --- a/crates/gpui/src/platform/test/platform.rs +++ b/crates/gpui/src/platform/test/platform.rs @@ -282,8 +282,7 @@ impl Platform for TestPlatform { } fn should_auto_hide_scrollbars(&self) -> bool { - // todo() - true + false } fn write_to_clipboard(&self, item: ClipboardItem) { diff --git a/crates/vcs_menu/src/lib.rs b/crates/vcs_menu/src/lib.rs index 2735f81677c86331e554dd3f27b2aa2362587d5d..0774c6f5755324de77b16f691ee0dde11ed38f16 100644 --- a/crates/vcs_menu/src/lib.rs +++ b/crates/vcs_menu/src/lib.rs @@ -18,7 +18,6 @@ use workspace::{ModalView, Toast, Workspace}; actions!(branches, [OpenRecent]); pub fn init(cx: &mut AppContext) { - // todo!() po cx.observe_new_views(|workspace: &mut Workspace, _| { workspace.register_action(|workspace, action, cx| { BranchList::toggle_modal(workspace, action, cx).log_err(); diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index e28d0e63deda2a97ac3c4a9cd77d4beebb802333..3dcdeec37f4e2cceb9e2159d6577b303f5355979 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -12,7 +12,7 @@ use serde::Deserialize; use std::sync::Arc; use ui::{prelude::*, Button}; -const HANDLE_HITBOX_SIZE: f32 = 4.0; +pub const HANDLE_HITBOX_SIZE: f32 = 4.0; const HORIZONTAL_MIN_SIZE: f32 = 80.; const VERTICAL_MIN_SIZE: f32 = 100.; @@ -268,15 +268,6 @@ impl Member { ) }) .into_any() - - // let el = div() - // .flex() - // .flex_1() - // .gap_px() - // .w_full() - // .h_full() - // .bg(cx.theme().colors().editor) - // .children(); } Member::Axis(axis) => axis .render( diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 2bff02b4f6b14ead673d359e7c579e9f00f007e5..a1d6ab499ae11a77425b2430eb9a0c5420f5011e 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -852,6 +852,10 @@ impl Workspace { &self.right_dock } + pub fn is_edited(&self) -> bool { + self.window_edited + } + pub fn add_panel(&mut self, panel: View, cx: &mut ViewContext) { let dock = match panel.position(cx) { DockPosition::Left => &self.left_dock, @@ -2055,7 +2059,7 @@ impl Workspace { _ => bounding_box.center(), }; - let distance_to_next = 8.; //todo(pane dividers styling) + let distance_to_next = pane_group::HANDLE_HITBOX_SIZE; let target = match direction { SplitDirection::Left => { From 1bf33b4b61acfda1cf4ee0c6f67e4059bc6e839a Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 9 Jan 2024 20:31:13 -0700 Subject: [PATCH 63/78] Ensure focus_in and focus_out fire on window activation Also: - Rename cx.on_blur to cx.on_focus_lost - Fix a bug where notify calls in focus handlers were ignored - Fix a bug where vim would get stuck in the wrong mode when switching windows --- crates/gpui/src/window.rs | 63 ++++++++++++++++++++----------- crates/vim/src/editor_events.rs | 40 +++++++++++++++++++- crates/workspace/src/workspace.rs | 2 +- 3 files changed, 80 insertions(+), 25 deletions(-) diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index ec4713639e930cd759a8af5cb4435ea588c5ec30..bd0e72054982d4d3760d0d06b32b17fe58c0c38f 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -269,7 +269,7 @@ pub struct Window { frame_arena: Arena, pub(crate) focus_handles: Arc>>, focus_listeners: SubscriberSet<(), AnyWindowFocusListener>, - blur_listeners: SubscriberSet<(), AnyObserver>, + focus_lost_listeners: SubscriberSet<(), AnyObserver>, default_prevented: bool, mouse_position: Point, modifiers: Modifiers, @@ -296,6 +296,7 @@ pub(crate) struct ElementStateBox { pub(crate) struct Frame { focus: Option, + window_active: bool, pub(crate) element_states: FxHashMap, mouse_listeners: FxHashMap>, pub(crate) dispatch_tree: DispatchTree, @@ -311,6 +312,7 @@ impl Frame { fn new(dispatch_tree: DispatchTree) -> Self { Frame { focus: None, + window_active: false, element_states: FxHashMap::default(), mouse_listeners: FxHashMap::default(), dispatch_tree, @@ -417,7 +419,7 @@ impl Window { frame_arena: Arena::new(1024 * 1024), focus_handles: Arc::new(RwLock::new(SlotMap::with_key())), focus_listeners: SubscriberSet::new(), - blur_listeners: SubscriberSet::new(), + focus_lost_listeners: SubscriberSet::new(), default_prevented: true, mouse_position, modifiers, @@ -1406,29 +1408,14 @@ impl<'a> WindowContext<'a> { self.window.focus, ); self.window.next_frame.focus = self.window.focus; + self.window.next_frame.window_active = self.window.active; self.window.root_view = Some(root_view); let previous_focus_path = self.window.rendered_frame.focus_path(); + let previous_window_active = self.window.rendered_frame.window_active; mem::swap(&mut self.window.rendered_frame, &mut self.window.next_frame); let current_focus_path = self.window.rendered_frame.focus_path(); - - if previous_focus_path != current_focus_path { - if !previous_focus_path.is_empty() && current_focus_path.is_empty() { - self.window - .blur_listeners - .clone() - .retain(&(), |listener| listener(self)); - } - - let event = FocusEvent { - previous_focus_path, - current_focus_path, - }; - self.window - .focus_listeners - .clone() - .retain(&(), |listener| listener(&event, self)); - } + let current_window_active = self.window.rendered_frame.window_active; let scene = self.window.rendered_frame.scene_builder.build(); @@ -1445,6 +1432,34 @@ impl<'a> WindowContext<'a> { self.window.drawing = false; ELEMENT_ARENA.with_borrow_mut(|element_arena| element_arena.clear()); + if previous_focus_path != current_focus_path + || previous_window_active != current_window_active + { + if !previous_focus_path.is_empty() && current_focus_path.is_empty() { + self.window + .focus_lost_listeners + .clone() + .retain(&(), |listener| listener(self)); + } + + let event = FocusEvent { + previous_focus_path: if previous_window_active { + previous_focus_path + } else { + Default::default() + }, + current_focus_path: if current_window_active { + current_focus_path + } else { + Default::default() + }, + }; + self.window + .focus_listeners + .clone() + .retain(&(), |listener| listener(&event, self)); + } + scene } @@ -2645,14 +2660,16 @@ impl<'a, V: 'static> ViewContext<'a, V> { subscription } - /// Register a listener to be called when the window loses focus. + /// Register a listener to be called when nothing in the window has focus. + /// This typically happens when the node that was focused is removed from the tree, + /// and this callback lets you chose a default place to restore the users focus. /// Returns a subscription and persists until the subscription is dropped. - pub fn on_blur_window( + pub fn on_focus_lost( &mut self, mut listener: impl FnMut(&mut V, &mut ViewContext) + 'static, ) -> Subscription { let view = self.view.downgrade(); - let (subscription, activate) = self.window.blur_listeners.insert( + let (subscription, activate) = self.window.focus_lost_listeners.insert( (), Box::new(move |cx| view.update(cx, |view, cx| listener(view, cx)).is_ok()), ); diff --git a/crates/vim/src/editor_events.rs b/crates/vim/src/editor_events.rs index 3c2f373f9455209a36ef9dac88c3144f7f1a4bdd..e3c759aaebdcc7de27d593036a7046e4784269b1 100644 --- a/crates/vim/src/editor_events.rs +++ b/crates/vim/src/editor_events.rs @@ -69,7 +69,7 @@ fn released(entity_id: EntityId, cx: &mut AppContext) { mod test { use crate::{test::VimTestContext, Vim}; use editor::Editor; - use gpui::{Context, Entity}; + use gpui::{Context, Entity, VisualTestContext}; use language::Buffer; // regression test for blur called with a different active editor @@ -101,4 +101,42 @@ mod test { editor1.handle_blur(cx); }); } + + // regression test for focus_in/focus_out being called on window activation + #[gpui::test] + async fn test_focus_across_windows(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + + let mut cx1 = VisualTestContext::from_window(cx.window, &cx); + let editor1 = cx.editor.clone(); + dbg!(editor1.entity_id()); + + let buffer = cx.new_model(|_| Buffer::new(0, 0, "a = 1\nb = 2\n")); + let (editor2, cx2) = cx.add_window_view(|cx| Editor::for_buffer(buffer, None, cx)); + + editor2.update(cx2, |_, cx| { + cx.focus_self(); + cx.activate_window(); + }); + cx.run_until_parked(); + + cx1.update(|cx| { + assert_eq!( + Vim::read(cx).active_editor.as_ref().unwrap().entity_id(), + editor2.entity_id(), + ) + }); + + cx1.update(|cx| { + cx.activate_window(); + }); + cx.run_until_parked(); + + cx.update(|cx| { + assert_eq!( + Vim::read(cx).active_editor.as_ref().unwrap().entity_id(), + editor1.entity_id(), + ) + }); + } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 09e0a1378d440e0bbf2eb2a7dc21ec88557fa320..ad74f44f17bb9775b74cde8ecbc32fc377f8503d 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -537,7 +537,7 @@ impl Workspace { }) .detach(); - cx.on_blur_window(|this, cx| { + cx.on_focus_lost(|this, cx| { let focus_handle = this.focus_handle(cx); cx.focus(&focus_handle); }) From 72c022f4135e26359fb215feb1ab17b552bbd140 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 9 Jan 2024 21:37:25 -0700 Subject: [PATCH 64/78] Ensure focus-sensitive tests have active windows --- crates/collab/src/tests/following_tests.rs | 4 ++++ crates/collab/src/tests/test_server.rs | 5 ++++- crates/gpui/src/app/test_context.rs | 1 + crates/vim/src/editor_events.rs | 2 ++ 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/crates/collab/src/tests/following_tests.rs b/crates/collab/src/tests/following_tests.rs index 0486e294619fd4fd34604c6cc4113fb03f1057ad..d50b34859d13c19d9cac7b7796ba3b840b2da3a9 100644 --- a/crates/collab/src/tests/following_tests.rs +++ b/crates/collab/src/tests/following_tests.rs @@ -76,6 +76,10 @@ async fn test_basic_following( let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a); let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); + cx_b.update(|cx| { + assert!(cx.is_window_active()); + }); + // Client A opens some editors. let pane_a = workspace_a.update(cx_a, |workspace, _| workspace.active_pane().clone()); let editor_a1 = workspace_a diff --git a/crates/collab/src/tests/test_server.rs b/crates/collab/src/tests/test_server.rs index 034a85961f8e98966a2764c4196682fabf4b33cf..4f3634c7b1f9c9575a177810d9e5eb89ed12c891 100644 --- a/crates/collab/src/tests/test_server.rs +++ b/crates/collab/src/tests/test_server.rs @@ -617,7 +617,10 @@ impl TestClient { project: &Model, cx: &'a mut TestAppContext, ) -> (View, &'a mut VisualTestContext) { - cx.add_window_view(|cx| Workspace::new(0, project.clone(), self.app_state.clone(), cx)) + cx.add_window_view(|cx| { + cx.activate_window(); + Workspace::new(0, project.clone(), self.app_state.clone(), cx) + }) } } diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index de31339b8d79bb3b4a80d4e8c9bc95834ffef92c..7ff751ef1ce7232e7714eac6bf2ddc87892a9e39 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -174,6 +174,7 @@ impl TestAppContext { drop(cx); let view = window.root_view(self).unwrap(); let cx = Box::new(VisualTestContext::from_window(*window.deref(), self)); + cx.run_until_parked(); // it might be nice to try and cleanup these at the end of each test. (view, Box::leak(cx)) } diff --git a/crates/vim/src/editor_events.rs b/crates/vim/src/editor_events.rs index e3c759aaebdcc7de27d593036a7046e4784269b1..e3ed076698d101a12f92c16048748532ea596e1c 100644 --- a/crates/vim/src/editor_events.rs +++ b/crates/vim/src/editor_events.rs @@ -82,11 +82,13 @@ mod test { let editor2 = cx .update(|cx| { window2.update(cx, |_, cx| { + cx.activate_window(); cx.focus_self(); cx.view().clone() }) }) .unwrap(); + cx.run_until_parked(); cx.update(|cx| { let vim = Vim::read(cx); From 2ca462722cea521dc66935e88f87f1b3fffc78ca Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 9 Jan 2024 22:11:09 -0700 Subject: [PATCH 65/78] Fix some tests (mostly more run_until_parked's...) --- crates/collab/src/tests/channel_tests.rs | 1 + crates/collab/src/tests/following_tests.rs | 15 +++++---------- crates/collab/src/tests/integration_tests.rs | 1 + crates/gpui/src/app/test_context.rs | 3 ++- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/crates/collab/src/tests/channel_tests.rs b/crates/collab/src/tests/channel_tests.rs index 49e7060301a4279adda4439aa39784b066ed76dd..2a88bc4c579db9855184e278d1fef37200d1f470 100644 --- a/crates/collab/src/tests/channel_tests.rs +++ b/crates/collab/src/tests/channel_tests.rs @@ -1337,6 +1337,7 @@ async fn test_guest_access( }) .await .unwrap(); + executor.run_until_parked(); assert_channels_list_shape(client_b.channel_store(), cx_b, &[]); diff --git a/crates/collab/src/tests/following_tests.rs b/crates/collab/src/tests/following_tests.rs index 0486e294619fd4fd34604c6cc4113fb03f1057ad..46524319fbb4aab97d570f801c8b187867a52af4 100644 --- a/crates/collab/src/tests/following_tests.rs +++ b/crates/collab/src/tests/following_tests.rs @@ -234,14 +234,14 @@ async fn test_basic_following( workspace_c.update(cx_c, |workspace, cx| { workspace.close_window(&Default::default(), cx); }); - cx_c.update(|_| { - drop(workspace_c); - }); - cx_b.executor().run_until_parked(); + executor.run_until_parked(); // are you sure you want to leave the call? cx_c.simulate_prompt_answer(0); - cx_b.executor().run_until_parked(); + cx_c.cx.update(|_| { + drop(workspace_c); + }); executor.run_until_parked(); + cx_c.cx.update(|_| {}); weak_workspace_c.assert_dropped(); weak_project_c.assert_dropped(); @@ -1363,8 +1363,6 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut let mut server = TestServer::start(executor.clone()).await; let client_a = server.create_client(cx_a, "user_a").await; let client_b = server.create_client(cx_b, "user_b").await; - cx_a.update(editor::init); - cx_b.update(editor::init); client_a .fs() @@ -1400,9 +1398,6 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a); let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); - cx_a.update(|cx| collab_ui::init(&client_a.app_state, cx)); - cx_b.update(|cx| collab_ui::init(&client_b.app_state, cx)); - active_call_a .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .await diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index a21235b6f3914a8cc6e181c9ae128da011f8bce3..cedc841527ff5c4c40d2630dc9c15166ccec46d8 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -3065,6 +3065,7 @@ async fn test_local_settings( .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .await .unwrap(); + executor.run_until_parked(); // As client B, join that project and observe the local settings. let project_b = client_b.build_remote_project(project_id, cx_b).await; diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index 2b6c361a782bc7f4852aa5755fbfdd31f2b6a36f..d20743483581f1b0cfe771889dd36b855b7d2ed2 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -545,7 +545,8 @@ use derive_more::{Deref, DerefMut}; pub struct VisualTestContext { #[deref] #[deref_mut] - cx: TestAppContext, + /// cx is the original TestAppContext (you can more easily access this using Deref) + pub cx: TestAppContext, window: AnyWindowHandle, } From 6809b92e34bb038f8e0486dcd7dd471384aafccb Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 10 Jan 2024 10:16:09 +0200 Subject: [PATCH 66/78] Disable synthetic drag on drag and drop Otherwise, conflicting MouseMove events are generated and page regions start to flicker. --- crates/gpui/src/platform/mac/window.rs | 27 +++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 2beac528c18f53cfa9a39b008dbebf3825502b30..6d03a3b5cd698c62147bd75b62c3594309f38797 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -338,6 +338,7 @@ struct MacWindowState { ime_state: ImeState, // Retains the last IME Text ime_text: Option, + external_files_dragged: bool, } impl MacWindowState { @@ -567,6 +568,7 @@ impl MacWindow { previous_modifiers_changed_event: None, ime_state: ImeState::None, ime_text: None, + external_files_dragged: false, }))); (*native_window).set_ivar( @@ -1223,15 +1225,20 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { .. }, ) => { - lock.synthetic_drag_counter += 1; - let executor = lock.executor.clone(); - executor - .spawn(synthetic_drag( - weak_window_state, - lock.synthetic_drag_counter, - event.clone(), - )) - .detach(); + // Synthetic drag is used for selecting long buffer contents while buffer is being scrolled. + // External file drag and drop is able to emit its own synthetic mouse events which will conflict + // with these ones. + if !lock.external_files_dragged { + lock.synthetic_drag_counter += 1; + let executor = lock.executor.clone(); + executor + .spawn(synthetic_drag( + weak_window_state, + lock.synthetic_drag_counter, + event.clone(), + )) + .detach(); + } } InputEvent::MouseMove(_) if !(is_active || lock.kind == WindowKind::PopUp) => return, @@ -1675,6 +1682,7 @@ extern "C" fn dragging_entered(this: &Object, _: Sel, dragging_info: id) -> NSDr let paths = external_paths_from_event(dragging_info); InputEvent::FileDrop(FileDropEvent::Entered { position, paths }) }) { + window_state.lock().external_files_dragged = true; NSDragOperationCopy } else { NSDragOperationNone @@ -1697,6 +1705,7 @@ extern "C" fn dragging_updated(this: &Object, _: Sel, dragging_info: id) -> NSDr extern "C" fn dragging_exited(this: &Object, _: Sel, _: id) { let window_state = unsafe { get_window_state(this) }; send_new_event(&window_state, InputEvent::FileDrop(FileDropEvent::Exited)); + window_state.lock().external_files_dragged = false; } extern "C" fn perform_drag_operation(this: &Object, _: Sel, dragging_info: id) -> BOOL { From 927e0db750a55460c58b07947b2773cbd8284bc6 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 9 Jan 2024 23:31:06 +0200 Subject: [PATCH 67/78] An attempt to defer scrolls during empty initial state --- crates/gpui/src/elements/uniform_list.rs | 32 +++++++++++++++++------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/crates/gpui/src/elements/uniform_list.rs b/crates/gpui/src/elements/uniform_list.rs index ffa678e9e5ed4ef8281c97067bc411451dda507e..244e8cde088be5942ad4a5887733b8b9795adbd7 100644 --- a/crates/gpui/src/elements/uniform_list.rs +++ b/crates/gpui/src/elements/uniform_list.rs @@ -64,7 +64,10 @@ pub struct UniformList { } #[derive(Clone, Default)] -pub struct UniformListScrollHandle(Rc>>); +pub struct UniformListScrollHandle { + state: Rc>>, + deferred_scroll_to_item: Option, +} #[derive(Clone, Debug)] struct ScrollHandleState { @@ -75,11 +78,14 @@ struct ScrollHandleState { impl UniformListScrollHandle { pub fn new() -> Self { - Self(Rc::new(RefCell::new(None))) + Self { + state: Rc::new(RefCell::new(None)), + deferred_scroll_to_item: None, + } } - pub fn scroll_to_item(&self, ix: usize) { - if let Some(state) = &*self.0.borrow() { + pub fn scroll_to_item(&mut self, ix: usize) { + if let Some(state) = &*self.state.borrow() { let mut scroll_offset = state.scroll_offset.borrow_mut(); let item_top = state.item_height * ix; let item_bottom = item_top + state.item_height; @@ -89,13 +95,16 @@ impl UniformListScrollHandle { } else if item_bottom > scroll_top + state.list_height { scroll_offset.y = -(item_bottom - state.list_height); } + } else { + self.deferred_scroll_to_item = Some(ix); } } - pub fn scroll_top(&self) -> Pixels { - if let Some(state) = &*self.0.borrow() { + pub fn scroll_top(&mut self) -> Pixels { + if let Some(state) = &*self.state.borrow() { -state.scroll_offset.borrow().y } else { + self.deferred_scroll_to_item = Some(0); Pixels::ZERO } } @@ -192,7 +201,7 @@ impl Element for UniformList { .scroll_offset .get_or_insert_with(|| { if let Some(scroll_handle) = self.scroll_handle.as_ref() { - if let Some(scroll_handle) = scroll_handle.0.borrow().as_ref() { + if let Some(scroll_handle) = scroll_handle.state.borrow().as_ref() { return scroll_handle.scroll_offset.clone(); } } @@ -228,12 +237,17 @@ impl Element for UniformList { scroll_offset.y = min_scroll_offset; } - if let Some(scroll_handle) = self.scroll_handle.clone() { - scroll_handle.0.borrow_mut().replace(ScrollHandleState { + if let Some(scroll_handle) = self.scroll_handle.as_mut() { + scroll_handle.state.borrow_mut().replace(ScrollHandleState { item_height, list_height: padded_bounds.size.height, scroll_offset: shared_scroll_offset, }); + if let Some(ix) = scroll_handle.deferred_scroll_to_item.take() { + dbg!("@@@@"); + scroll_handle.scroll_to_item(ix); + cx.notify(); + } } let first_visible_element_ix = From f57ff1c660d85e596dcbfa8237f8c7471822c7d7 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 10 Jan 2024 12:16:06 +0200 Subject: [PATCH 68/78] Make the scroll position updated as soon as possible to the correct deferred value Co-Authored-By: Antonio Scandurra --- crates/gpui/src/elements/uniform_list.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/crates/gpui/src/elements/uniform_list.rs b/crates/gpui/src/elements/uniform_list.rs index 244e8cde088be5942ad4a5887733b8b9795adbd7..793474f10b42cf429d66be2daaaa35b1cddc5a3d 100644 --- a/crates/gpui/src/elements/uniform_list.rs +++ b/crates/gpui/src/elements/uniform_list.rs @@ -95,6 +95,7 @@ impl UniformListScrollHandle { } else if item_bottom > scroll_top + state.list_height { scroll_offset.y = -(item_bottom - state.list_height); } + self.deferred_scroll_to_item = None; } else { self.deferred_scroll_to_item = Some(ix); } @@ -104,7 +105,6 @@ impl UniformListScrollHandle { if let Some(state) = &*self.state.borrow() { -state.scroll_offset.borrow().y } else { - self.deferred_scroll_to_item = Some(0); Pixels::ZERO } } @@ -241,12 +241,15 @@ impl Element for UniformList { scroll_handle.state.borrow_mut().replace(ScrollHandleState { item_height, list_height: padded_bounds.size.height, - scroll_offset: shared_scroll_offset, + scroll_offset: shared_scroll_offset.clone(), }); - if let Some(ix) = scroll_handle.deferred_scroll_to_item.take() { - dbg!("@@@@"); - scroll_handle.scroll_to_item(ix); - cx.notify(); + if let Some(scroll_handle) = self.scroll_handle.as_mut() { + if scroll_handle.state.borrow().is_some() { + if let Some(ix) = scroll_handle.deferred_scroll_to_item.take() { + scroll_handle.scroll_to_item(ix); + scroll_offset = *shared_scroll_offset.borrow(); + } + } } } From c197ea49ca0c4c975db323ccd509658e26f810af Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 10 Jan 2024 13:44:08 +0200 Subject: [PATCH 69/78] Simplify uniform list scrolling logic --- crates/gpui/src/elements/uniform_list.rs | 76 ++++-------- crates/language_tools/src/syntax_tree_view.rs | 112 ++++++++---------- 2 files changed, 67 insertions(+), 121 deletions(-) diff --git a/crates/gpui/src/elements/uniform_list.rs b/crates/gpui/src/elements/uniform_list.rs index 793474f10b42cf429d66be2daaaa35b1cddc5a3d..77ef7df10c206c35ff59d354e1c90c88e7cbec5f 100644 --- a/crates/gpui/src/elements/uniform_list.rs +++ b/crates/gpui/src/elements/uniform_list.rs @@ -1,7 +1,7 @@ use crate::{ point, px, size, AnyElement, AvailableSpace, BorrowWindow, Bounds, ContentMask, Element, ElementId, InteractiveElement, InteractiveElementState, Interactivity, IntoElement, LayoutId, - Pixels, Point, Render, Size, StyleRefinement, Styled, View, ViewContext, WindowContext, + Pixels, Render, Size, StyleRefinement, Styled, View, ViewContext, WindowContext, }; use smallvec::SmallVec; use std::{cell::RefCell, cmp, ops::Range, rc::Rc}; @@ -65,48 +65,18 @@ pub struct UniformList { #[derive(Clone, Default)] pub struct UniformListScrollHandle { - state: Rc>>, - deferred_scroll_to_item: Option, -} - -#[derive(Clone, Debug)] -struct ScrollHandleState { - item_height: Pixels, - list_height: Pixels, - scroll_offset: Rc>>, + deferred_scroll_to_item: Rc>>, } impl UniformListScrollHandle { pub fn new() -> Self { Self { - state: Rc::new(RefCell::new(None)), - deferred_scroll_to_item: None, + deferred_scroll_to_item: Rc::new(RefCell::new(None)), } } pub fn scroll_to_item(&mut self, ix: usize) { - if let Some(state) = &*self.state.borrow() { - let mut scroll_offset = state.scroll_offset.borrow_mut(); - let item_top = state.item_height * ix; - let item_bottom = item_top + state.item_height; - let scroll_top = -scroll_offset.y; - if item_top < scroll_top { - scroll_offset.y = -item_top; - } else if item_bottom > scroll_top + state.list_height { - scroll_offset.y = -(item_bottom - state.list_height); - } - self.deferred_scroll_to_item = None; - } else { - self.deferred_scroll_to_item = Some(ix); - } - } - - pub fn scroll_top(&mut self) -> Pixels { - if let Some(state) = &*self.state.borrow() { - -state.scroll_offset.borrow().y - } else { - Pixels::ZERO - } + self.deferred_scroll_to_item.replace(Some(ix)); } } @@ -199,18 +169,14 @@ impl Element for UniformList { let shared_scroll_offset = element_state .interactive .scroll_offset - .get_or_insert_with(|| { - if let Some(scroll_handle) = self.scroll_handle.as_ref() { - if let Some(scroll_handle) = scroll_handle.state.borrow().as_ref() { - return scroll_handle.scroll_offset.clone(); - } - } - - Rc::default() - }) + .get_or_insert_with(|| Rc::default()) .clone(); let item_height = self.measure_item(Some(padded_bounds.size.width), cx).height; + let shared_scroll_to_item = self + .scroll_handle + .as_mut() + .and_then(|handle| handle.deferred_scroll_to_item.take()); self.interactivity.paint( bounds, @@ -237,20 +203,18 @@ impl Element for UniformList { scroll_offset.y = min_scroll_offset; } - if let Some(scroll_handle) = self.scroll_handle.as_mut() { - scroll_handle.state.borrow_mut().replace(ScrollHandleState { - item_height, - list_height: padded_bounds.size.height, - scroll_offset: shared_scroll_offset.clone(), - }); - if let Some(scroll_handle) = self.scroll_handle.as_mut() { - if scroll_handle.state.borrow().is_some() { - if let Some(ix) = scroll_handle.deferred_scroll_to_item.take() { - scroll_handle.scroll_to_item(ix); - scroll_offset = *shared_scroll_offset.borrow(); - } - } + if let Some(ix) = shared_scroll_to_item { + let list_height = padded_bounds.size.height; + let mut updated_scroll_offset = shared_scroll_offset.borrow_mut(); + let item_top = item_height * ix; + let item_bottom = item_top + item_height; + let scroll_top = -updated_scroll_offset.y; + if item_top < scroll_top { + updated_scroll_offset.y = -item_top; + } else if item_bottom > scroll_top + list_height { + updated_scroll_offset.y = -(item_bottom - list_height); } + scroll_offset = *updated_scroll_offset; } let first_visible_element_ix = diff --git a/crates/language_tools/src/syntax_tree_view.rs b/crates/language_tools/src/syntax_tree_view.rs index c30564e9bfe3f8c0e0fd1f5c2f6827a4f070fd81..b49e4eb649ae28fea72ad2d83402653961caf0b8 100644 --- a/crates/language_tools/src/syntax_tree_view.rs +++ b/crates/language_tools/src/syntax_tree_view.rs @@ -2,13 +2,12 @@ use editor::{scroll::autoscroll::Autoscroll, Anchor, Editor, ExcerptId}; use gpui::{ actions, canvas, div, rems, uniform_list, AnyElement, AppContext, AvailableSpace, Div, EventEmitter, FocusHandle, FocusableView, Hsla, InteractiveElement, IntoElement, Model, - MouseButton, MouseDownEvent, MouseMoveEvent, ParentElement, Pixels, Render, Styled, + MouseButton, MouseDownEvent, MouseMoveEvent, ParentElement, Render, Styled, UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, WindowContext, }; use language::{Buffer, OwnedSyntaxLayerInfo}; -use settings::Settings; use std::{mem, ops::Range}; -use theme::{ActiveTheme, ThemeSettings}; +use theme::ActiveTheme; use tree_sitter::{Node, TreeCursor}; use ui::{h_stack, popover_menu, ButtonLike, Color, ContextMenu, Label, LabelCommon, PopoverMenu}; use workspace::{ @@ -34,8 +33,6 @@ pub fn init(cx: &mut AppContext) { pub struct SyntaxTreeView { workspace_handle: WeakView, editor: Option, - mouse_y: Option, - line_height: Option, list_scroll_handle: UniformListScrollHandle, selected_descendant_ix: Option, hovered_descendant_ix: Option, @@ -70,8 +67,6 @@ impl SyntaxTreeView { workspace_handle: workspace_handle.clone(), list_scroll_handle: UniformListScrollHandle::new(), editor: None, - mouse_y: None, - line_height: None, hovered_descendant_ix: None, selected_descendant_ix: None, focus_handle: cx.focus_handle(), @@ -208,39 +203,6 @@ impl SyntaxTreeView { Some(()) } - fn handle_click(&mut self, y: Pixels, cx: &mut ViewContext) -> Option<()> { - let line_height = self.line_height?; - let ix = ((self.list_scroll_handle.scroll_top() + y) / line_height) as usize; - - self.update_editor_with_range_for_descendant_ix(ix, cx, |editor, mut range, cx| { - // Put the cursor at the beginning of the node. - mem::swap(&mut range.start, &mut range.end); - - editor.change_selections(Some(Autoscroll::newest()), cx, |selections| { - selections.select_ranges(vec![range]); - }); - }); - Some(()) - } - - fn hover_state_changed(&mut self, cx: &mut ViewContext) { - if let Some((y, line_height)) = self.mouse_y.zip(self.line_height) { - let ix = ((self.list_scroll_handle.scroll_top() + y) / line_height) as usize; - if self.hovered_descendant_ix != Some(ix) { - self.hovered_descendant_ix = Some(ix); - self.update_editor_with_range_for_descendant_ix(ix, cx, |editor, range, cx| { - editor.clear_background_highlights::(cx); - editor.highlight_background::( - vec![range], - |theme| theme.editor_document_highlight_write_background, - cx, - ); - }); - cx.notify(); - } - } - } - fn update_editor_with_range_for_descendant_ix( &self, descendant_ix: usize, @@ -306,15 +268,6 @@ impl SyntaxTreeView { impl Render for SyntaxTreeView { fn render(&mut self, cx: &mut gpui::ViewContext<'_, Self>) -> impl IntoElement { - let settings = ThemeSettings::get_global(cx); - let line_height = cx - .text_style() - .line_height_in_pixels(settings.buffer_font_size(cx)); - if Some(line_height) != self.line_height { - self.line_height = Some(line_height); - self.hover_state_changed(cx); - } - let mut rendered = div().flex_1(); if let Some(layer) = self @@ -345,12 +298,51 @@ impl Render for SyntaxTreeView { break; } } else { - items.push(Self::render_node( - &cursor, - depth, - Some(descendant_ix) == this.selected_descendant_ix, - cx, - )); + items.push( + Self::render_node( + &cursor, + depth, + Some(descendant_ix) == this.selected_descendant_ix, + cx, + ) + .on_mouse_down( + MouseButton::Left, + cx.listener(move |tree_view, _: &MouseDownEvent, cx| { + tree_view.update_editor_with_range_for_descendant_ix( + descendant_ix, + cx, + |editor, mut range, cx| { + // Put the cursor at the beginning of the node. + mem::swap(&mut range.start, &mut range.end); + + editor.change_selections( + Some(Autoscroll::newest()), + cx, + |selections| { + selections.select_ranges(vec![range]); + }, + ); + }, + ); + }), + ) + .on_mouse_move(cx.listener( + move |tree_view, _: &MouseMoveEvent, cx| { + if tree_view.hovered_descendant_ix != Some(descendant_ix) { + tree_view.hovered_descendant_ix = Some(descendant_ix); + tree_view.update_editor_with_range_for_descendant_ix(descendant_ix, cx, |editor, range, cx| { + editor.clear_background_highlights::(cx); + editor.highlight_background::( + vec![range], + |theme| theme.editor_document_highlight_write_background, + cx, + ); + }); + cx.notify(); + } + }, + )), + ); descendant_ix += 1; if cursor.goto_first_child() { depth += 1; @@ -364,16 +356,6 @@ impl Render for SyntaxTreeView { ) .size_full() .track_scroll(self.list_scroll_handle.clone()) - .on_mouse_move(cx.listener(move |tree_view, event: &MouseMoveEvent, cx| { - tree_view.mouse_y = Some(event.position.y); - tree_view.hover_state_changed(cx); - })) - .on_mouse_down( - MouseButton::Left, - cx.listener(move |tree_view, event: &MouseDownEvent, cx| { - tree_view.handle_click(event.position.y, cx); - }), - ) .text_bg(cx.theme().colors().background); rendered = rendered.child( From 1c7710405090c1acd7ad7d69dcefee1fa15feefe Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Wed, 10 Jan 2024 13:43:49 +0100 Subject: [PATCH 70/78] chore: Enable asset compression This reduces size of release binary by ~20% from 134MB to 107MB without noticeable slowdown on startup. Assets are decompressed granularly, on first access --- Cargo.lock | 67 ++++++++++++++++++++++++++++++++ Cargo.toml | 2 +- crates/semantic_index/Cargo.toml | 2 +- 3 files changed, 69 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fe78434a7accf9852dfd45275902402adaaf5040..f065290e1430befc01cf3b0ce4fed347bda0ee07 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3440,6 +3440,40 @@ dependencies = [ "tiff", ] +[[package]] +name = "include-flate" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e11569346406931d20276cc460215ee2826e7cad43aa986999cb244dd7adb0" +dependencies = [ + "include-flate-codegen-exports", + "lazy_static", + "libflate", +] + +[[package]] +name = "include-flate-codegen" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a7d6e1419fa3129eb0802b4c99603c0d425c79fb5d76191d5a20d0ab0d664e8" +dependencies = [ + "libflate", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "include-flate-codegen-exports" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75657043ffe3d8280f1cb8aef0f505532b392ed7758e0baeac22edadcee31a03" +dependencies = [ + "include-flate-codegen", + "proc-macro-hack", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -3831,6 +3865,26 @@ version = "0.2.148" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" +[[package]] +name = "libflate" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ff4ae71b685bbad2f2f391fe74f6b7659a34871c08b210fdc039e43bee07d18" +dependencies = [ + "adler32", + "crc32fast", + "libflate_lz77", +] + +[[package]] +name = "libflate_lz77" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a52d3a8bfc85f250440e4424db7d857e241a3aebbbe301f3eb606ab15c39acbf" +dependencies = [ + "rle-decode-fast", +] + [[package]] name = "libgit2-sys" version = "0.14.2+1.5.1" @@ -5408,6 +5462,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + [[package]] name = "proc-macro2" version = "1.0.67" @@ -6102,6 +6162,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "rle-decode-fast" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422" + [[package]] name = "rmp" version = "0.8.12" @@ -6249,6 +6315,7 @@ version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1e7d90385b59f0a6bf3d3b757f3ca4ece2048265d70db20a2016043d4509a40" dependencies = [ + "include-flate", "rust-embed-impl", "rust-embed-utils", "walkdir", diff --git a/Cargo.toml b/Cargo.toml index 79d28821d4b040f35fc1ab1dd391cddeee93bc47..7ea79f094c0b6d5d5cd4a1e63f70059db05ec6e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -110,7 +110,7 @@ prost = { version = "0.8" } rand = { version = "0.8.5" } refineable = { path = "./crates/refineable" } regex = { version = "1.5" } -rust-embed = { version = "8.0", features = ["include-exclude"] } +rust-embed = { version = "8.0", features = ["include-exclude", "compression"] } rusqlite = { version = "0.29.0", features = ["blob", "array", "modern_sqlite"] } schemars = { version = "0.8" } serde = { version = "1.0", features = ["derive", "rc"] } diff --git a/crates/semantic_index/Cargo.toml b/crates/semantic_index/Cargo.toml index a437a596a526de3878c35a1d409bb3f11dd7afee..41a3ab2d579867e602746775e55d68496d6a5387 100644 --- a/crates/semantic_index/Cargo.toml +++ b/crates/semantic_index/Cargo.toml @@ -47,7 +47,7 @@ project = { path = "../project", features = ["test-support"] } rpc = { path = "../rpc", features = ["test-support"] } workspace = { path = "../workspace", features = ["test-support"] } settings = { path = "../settings", features = ["test-support"]} -rust-embed = { version = "8.0", features = ["include-exclude"] } +rust-embed.workspace = true client = { path = "../client" } node_runtime = { path = "../node_runtime"} From aff119b80a21e3c3bdaf65d0ec55360da49d398b Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 10 Jan 2024 10:09:48 -0500 Subject: [PATCH 71/78] Fix possessive "its" in docs and comments (#3998) This PR fixes a number of places where we were incorrectly using "it's" where we needed to use the possessive "its". Release Notes: - N/A --- crates/editor/src/editor.rs | 2 +- crates/gpui/src/action.rs | 2 +- crates/gpui/src/color.rs | 2 +- crates/gpui/src/elements/overlay.rs | 2 +- crates/plugin_runtime/OPAQUE.md | 34 ++++++++++----------- crates/plugin_runtime/README.md | 14 ++++----- crates/rope/src/rope.rs | 2 +- crates/search/src/buffer_search.rs | 2 +- crates/semantic_index/src/semantic_index.rs | 2 +- crates/terminal/src/terminal.rs | 8 ++--- crates/ui/docs/hello-world.md | 2 +- crates/ui/src/components/checkbox.rs | 2 +- crates/ui/src/disableable.rs | 2 +- crates/util/src/util.rs | 8 ++--- docs/src/developing_zed__building_zed.md | 2 +- 15 files changed, 43 insertions(+), 43 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 95aec1a2007e19df4eb3099bff0f104bd4754a80..66687377bda8d9d217ea6c5acc9b6c5dc6acc186 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -7050,7 +7050,7 @@ impl Editor { let buffer = self.buffer.read(cx).snapshot(cx); let selection = self.selections.newest::(cx); - // If there is an active Diagnostic Popover. Jump to it's diagnostic instead. + // If there is an active Diagnostic Popover jump to its diagnostic instead. if direction == Direction::Next { if let Some(popover) = self.hover_state.diagnostic_popover.as_ref() { let (group_id, jump_to) = popover.activation_info(); diff --git a/crates/gpui/src/action.rs b/crates/gpui/src/action.rs index 04e6ccdbfa6f3217306cd795ef9f142c4b54e68f..ef02316f83ea9dfa57c68106f6b5706b755e3cd6 100644 --- a/crates/gpui/src/action.rs +++ b/crates/gpui/src/action.rs @@ -104,7 +104,7 @@ pub struct ActionData { } /// This constant must be public to be accessible from other crates. -/// But it's existence is an implementation detail and should not be used directly. +/// But its existence is an implementation detail and should not be used directly. #[doc(hidden)] #[linkme::distributed_slice] pub static __GPUI_ACTIONS: [MacroActionBuilder]; diff --git a/crates/gpui/src/color.rs b/crates/gpui/src/color.rs index dc0f5055e70a123b99a7cc8b746ee69bd23652a3..bc764e564c3340957f947ef669243be0a7d27e4d 100644 --- a/crates/gpui/src/color.rs +++ b/crates/gpui/src/color.rs @@ -321,7 +321,7 @@ impl Hsla { /// /// Assumptions: /// - Alpha values are contained in the range [0, 1], with 1 as fully opaque and 0 as fully transparent. - /// - The relative contributions of `self` and `other` is based on `self`'s alpha value (`self.a`) and `other`'s alpha value (`other.a`), `self` contributing `self.a * (1.0 - other.a)` and `other` contributing it's own alpha value. + /// - The relative contributions of `self` and `other` is based on `self`'s alpha value (`self.a`) and `other`'s alpha value (`other.a`), `self` contributing `self.a * (1.0 - other.a)` and `other` contributing its own alpha value. /// - RGB color components are contained in the range [0, 1]. /// - If `self` and `other` colors are out of the valid range, the blend operation's output and behavior is undefined. pub fn blend(self, other: Hsla) -> Hsla { diff --git a/crates/gpui/src/elements/overlay.rs b/crates/gpui/src/elements/overlay.rs index 019436493d937bdc5898db695622f53ff9d8f997..6996a13cfaac61b84e9ba7df10f240710ca2d162 100644 --- a/crates/gpui/src/elements/overlay.rs +++ b/crates/gpui/src/elements/overlay.rs @@ -45,7 +45,7 @@ impl Overlay { } /// Sets the position mode for this overlay. Local will have this - /// interpret it's [Overlay::position] as relative to the parent element. + /// interpret its [`Overlay::position`] as relative to the parent element. /// While Window will have it interpret the position as relative to the window. pub fn position_mode(mut self, mode: OverlayPositionMode) -> Self { self.position_mode = mode; diff --git a/crates/plugin_runtime/OPAQUE.md b/crates/plugin_runtime/OPAQUE.md index 52ee75dbf9bab78811d0ad858d2036889d9a0517..4d38409ec2629b73aaa83287c4bb3c522ea842f0 100644 --- a/crates/plugin_runtime/OPAQUE.md +++ b/crates/plugin_runtime/OPAQUE.md @@ -34,7 +34,7 @@ Rhai actually exposes a pretty nice interface for working with native Rust types > **Note**: Rhai uses strings, but I wonder if you could get away with something more compact using `TypeIds`. Maybe not, given that `TypeId`s are not deterministic across builds, and we'd need matching IDs both host-side and guest side. -In Rhai, we can alternatively use the method `Engine::register_type_with_name::(name: &str)` if we have a different type name host-side (in Rust) and guest-side (in Rhai). +In Rhai, we can alternatively use the method `Engine::register_type_with_name::(name: &str)` if we have a different type name host-side (in Rust) and guest-side (in Rhai). With respect to Wasm plugins, I think an interface like this is fairly important, because we don't know whether the original plugin was written in Rust. (This may not be true now, because we write all the plugins Zed uses, but once we allow packaging and shipping plugins, it's important to maintain a consistent interface, because even Rust changes over time.) @@ -72,15 +72,15 @@ Union::Variant(v, ..) => (*v).as_boxed_any().downcast().ok().map(|x| *x), Now Rhai can do this because it's implemented in Rust. In other words, unlike Wasm, Rhai scripts can, indirectly, hold references to places in host memory. For us to implement something like this for Wasm plugins, we'd have to keep track of a "`ResourcePool`"—alive for the duration of each function call—that we can check rust types into and out of. I think I've got a handle on how Rhai works now, so let's stop talking about Rhai and discuss what this opaque object system would look like if we implemented it in Rust. - + # Design Sketch - + First things first, we'd have to generalize the arguments we can pass to and return from functions host-side. Currently, we support anything that's `serde`able. We'd have to create a new trait, say `Value`, that has blanket implementations for both `serde` and `Clone` (or something like this; if a type is both `serde` and `clone`, we'd have to figure out a way to disambiguate). - - We'd also create a `ResourcePool` struct that essentially is a `Vec` of `Box`. When calling a function, all `Value` arguments that are resources (e.g. `Clone` instead of `serde`) would be typecasted to `dyn Any` and stored in the `ResourcePool`. - + + We'd also create a `ResourcePool` struct that essentially is a `Vec` of `Box`. When calling a function, all `Value` arguments that are resources (e.g. `Clone` instead of `serde`) would be typecasted to `dyn Any` and stored in the `ResourcePool`. + We'd probably also need a `Resource` trait that defines an associated handle for a resource. Something like this: - + ```rust pub trait Resource { type Handle: Serialize + DeserializeOwned; @@ -88,24 +88,24 @@ First things first, we'd have to generalize the arguments we can pass to and ret fn index(handle: Self) -> u32; } ``` - + Where a handle is just a dead-simple wrapper around a `u32`: - - ```rust + + ```rust #[derive(Serialize, Deserialize)] pub struct CoolHandle(u32); ``` - + It's important that this handle be accessible *both* host-side and plugin side. I don't know if this means that we have another crate, like `plugin_handles`, that contains a bunch of u32 wrappers, or something else. Because a `Resource::Handle` is just a u32, it's trivially `serde`, and can cross the ABI boundary. - - So when we add each `T: Resource` to the `ResourcePool`, the resource pool typecasts it to `Any`, appends it to the `Vec`, and returns the associated `Resource::Handle`. This handle is what we pass through to Wasm. - + + So when we add each `T: Resource` to the `ResourcePool`, the resource pool typecasts it to `Any`, appends it to the `Vec`, and returns the associated `Resource::Handle`. This handle is what we pass through to Wasm. + ```rust // Implementations and attributes omitted pub struct Rope { ... }; pub struct RopeHandle(u32); impl Resource for Arc> { ... } - + let builder: PluginBuilder = ...; let builder = builder .host_fn_async( @@ -127,7 +127,7 @@ use plugin_handles::RopeHandle; pub fn append(rope: RopeHandle, string: &str); ``` -This allows us to perform an operation on a `Rope`, but how do we get a `RopeHandle` into a plugin? Well, as plugins, we can only acquire resources to handles we're given, so we'd need to expose a function that takes a handle. +This allows us to perform an operation on a `Rope`, but how do we get a `RopeHandle` into a plugin? Well, as plugins, we can only acquire resources to handles we're given, so we'd need to expose a function that takes a handle. To illustrate that point, here's an example. First, we'd define a plugin-side function as follows: @@ -185,4 +185,4 @@ Using this approach, it should be possible to add fairly good support for resour This next week, I'll try to get a production-ready version of this working, using the `Language` resource required by some Language Server Adapters. -Hope this guide made sense! \ No newline at end of file +Hope this guide made sense! diff --git a/crates/plugin_runtime/README.md b/crates/plugin_runtime/README.md index 38d1c0bb5d1d2bc44f0c150c8667774a998befaf..25524dd2721ac1b6bf8580f2c062a56bb1d835ed 100644 --- a/crates/plugin_runtime/README.md +++ b/crates/plugin_runtime/README.md @@ -164,7 +164,7 @@ To call the functions that a plugin exports host-side, you need to have 'handles For example, let's suppose we're creating a plugin that: -1. formats a message +1. formats a message 2. processes a list of numbers somehow We could create a struct for this plugin as follows: @@ -179,7 +179,7 @@ pub struct CoolPlugin { } ``` -Note that this plugin also holds an owned reference to the runtime, which is stored in the `Plugin` type. In asynchronous or multithreaded contexts, it may be required to put `Plugin` behind an `Arc>`. Although plugins expose an asynchronous interface, the underlying Wasm engine can only execute a single function at a time. +Note that this plugin also holds an owned reference to the runtime, which is stored in the `Plugin` type. In asynchronous or multithreaded contexts, it may be required to put `Plugin` behind an `Arc>`. Although plugins expose an asynchronous interface, the underlying Wasm engine can only execute a single function at a time. > **Note**: This is a limitation of the WebAssembly standard itself. In the future, to work around this, we've been considering starting a pool of plugins, or instantiating a new plugin per call (this isn't as bad as it sounds, as instantiating a new plugin only takes about 30µs). @@ -203,7 +203,7 @@ To add a sync native function to a plugin, use the `.host_function` method: ```rust let builder = builder.host_function( - "add_f64", + "add_f64", |(a, b): (f64, f64)| a + b, ).unwrap(); ``` @@ -224,7 +224,7 @@ To add an async native function to a plugin, use the `.host_function_async` meth ```rust let builder = builder.host_function_async( - "half", + "half", |n: f64| async move { n / 2.0 }, ).unwrap(); ``` @@ -252,9 +252,9 @@ let plugin = builder .unwrap(); ``` -The `.init` method takes a single argument containing the plugin binary. +The `.init` method takes a single argument containing the plugin binary. -1. If not precompiled, use `PluginBinary::Wasm(bytes)`. This supports both the WebAssembly Textual format (`.wat`) and the WebAssembly Binary format (`.wasm`). +1. If not precompiled, use `PluginBinary::Wasm(bytes)`. This supports both the WebAssembly Textual format (`.wat`) and the WebAssembly Binary format (`.wasm`). 2. If precompiled, use `PluginBinary::Precompiled(bytes)`. This supports precompiled plugins ending in `.wasm.pre`. You need to be extra-careful when using precompiled plugins to ensure that the plugin target matches the target of the binary you are compiling. @@ -317,4 +317,4 @@ The `.call` method takes two arguments: This method is async, and must be `.await`ed. If something goes wrong (e.g. the plugin panics, or there is a type mismatch between the plugin and `WasiFn`), then this method will return an error. ## Last Notes -This has been a brief overview of how the plugin system currently works in Zed. We hope to implement higher-level affordances as time goes on, to make writing plugins easier, and providing tooling so that users of Zed may also write plugins to extend their own editors. \ No newline at end of file +This has been a brief overview of how the plugin system currently works in Zed. We hope to implement higher-level affordances as time goes on, to make writing plugins easier, and providing tooling so that users of Zed may also write plugins to extend their own editors. diff --git a/crates/rope/src/rope.rs b/crates/rope/src/rope.rs index 05873818c7a5601f494ccc0cd87d3f5d25cf94cd..f9922bf5172a08fb2194816d4552a5776fb6c328 100644 --- a/crates/rope/src/rope.rs +++ b/crates/rope/src/rope.rs @@ -78,7 +78,7 @@ impl Rope { } pub fn slice_rows(&self, range: Range) -> Rope { - //This would be more efficient with a forward advance after the first, but it's fine + // This would be more efficient with a forward advance after the first, but it's fine. let start = self.point_to_offset(Point::new(range.start, 0)); let end = self.point_to_offset(Point::new(range.end, 0)); self.slice(start..end) diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 7dd4b85231695d8ece48dbd6075cd030c2081dfc..7ef21c42ed3886a81ad6b9b08a17f626d99acb1f 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -423,7 +423,7 @@ impl ToolbarItemView for BufferSearchBar { } } -/// Registrar inverts the dependency between search and it's downstream user, allowing said downstream user to register search action without knowing exactly what those actions are. +/// Registrar inverts the dependency between search and its downstream user, allowing said downstream user to register search action without knowing exactly what those actions are. pub trait SearchActionsRegistrar { fn register_handler( &mut self, diff --git a/crates/semantic_index/src/semantic_index.rs b/crates/semantic_index/src/semantic_index.rs index 81c4fbbc3d04b0d0bca6e4b70c074e3f75467999..801f02e600c1130eb5364e352f7f3823d6821f7a 100644 --- a/crates/semantic_index/src/semantic_index.rs +++ b/crates/semantic_index/src/semantic_index.rs @@ -1248,7 +1248,7 @@ impl SemanticIndex { impl Drop for JobHandle { fn drop(&mut self) { if let Some(inner) = Arc::get_mut(&mut self.tx) { - // This is the last instance of the JobHandle (regardless of it's origin - whether it was cloned or not) + // This is the last instance of the JobHandle (regardless of its origin - whether it was cloned or not) if let Some(tx) = inner.upgrade() { let mut tx = tx.lock(); *tx.borrow_mut() -= 1; diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index fa8112bac7d3cc0b33cab2d4cbfdf633cd9ee610..3a01f01ca89e03a35586ed325db69a93844ed270 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -983,7 +983,7 @@ impl Terminal { let mut terminal = if let Some(term) = term.try_lock_unfair() { term } else if self.last_synced.elapsed().as_secs_f32() > 0.25 { - term.lock_unfair() //It's been too long, force block + term.lock_unfair() // It's been too long, force block } else if let None = self.sync_task { //Skip this frame let delay = cx.background_executor().timer(Duration::from_millis(16)); @@ -1402,9 +1402,9 @@ fn content_index_for_mouse(pos: Point, size: &TerminalSize) -> usize { clamped_row * size.columns() + clamped_col } -///Converts an 8 bit ANSI color to it's GPUI equivalent. -///Accepts usize for compatibility with the alacritty::Colors interface, -///Other than that use case, should only be called with values in the [0,255] range +/// Converts an 8 bit ANSI color to it's GPUI equivalent. +/// Accepts `usize` for compatibility with the `alacritty::Colors` interface, +/// Other than that use case, should only be called with values in the [0,255] range pub fn get_color_at_index(index: usize, theme: &Theme) -> Hsla { let colors = theme.colors(); diff --git a/crates/ui/docs/hello-world.md b/crates/ui/docs/hello-world.md index 280763e27b114e56cbb27bb6859a15ec940d881b..12ee4d7abaaef55f0d7f5e674cdff97927a4182c 100644 --- a/crates/ui/docs/hello-world.md +++ b/crates/ui/docs/hello-world.md @@ -74,7 +74,7 @@ As you start using the Tailwind-style conventions you will be surprised how quic **Why `50.0/360.0` in `hsla()`?** -gpui [gpui::Hsla] use `0.0-1.0` for all it's values, but it is common for tools to use `0-360` for hue. +gpui [gpui::Hsla] use `0.0-1.0` for all its values, but it is common for tools to use `0-360` for hue. This may change in the future, but this is a little trick that let's you use familiar looking values. diff --git a/crates/ui/src/components/checkbox.rs b/crates/ui/src/components/checkbox.rs index 08c95f2d939f0fc76be90d2186fab9a6788c8f8f..2180e0061773f8626292224e8fb3a2b97ab4cc65 100644 --- a/crates/ui/src/components/checkbox.rs +++ b/crates/ui/src/components/checkbox.rs @@ -73,7 +73,7 @@ impl RenderOnce for Checkbox { // - a previously agreed to license that has been updated // // For the sake of styles we treat the indeterminate state as selected, - // but it's icon will be different. + // but its icon will be different. let selected = self.checked == Selection::Selected || self.checked == Selection::Indeterminate; diff --git a/crates/ui/src/disableable.rs b/crates/ui/src/disableable.rs index ebd34fba1cf8289a830b56516e027c7eabaf8f12..9f08ed8d12dd1e0d468e40de85420f13dbbd1698 100644 --- a/crates/ui/src/disableable.rs +++ b/crates/ui/src/disableable.rs @@ -1,4 +1,4 @@ -/// A trait for elements that can be disabled. Generally used to implement disabling an element's interactivity and changing it's appearance to reflect that it is disabled. +/// A trait for elements that can be disabled. Generally used to implement disabling an element's interactivity and changing its appearance to reflect that it is disabled. pub trait Disableable { /// Sets whether the element is disabled. fn disabled(self, disabled: bool) -> Self; diff --git a/crates/util/src/util.rs b/crates/util/src/util.rs index 613a79b19ed546283f3d54b45ea571becb3b9503..e32dd88b86fab6f0e8ad3fa9de1ce7ce6e8793a6 100644 --- a/crates/util/src/util.rs +++ b/crates/util/src/util.rs @@ -41,8 +41,8 @@ pub fn truncate(s: &str, max_chars: usize) -> &str { } } -/// Removes characters from the end of the string if it's length is greater than `max_chars` and -/// appends "..." to the string. Returns string unchanged if it's length is smaller than max_chars. +/// Removes characters from the end of the string if its length is greater than `max_chars` and +/// appends "..." to the string. Returns string unchanged if its length is smaller than max_chars. pub fn truncate_and_trailoff(s: &str, max_chars: usize) -> String { debug_assert!(max_chars >= 5); @@ -53,8 +53,8 @@ pub fn truncate_and_trailoff(s: &str, max_chars: usize) -> String { } } -/// Removes characters from the front of the string if it's length is greater than `max_chars` and -/// prepends the string with "...". Returns string unchanged if it's length is smaller than max_chars. +/// Removes characters from the front of the string if its length is greater than `max_chars` and +/// prepends the string with "...". Returns string unchanged if its length is smaller than max_chars. pub fn truncate_and_remove_front(s: &str, max_chars: usize) -> String { debug_assert!(max_chars >= 5); diff --git a/docs/src/developing_zed__building_zed.md b/docs/src/developing_zed__building_zed.md index cb30051ffa19f551b46d6ddefdbf981077cb9fed..7606e369d05cf02226ea5b783f4ff8369a661be3 100644 --- a/docs/src/developing_zed__building_zed.md +++ b/docs/src/developing_zed__building_zed.md @@ -40,7 +40,7 @@ Expect this to take 30min to an hour! Some of these steps will take quite a whil - (not applicable) Fine-grained Tokens, at the moment of writing, did not allow any kind of access of non-owned private repos - Keep the token in the browser tab/editor for the next two steps 1. (Optional but reccomended) Add your GITHUB_TOKEN to your `.zshrc` or `.bashrc` like this: `export GITHUB_TOKEN=yourGithubAPIToken` -1. Ensure the Zed.dev website is checked out in a sibling directory and install it's dependencies: +1. Ensure the Zed.dev website is checked out in a sibling directory and install its dependencies: ``` cd .. git clone https://github.com/zed-industries/zed.dev From a5203364b1240c88aa464a13e87b15341d8f4d73 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 10 Jan 2024 10:35:06 -0500 Subject: [PATCH 72/78] Use the `.selected` style for buffer search option buttons (#4000) This PR updates the `IconButton`s used to control the buffer search options to use the `.selected` state to denote when they are active. This matches what we are doing in the project search. This should improve the contrast in certain themes. Release Notes: - Improved the active style for the search options in buffer search. --- crates/search/src/search.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/search/src/search.rs b/crates/search/src/search.rs index 1b29801e03a8b370411be0d1def77c392cccb1dd..748996c389370bc576949e9c6752c26cb2e39f29 100644 --- a/crates/search/src/search.rs +++ b/crates/search/src/search.rs @@ -98,7 +98,7 @@ impl SearchOptions { IconButton::new(self.label(), self.icon()) .on_click(action) .style(ButtonStyle::Subtle) - .when(active, |button| button.style(ButtonStyle::Filled)) + .selected(active) .tooltip({ let action = self.to_toggle_action(); let label: SharedString = format!("Toggle {}", self.label()).into(); From f8e4fd012fec52879a2b7fd041c4d09e29ee694f Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 10 Jan 2024 09:26:15 -0700 Subject: [PATCH 73/78] collab 0.36.0 --- Cargo.lock | 2 +- crates/collab/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f065290e1430befc01cf3b0ce4fed347bda0ee07..0a49aa72e2b3cb69c5fefe01836f86b7cd9e03a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1452,7 +1452,7 @@ dependencies = [ [[package]] name = "collab" -version = "0.35.0" +version = "0.36.0" dependencies = [ "anyhow", "async-trait", diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index 13bbd0cf2511d8b91015fd167842d82ed7b12df4..b0917104f98bc30d193e21171aca81f56199c83b 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] default-run = "collab" edition = "2021" name = "collab" -version = "0.35.0" +version = "0.36.0" publish = false [[bin]] From 1c1151a0ed2f26ea2cf637aa4d07567ab6b4f372 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 10 Jan 2024 09:29:44 -0700 Subject: [PATCH 74/78] Remove ChannelsAlpha flag Welcome to the party! --- crates/collab_ui/src/chat_panel.rs | 9 +- crates/collab_ui/src/collab_panel.rs | 196 +++++++++++----------- crates/collab_ui/src/collab_ui.rs | 5 - crates/feature_flags/src/feature_flags.rs | 6 - 4 files changed, 98 insertions(+), 118 deletions(-) diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index 5786ab10d4ca59b998b1b16ea7bb3c53611b4399..d52f285c898549d082a90db37a962698f27a577a 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -1,4 +1,4 @@ -use crate::{channel_view::ChannelView, is_channels_feature_enabled, ChatPanelSettings}; +use crate::{channel_view::ChannelView, ChatPanelSettings}; use anyhow::Result; use call::ActiveCall; use channel::{ChannelChat, ChannelChatEvent, ChannelMessageId, ChannelStore}; @@ -612,9 +612,6 @@ impl Panel for ChatPanel { self.active = active; if active { self.acknowledge_last_message(cx); - if !is_channels_feature_enabled(cx) { - cx.emit(Event::Dismissed); - } } } @@ -623,10 +620,6 @@ impl Panel for ChatPanel { } fn icon(&self, cx: &WindowContext) -> Option { - if !is_channels_feature_enabled(cx) { - return None; - } - Some(ui::IconName::MessageBubbles).filter(|_| ChatPanelSettings::get_global(cx).button) } diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 5ad3d6cfa3213119551ed341be33816336f8ca5c..aa348ea9d504a46d4477029f43c6627c16fbe7d9 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -12,7 +12,6 @@ use client::{Client, Contact, User, UserStore}; use contact_finder::ContactFinder; use db::kvp::KEY_VALUE_STORE; use editor::{Editor, EditorElement, EditorStyle}; -use feature_flags::{ChannelsAlpha, FeatureFlagAppExt, FeatureFlagViewExt}; use fuzzy::{match_strings, StringMatchCandidate}; use gpui::{ actions, canvas, div, fill, list, overlay, point, prelude::*, px, AnyElement, AppContext, @@ -278,10 +277,6 @@ impl CollabPanel { })); this.subscriptions .push(cx.observe(&active_call, |this, _, cx| this.update_entries(true, cx))); - this.subscriptions - .push(cx.observe_flag::(move |_, this, cx| { - this.update_entries(true, cx) - })); this.subscriptions.push(cx.subscribe( &this.channel_store, |this, _channel_store, e, cx| match e { @@ -517,115 +512,118 @@ impl CollabPanel { let mut request_entries = Vec::new(); - if cx.has_flag::() { - self.entries.push(ListEntry::Header(Section::Channels)); + self.entries.push(ListEntry::Header(Section::Channels)); - if channel_store.channel_count() > 0 || self.channel_editing_state.is_some() { - self.match_candidates.clear(); - self.match_candidates - .extend(channel_store.ordered_channels().enumerate().map( - |(ix, (_, channel))| StringMatchCandidate { + if channel_store.channel_count() > 0 || self.channel_editing_state.is_some() { + self.match_candidates.clear(); + self.match_candidates + .extend( + channel_store + .ordered_channels() + .enumerate() + .map(|(ix, (_, channel))| StringMatchCandidate { id: ix, string: channel.name.clone().into(), char_bag: channel.name.chars().collect(), - }, - )); - let matches = executor.block(match_strings( - &self.match_candidates, - &query, - true, - usize::MAX, - &Default::default(), - executor.clone(), - )); - if let Some(state) = &self.channel_editing_state { - if matches!(state, ChannelEditingState::Create { location: None, .. }) { - self.entries.push(ListEntry::ChannelEditor { depth: 0 }); - } + }), + ); + let matches = executor.block(match_strings( + &self.match_candidates, + &query, + true, + usize::MAX, + &Default::default(), + executor.clone(), + )); + if let Some(state) = &self.channel_editing_state { + if matches!(state, ChannelEditingState::Create { location: None, .. }) { + self.entries.push(ListEntry::ChannelEditor { depth: 0 }); } - let mut collapse_depth = None; - for mat in matches { - let channel = channel_store.channel_at_index(mat.candidate_id).unwrap(); - let depth = channel.parent_path.len(); - - if collapse_depth.is_none() && self.is_channel_collapsed(channel.id) { + } + let mut collapse_depth = None; + for mat in matches { + let channel = channel_store.channel_at_index(mat.candidate_id).unwrap(); + let depth = channel.parent_path.len(); + + if collapse_depth.is_none() && self.is_channel_collapsed(channel.id) { + collapse_depth = Some(depth); + } else if let Some(collapsed_depth) = collapse_depth { + if depth > collapsed_depth { + continue; + } + if self.is_channel_collapsed(channel.id) { collapse_depth = Some(depth); - } else if let Some(collapsed_depth) = collapse_depth { - if depth > collapsed_depth { - continue; - } - if self.is_channel_collapsed(channel.id) { - collapse_depth = Some(depth); - } else { - collapse_depth = None; - } + } else { + collapse_depth = None; } + } - let has_children = channel_store - .channel_at_index(mat.candidate_id + 1) - .map_or(false, |next_channel| { - next_channel.parent_path.ends_with(&[channel.id]) - }); + let has_children = channel_store + .channel_at_index(mat.candidate_id + 1) + .map_or(false, |next_channel| { + next_channel.parent_path.ends_with(&[channel.id]) + }); - match &self.channel_editing_state { - Some(ChannelEditingState::Create { - location: parent_id, - .. - }) if *parent_id == Some(channel.id) => { - self.entries.push(ListEntry::Channel { - channel: channel.clone(), - depth, - has_children: false, - }); - self.entries - .push(ListEntry::ChannelEditor { depth: depth + 1 }); - } - Some(ChannelEditingState::Rename { - location: parent_id, - .. - }) if parent_id == &channel.id => { - self.entries.push(ListEntry::ChannelEditor { depth }); - } - _ => { - self.entries.push(ListEntry::Channel { - channel: channel.clone(), - depth, - has_children, - }); - } + match &self.channel_editing_state { + Some(ChannelEditingState::Create { + location: parent_id, + .. + }) if *parent_id == Some(channel.id) => { + self.entries.push(ListEntry::Channel { + channel: channel.clone(), + depth, + has_children: false, + }); + self.entries + .push(ListEntry::ChannelEditor { depth: depth + 1 }); + } + Some(ChannelEditingState::Rename { + location: parent_id, + .. + }) if parent_id == &channel.id => { + self.entries.push(ListEntry::ChannelEditor { depth }); + } + _ => { + self.entries.push(ListEntry::Channel { + channel: channel.clone(), + depth, + has_children, + }); } } } + } - let channel_invites = channel_store.channel_invitations(); - if !channel_invites.is_empty() { - self.match_candidates.clear(); - self.match_candidates - .extend(channel_invites.iter().enumerate().map(|(ix, channel)| { - StringMatchCandidate { - id: ix, - string: channel.name.clone().into(), - char_bag: channel.name.chars().collect(), - } - })); - let matches = executor.block(match_strings( - &self.match_candidates, - &query, - true, - usize::MAX, - &Default::default(), - executor.clone(), - )); - request_entries.extend(matches.iter().map(|mat| { - ListEntry::ChannelInvite(channel_invites[mat.candidate_id].clone()) + let channel_invites = channel_store.channel_invitations(); + if !channel_invites.is_empty() { + self.match_candidates.clear(); + self.match_candidates + .extend(channel_invites.iter().enumerate().map(|(ix, channel)| { + StringMatchCandidate { + id: ix, + string: channel.name.clone().into(), + char_bag: channel.name.chars().collect(), + } })); + let matches = executor.block(match_strings( + &self.match_candidates, + &query, + true, + usize::MAX, + &Default::default(), + executor.clone(), + )); + request_entries.extend( + matches + .iter() + .map(|mat| ListEntry::ChannelInvite(channel_invites[mat.candidate_id].clone())), + ); - if !request_entries.is_empty() { - self.entries - .push(ListEntry::Header(Section::ChannelInvites)); - if !self.collapsed_sections.contains(&Section::ChannelInvites) { - self.entries.append(&mut request_entries); - } + if !request_entries.is_empty() { + self.entries + .push(ListEntry::Header(Section::ChannelInvites)); + if !self.collapsed_sections.contains(&Section::ChannelInvites) { + self.entries.append(&mut request_entries); } } } diff --git a/crates/collab_ui/src/collab_ui.rs b/crates/collab_ui/src/collab_ui.rs index 3c0473e67d0a687308c00097c554fc87f47645c1..455ae64ef1d35a9f1ec0896ac2cb2367fdf47d3a 100644 --- a/crates/collab_ui/src/collab_ui.rs +++ b/crates/collab_ui/src/collab_ui.rs @@ -12,7 +12,6 @@ use std::{rc::Rc, sync::Arc}; use call::{report_call_event_for_room, ActiveCall, Room}; pub use collab_panel::CollabPanel; pub use collab_titlebar_item::CollabTitlebarItem; -use feature_flags::{ChannelsAlpha, FeatureFlagAppExt}; use gpui::{ actions, point, AppContext, GlobalPixels, Pixels, PlatformDisplay, Size, Task, WindowBounds, WindowKind, WindowOptions, @@ -159,7 +158,3 @@ fn notification_window_options( // .with_style(container) // .into_any() // } - -fn is_channels_feature_enabled(cx: &gpui::WindowContext<'_>) -> bool { - cx.is_staff() || cx.has_flag::() -} diff --git a/crates/feature_flags/src/feature_flags.rs b/crates/feature_flags/src/feature_flags.rs index 065d06f96d1b1765828329464ddb149da371d63b..ea16ff3f7291fd838be45fd826e7c7504ba914fd 100644 --- a/crates/feature_flags/src/feature_flags.rs +++ b/crates/feature_flags/src/feature_flags.rs @@ -16,12 +16,6 @@ pub trait FeatureFlag { const NAME: &'static str; } -pub enum ChannelsAlpha {} - -impl FeatureFlag for ChannelsAlpha { - const NAME: &'static str = "channels_alpha"; -} - pub trait FeatureFlagViewExt { fn observe_flag(&mut self, callback: F) -> Subscription where From 282184a673514c66c274290e2b7f16460a87fc12 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Wed, 10 Jan 2024 18:11:05 +0100 Subject: [PATCH 75/78] editor: Use inclusive ranges for git diff resolution. (#3999) The culprit was in display map which was resolving next valid point for the editor, without regard for whether that point belongs to the same excerpt. We now make an end point a minimum of the end point passed in and the start of excerpt header, if there are any. This bug existed in Zed1 as well. Fixes: Diff markers in multibuffer search overlap with dividers between excepts (shouldn't extend all the way into the divider region) Release Notes: - Fixed diff markers being drawn incorrectly near headers in multibuffer views. --- crates/editor/src/element.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index fc3c53f34343684ed5d39305d1c30d4fe7872881..7efb43bd4852fc7ef1d8e5a5a43a79ad72a0303e 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -804,9 +804,22 @@ impl EditorElement { let start_row = display_row_range.start; let end_row = display_row_range.end; + // If we're in a multibuffer, row range span might include an + // excerpt header, so if we were to draw the marker straight away, + // the hunk might include the rows of that header. + // Making the range inclusive doesn't quite cut it, as we rely on the exclusivity for the soft wrap. + // Instead, we simply check whether the range we're dealing with includes + // any custom elements and if so, we stop painting the diff hunk on the first row of that custom element. + let end_row_in_current_excerpt = layout + .position_map + .snapshot + .blocks_in_range(start_row..end_row) + .next() + .map(|(start_row, _)| start_row) + .unwrap_or(end_row); let start_y = start_row as f32 * line_height - scroll_top; - let end_y = end_row as f32 * line_height - scroll_top; + let end_y = end_row_in_current_excerpt as f32 * line_height - scroll_top; let width = 0.275 * line_height; let highlight_origin = bounds.origin + point(-width, start_y); From 8d1bca450faa7fcbac4779b391258d8520c4b58f Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 10 Jan 2024 10:18:45 -0700 Subject: [PATCH 76/78] Remove extra assertion As part of debugging the port of following tests we added an assertion that the project was dropped. Now that we initialize the editor and handle focus correctly in tests, the project is retained by `refresh_document_highlights`. That doesn't affect the meaning of the tests --- crates/collab/src/tests/following_tests.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/collab/src/tests/following_tests.rs b/crates/collab/src/tests/following_tests.rs index 8f6433c66809407c63989f2e8a865c1644ba35ab..9209760353935bfdd3573317a9cd3d0ca4e85573 100644 --- a/crates/collab/src/tests/following_tests.rs +++ b/crates/collab/src/tests/following_tests.rs @@ -161,7 +161,6 @@ async fn test_basic_following( .update(cx_c, |call, cx| call.set_location(Some(&project_c), cx)) .await .unwrap(); - let weak_project_c = project_c.downgrade(); drop(project_c); // Client C also follows client A. @@ -248,7 +247,6 @@ async fn test_basic_following( cx_c.cx.update(|_| {}); weak_workspace_c.assert_dropped(); - weak_project_c.assert_dropped(); // Clients A and B see that client B is following A, and client C is not present in the followers. executor.run_until_parked(); From 69a93edabdafc6c97550b2368b1f085e142a5e1e Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 10 Jan 2024 10:36:08 -0800 Subject: [PATCH 77/78] Ensure `ArenaRef` pointers are aligned to their contained type Co-Authored-By: Antonio Scandurra --- crates/gpui/src/arena.rs | 50 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/crates/gpui/src/arena.rs b/crates/gpui/src/arena.rs index bb493a6d06487a5b07f7a2afbedc36d85fa9f715..b3d7f9b0ecf530a2dbe2c8f9d4dd286ac971a2d9 100644 --- a/crates/gpui/src/arena.rs +++ b/crates/gpui/src/arena.rs @@ -66,18 +66,19 @@ impl Arena { } unsafe { - let layout = alloc::Layout::new::().pad_to_align(); - let next_offset = self.offset.add(layout.size()); - assert!(next_offset <= self.end); + let layout = alloc::Layout::new::(); + let offset = self.offset.add(self.offset.align_offset(layout.align())); + let next_offset = offset.add(layout.size()); + assert!(next_offset <= self.end, "not enough space in Arena"); let result = ArenaBox { - ptr: self.offset.cast(), + ptr: offset.cast(), valid: self.valid.clone(), }; inner_writer(result.ptr, f); self.elements.push(ArenaElement { - value: self.offset, + value: offset, drop: drop::, }); self.offset = next_offset; @@ -199,4 +200,43 @@ mod tests { arena.clear(); assert!(dropped.get()); } + + #[test] + #[should_panic(expected = "not enough space in Arena")] + fn test_arena_overflow() { + let mut arena = Arena::new(16); + arena.alloc(|| 1u64); + arena.alloc(|| 2u64); + // This should panic. + arena.alloc(|| 3u64); + } + + #[test] + fn test_arena_alignment() { + let mut arena = Arena::new(256); + let x1 = arena.alloc(|| 1u8); + let x2 = arena.alloc(|| 2u16); + let x3 = arena.alloc(|| 3u32); + let x4 = arena.alloc(|| 4u64); + let x5 = arena.alloc(|| 5u64); + + assert_eq!(*x1, 1); + assert_eq!(*x2, 2); + assert_eq!(*x3, 3); + assert_eq!(*x4, 4); + assert_eq!(*x5, 5); + + assert_eq!(x1.ptr.align_offset(std::mem::align_of_val(&*x1)), 0); + assert_eq!(x2.ptr.align_offset(std::mem::align_of_val(&*x2)), 0); + } + + #[test] + #[should_panic(expected = "attempted to dereference an ArenaRef after its Arena was cleared")] + fn test_arena_use_after_clear() { + let mut arena = Arena::new(16); + let value = arena.alloc(|| 1u64); + + arena.clear(); + let _read_value = *value; + } } From 61a9a3a274698f81561cb3ee0420848baeb55608 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 10 Jan 2024 11:52:33 -0700 Subject: [PATCH 78/78] Revert "Remove ChannelsAlpha flag" This reverts commit 1c1151a0ed2f26ea2cf637aa4d07567ab6b4f372. --- crates/collab_ui/src/chat_panel.rs | 9 +- crates/collab_ui/src/collab_panel.rs | 196 +++++++++++----------- crates/collab_ui/src/collab_ui.rs | 5 + crates/feature_flags/src/feature_flags.rs | 6 + 4 files changed, 118 insertions(+), 98 deletions(-) diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index d52f285c898549d082a90db37a962698f27a577a..5786ab10d4ca59b998b1b16ea7bb3c53611b4399 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -1,4 +1,4 @@ -use crate::{channel_view::ChannelView, ChatPanelSettings}; +use crate::{channel_view::ChannelView, is_channels_feature_enabled, ChatPanelSettings}; use anyhow::Result; use call::ActiveCall; use channel::{ChannelChat, ChannelChatEvent, ChannelMessageId, ChannelStore}; @@ -612,6 +612,9 @@ impl Panel for ChatPanel { self.active = active; if active { self.acknowledge_last_message(cx); + if !is_channels_feature_enabled(cx) { + cx.emit(Event::Dismissed); + } } } @@ -620,6 +623,10 @@ impl Panel for ChatPanel { } fn icon(&self, cx: &WindowContext) -> Option { + if !is_channels_feature_enabled(cx) { + return None; + } + Some(ui::IconName::MessageBubbles).filter(|_| ChatPanelSettings::get_global(cx).button) } diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index aa348ea9d504a46d4477029f43c6627c16fbe7d9..5ad3d6cfa3213119551ed341be33816336f8ca5c 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -12,6 +12,7 @@ use client::{Client, Contact, User, UserStore}; use contact_finder::ContactFinder; use db::kvp::KEY_VALUE_STORE; use editor::{Editor, EditorElement, EditorStyle}; +use feature_flags::{ChannelsAlpha, FeatureFlagAppExt, FeatureFlagViewExt}; use fuzzy::{match_strings, StringMatchCandidate}; use gpui::{ actions, canvas, div, fill, list, overlay, point, prelude::*, px, AnyElement, AppContext, @@ -277,6 +278,10 @@ impl CollabPanel { })); this.subscriptions .push(cx.observe(&active_call, |this, _, cx| this.update_entries(true, cx))); + this.subscriptions + .push(cx.observe_flag::(move |_, this, cx| { + this.update_entries(true, cx) + })); this.subscriptions.push(cx.subscribe( &this.channel_store, |this, _channel_store, e, cx| match e { @@ -512,118 +517,115 @@ impl CollabPanel { let mut request_entries = Vec::new(); - self.entries.push(ListEntry::Header(Section::Channels)); + if cx.has_flag::() { + self.entries.push(ListEntry::Header(Section::Channels)); - if channel_store.channel_count() > 0 || self.channel_editing_state.is_some() { - self.match_candidates.clear(); - self.match_candidates - .extend( - channel_store - .ordered_channels() - .enumerate() - .map(|(ix, (_, channel))| StringMatchCandidate { + if channel_store.channel_count() > 0 || self.channel_editing_state.is_some() { + self.match_candidates.clear(); + self.match_candidates + .extend(channel_store.ordered_channels().enumerate().map( + |(ix, (_, channel))| StringMatchCandidate { id: ix, string: channel.name.clone().into(), char_bag: channel.name.chars().collect(), - }), - ); - let matches = executor.block(match_strings( - &self.match_candidates, - &query, - true, - usize::MAX, - &Default::default(), - executor.clone(), - )); - if let Some(state) = &self.channel_editing_state { - if matches!(state, ChannelEditingState::Create { location: None, .. }) { - self.entries.push(ListEntry::ChannelEditor { depth: 0 }); - } - } - let mut collapse_depth = None; - for mat in matches { - let channel = channel_store.channel_at_index(mat.candidate_id).unwrap(); - let depth = channel.parent_path.len(); - - if collapse_depth.is_none() && self.is_channel_collapsed(channel.id) { - collapse_depth = Some(depth); - } else if let Some(collapsed_depth) = collapse_depth { - if depth > collapsed_depth { - continue; - } - if self.is_channel_collapsed(channel.id) { - collapse_depth = Some(depth); - } else { - collapse_depth = None; + }, + )); + let matches = executor.block(match_strings( + &self.match_candidates, + &query, + true, + usize::MAX, + &Default::default(), + executor.clone(), + )); + if let Some(state) = &self.channel_editing_state { + if matches!(state, ChannelEditingState::Create { location: None, .. }) { + self.entries.push(ListEntry::ChannelEditor { depth: 0 }); } } + let mut collapse_depth = None; + for mat in matches { + let channel = channel_store.channel_at_index(mat.candidate_id).unwrap(); + let depth = channel.parent_path.len(); - let has_children = channel_store - .channel_at_index(mat.candidate_id + 1) - .map_or(false, |next_channel| { - next_channel.parent_path.ends_with(&[channel.id]) - }); - - match &self.channel_editing_state { - Some(ChannelEditingState::Create { - location: parent_id, - .. - }) if *parent_id == Some(channel.id) => { - self.entries.push(ListEntry::Channel { - channel: channel.clone(), - depth, - has_children: false, - }); - self.entries - .push(ListEntry::ChannelEditor { depth: depth + 1 }); - } - Some(ChannelEditingState::Rename { - location: parent_id, - .. - }) if parent_id == &channel.id => { - self.entries.push(ListEntry::ChannelEditor { depth }); + if collapse_depth.is_none() && self.is_channel_collapsed(channel.id) { + collapse_depth = Some(depth); + } else if let Some(collapsed_depth) = collapse_depth { + if depth > collapsed_depth { + continue; + } + if self.is_channel_collapsed(channel.id) { + collapse_depth = Some(depth); + } else { + collapse_depth = None; + } } - _ => { - self.entries.push(ListEntry::Channel { - channel: channel.clone(), - depth, - has_children, + + let has_children = channel_store + .channel_at_index(mat.candidate_id + 1) + .map_or(false, |next_channel| { + next_channel.parent_path.ends_with(&[channel.id]) }); + + match &self.channel_editing_state { + Some(ChannelEditingState::Create { + location: parent_id, + .. + }) if *parent_id == Some(channel.id) => { + self.entries.push(ListEntry::Channel { + channel: channel.clone(), + depth, + has_children: false, + }); + self.entries + .push(ListEntry::ChannelEditor { depth: depth + 1 }); + } + Some(ChannelEditingState::Rename { + location: parent_id, + .. + }) if parent_id == &channel.id => { + self.entries.push(ListEntry::ChannelEditor { depth }); + } + _ => { + self.entries.push(ListEntry::Channel { + channel: channel.clone(), + depth, + has_children, + }); + } } } } - } - let channel_invites = channel_store.channel_invitations(); - if !channel_invites.is_empty() { - self.match_candidates.clear(); - self.match_candidates - .extend(channel_invites.iter().enumerate().map(|(ix, channel)| { - StringMatchCandidate { - id: ix, - string: channel.name.clone().into(), - char_bag: channel.name.chars().collect(), - } + let channel_invites = channel_store.channel_invitations(); + if !channel_invites.is_empty() { + self.match_candidates.clear(); + self.match_candidates + .extend(channel_invites.iter().enumerate().map(|(ix, channel)| { + StringMatchCandidate { + id: ix, + string: channel.name.clone().into(), + char_bag: channel.name.chars().collect(), + } + })); + let matches = executor.block(match_strings( + &self.match_candidates, + &query, + true, + usize::MAX, + &Default::default(), + executor.clone(), + )); + request_entries.extend(matches.iter().map(|mat| { + ListEntry::ChannelInvite(channel_invites[mat.candidate_id].clone()) })); - let matches = executor.block(match_strings( - &self.match_candidates, - &query, - true, - usize::MAX, - &Default::default(), - executor.clone(), - )); - request_entries.extend( - matches - .iter() - .map(|mat| ListEntry::ChannelInvite(channel_invites[mat.candidate_id].clone())), - ); - if !request_entries.is_empty() { - self.entries - .push(ListEntry::Header(Section::ChannelInvites)); - if !self.collapsed_sections.contains(&Section::ChannelInvites) { - self.entries.append(&mut request_entries); + if !request_entries.is_empty() { + self.entries + .push(ListEntry::Header(Section::ChannelInvites)); + if !self.collapsed_sections.contains(&Section::ChannelInvites) { + self.entries.append(&mut request_entries); + } } } } diff --git a/crates/collab_ui/src/collab_ui.rs b/crates/collab_ui/src/collab_ui.rs index 455ae64ef1d35a9f1ec0896ac2cb2367fdf47d3a..3c0473e67d0a687308c00097c554fc87f47645c1 100644 --- a/crates/collab_ui/src/collab_ui.rs +++ b/crates/collab_ui/src/collab_ui.rs @@ -12,6 +12,7 @@ use std::{rc::Rc, sync::Arc}; use call::{report_call_event_for_room, ActiveCall, Room}; pub use collab_panel::CollabPanel; pub use collab_titlebar_item::CollabTitlebarItem; +use feature_flags::{ChannelsAlpha, FeatureFlagAppExt}; use gpui::{ actions, point, AppContext, GlobalPixels, Pixels, PlatformDisplay, Size, Task, WindowBounds, WindowKind, WindowOptions, @@ -158,3 +159,7 @@ fn notification_window_options( // .with_style(container) // .into_any() // } + +fn is_channels_feature_enabled(cx: &gpui::WindowContext<'_>) -> bool { + cx.is_staff() || cx.has_flag::() +} diff --git a/crates/feature_flags/src/feature_flags.rs b/crates/feature_flags/src/feature_flags.rs index ea16ff3f7291fd838be45fd826e7c7504ba914fd..065d06f96d1b1765828329464ddb149da371d63b 100644 --- a/crates/feature_flags/src/feature_flags.rs +++ b/crates/feature_flags/src/feature_flags.rs @@ -16,6 +16,12 @@ pub trait FeatureFlag { const NAME: &'static str; } +pub enum ChannelsAlpha {} + +impl FeatureFlag for ChannelsAlpha { + const NAME: &'static str = "channels_alpha"; +} + pub trait FeatureFlagViewExt { fn observe_flag(&mut self, callback: F) -> Subscription where