Merge branch 'main' into v0.119.x

Joseph T. Lyons created

Change summary

Cargo.lock                                                                    |   2 
crates/collab/Cargo.toml                                                      |   2 
crates/collab/migrations.sqlite/20221109000000_test_schema.sql                |   1 
crates/collab/migrations/20240117150300_add_impersonator_to_access_tokens.sql |   1 
crates/collab/src/api.rs                                                      |   9 
crates/collab/src/auth.rs                                                     |  99 
crates/collab/src/db/queries/access_tokens.rs                                 |   2 
crates/collab/src/db/tables/access_token.rs                                   |   1 
crates/collab/src/db/tests/db_tests.rs                                        | 104 
crates/collab/src/rpc.rs                                                      |  20 
crates/collab/src/tests/test_server.rs                                        |   1 
crates/collab_ui/src/face_pile.rs                                             |   2 
crates/gpui/src/style.rs                                                      |   8 
crates/gpui/src/styled.rs                                                     |   2 
crates/gpui/src/window.rs                                                     |  71 
crates/storybook/src/stories/z_index.rs                                       |   4 
crates/terminal/src/terminal.rs                                               |  35 
crates/terminal_view/src/terminal_element.rs                                  |   2 
crates/ui/src/styles/elevation.rs                                             |   2 
crates/workspace/src/item.rs                                                  |  12 
crates/workspace/src/pane.rs                                                  |   1 
crates/workspace/src/workspace.rs                                             |   2 
docs/src/developing_zed__building_zed.md                                      |   2 
script/lib/squawk.toml                                                        |   4 
script/squawk                                                                 |   5 
25 files changed, 262 insertions(+), 132 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -1452,7 +1452,7 @@ dependencies = [
 
 [[package]]
 name = "collab"
-version = "0.36.1"
+version = "0.37.0"
 dependencies = [
  "anyhow",
  "async-trait",

crates/collab/Cargo.toml 🔗

@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathan@zed.dev>"]
 default-run = "collab"
 edition = "2021"
 name = "collab"
-version = "0.36.1"
+version = "0.37.0"
 publish = false
 
 [[bin]]

crates/collab/migrations.sqlite/20221109000000_test_schema.sql 🔗

@@ -19,6 +19,7 @@ CREATE INDEX "index_users_on_github_user_id" ON "users" ("github_user_id");
 CREATE TABLE "access_tokens" (
     "id" INTEGER PRIMARY KEY AUTOINCREMENT,
     "user_id" INTEGER REFERENCES users (id),
+    "impersonated_user_id" INTEGER REFERENCES users (id),
     "hash" VARCHAR(128)
 );
 CREATE INDEX "index_access_tokens_user_id" ON "access_tokens" ("user_id");

crates/collab/src/api.rs 🔗

@@ -156,11 +156,11 @@ async fn create_access_token(
         .await?
         .ok_or_else(|| anyhow!("user not found"))?;
 
-    let mut user_id = user.id;
+    let mut impersonated_user_id = None;
     if let Some(impersonate) = params.impersonate {
         if user.admin {
             if let Some(impersonated_user) = app.db.get_user_by_github_login(&impersonate).await? {
-                user_id = impersonated_user.id;
+                impersonated_user_id = Some(impersonated_user.id);
             } else {
                 return Err(Error::Http(
                     StatusCode::UNPROCESSABLE_ENTITY,
@@ -175,12 +175,13 @@ async fn create_access_token(
         }
     }
 
-    let access_token = auth::create_access_token(app.db.as_ref(), user_id).await?;
+    let access_token =
+        auth::create_access_token(app.db.as_ref(), user_id, impersonated_user_id).await?;
     let encrypted_access_token =
         auth::encrypt_access_token(&access_token, params.public_key.clone())?;
 
     Ok(Json(CreateAccessTokenResponse {
-        user_id,
+        user_id: impersonated_user_id.unwrap_or(user_id),
         encrypted_access_token,
     }))
 }

crates/collab/src/auth.rs 🔗

@@ -27,6 +27,9 @@ lazy_static! {
     .unwrap();
 }
 
+#[derive(Clone, Debug, Default, PartialEq, Eq)]
+pub struct Impersonator(pub Option<db::User>);
+
 /// Validates the authorization header. This has two mechanisms, one for the ADMIN_TOKEN
 /// and one for the access tokens that we issue.
 pub async fn validate_header<B>(mut req: Request<B>, next: Next<B>) -> impl IntoResponse {
@@ -57,28 +60,50 @@ pub async fn validate_header<B>(mut req: Request<B>, next: Next<B>) -> impl Into
     })?;
 
     let state = req.extensions().get::<Arc<AppState>>().unwrap();
-    let credentials_valid = if let Some(admin_token) = access_token.strip_prefix("ADMIN_TOKEN:") {
-        state.config.api_token == admin_token
+
+    // In development, allow impersonation using the admin API token.
+    // Don't allow this in production because we can't tell who is doing
+    // the impersonating.
+    let validate_result = if let (Some(admin_token), true) = (
+        access_token.strip_prefix("ADMIN_TOKEN:"),
+        state.config.is_development(),
+    ) {
+        Ok(VerifyAccessTokenResult {
+            is_valid: state.config.api_token == admin_token,
+            impersonator_id: None,
+        })
     } else {
-        verify_access_token(&access_token, user_id, &state.db)
-            .await
-            .unwrap_or(false)
+        verify_access_token(&access_token, user_id, &state.db).await
     };
 
-    if credentials_valid {
-        let user = state
-            .db
-            .get_user_by_id(user_id)
-            .await?
-            .ok_or_else(|| anyhow!("user {} not found", user_id))?;
-        req.extensions_mut().insert(user);
-        Ok::<_, Error>(next.run(req).await)
-    } else {
-        Err(Error::Http(
-            StatusCode::UNAUTHORIZED,
-            "invalid credentials".to_string(),
-        ))
+    if let Ok(validate_result) = validate_result {
+        if validate_result.is_valid {
+            let user = state
+                .db
+                .get_user_by_id(user_id)
+                .await?
+                .ok_or_else(|| anyhow!("user {} not found", user_id))?;
+
+            let impersonator = if let Some(impersonator_id) = validate_result.impersonator_id {
+                let impersonator = state
+                    .db
+                    .get_user_by_id(impersonator_id)
+                    .await?
+                    .ok_or_else(|| anyhow!("user {} not found", impersonator_id))?;
+                Some(impersonator)
+            } else {
+                None
+            };
+            req.extensions_mut().insert(user);
+            req.extensions_mut().insert(Impersonator(impersonator));
+            return Ok::<_, Error>(next.run(req).await);
+        }
     }
+
+    Err(Error::Http(
+        StatusCode::UNAUTHORIZED,
+        "invalid credentials".to_string(),
+    ))
 }
 
 const MAX_ACCESS_TOKENS_TO_STORE: usize = 8;
@@ -92,13 +117,22 @@ struct AccessTokenJson {
 
 /// Creates a new access token to identify the given user. before returning it, you should
 /// encrypt it with the user's public key.
-pub async fn create_access_token(db: &db::Database, user_id: UserId) -> Result<String> {
+pub async fn create_access_token(
+    db: &db::Database,
+    user_id: UserId,
+    impersonated_user_id: Option<UserId>,
+) -> Result<String> {
     const VERSION: usize = 1;
     let access_token = rpc::auth::random_token();
     let access_token_hash =
         hash_access_token(&access_token).context("failed to hash access token")?;
     let id = db
-        .create_access_token(user_id, &access_token_hash, MAX_ACCESS_TOKENS_TO_STORE)
+        .create_access_token(
+            user_id,
+            impersonated_user_id,
+            &access_token_hash,
+            MAX_ACCESS_TOKENS_TO_STORE,
+        )
         .await?;
     Ok(serde_json::to_string(&AccessTokenJson {
         version: VERSION,
@@ -137,12 +171,22 @@ pub fn encrypt_access_token(access_token: &str, public_key: String) -> Result<St
     Ok(encrypted_access_token)
 }
 
-/// verify access token returns true if the given token is valid for the given user.
-pub async fn verify_access_token(token: &str, user_id: UserId, db: &Arc<Database>) -> Result<bool> {
+pub struct VerifyAccessTokenResult {
+    pub is_valid: bool,
+    pub impersonator_id: Option<UserId>,
+}
+
+/// Checks that the given access token is valid for the given user.
+pub async fn verify_access_token(
+    token: &str,
+    user_id: UserId,
+    db: &Arc<Database>,
+) -> Result<VerifyAccessTokenResult> {
     let token: AccessTokenJson = serde_json::from_str(&token)?;
 
     let db_token = db.get_access_token(token.id).await?;
-    if db_token.user_id != user_id {
+    let token_user_id = db_token.impersonated_user_id.unwrap_or(db_token.user_id);
+    if token_user_id != user_id {
         return Err(anyhow!("no such access token"))?;
     }
 
@@ -154,5 +198,12 @@ pub async fn verify_access_token(token: &str, user_id: UserId, db: &Arc<Database
     let duration = t0.elapsed();
     log::info!("hashed access token in {:?}", duration);
     METRIC_ACCESS_TOKEN_HASHING_TIME.observe(duration.as_millis() as f64);
-    Ok(is_valid)
+    Ok(VerifyAccessTokenResult {
+        is_valid,
+        impersonator_id: if db_token.impersonated_user_id.is_some() {
+            Some(db_token.user_id)
+        } else {
+            None
+        },
+    })
 }

crates/collab/src/db/queries/access_tokens.rs 🔗

@@ -6,6 +6,7 @@ impl Database {
     pub async fn create_access_token(
         &self,
         user_id: UserId,
+        impersonated_user_id: Option<UserId>,
         access_token_hash: &str,
         max_access_token_count: usize,
     ) -> Result<AccessTokenId> {
@@ -14,6 +15,7 @@ impl Database {
 
             let token = access_token::ActiveModel {
                 user_id: ActiveValue::set(user_id),
+                impersonated_user_id: ActiveValue::set(impersonated_user_id),
                 hash: ActiveValue::set(access_token_hash.into()),
                 ..Default::default()
             }

crates/collab/src/db/tests/db_tests.rs 🔗

@@ -146,7 +146,7 @@ test_both_dbs!(
 );
 
 async fn test_create_access_tokens(db: &Arc<Database>) {
-    let user = db
+    let user_1 = db
         .create_user(
             "u1@example.com",
             false,
@@ -158,14 +158,27 @@ async fn test_create_access_tokens(db: &Arc<Database>) {
         .await
         .unwrap()
         .user_id;
+    let user_2 = db
+        .create_user(
+            "u2@example.com",
+            false,
+            NewUserParams {
+                github_login: "u2".into(),
+                github_user_id: 2,
+            },
+        )
+        .await
+        .unwrap()
+        .user_id;
 
-    let token_1 = db.create_access_token(user, "h1", 2).await.unwrap();
-    let token_2 = db.create_access_token(user, "h2", 2).await.unwrap();
+    let token_1 = db.create_access_token(user_1, None, "h1", 2).await.unwrap();
+    let token_2 = db.create_access_token(user_1, None, "h2", 2).await.unwrap();
     assert_eq!(
         db.get_access_token(token_1).await.unwrap(),
         access_token::Model {
             id: token_1,
-            user_id: user,
+            user_id: user_1,
+            impersonated_user_id: None,
             hash: "h1".into(),
         }
     );
@@ -173,17 +186,19 @@ async fn test_create_access_tokens(db: &Arc<Database>) {
         db.get_access_token(token_2).await.unwrap(),
         access_token::Model {
             id: token_2,
-            user_id: user,
+            user_id: user_1,
+            impersonated_user_id: None,
             hash: "h2".into()
         }
     );
 
-    let token_3 = db.create_access_token(user, "h3", 2).await.unwrap();
+    let token_3 = db.create_access_token(user_1, None, "h3", 2).await.unwrap();
     assert_eq!(
         db.get_access_token(token_3).await.unwrap(),
         access_token::Model {
             id: token_3,
-            user_id: user,
+            user_id: user_1,
+            impersonated_user_id: None,
             hash: "h3".into()
         }
     );
@@ -191,18 +206,20 @@ async fn test_create_access_tokens(db: &Arc<Database>) {
         db.get_access_token(token_2).await.unwrap(),
         access_token::Model {
             id: token_2,
-            user_id: user,
+            user_id: user_1,
+            impersonated_user_id: None,
             hash: "h2".into()
         }
     );
     assert!(db.get_access_token(token_1).await.is_err());
 
-    let token_4 = db.create_access_token(user, "h4", 2).await.unwrap();
+    let token_4 = db.create_access_token(user_1, None, "h4", 2).await.unwrap();
     assert_eq!(
         db.get_access_token(token_4).await.unwrap(),
         access_token::Model {
             id: token_4,
-            user_id: user,
+            user_id: user_1,
+            impersonated_user_id: None,
             hash: "h4".into()
         }
     );
@@ -210,12 +227,77 @@ async fn test_create_access_tokens(db: &Arc<Database>) {
         db.get_access_token(token_3).await.unwrap(),
         access_token::Model {
             id: token_3,
-            user_id: user,
+            user_id: user_1,
+            impersonated_user_id: None,
             hash: "h3".into()
         }
     );
     assert!(db.get_access_token(token_2).await.is_err());
     assert!(db.get_access_token(token_1).await.is_err());
+
+    // An access token for user 2 impersonating user 1 does not
+    // count against user 1's access token limit (of 2).
+    let token_5 = db
+        .create_access_token(user_2, Some(user_1), "h5", 2)
+        .await
+        .unwrap();
+    assert_eq!(
+        db.get_access_token(token_5).await.unwrap(),
+        access_token::Model {
+            id: token_5,
+            user_id: user_2,
+            impersonated_user_id: Some(user_1),
+            hash: "h5".into()
+        }
+    );
+    assert_eq!(
+        db.get_access_token(token_3).await.unwrap(),
+        access_token::Model {
+            id: token_3,
+            user_id: user_1,
+            impersonated_user_id: None,
+            hash: "h3".into()
+        }
+    );
+
+    // Only a limited number (2) of access tokens are stored for user 2
+    // impersonating other users.
+    let token_6 = db
+        .create_access_token(user_2, Some(user_1), "h6", 2)
+        .await
+        .unwrap();
+    let token_7 = db
+        .create_access_token(user_2, Some(user_1), "h7", 2)
+        .await
+        .unwrap();
+    assert_eq!(
+        db.get_access_token(token_6).await.unwrap(),
+        access_token::Model {
+            id: token_6,
+            user_id: user_2,
+            impersonated_user_id: Some(user_1),
+            hash: "h6".into()
+        }
+    );
+    assert_eq!(
+        db.get_access_token(token_7).await.unwrap(),
+        access_token::Model {
+            id: token_7,
+            user_id: user_2,
+            impersonated_user_id: Some(user_1),
+            hash: "h7".into()
+        }
+    );
+    assert!(db.get_access_token(token_5).await.is_err());
+    assert_eq!(
+        db.get_access_token(token_3).await.unwrap(),
+        access_token::Model {
+            id: token_3,
+            user_id: user_1,
+            impersonated_user_id: None,
+            hash: "h3".into()
+        }
+    );
 }
 
 test_both_dbs!(

crates/collab/src/rpc.rs 🔗

@@ -1,7 +1,7 @@
 mod connection_pool;
 
 use crate::{
-    auth,
+    auth::{self, Impersonator},
     db::{
         self, BufferId, ChannelId, ChannelRole, ChannelsForUser, CreateChannelResult,
         CreatedChannelMessage, Database, InviteMemberResult, MembershipUpdated, MessageId,
@@ -65,7 +65,7 @@ use std::{
 use time::OffsetDateTime;
 use tokio::sync::{watch, Semaphore};
 use tower::ServiceBuilder;
-use tracing::{info_span, instrument, Instrument};
+use tracing::{field, info_span, instrument, Instrument};
 
 pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30);
 pub const CLEANUP_TIMEOUT: Duration = Duration::from_secs(10);
@@ -561,13 +561,17 @@ impl Server {
         connection: Connection,
         address: String,
         user: User,
+        impersonator: Option<User>,
         mut send_connection_id: Option<oneshot::Sender<ConnectionId>>,
         executor: Executor,
     ) -> impl Future<Output = Result<()>> {
         let this = self.clone();
         let user_id = user.id;
         let login = user.github_login;
-        let span = info_span!("handle connection", %user_id, %login, %address);
+        let span = info_span!("handle connection", %user_id, %login, %address, impersonator = field::Empty);
+        if let Some(impersonator) = impersonator {
+            span.record("impersonator", &impersonator.github_login);
+        }
         let mut teardown = self.teardown.subscribe();
         async move {
             let (connection_id, handle_io, mut incoming_rx) = this
@@ -839,6 +843,7 @@ pub async fn handle_websocket_request(
     ConnectInfo(socket_address): ConnectInfo<SocketAddr>,
     Extension(server): Extension<Arc<Server>>,
     Extension(user): Extension<User>,
+    Extension(impersonator): Extension<Impersonator>,
     ws: WebSocketUpgrade,
 ) -> axum::response::Response {
     if protocol_version != rpc::PROTOCOL_VERSION {
@@ -858,7 +863,14 @@ pub async fn handle_websocket_request(
         let connection = Connection::new(Box::pin(socket));
         async move {
             server
-                .handle_connection(connection, socket_address, user, None, Executor::Production)
+                .handle_connection(
+                    connection,
+                    socket_address,
+                    user,
+                    impersonator.0,
+                    None,
+                    Executor::Production,
+                )
                 .await
                 .log_err();
         }

crates/collab_ui/src/face_pile.rs 🔗

@@ -13,7 +13,7 @@ impl RenderOnce for FacePile {
             let isnt_last = ix < player_count - 1;
 
             div()
-                .z_index((player_count - ix) as u8)
+                .z_index((player_count - ix) as u16)
                 .when(isnt_last, |div| div.neg_mr_1())
                 .child(player)
         });

crates/gpui/src/style.rs 🔗

@@ -110,7 +110,7 @@ pub struct Style {
     /// The mouse cursor style shown when the mouse pointer is over an element.
     pub mouse_cursor: Option<CursorStyle>,
 
-    pub z_index: Option<u8>,
+    pub z_index: Option<u16>,
 
     #[cfg(debug_assertions)]
     pub debug: bool,
@@ -386,7 +386,7 @@ impl Style {
 
         let background_color = self.background.as_ref().and_then(Fill::color);
         if background_color.map_or(false, |color| !color.is_transparent()) {
-            cx.with_z_index(0, |cx| {
+            cx.with_z_index(1, |cx| {
                 let mut border_color = background_color.unwrap_or_default();
                 border_color.a = 0.;
                 cx.paint_quad(quad(
@@ -399,12 +399,12 @@ impl Style {
             });
         }
 
-        cx.with_z_index(0, |cx| {
+        cx.with_z_index(2, |cx| {
             continuation(cx);
         });
 
         if self.is_border_visible() {
-            cx.with_z_index(0, |cx| {
+            cx.with_z_index(3, |cx| {
                 let corner_radii = self.corner_radii.to_pixels(bounds.size, rem_size);
                 let border_widths = self.border_widths.to_pixels(rem_size);
                 let max_border_width = border_widths.max();

crates/gpui/src/styled.rs 🔗

@@ -12,7 +12,7 @@ pub trait Styled: Sized {
 
     gpui_macros::style_helpers!();
 
-    fn z_index(mut self, z_index: u8) -> Self {
+    fn z_index(mut self, z_index: u16) -> Self {
         self.style().z_index = Some(z_index);
         self
     }

crates/gpui/src/window.rs 🔗

@@ -43,30 +43,23 @@ use std::{
 };
 use util::{post_inc, ResultExt};
 
-const ACTIVE_DRAG_Z_INDEX: u8 = 1;
+const ACTIVE_DRAG_Z_INDEX: u16 = 1;
 
 /// A global stacking order, which is created by stacking successive z-index values.
 /// Each z-index will always be interpreted in the context of its parent z-index.
-#[derive(Deref, DerefMut, Clone, Ord, PartialOrd, PartialEq, Eq, Default)]
-pub struct StackingOrder {
-    #[deref]
-    #[deref_mut]
-    context_stack: SmallVec<[u8; 64]>,
-    id: u32,
+#[derive(Debug, Deref, DerefMut, Clone, Ord, PartialOrd, PartialEq, Eq, Default)]
+pub struct StackingOrder(SmallVec<[StackingContext; 64]>);
+
+/// A single entry in a primitive's z-index stacking order
+#[derive(Clone, Ord, PartialOrd, PartialEq, Eq, Default)]
+pub struct StackingContext {
+    z_index: u16,
+    id: u16,
 }
 
-impl std::fmt::Debug for StackingOrder {
+impl std::fmt::Debug for StackingContext {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        let mut stacks = self.context_stack.iter().peekable();
-        write!(f, "[({}): ", self.id)?;
-        while let Some(z_index) = stacks.next() {
-            write!(f, "{z_index}")?;
-            if stacks.peek().is_some() {
-                write!(f, "->")?;
-            }
-        }
-        write!(f, "]")?;
-        Ok(())
+        write!(f, "{{{}.{}}} ", self.z_index, self.id)
     }
 }
 
@@ -315,8 +308,8 @@ pub(crate) struct Frame {
     pub(crate) scene: Scene,
     pub(crate) depth_map: Vec<(StackingOrder, EntityId, Bounds<Pixels>)>,
     pub(crate) z_index_stack: StackingOrder,
-    pub(crate) next_stacking_order_id: u32,
-    next_root_z_index: u8,
+    next_stacking_order_id: u16,
+    next_root_z_index: u16,
     content_mask_stack: Vec<ContentMask<Pixels>>,
     element_offset_stack: Vec<Point<Pixels>>,
     requested_input_handler: Option<RequestedInputHandler>,
@@ -1105,7 +1098,11 @@ impl<'a> WindowContext<'a> {
             if level >= opaque_level {
                 break;
             }
-            if opaque_level.starts_with(&[ACTIVE_DRAG_Z_INDEX]) {
+            if opaque_level
+                .first()
+                .map(|c| c.z_index == ACTIVE_DRAG_Z_INDEX)
+                .unwrap_or(false)
+            {
                 continue;
             }
 
@@ -2452,36 +2449,40 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
                 size: self.window().viewport_size,
             },
         };
+
+        let new_root_z_index = post_inc(&mut self.window_mut().next_frame.next_root_z_index);
         let new_stacking_order_id =
             post_inc(&mut self.window_mut().next_frame.next_stacking_order_id);
-        let new_root_z_index = post_inc(&mut self.window_mut().next_frame.next_root_z_index);
+        let new_context = StackingContext {
+            z_index: new_root_z_index,
+            id: new_stacking_order_id,
+        };
+
         let old_stacking_order = mem::take(&mut self.window_mut().next_frame.z_index_stack);
-        self.window_mut().next_frame.z_index_stack.id = new_stacking_order_id;
-        self.window_mut()
-            .next_frame
-            .z_index_stack
-            .push(new_root_z_index);
+
+        self.window_mut().next_frame.z_index_stack.push(new_context);
         self.window_mut().next_frame.content_mask_stack.push(mask);
         let result = f(self);
         self.window_mut().next_frame.content_mask_stack.pop();
         self.window_mut().next_frame.z_index_stack = old_stacking_order;
+
         result
     }
 
     /// Called during painting to invoke the given closure in a new stacking context. The given
     /// z-index is interpreted relative to the previous call to `stack`.
-    fn with_z_index<R>(&mut self, z_index: u8, f: impl FnOnce(&mut Self) -> R) -> R {
+    fn with_z_index<R>(&mut self, z_index: u16, f: impl FnOnce(&mut Self) -> R) -> R {
         let new_stacking_order_id =
             post_inc(&mut self.window_mut().next_frame.next_stacking_order_id);
-        let old_stacking_order_id = mem::replace(
-            &mut self.window_mut().next_frame.z_index_stack.id,
-            new_stacking_order_id,
-        );
-        self.window_mut().next_frame.z_index_stack.id = new_stacking_order_id;
-        self.window_mut().next_frame.z_index_stack.push(z_index);
+        let new_context = StackingContext {
+            z_index,
+            id: new_stacking_order_id,
+        };
+
+        self.window_mut().next_frame.z_index_stack.push(new_context);
         let result = f(self);
-        self.window_mut().next_frame.z_index_stack.id = old_stacking_order_id;
         self.window_mut().next_frame.z_index_stack.pop();
+
         result
     }
 

crates/storybook/src/stories/z_index.rs 🔗

@@ -76,7 +76,7 @@ impl Styles for Div {}
 
 #[derive(IntoElement)]
 struct ZIndexExample {
-    z_index: u8,
+    z_index: u16,
 }
 
 impl RenderOnce for ZIndexExample {
@@ -166,7 +166,7 @@ impl RenderOnce for ZIndexExample {
 }
 
 impl ZIndexExample {
-    pub fn new(z_index: u8) -> Self {
+    pub fn new(z_index: u16) -> Self {
         Self { z_index }
     }
 }

crates/terminal/src/terminal.rs 🔗

@@ -47,7 +47,7 @@ use std::{
     os::unix::prelude::AsRawFd,
     path::PathBuf,
     sync::Arc,
-    time::{Duration, Instant},
+    time::Duration,
 };
 use thiserror::Error;
 
@@ -385,8 +385,6 @@ impl TerminalBuilder {
             last_content: Default::default(),
             last_mouse: None,
             matches: Vec::new(),
-            last_synced: Instant::now(),
-            sync_task: None,
             selection_head: None,
             shell_fd: fd as u32,
             shell_pid,
@@ -542,8 +540,6 @@ pub struct Terminal {
     last_mouse_position: Option<Point<Pixels>>,
     pub matches: Vec<RangeInclusive<AlacPoint>>,
     pub last_content: TerminalContent,
-    last_synced: Instant,
-    sync_task: Option<Task<()>>,
     pub selection_head: Option<AlacPoint>,
     pub breadcrumb_text: String,
     shell_pid: u32,
@@ -977,40 +973,15 @@ impl Terminal {
         self.input(paste_text);
     }
 
-    pub fn try_sync(&mut self, cx: &mut ModelContext<Self>) {
+    pub fn sync(&mut self, cx: &mut ModelContext<Self>) {
         let term = self.term.clone();
-
-        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
-        } else if let None = self.sync_task {
-            //Skip this frame
-            let delay = cx.background_executor().timer(Duration::from_millis(16));
-            self.sync_task = Some(cx.spawn(|weak_handle, mut cx| async move {
-                delay.await;
-                if let Some(handle) = weak_handle.upgrade() {
-                    handle
-                        .update(&mut cx, |terminal, cx| {
-                            terminal.sync_task.take();
-                            cx.notify();
-                        })
-                        .ok();
-                }
-            }));
-            return;
-        } else {
-            //No lock and delayed rendering already scheduled, nothing to do
-            return;
-        };
-
+        let mut terminal = term.lock_unfair();
         //Note that the ordering of events matters for event processing
         while let Some(e) = self.events.pop_front() {
             self.process_terminal_event(&e, &mut terminal, cx)
         }
 
         self.last_content = Self::make_content(&terminal, &self.last_content);
-        self.last_synced = Instant::now();
     }
 
     fn make_content(term: &Term<ZedListener>, last_content: &TerminalContent) -> TerminalContent {

crates/terminal_view/src/terminal_element.rs 🔗

@@ -446,7 +446,7 @@ impl TerminalElement {
 
         let last_hovered_word = self.terminal.update(cx, |terminal, cx| {
             terminal.set_size(dimensions);
-            terminal.try_sync(cx);
+            terminal.sync(cx);
             if self.can_navigate_to_selected_word && terminal.can_navigate_to_selected_word() {
                 terminal.last_content.last_hovered_word.clone()
             } else {

crates/ui/src/styles/elevation.rs 🔗

@@ -20,7 +20,7 @@ pub enum ElevationIndex {
 }
 
 impl ElevationIndex {
-    pub fn z_index(self) -> u8 {
+    pub fn z_index(self) -> u16 {
         match self {
             ElevationIndex::Background => 0,
             ElevationIndex::Surface => 42,

crates/workspace/src/item.rs 🔗

@@ -448,11 +448,13 @@ impl<T: Item> ItemHandle for View<T> {
                             workspace.unfollow(&pane, cx);
                         }
 
-                        if item.add_event_to_update_proto(
-                            event,
-                            &mut *pending_update.borrow_mut(),
-                            cx,
-                        ) && !pending_update_scheduled.load(Ordering::SeqCst)
+                        if item.focus_handle(cx).contains_focused(cx)
+                            && item.add_event_to_update_proto(
+                                event,
+                                &mut *pending_update.borrow_mut(),
+                                cx,
+                            )
+                            && !pending_update_scheduled.load(Ordering::SeqCst)
                         {
                             pending_update_scheduled.store(true, Ordering::SeqCst);
                             cx.defer({

crates/workspace/src/pane.rs 🔗

@@ -239,6 +239,7 @@ impl Pane {
         let focus_handle = cx.focus_handle();
 
         let subscriptions = vec![
+            cx.on_focus(&focus_handle, Pane::focus_in),
             cx.on_focus_in(&focus_handle, Pane::focus_in),
             cx.on_focus_out(&focus_handle, Pane::focus_out),
         ];

crates/workspace/src/workspace.rs 🔗

@@ -4334,7 +4334,7 @@ impl Element for DisconnectedOverlay {
     }
 
     fn paint(&mut self, bounds: Bounds<Pixels>, overlay: &mut Self::State, cx: &mut WindowContext) {
-        cx.with_z_index(u8::MAX, |cx| {
+        cx.with_z_index(u16::MAX, |cx| {
             cx.add_opaque_layer(bounds);
             overlay.paint(cx);
         })

docs/src/developing_zed__building_zed.md 🔗

@@ -14,7 +14,7 @@
 - Ensure that the Xcode command line tools are using your newly installed copy of Xcode:
 
     ```
-    sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer.
+    sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
     ```
 
 * Install the Rust wasm toolchain:

script/squawk 🔗

@@ -8,13 +8,12 @@ set -e
 
 if [ -z "$GITHUB_BASE_REF" ]; then
   echo 'Not a pull request, skipping squawk modified migrations linting'
-  return 0
+  exit
 fi
 
 SQUAWK_VERSION=0.26.0
 SQUAWK_BIN="./target/squawk-$SQUAWK_VERSION"
-SQUAWK_ARGS="--assume-in-transaction"
-
+SQUAWK_ARGS="--assume-in-transaction --config script/lib/squawk.toml"
 
 if  [ ! -f "$SQUAWK_BIN" ]; then
   curl -L -o "$SQUAWK_BIN" "https://github.com/sbdchd/squawk/releases/download/v$SQUAWK_VERSION/squawk-darwin-x86_64"