Cargo.lock 🔗
@@ -1452,7 +1452,7 @@ dependencies = [
[[package]]
name = "collab"
-version = "0.36.1"
+version = "0.37.0"
dependencies = [
"anyhow",
"async-trait",
Joseph T. Lyons created
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(-)
@@ -1452,7 +1452,7 @@ dependencies = [
[[package]]
name = "collab"
-version = "0.36.1"
+version = "0.37.0"
dependencies = [
"anyhow",
"async-trait",
@@ -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]]
@@ -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");
@@ -0,0 +1 @@
+ALTER TABLE access_tokens ADD COLUMN impersonated_user_id integer;
@@ -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,
}))
}
@@ -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
+ },
+ })
}
@@ -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()
}
@@ -7,6 +7,7 @@ pub struct Model {
#[sea_orm(primary_key)]
pub id: AccessTokenId,
pub user_id: UserId,
+ pub impersonated_user_id: Option<UserId>,
pub hash: String,
}
@@ -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!(
@@ -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();
}
@@ -213,6 +213,7 @@ impl TestServer {
server_conn,
client_name,
user,
+ None,
Some(connection_id_tx),
Executor::Deterministic(cx.background_executor().clone()),
))
@@ -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)
});
@@ -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();
@@ -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
}
@@ -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
}
@@ -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 }
}
}
@@ -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 {
@@ -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 {
@@ -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,
@@ -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({
@@ -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),
];
@@ -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);
})
@@ -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:
@@ -0,0 +1,4 @@
+excluded_rules = [
+ "prefer-big-int",
+ "prefer-bigint-over-int",
+]
@@ -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"