From a5ee8fc805382f0385a14c33c238687333e5dcfb Mon Sep 17 00:00:00 2001 From: KCaverly Date: Fri, 8 Sep 2023 12:35:15 -0400 Subject: [PATCH 01/10] initial outline for rate limiting status updates --- crates/search/src/project_search.rs | 16 +++- crates/semantic_index/src/embedding.rs | 75 +++++++++++++++++++ crates/semantic_index/src/semantic_index.rs | 17 +++-- .../src/semantic_index_tests.rs | 6 +- 4 files changed, 106 insertions(+), 8 deletions(-) diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index c52be64141..977ead8c9e 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -34,6 +34,7 @@ use std::{ ops::{Not, Range}, path::PathBuf, sync::Arc, + time::Duration, }; use util::ResultExt as _; use workspace::{ @@ -319,11 +320,22 @@ impl View for ProjectSearchView { let status = semantic.index_status; match status { SemanticIndexStatus::Indexed => Some("Indexing complete".to_string()), - SemanticIndexStatus::Indexing { remaining_files } => { + SemanticIndexStatus::Indexing { + remaining_files, + rate_limiting, + } => { if remaining_files == 0 { Some(format!("Indexing...")) } else { - Some(format!("Remaining files to index: {}", remaining_files)) + if rate_limiting > Duration::ZERO { + Some(format!( + "Remaining files to index (rate limit resets in {}s): {}", + rate_limiting.as_secs(), + remaining_files + )) + } else { + Some(format!("Remaining files to index: {}", remaining_files)) + } } } SemanticIndexStatus::NotIndexed => None, diff --git a/crates/semantic_index/src/embedding.rs b/crates/semantic_index/src/embedding.rs index 7228738525..6affac2556 100644 --- a/crates/semantic_index/src/embedding.rs +++ b/crates/semantic_index/src/embedding.rs @@ -7,7 +7,9 @@ use isahc::http::StatusCode; use isahc::prelude::Configurable; use isahc::{AsyncBody, Response}; use lazy_static::lazy_static; +use parking_lot::Mutex; use parse_duration::parse; +use postage::watch; use rusqlite::types::{FromSql, FromSqlResult, ToSqlOutput, ValueRef}; use rusqlite::ToSql; use serde::{Deserialize, Serialize}; @@ -82,6 +84,8 @@ impl ToSql for Embedding { pub struct OpenAIEmbeddings { pub client: Arc, pub executor: Arc, + rate_limit_count_rx: watch::Receiver<(Duration, usize)>, + rate_limit_count_tx: Arc>>, } #[derive(Serialize)] @@ -114,12 +118,16 @@ pub trait EmbeddingProvider: Sync + Send { async fn embed_batch(&self, spans: Vec) -> Result>; fn max_tokens_per_batch(&self) -> usize; fn truncate(&self, span: &str) -> (String, usize); + fn rate_limit_expiration(&self) -> Duration; } pub struct DummyEmbeddings {} #[async_trait] impl EmbeddingProvider for DummyEmbeddings { + fn rate_limit_expiration(&self) -> Duration { + Duration::ZERO + } async fn embed_batch(&self, spans: Vec) -> Result> { // 1024 is the OpenAI Embeddings size for ada models. // the model we will likely be starting with. @@ -149,6 +157,53 @@ impl EmbeddingProvider for DummyEmbeddings { const OPENAI_INPUT_LIMIT: usize = 8190; impl OpenAIEmbeddings { + pub fn new(client: Arc, executor: Arc) -> Self { + let (rate_limit_count_tx, rate_limit_count_rx) = watch::channel_with((Duration::ZERO, 0)); + let rate_limit_count_tx = Arc::new(Mutex::new(rate_limit_count_tx)); + + OpenAIEmbeddings { + client, + executor, + rate_limit_count_rx, + rate_limit_count_tx, + } + } + + fn resolve_rate_limit(&self) { + let (current_delay, delay_count) = *self.rate_limit_count_tx.lock().borrow(); + let updated_count = delay_count - 1; + let updated_duration = if updated_count == 0 { + Duration::ZERO + } else { + current_delay + }; + + log::trace!( + "resolving rate limit: Count: {:?} Duration: {:?}", + updated_count, + updated_duration + ); + + *self.rate_limit_count_tx.lock().borrow_mut() = (updated_duration, updated_count); + } + + fn update_rate_limit(&self, delay_duration: Duration, count_increase: usize) { + let (current_delay, delay_count) = *self.rate_limit_count_tx.lock().borrow(); + let updated_count = delay_count + count_increase; + let updated_duration = if current_delay < delay_duration { + delay_duration + } else { + current_delay + }; + + log::trace!( + "updating rate limit: Count: {:?} Duration: {:?}", + updated_count, + updated_duration + ); + + *self.rate_limit_count_tx.lock().borrow_mut() = (updated_duration, updated_count); + } async fn send_request( &self, api_key: &str, @@ -179,6 +234,10 @@ impl EmbeddingProvider for OpenAIEmbeddings { 50000 } + fn rate_limit_expiration(&self) -> Duration { + let (duration, _) = *self.rate_limit_count_rx.borrow(); + duration + } fn truncate(&self, span: &str) -> (String, usize) { let mut tokens = OPENAI_BPE_TOKENIZER.encode_with_special_tokens(span); let output = if tokens.len() > OPENAI_INPUT_LIMIT { @@ -203,6 +262,7 @@ impl EmbeddingProvider for OpenAIEmbeddings { .ok_or_else(|| anyhow!("no api key"))?; let mut request_number = 0; + let mut rate_limiting = false; let mut request_timeout: u64 = 15; let mut response: Response; while request_number < MAX_RETRIES { @@ -229,6 +289,12 @@ impl EmbeddingProvider for OpenAIEmbeddings { response.usage.total_tokens ); + // If we complete a request successfully that was previously rate_limited + // resolve the rate limit + if rate_limiting { + self.resolve_rate_limit() + } + return Ok(response .data .into_iter() @@ -254,6 +320,15 @@ impl EmbeddingProvider for OpenAIEmbeddings { } }; + // If we've previously rate limited, increment the duration but not the count + if rate_limiting { + self.update_rate_limit(delay_duration, 0); + } else { + self.update_rate_limit(delay_duration, 1); + } + + rate_limiting = true; + log::trace!( "openai rate limiting: waiting {:?} until lifted", &delay_duration diff --git a/crates/semantic_index/src/semantic_index.rs b/crates/semantic_index/src/semantic_index.rs index 0e18c42049..8fba7de0f0 100644 --- a/crates/semantic_index/src/semantic_index.rs +++ b/crates/semantic_index/src/semantic_index.rs @@ -91,10 +91,7 @@ pub fn init( let semantic_index = SemanticIndex::new( fs, db_file_path, - Arc::new(OpenAIEmbeddings { - client: http_client, - executor: cx.background(), - }), + Arc::new(OpenAIEmbeddings::new(http_client, cx.background())), language_registry, cx.clone(), ) @@ -113,7 +110,10 @@ pub fn init( pub enum SemanticIndexStatus { NotIndexed, Indexed, - Indexing { remaining_files: usize }, + Indexing { + remaining_files: usize, + rate_limiting: Duration, + }, } pub struct SemanticIndex { @@ -132,6 +132,8 @@ struct ProjectState { pending_file_count_rx: watch::Receiver, pending_file_count_tx: Arc>>, pending_index: usize, + rate_limiting_count_rx: watch::Receiver, + rate_limiting_count_tx: Arc>>, _subscription: gpui::Subscription, _observe_pending_file_count: Task<()>, } @@ -223,11 +225,15 @@ impl ProjectState { fn new(subscription: gpui::Subscription, cx: &mut ModelContext) -> Self { let (pending_file_count_tx, pending_file_count_rx) = watch::channel_with(0); let pending_file_count_tx = Arc::new(Mutex::new(pending_file_count_tx)); + let (rate_limiting_count_tx, rate_limiting_count_rx) = watch::channel_with(0); + let rate_limiting_count_tx = Arc::new(Mutex::new(rate_limiting_count_tx)); Self { worktrees: Default::default(), pending_file_count_rx: pending_file_count_rx.clone(), pending_file_count_tx, pending_index: 0, + rate_limiting_count_rx: rate_limiting_count_rx.clone(), + rate_limiting_count_tx, _subscription: subscription, _observe_pending_file_count: cx.spawn_weak({ let mut pending_file_count_rx = pending_file_count_rx.clone(); @@ -293,6 +299,7 @@ impl SemanticIndex { } else { SemanticIndexStatus::Indexing { remaining_files: project_state.pending_file_count_rx.borrow().clone(), + rate_limiting: self.embedding_provider.rate_limit_expiration(), } } } else { diff --git a/crates/semantic_index/src/semantic_index_tests.rs b/crates/semantic_index/src/semantic_index_tests.rs index ffd8db8781..09c94b9a94 100644 --- a/crates/semantic_index/src/semantic_index_tests.rs +++ b/crates/semantic_index/src/semantic_index_tests.rs @@ -21,7 +21,7 @@ use std::{ atomic::{self, AtomicUsize}, Arc, }, - time::SystemTime, + time::{Duration, SystemTime}, }; use unindent::Unindent; use util::RandomCharIter; @@ -1275,6 +1275,10 @@ impl EmbeddingProvider for FakeEmbeddingProvider { 200 } + fn rate_limit_expiration(&self) -> Duration { + Duration::ZERO + } + async fn embed_batch(&self, spans: Vec) -> Result> { self.embedding_count .fetch_add(spans.len(), atomic::Ordering::SeqCst); From bf43f93197e9149ef7625ed7b61f368edca79e82 Mon Sep 17 00:00:00 2001 From: KCaverly Date: Fri, 8 Sep 2023 15:04:50 -0400 Subject: [PATCH 02/10] updated semantic_index reset status to leverage target reset system time as opposed to duration --- crates/search/src/project_search.rs | 22 +++--- crates/semantic_index/src/embedding.rs | 67 +++++++++---------- crates/semantic_index/src/semantic_index.rs | 10 +-- .../src/semantic_index_tests.rs | 6 +- 4 files changed, 50 insertions(+), 55 deletions(-) diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 977ead8c9e..6b5ebd56d4 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -34,7 +34,7 @@ use std::{ ops::{Not, Range}, path::PathBuf, sync::Arc, - time::Duration, + time::SystemTime, }; use util::ResultExt as _; use workspace::{ @@ -322,17 +322,23 @@ impl View for ProjectSearchView { SemanticIndexStatus::Indexed => Some("Indexing complete".to_string()), SemanticIndexStatus::Indexing { remaining_files, - rate_limiting, + rate_limit_expiration_time, } => { if remaining_files == 0 { Some(format!("Indexing...")) } else { - if rate_limiting > Duration::ZERO { - Some(format!( - "Remaining files to index (rate limit resets in {}s): {}", - rate_limiting.as_secs(), - remaining_files - )) + if let Some(rate_limit_expiration_time) = rate_limit_expiration_time { + if let Ok(remaining_seconds) = + rate_limit_expiration_time.duration_since(SystemTime::now()) + { + Some(format!( + "Remaining files to index(rate limit resets in {}s): {}", + remaining_seconds.as_secs(), + remaining_files + )) + } else { + Some(format!("Remaining files to index: {}", remaining_files)) + } } else { Some(format!("Remaining files to index: {}", remaining_files)) } diff --git a/crates/semantic_index/src/embedding.rs b/crates/semantic_index/src/embedding.rs index 6affac2556..148b354794 100644 --- a/crates/semantic_index/src/embedding.rs +++ b/crates/semantic_index/src/embedding.rs @@ -14,8 +14,9 @@ use rusqlite::types::{FromSql, FromSqlResult, ToSqlOutput, ValueRef}; use rusqlite::ToSql; use serde::{Deserialize, Serialize}; use std::env; +use std::ops::Add; use std::sync::Arc; -use std::time::Duration; +use std::time::{Duration, SystemTime}; use tiktoken_rs::{cl100k_base, CoreBPE}; use util::http::{HttpClient, Request}; @@ -84,8 +85,8 @@ impl ToSql for Embedding { pub struct OpenAIEmbeddings { pub client: Arc, pub executor: Arc, - rate_limit_count_rx: watch::Receiver<(Duration, usize)>, - rate_limit_count_tx: Arc>>, + rate_limit_count_rx: watch::Receiver<(Option, usize)>, + rate_limit_count_tx: Arc, usize)>>>, } #[derive(Serialize)] @@ -118,15 +119,15 @@ pub trait EmbeddingProvider: Sync + Send { async fn embed_batch(&self, spans: Vec) -> Result>; fn max_tokens_per_batch(&self) -> usize; fn truncate(&self, span: &str) -> (String, usize); - fn rate_limit_expiration(&self) -> Duration; + fn rate_limit_expiration(&self) -> Option; } pub struct DummyEmbeddings {} #[async_trait] impl EmbeddingProvider for DummyEmbeddings { - fn rate_limit_expiration(&self) -> Duration { - Duration::ZERO + fn rate_limit_expiration(&self) -> Option { + None } async fn embed_batch(&self, spans: Vec) -> Result> { // 1024 is the OpenAI Embeddings size for ada models. @@ -158,7 +159,7 @@ const OPENAI_INPUT_LIMIT: usize = 8190; impl OpenAIEmbeddings { pub fn new(client: Arc, executor: Arc) -> Self { - let (rate_limit_count_tx, rate_limit_count_rx) = watch::channel_with((Duration::ZERO, 0)); + let (rate_limit_count_tx, rate_limit_count_rx) = watch::channel_with((None, 0)); let rate_limit_count_tx = Arc::new(Mutex::new(rate_limit_count_tx)); OpenAIEmbeddings { @@ -170,39 +171,32 @@ impl OpenAIEmbeddings { } fn resolve_rate_limit(&self) { - let (current_delay, delay_count) = *self.rate_limit_count_tx.lock().borrow(); + let (reset_time, delay_count) = *self.rate_limit_count_tx.lock().borrow(); let updated_count = delay_count - 1; - let updated_duration = if updated_count == 0 { - Duration::ZERO - } else { - current_delay - }; + let updated_time = if updated_count == 0 { None } else { reset_time }; - log::trace!( - "resolving rate limit: Count: {:?} Duration: {:?}", - updated_count, - updated_duration - ); + log::trace!("resolving rate limit: Count: {:?}", updated_count); - *self.rate_limit_count_tx.lock().borrow_mut() = (updated_duration, updated_count); + *self.rate_limit_count_tx.lock().borrow_mut() = (updated_time, updated_count); } - fn update_rate_limit(&self, delay_duration: Duration, count_increase: usize) { - let (current_delay, delay_count) = *self.rate_limit_count_tx.lock().borrow(); - let updated_count = delay_count + count_increase; - let updated_duration = if current_delay < delay_duration { - delay_duration + fn update_rate_limit(&self, reset_time: SystemTime, count_increase: usize) { + let (original_time, original_count) = *self.rate_limit_count_tx.lock().borrow(); + let updated_count = original_count + count_increase; + + let updated_time = if let Some(original_time) = original_time { + if reset_time < original_time { + Some(reset_time) + } else { + Some(original_time) + } } else { - current_delay + Some(reset_time) }; - log::trace!( - "updating rate limit: Count: {:?} Duration: {:?}", - updated_count, - updated_duration - ); + log::trace!("updating rate limit: Count: {:?}", updated_count); - *self.rate_limit_count_tx.lock().borrow_mut() = (updated_duration, updated_count); + *self.rate_limit_count_tx.lock().borrow_mut() = (updated_time, updated_count); } async fn send_request( &self, @@ -234,9 +228,9 @@ impl EmbeddingProvider for OpenAIEmbeddings { 50000 } - fn rate_limit_expiration(&self) -> Duration { - let (duration, _) = *self.rate_limit_count_rx.borrow(); - duration + fn rate_limit_expiration(&self) -> Option { + let (expiration_time, _) = *self.rate_limit_count_rx.borrow(); + expiration_time } fn truncate(&self, span: &str) -> (String, usize) { let mut tokens = OPENAI_BPE_TOKENIZER.encode_with_special_tokens(span); @@ -321,10 +315,11 @@ impl EmbeddingProvider for OpenAIEmbeddings { }; // If we've previously rate limited, increment the duration but not the count + let reset_time = SystemTime::now().add(delay_duration); if rate_limiting { - self.update_rate_limit(delay_duration, 0); + self.update_rate_limit(reset_time, 0); } else { - self.update_rate_limit(delay_duration, 1); + self.update_rate_limit(reset_time, 1); } rate_limiting = true; diff --git a/crates/semantic_index/src/semantic_index.rs b/crates/semantic_index/src/semantic_index.rs index 8fba7de0f0..b60d697b43 100644 --- a/crates/semantic_index/src/semantic_index.rs +++ b/crates/semantic_index/src/semantic_index.rs @@ -112,7 +112,7 @@ pub enum SemanticIndexStatus { Indexed, Indexing { remaining_files: usize, - rate_limiting: Duration, + rate_limit_expiration_time: Option, }, } @@ -132,8 +132,6 @@ struct ProjectState { pending_file_count_rx: watch::Receiver, pending_file_count_tx: Arc>>, pending_index: usize, - rate_limiting_count_rx: watch::Receiver, - rate_limiting_count_tx: Arc>>, _subscription: gpui::Subscription, _observe_pending_file_count: Task<()>, } @@ -225,15 +223,11 @@ impl ProjectState { fn new(subscription: gpui::Subscription, cx: &mut ModelContext) -> Self { let (pending_file_count_tx, pending_file_count_rx) = watch::channel_with(0); let pending_file_count_tx = Arc::new(Mutex::new(pending_file_count_tx)); - let (rate_limiting_count_tx, rate_limiting_count_rx) = watch::channel_with(0); - let rate_limiting_count_tx = Arc::new(Mutex::new(rate_limiting_count_tx)); Self { worktrees: Default::default(), pending_file_count_rx: pending_file_count_rx.clone(), pending_file_count_tx, pending_index: 0, - rate_limiting_count_rx: rate_limiting_count_rx.clone(), - rate_limiting_count_tx, _subscription: subscription, _observe_pending_file_count: cx.spawn_weak({ let mut pending_file_count_rx = pending_file_count_rx.clone(); @@ -299,7 +293,7 @@ impl SemanticIndex { } else { SemanticIndexStatus::Indexing { remaining_files: project_state.pending_file_count_rx.borrow().clone(), - rate_limiting: self.embedding_provider.rate_limit_expiration(), + rate_limit_expiration_time: self.embedding_provider.rate_limit_expiration(), } } } else { diff --git a/crates/semantic_index/src/semantic_index_tests.rs b/crates/semantic_index/src/semantic_index_tests.rs index 09c94b9a94..4bc95bec62 100644 --- a/crates/semantic_index/src/semantic_index_tests.rs +++ b/crates/semantic_index/src/semantic_index_tests.rs @@ -21,7 +21,7 @@ use std::{ atomic::{self, AtomicUsize}, Arc, }, - time::{Duration, SystemTime}, + time::SystemTime, }; use unindent::Unindent; use util::RandomCharIter; @@ -1275,8 +1275,8 @@ impl EmbeddingProvider for FakeEmbeddingProvider { 200 } - fn rate_limit_expiration(&self) -> Duration { - Duration::ZERO + fn rate_limit_expiration(&self) -> Option { + None } async fn embed_batch(&self, spans: Vec) -> Result> { From 37915ec4f2f5d03eedc5accd979c37f4c3da0121 Mon Sep 17 00:00:00 2001 From: KCaverly Date: Fri, 8 Sep 2023 16:53:16 -0400 Subject: [PATCH 03/10] updated notify to accomodate for updated countdown --- crates/search/src/project_search.rs | 2 +- crates/semantic_index/src/embedding.rs | 42 ++++++++++----------- crates/semantic_index/src/semantic_index.rs | 17 +++++++-- 3 files changed, 35 insertions(+), 26 deletions(-) diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 6b5ebd56d4..5a1d5992a6 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -332,7 +332,7 @@ impl View for ProjectSearchView { rate_limit_expiration_time.duration_since(SystemTime::now()) { Some(format!( - "Remaining files to index(rate limit resets in {}s): {}", + "Remaining files to index (rate limit resets in {}s): {}", remaining_seconds.as_secs(), remaining_files )) diff --git a/crates/semantic_index/src/embedding.rs b/crates/semantic_index/src/embedding.rs index 148b354794..7bac809c97 100644 --- a/crates/semantic_index/src/embedding.rs +++ b/crates/semantic_index/src/embedding.rs @@ -85,8 +85,8 @@ impl ToSql for Embedding { pub struct OpenAIEmbeddings { pub client: Arc, pub executor: Arc, - rate_limit_count_rx: watch::Receiver<(Option, usize)>, - rate_limit_count_tx: Arc, usize)>>>, + rate_limit_count_rx: watch::Receiver>, + rate_limit_count_tx: Arc>>>, } #[derive(Serialize)] @@ -159,7 +159,7 @@ const OPENAI_INPUT_LIMIT: usize = 8190; impl OpenAIEmbeddings { pub fn new(client: Arc, executor: Arc) -> Self { - let (rate_limit_count_tx, rate_limit_count_rx) = watch::channel_with((None, 0)); + let (rate_limit_count_tx, rate_limit_count_rx) = watch::channel_with(None); let rate_limit_count_tx = Arc::new(Mutex::new(rate_limit_count_tx)); OpenAIEmbeddings { @@ -171,18 +171,22 @@ impl OpenAIEmbeddings { } fn resolve_rate_limit(&self) { - let (reset_time, delay_count) = *self.rate_limit_count_tx.lock().borrow(); - let updated_count = delay_count - 1; - let updated_time = if updated_count == 0 { None } else { reset_time }; + let reset_time = *self.rate_limit_count_tx.lock().borrow(); - log::trace!("resolving rate limit: Count: {:?}", updated_count); + if let Some(reset_time) = reset_time { + if SystemTime::now() >= reset_time { + *self.rate_limit_count_tx.lock().borrow_mut() = None + } + } - *self.rate_limit_count_tx.lock().borrow_mut() = (updated_time, updated_count); + log::trace!( + "resolving reset time: {:?}", + *self.rate_limit_count_tx.lock().borrow() + ); } - fn update_rate_limit(&self, reset_time: SystemTime, count_increase: usize) { - let (original_time, original_count) = *self.rate_limit_count_tx.lock().borrow(); - let updated_count = original_count + count_increase; + fn update_reset_time(&self, reset_time: SystemTime) { + let original_time = *self.rate_limit_count_tx.lock().borrow(); let updated_time = if let Some(original_time) = original_time { if reset_time < original_time { @@ -194,9 +198,9 @@ impl OpenAIEmbeddings { Some(reset_time) }; - log::trace!("updating rate limit: Count: {:?}", updated_count); + log::trace!("updating rate limit time: {:?}", updated_time); - *self.rate_limit_count_tx.lock().borrow_mut() = (updated_time, updated_count); + *self.rate_limit_count_tx.lock().borrow_mut() = updated_time; } async fn send_request( &self, @@ -229,8 +233,7 @@ impl EmbeddingProvider for OpenAIEmbeddings { } fn rate_limit_expiration(&self) -> Option { - let (expiration_time, _) = *self.rate_limit_count_rx.borrow(); - expiration_time + *self.rate_limit_count_rx.borrow() } fn truncate(&self, span: &str) -> (String, usize) { let mut tokens = OPENAI_BPE_TOKENIZER.encode_with_special_tokens(span); @@ -296,6 +299,7 @@ impl EmbeddingProvider for OpenAIEmbeddings { .collect()); } StatusCode::TOO_MANY_REQUESTS => { + rate_limiting = true; let mut body = String::new(); response.body_mut().read_to_string(&mut body).await?; @@ -316,13 +320,7 @@ impl EmbeddingProvider for OpenAIEmbeddings { // If we've previously rate limited, increment the duration but not the count let reset_time = SystemTime::now().add(delay_duration); - if rate_limiting { - self.update_rate_limit(reset_time, 0); - } else { - self.update_rate_limit(reset_time, 1); - } - - rate_limiting = true; + self.update_reset_time(reset_time); log::trace!( "openai rate limiting: waiting {:?} until lifted", diff --git a/crates/semantic_index/src/semantic_index.rs b/crates/semantic_index/src/semantic_index.rs index b60d697b43..92b11f00d1 100644 --- a/crates/semantic_index/src/semantic_index.rs +++ b/crates/semantic_index/src/semantic_index.rs @@ -232,9 +232,20 @@ impl ProjectState { _observe_pending_file_count: cx.spawn_weak({ let mut pending_file_count_rx = pending_file_count_rx.clone(); |this, mut cx| async move { - while let Some(_) = pending_file_count_rx.next().await { - if let Some(this) = this.upgrade(&cx) { - this.update(&mut cx, |_, cx| cx.notify()); + loop { + let mut timer = cx.background().timer(Duration::from_millis(350)).fuse(); + let mut pending_file_count = pending_file_count_rx.next().fuse(); + futures::select_biased! { + _ = pending_file_count => { + if let Some(this) = this.upgrade(&cx) { + this.update(&mut cx, |_, cx| cx.notify()); + } + }, + _ = timer => { + if let Some(this) = this.upgrade(&cx) { + this.update(&mut cx, |_, cx| cx.notify()); + } + } } } } From ba1c350dad27cc73e0887c243cfc9c7ac2a7ce5b Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 8 Sep 2023 13:55:13 -0600 Subject: [PATCH 04/10] vim: Add ZZ and ZQ The major change here is a refactoring to allow controling the save behaviour when closing items, which is pre-work needed for vim command palette. For zed-industries/community#1868 --- assets/keymaps/vim.json | 12 ++ crates/collab/src/tests/integration_tests.rs | 4 +- crates/file_finder/src/file_finder.rs | 9 +- crates/terminal_view/src/terminal_view.rs | 7 +- crates/workspace/src/item.rs | 10 +- crates/workspace/src/pane.rs | 198 ++++++++++++++----- crates/workspace/src/workspace.rs | 46 +++-- crates/zed/src/menus.rs | 7 +- crates/zed/src/zed.rs | 54 +++-- 9 files changed, 258 insertions(+), 89 deletions(-) diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 45891adee6..b47907783e 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -198,6 +198,18 @@ "z c": "editor::Fold", "z o": "editor::UnfoldLines", "z f": "editor::FoldSelectedRanges", + "shift-z shift-q": [ + "pane::CloseActiveItem", + { + "saveBehavior": "dontSave" + } + ], + "shift-z shift-z": [ + "pane::CloseActiveItem", + { + "saveBehavior": "promptOnConflict" + } + ], // Count support "1": [ "vim::Number", diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 8121b0ac91..405956e5db 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -1530,7 +1530,9 @@ async fn test_host_disconnect( // Ensure client B is not prompted to save edits when closing window after disconnecting. let can_close = workspace_b - .update(cx_b, |workspace, cx| workspace.prepare_to_close(true, cx)) + .update(cx_b, |workspace, cx| { + workspace.prepare_to_close(true, workspace::SaveBehavior::PromptOnWrite, cx) + }) .await .unwrap(); assert!(can_close); diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 523d6e8a5c..c2d8cc52b2 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -1528,8 +1528,13 @@ mod tests { let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone()); active_pane .update(cx, |pane, cx| { - pane.close_active_item(&workspace::CloseActiveItem, cx) - .unwrap() + pane.close_active_item( + &workspace::CloseActiveItem { + save_behavior: None, + }, + cx, + ) + .unwrap() }) .await .unwrap(); diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 104d181a7b..a12f9d3c3c 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -283,7 +283,12 @@ impl TerminalView { pub fn deploy_context_menu(&mut self, position: Vector2F, cx: &mut ViewContext) { let menu_entries = vec![ ContextMenuItem::action("Clear", Clear), - ContextMenuItem::action("Close", pane::CloseActiveItem), + ContextMenuItem::action( + "Close", + pane::CloseActiveItem { + save_behavior: None, + }, + ), ]; self.context_menu.update(cx, |menu, cx| { diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 4e24c831f4..ea747b3a36 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -474,8 +474,14 @@ impl ItemHandle for ViewHandle { for item_event in T::to_item_events(event).into_iter() { match item_event { ItemEvent::CloseItem => { - pane.update(cx, |pane, cx| pane.close_item_by_id(item.id(), cx)) - .detach_and_log_err(cx); + pane.update(cx, |pane, cx| { + pane.close_item_by_id( + item.id(), + crate::SaveBehavior::PromptOnWrite, + cx, + ) + }) + .detach_and_log_err(cx); return; } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index fe3173ac9b..eb78e30e98 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -43,6 +43,19 @@ use std::{ }; use theme::{Theme, ThemeSettings}; +#[derive(PartialEq, Clone, Copy, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub enum SaveBehavior { + /// ask before overwriting conflicting files (used by default with %s) + PromptOnConflict, + /// ask before writing any file that wouldn't be auto-saved (used by default with %w) + PromptOnWrite, + /// never prompt, write on conflict (used with vim's :w!) + SilentlyOverwrite, + /// skip all save-related behaviour (used with vim's :cq) + DontSave, +} + #[derive(Clone, Deserialize, PartialEq)] pub struct ActivateItem(pub usize); @@ -64,13 +77,17 @@ pub struct CloseItemsToTheRightById { pub pane: WeakViewHandle, } +#[derive(Clone, PartialEq, Debug, Deserialize, Default)] +pub struct CloseActiveItem { + pub save_behavior: Option, +} + actions!( pane, [ ActivatePrevItem, ActivateNextItem, ActivateLastItem, - CloseActiveItem, CloseInactiveItems, CloseCleanItems, CloseItemsToTheLeft, @@ -86,7 +103,7 @@ actions!( ] ); -impl_actions!(pane, [ActivateItem]); +impl_actions!(pane, [ActivateItem, CloseActiveItem]); const MAX_NAVIGATION_HISTORY_LEN: usize = 1024; @@ -696,22 +713,29 @@ impl Pane { pub fn close_active_item( &mut self, - _: &CloseActiveItem, + action: &CloseActiveItem, cx: &mut ViewContext, ) -> Option>> { if self.items.is_empty() { return None; } let active_item_id = self.items[self.active_item_index].id(); - Some(self.close_item_by_id(active_item_id, cx)) + Some(self.close_item_by_id( + active_item_id, + action.save_behavior.unwrap_or(SaveBehavior::PromptOnWrite), + cx, + )) } pub fn close_item_by_id( &mut self, item_id_to_close: usize, + save_behavior: SaveBehavior, cx: &mut ViewContext, ) -> Task> { - self.close_items(cx, move |view_id| view_id == item_id_to_close) + self.close_items(cx, save_behavior, move |view_id| { + view_id == item_id_to_close + }) } pub fn close_inactive_items( @@ -724,7 +748,11 @@ impl Pane { } let active_item_id = self.items[self.active_item_index].id(); - Some(self.close_items(cx, move |item_id| item_id != active_item_id)) + Some( + self.close_items(cx, SaveBehavior::PromptOnWrite, move |item_id| { + item_id != active_item_id + }), + ) } pub fn close_clean_items( @@ -737,7 +765,11 @@ impl Pane { .filter(|item| !item.is_dirty(cx)) .map(|item| item.id()) .collect(); - Some(self.close_items(cx, move |item_id| item_ids.contains(&item_id))) + Some( + self.close_items(cx, SaveBehavior::PromptOnWrite, move |item_id| { + item_ids.contains(&item_id) + }), + ) } pub fn close_items_to_the_left( @@ -762,7 +794,9 @@ impl Pane { .take_while(|item| item.id() != item_id) .map(|item| item.id()) .collect(); - self.close_items(cx, move |item_id| item_ids.contains(&item_id)) + self.close_items(cx, SaveBehavior::PromptOnWrite, move |item_id| { + item_ids.contains(&item_id) + }) } pub fn close_items_to_the_right( @@ -788,7 +822,9 @@ impl Pane { .take_while(|item| item.id() != item_id) .map(|item| item.id()) .collect(); - self.close_items(cx, move |item_id| item_ids.contains(&item_id)) + self.close_items(cx, SaveBehavior::PromptOnWrite, move |item_id| { + item_ids.contains(&item_id) + }) } pub fn close_all_items( @@ -800,12 +836,13 @@ impl Pane { return None; } - Some(self.close_items(cx, move |_| true)) + Some(self.close_items(cx, SaveBehavior::PromptOnWrite, |_| true)) } pub fn close_items( &mut self, cx: &mut ViewContext, + save_behavior: SaveBehavior, should_close: impl 'static + Fn(usize) -> bool, ) -> Task> { // Find the items to close. @@ -858,8 +895,15 @@ impl Pane { .any(|id| saved_project_items_ids.insert(*id)); if should_save - && !Self::save_item(project.clone(), &pane, item_ix, &*item, true, &mut cx) - .await? + && !Self::save_item( + project.clone(), + &pane, + item_ix, + &*item, + save_behavior, + &mut cx, + ) + .await? { break; } @@ -954,13 +998,17 @@ impl Pane { pane: &WeakViewHandle, item_ix: usize, item: &dyn ItemHandle, - should_prompt_for_save: bool, + save_behavior: SaveBehavior, cx: &mut AsyncAppContext, ) -> Result { const CONFLICT_MESSAGE: &str = "This file has changed on disk since you started editing it. Do you want to overwrite it?"; const DIRTY_MESSAGE: &str = "This file contains unsaved edits. Do you want to save it?"; + if save_behavior == SaveBehavior::DontSave { + return Ok(true); + } + let (has_conflict, is_dirty, can_save, is_singleton) = cx.read(|cx| { ( item.has_conflict(cx), @@ -971,18 +1019,22 @@ impl Pane { }); if has_conflict && can_save { - let mut answer = pane.update(cx, |pane, cx| { - pane.activate_item(item_ix, true, true, cx); - cx.prompt( - PromptLevel::Warning, - CONFLICT_MESSAGE, - &["Overwrite", "Discard", "Cancel"], - ) - })?; - match answer.next().await { - Some(0) => pane.update(cx, |_, cx| item.save(project, cx))?.await?, - Some(1) => pane.update(cx, |_, cx| item.reload(project, cx))?.await?, - _ => return Ok(false), + if save_behavior == SaveBehavior::SilentlyOverwrite { + pane.update(cx, |_, cx| item.save(project, cx))?.await?; + } else { + let mut answer = pane.update(cx, |pane, cx| { + pane.activate_item(item_ix, true, true, cx); + cx.prompt( + PromptLevel::Warning, + CONFLICT_MESSAGE, + &["Overwrite", "Discard", "Cancel"], + ) + })?; + match answer.next().await { + Some(0) => pane.update(cx, |_, cx| item.save(project, cx))?.await?, + Some(1) => pane.update(cx, |_, cx| item.reload(project, cx))?.await?, + _ => return Ok(false), + } } } else if is_dirty && (can_save || is_singleton) { let will_autosave = cx.read(|cx| { @@ -991,7 +1043,7 @@ impl Pane { AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange ) && Self::can_autosave_item(&*item, cx) }); - let should_save = if should_prompt_for_save && !will_autosave { + let should_save = if save_behavior == SaveBehavior::PromptOnWrite && !will_autosave { let mut answer = pane.update(cx, |pane, cx| { pane.activate_item(item_ix, true, true, cx); cx.prompt( @@ -1113,7 +1165,12 @@ impl Pane { AnchorCorner::TopLeft, if is_active_item { vec![ - ContextMenuItem::action("Close Active Item", CloseActiveItem), + ContextMenuItem::action( + "Close Active Item", + CloseActiveItem { + save_behavior: None, + }, + ), ContextMenuItem::action("Close Inactive Items", CloseInactiveItems), ContextMenuItem::action("Close Clean Items", CloseCleanItems), ContextMenuItem::action("Close Items To The Left", CloseItemsToTheLeft), @@ -1128,8 +1185,12 @@ impl Pane { move |cx| { if let Some(pane) = pane.upgrade(cx) { pane.update(cx, |pane, cx| { - pane.close_item_by_id(target_item_id, cx) - .detach_and_log_err(cx); + pane.close_item_by_id( + target_item_id, + SaveBehavior::PromptOnWrite, + cx, + ) + .detach_and_log_err(cx); }) } } @@ -1278,7 +1339,12 @@ impl Pane { .on_click(MouseButton::Middle, { let item_id = item.id(); move |_, pane, cx| { - pane.close_item_by_id(item_id, cx).detach_and_log_err(cx); + pane.close_item_by_id( + item_id, + SaveBehavior::PromptOnWrite, + cx, + ) + .detach_and_log_err(cx); } }) .on_down( @@ -1486,7 +1552,8 @@ impl Pane { cx.window_context().defer(move |cx| { if let Some(pane) = pane.upgrade(cx) { pane.update(cx, |pane, cx| { - pane.close_item_by_id(item_id, cx).detach_and_log_err(cx); + pane.close_item_by_id(item_id, SaveBehavior::PromptOnWrite, cx) + .detach_and_log_err(cx); }); } }); @@ -2089,7 +2156,14 @@ mod tests { let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); pane.update(cx, |pane, cx| { - assert!(pane.close_active_item(&CloseActiveItem, cx).is_none()) + assert!(pane + .close_active_item( + &CloseActiveItem { + save_behavior: None + }, + cx + ) + .is_none()) }); } @@ -2339,31 +2413,59 @@ mod tests { add_labeled_item(&pane, "1", false, cx); assert_item_labels(&pane, ["A", "B", "1*", "C", "D"], cx); - pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx)) - .unwrap() - .await - .unwrap(); + pane.update(cx, |pane, cx| { + pane.close_active_item( + &CloseActiveItem { + save_behavior: None, + }, + cx, + ) + }) + .unwrap() + .await + .unwrap(); assert_item_labels(&pane, ["A", "B*", "C", "D"], cx); pane.update(cx, |pane, cx| pane.activate_item(3, false, false, cx)); assert_item_labels(&pane, ["A", "B", "C", "D*"], cx); - pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx)) - .unwrap() - .await - .unwrap(); + pane.update(cx, |pane, cx| { + pane.close_active_item( + &CloseActiveItem { + save_behavior: None, + }, + cx, + ) + }) + .unwrap() + .await + .unwrap(); assert_item_labels(&pane, ["A", "B*", "C"], cx); - pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx)) - .unwrap() - .await - .unwrap(); + pane.update(cx, |pane, cx| { + pane.close_active_item( + &CloseActiveItem { + save_behavior: None, + }, + cx, + ) + }) + .unwrap() + .await + .unwrap(); assert_item_labels(&pane, ["A", "C*"], cx); - pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx)) - .unwrap() - .await - .unwrap(); + pane.update(cx, |pane, cx| { + pane.close_active_item( + &CloseActiveItem { + save_behavior: None, + }, + cx, + ) + }) + .unwrap() + .await + .unwrap(); assert_item_labels(&pane, ["A*"], cx); } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index be8148256d..abbed1fcf0 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1258,7 +1258,7 @@ impl Workspace { cx: &mut ViewContext, ) -> Option>> { let window = cx.window(); - let prepare = self.prepare_to_close(false, cx); + let prepare = self.prepare_to_close(false, SaveBehavior::PromptOnWrite, cx); Some(cx.spawn(|_, mut cx| async move { if prepare.await? { window.remove(&mut cx); @@ -1270,6 +1270,7 @@ impl Workspace { pub fn prepare_to_close( &mut self, quitting: bool, + save_behavior: SaveBehavior, cx: &mut ViewContext, ) -> Task> { let active_call = self.active_call().cloned(); @@ -1308,13 +1309,15 @@ impl Workspace { } Ok(this - .update(&mut cx, |this, cx| this.save_all_internal(true, cx))? + .update(&mut cx, |this, cx| { + this.save_all_internal(save_behavior, cx) + })? .await?) }) } fn save_all(&mut self, _: &SaveAll, cx: &mut ViewContext) -> Option>> { - let save_all = self.save_all_internal(false, cx); + let save_all = self.save_all_internal(SaveBehavior::PromptOnConflict, cx); Some(cx.foreground().spawn(async move { save_all.await?; Ok(()) @@ -1323,7 +1326,7 @@ impl Workspace { fn save_all_internal( &mut self, - should_prompt_to_save: bool, + save_behaviour: SaveBehavior, cx: &mut ViewContext, ) -> Task> { if self.project.read(cx).is_read_only() { @@ -1358,7 +1361,7 @@ impl Workspace { &pane, ix, &*item, - should_prompt_to_save, + save_behaviour, &mut cx, ) .await? @@ -1404,7 +1407,7 @@ impl Workspace { let close_task = if is_remote || has_worktree || has_dirty_items { None } else { - Some(self.prepare_to_close(false, cx)) + Some(self.prepare_to_close(false, SaveBehavior::PromptOnWrite, cx)) }; let app_state = self.app_state.clone(); @@ -4099,7 +4102,7 @@ pub fn restart(_: &Restart, cx: &mut AppContext) { // If the user cancels any save prompt, then keep the app open. for window in workspace_windows { if let Some(should_close) = window.update_root(&mut cx, |workspace, cx| { - workspace.prepare_to_close(true, cx) + workspace.prepare_to_close(true, SaveBehavior::PromptOnWrite, cx) }) { if !should_close.await? { return Ok(()); @@ -4289,7 +4292,9 @@ mod tests { // When there are no dirty items, there's nothing to do. let item1 = window.add_view(cx, |_| TestItem::new()); workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx)); - let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx)); + let task = workspace.update(cx, |w, cx| { + w.prepare_to_close(false, SaveBehavior::PromptOnWrite, cx) + }); assert!(task.await.unwrap()); // When there are dirty untitled items, prompt to save each one. If the user @@ -4304,7 +4309,9 @@ mod tests { w.add_item(Box::new(item2.clone()), cx); w.add_item(Box::new(item3.clone()), cx); }); - let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx)); + let task = workspace.update(cx, |w, cx| { + w.prepare_to_close(false, SaveBehavior::PromptOnWrite, cx) + }); cx.foreground().run_until_parked(); window.simulate_prompt_answer(2, cx); // cancel cx.foreground().run_until_parked(); @@ -4358,7 +4365,9 @@ mod tests { let item1_id = item1.id(); let item3_id = item3.id(); let item4_id = item4.id(); - pane.close_items(cx, move |id| [item1_id, item3_id, item4_id].contains(&id)) + pane.close_items(cx, SaveBehavior::PromptOnWrite, move |id| { + [item1_id, item3_id, item4_id].contains(&id) + }) }); cx.foreground().run_until_parked(); @@ -4493,7 +4502,9 @@ mod tests { // once for project entry 0, and once for project entry 2. After those two // prompts, the task should complete. - let close = left_pane.update(cx, |pane, cx| pane.close_items(cx, |_| true)); + let close = left_pane.update(cx, |pane, cx| { + pane.close_items(cx, SaveBehavior::PromptOnWrite, move |_| true) + }); cx.foreground().run_until_parked(); left_pane.read_with(cx, |pane, cx| { assert_eq!( @@ -4609,9 +4620,11 @@ mod tests { item.is_dirty = true; }); - pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id)) - .await - .unwrap(); + pane.update(cx, |pane, cx| { + pane.close_items(cx, SaveBehavior::PromptOnWrite, move |id| id == item_id) + }) + .await + .unwrap(); assert!(!window.has_pending_prompt(cx)); item.read_with(cx, |item, _| assert_eq!(item.save_count, 5)); @@ -4630,8 +4643,9 @@ mod tests { item.read_with(cx, |item, _| assert_eq!(item.save_count, 5)); // Ensure autosave is prevented for deleted files also when closing the buffer. - let _close_items = - pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id)); + let _close_items = pane.update(cx, |pane, cx| { + pane.close_items(cx, SaveBehavior::PromptOnWrite, move |id| id == item_id) + }); deterministic.run_until_parked(); assert!(window.has_pending_prompt(cx)); item.read_with(cx, |item, _| assert_eq!(item.save_count, 5)); diff --git a/crates/zed/src/menus.rs b/crates/zed/src/menus.rs index 22a260b588..6b5f7b3a35 100644 --- a/crates/zed/src/menus.rs +++ b/crates/zed/src/menus.rs @@ -41,7 +41,12 @@ pub fn menus() -> Vec> { MenuItem::action("Save", workspace::Save), MenuItem::action("Save As…", workspace::SaveAs), MenuItem::action("Save All", workspace::SaveAll), - MenuItem::action("Close Editor", workspace::CloseActiveItem), + MenuItem::action( + "Close Editor", + workspace::CloseActiveItem { + save_behavior: None, + }, + ), MenuItem::action("Close Window", workspace::CloseWindow), ], }, diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 424bce60f2..c00bb7ee0d 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -438,7 +438,7 @@ fn quit(_: &Quit, cx: &mut gpui::AppContext) { // If the user cancels any save prompt, then keep the app open. for window in workspace_windows { if let Some(should_close) = window.update_root(&mut cx, |workspace, cx| { - workspace.prepare_to_close(true, cx) + workspace.prepare_to_close(true, workspace::SaveBehavior::PromptOnWrite, cx) }) { if !should_close.await? { return Ok(()); @@ -733,7 +733,7 @@ mod tests { use theme::{ThemeRegistry, ThemeSettings}; use workspace::{ item::{Item, ItemHandle}, - open_new, open_paths, pane, NewFile, SplitDirection, WorkspaceHandle, + open_new, open_paths, pane, NewFile, SaveBehavior, SplitDirection, WorkspaceHandle, }; #[gpui::test] @@ -1495,7 +1495,12 @@ mod tests { pane2_item.downcast::().unwrap().downgrade() }); - cx.dispatch_action(window.into(), workspace::CloseActiveItem); + cx.dispatch_action( + window.into(), + workspace::CloseActiveItem { + save_behavior: None, + }, + ); cx.foreground().run_until_parked(); workspace.read_with(cx, |workspace, _| { @@ -1503,7 +1508,12 @@ mod tests { assert_eq!(workspace.active_pane(), &pane_1); }); - cx.dispatch_action(window.into(), workspace::CloseActiveItem); + cx.dispatch_action( + window.into(), + workspace::CloseActiveItem { + save_behavior: None, + }, + ); cx.foreground().run_until_parked(); window.simulate_prompt_answer(1, cx); cx.foreground().run_until_parked(); @@ -1661,7 +1671,7 @@ mod tests { pane.update(cx, |pane, cx| { let editor3_id = editor3.id(); drop(editor3); - pane.close_item_by_id(editor3_id, cx) + pane.close_item_by_id(editor3_id, SaveBehavior::PromptOnWrite, cx) }) .await .unwrap(); @@ -1696,7 +1706,7 @@ mod tests { pane.update(cx, |pane, cx| { let editor2_id = editor2.id(); drop(editor2); - pane.close_item_by_id(editor2_id, cx) + pane.close_item_by_id(editor2_id, SaveBehavior::PromptOnWrite, cx) }) .await .unwrap(); @@ -1852,24 +1862,32 @@ mod tests { assert_eq!(active_path(&workspace, cx), Some(file4.clone())); // Close all the pane items in some arbitrary order. - pane.update(cx, |pane, cx| pane.close_item_by_id(file1_item_id, cx)) - .await - .unwrap(); + pane.update(cx, |pane, cx| { + pane.close_item_by_id(file1_item_id, SaveBehavior::PromptOnWrite, cx) + }) + .await + .unwrap(); assert_eq!(active_path(&workspace, cx), Some(file4.clone())); - pane.update(cx, |pane, cx| pane.close_item_by_id(file4_item_id, cx)) - .await - .unwrap(); + pane.update(cx, |pane, cx| { + pane.close_item_by_id(file4_item_id, SaveBehavior::PromptOnWrite, cx) + }) + .await + .unwrap(); assert_eq!(active_path(&workspace, cx), Some(file3.clone())); - pane.update(cx, |pane, cx| pane.close_item_by_id(file2_item_id, cx)) - .await - .unwrap(); + pane.update(cx, |pane, cx| { + pane.close_item_by_id(file2_item_id, SaveBehavior::PromptOnWrite, cx) + }) + .await + .unwrap(); assert_eq!(active_path(&workspace, cx), Some(file3.clone())); - pane.update(cx, |pane, cx| pane.close_item_by_id(file3_item_id, cx)) - .await - .unwrap(); + pane.update(cx, |pane, cx| { + pane.close_item_by_id(file3_item_id, SaveBehavior::PromptOnWrite, cx) + }) + .await + .unwrap(); assert_eq!(active_path(&workspace, cx), None); // Reopen all the closed items, ensuring they are reopened in the same order From 4c92172ccac088825c8470b986159d1694787da8 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 8 Sep 2023 16:49:50 -0600 Subject: [PATCH 05/10] Partially roll back refactoring --- crates/collab/src/tests/integration_tests.rs | 4 +--- crates/workspace/src/workspace.rs | 17 ++++++----------- crates/zed/src/zed.rs | 2 +- 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 405956e5db..8121b0ac91 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -1530,9 +1530,7 @@ async fn test_host_disconnect( // Ensure client B is not prompted to save edits when closing window after disconnecting. let can_close = workspace_b - .update(cx_b, |workspace, cx| { - workspace.prepare_to_close(true, workspace::SaveBehavior::PromptOnWrite, cx) - }) + .update(cx_b, |workspace, cx| workspace.prepare_to_close(true, cx)) .await .unwrap(); assert!(can_close); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index abbed1fcf0..f0cb942026 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1258,7 +1258,7 @@ impl Workspace { cx: &mut ViewContext, ) -> Option>> { let window = cx.window(); - let prepare = self.prepare_to_close(false, SaveBehavior::PromptOnWrite, cx); + let prepare = self.prepare_to_close(false, cx); Some(cx.spawn(|_, mut cx| async move { if prepare.await? { window.remove(&mut cx); @@ -1270,7 +1270,6 @@ impl Workspace { pub fn prepare_to_close( &mut self, quitting: bool, - save_behavior: SaveBehavior, cx: &mut ViewContext, ) -> Task> { let active_call = self.active_call().cloned(); @@ -1310,7 +1309,7 @@ impl Workspace { Ok(this .update(&mut cx, |this, cx| { - this.save_all_internal(save_behavior, cx) + this.save_all_internal(SaveBehavior::PromptOnWrite, cx) })? .await?) }) @@ -1407,7 +1406,7 @@ impl Workspace { let close_task = if is_remote || has_worktree || has_dirty_items { None } else { - Some(self.prepare_to_close(false, SaveBehavior::PromptOnWrite, cx)) + Some(self.prepare_to_close(false, cx)) }; let app_state = self.app_state.clone(); @@ -4102,7 +4101,7 @@ pub fn restart(_: &Restart, cx: &mut AppContext) { // If the user cancels any save prompt, then keep the app open. for window in workspace_windows { if let Some(should_close) = window.update_root(&mut cx, |workspace, cx| { - workspace.prepare_to_close(true, SaveBehavior::PromptOnWrite, cx) + workspace.prepare_to_close(true, cx) }) { if !should_close.await? { return Ok(()); @@ -4292,9 +4291,7 @@ mod tests { // When there are no dirty items, there's nothing to do. let item1 = window.add_view(cx, |_| TestItem::new()); workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx)); - let task = workspace.update(cx, |w, cx| { - w.prepare_to_close(false, SaveBehavior::PromptOnWrite, cx) - }); + let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx)); assert!(task.await.unwrap()); // When there are dirty untitled items, prompt to save each one. If the user @@ -4309,9 +4306,7 @@ mod tests { w.add_item(Box::new(item2.clone()), cx); w.add_item(Box::new(item3.clone()), cx); }); - let task = workspace.update(cx, |w, cx| { - w.prepare_to_close(false, SaveBehavior::PromptOnWrite, cx) - }); + let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx)); cx.foreground().run_until_parked(); window.simulate_prompt_answer(2, cx); // cancel cx.foreground().run_until_parked(); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index c00bb7ee0d..f12dc8a98b 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -438,7 +438,7 @@ fn quit(_: &Quit, cx: &mut gpui::AppContext) { // If the user cancels any save prompt, then keep the app open. for window in workspace_windows { if let Some(should_close) = window.update_root(&mut cx, |workspace, cx| { - workspace.prepare_to_close(true, workspace::SaveBehavior::PromptOnWrite, cx) + workspace.prepare_to_close(true, cx) }) { if !should_close.await? { return Ok(()); From 7cc05c99c23bc161650565f2c2c389c86888ac8f Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 8 Sep 2023 23:46:12 -0600 Subject: [PATCH 06/10] Update getting started Just ran through this again. --- README.md | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8849f1aa73..72936e2746 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,31 @@ Welcome to Zed, a lightning-fast, collaborative code editor that makes your drea ### Dependencies -* Install [Postgres.app](https://postgresapp.com) and start it. +* Install Xcode from https://apps.apple.com/us/app/xcode/id497799835?mt=12, and accept the license: + ``` + sudo xcodebuild -license + ``` + +* Install homebrew, rust and node + ``` + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + brew install rust + brew install node + ``` + +* Ensure rust executables are in your $PATH + ``` + echo $HOME/.cargo/bin | sudo tee /etc/paths.d/10-rust + ``` + +* Install postgres and configure the database + ``` + brew install postgresql@15 + brew services start postgresql@15 + psql -c "CREATE ROLE postgres SUPERUSER LOGIN" postgres + psql -U postgres -c "CREATE DATABASE zed" + ``` + * Install the `LiveKit` server and the `foreman` process supervisor: ``` @@ -41,6 +65,17 @@ Welcome to Zed, a lightning-fast, collaborative code editor that makes your drea GITHUB_TOKEN=<$token> script/bootstrap ``` +* Now try running zed with collaboration disabled: + ``` + cargo run + ``` + +### Common errors + +* `xcrun: error: unable to find utility "metal", not a developer tool or in PATH` + * You need to install Xcode and then run: `xcode-select --switch /Applications/Xcode.app/Contents/Developer` + * (see https://github.com/gfx-rs/gfx/issues/2309) + ### Testing against locally-running servers Start the web and collab servers: From ef03e206d625063c68954ca6717fa22f0fe7e3e2 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 5 Sep 2023 11:01:55 -0700 Subject: [PATCH 07/10] WIP: Add nushell support --- Cargo.lock | 10 +++ Cargo.toml | 1 + .../LiveKitBridge/Package.resolved | 4 +- crates/zed/Cargo.toml | 1 + crates/zed/src/languages.rs | 1 + crates/zed/src/languages/nushell.rs | 0 crates/zed/src/languages/nushell/config.toml | 9 +++ .../zed/src/languages/nushell/highlights.scm | 61 +++++++++++++++++++ 8 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 crates/zed/src/languages/nushell.rs create mode 100644 crates/zed/src/languages/nushell/config.toml create mode 100644 crates/zed/src/languages/nushell/highlights.scm diff --git a/Cargo.lock b/Cargo.lock index d4b7beba7b..4bbe29742c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8406,6 +8406,15 @@ dependencies = [ "tree-sitter", ] +[[package]] +name = "tree-sitter-nu" +version = "0.0.1" +source = "git+https://github.com/mikayla-maki/tree-sitter-nu/?rev=6cf9ee39ceb3da79501de7646b10e5e1da800ab8#6cf9ee39ceb3da79501de7646b10e5e1da800ab8" +dependencies = [ + "cc", + "tree-sitter", +] + [[package]] name = "tree-sitter-php" version = "0.19.1" @@ -9868,6 +9877,7 @@ dependencies = [ "tree-sitter-lua", "tree-sitter-markdown", "tree-sitter-nix", + "tree-sitter-nu", "tree-sitter-php", "tree-sitter-python", "tree-sitter-racket", diff --git a/Cargo.toml b/Cargo.toml index 5938ecb402..01f42c3062 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -141,6 +141,7 @@ tree-sitter-racket = { git = "https://github.com/zed-industries/tree-sitter-rack tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "f545a41f57502e1b5ddf2a6668896c1b0620f930"} tree-sitter-lua = "0.0.14" tree-sitter-nix = { git = "https://github.com/nix-community/tree-sitter-nix", rev = "66e3e9ce9180ae08fc57372061006ef83f0abde7" } +tree-sitter-nu = { git = "https://github.com/mikayla-maki/tree-sitter-nu/", rev = "6cf9ee39ceb3da79501de7646b10e5e1da800ab8"} [patch.crates-io] tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "35a6052fbcafc5e5fc0f9415b8652be7dcaf7222" } diff --git a/crates/live_kit_client/LiveKitBridge/Package.resolved b/crates/live_kit_client/LiveKitBridge/Package.resolved index 85ae088565..b925bc8f0d 100644 --- a/crates/live_kit_client/LiveKitBridge/Package.resolved +++ b/crates/live_kit_client/LiveKitBridge/Package.resolved @@ -42,8 +42,8 @@ "repositoryURL": "https://github.com/apple/swift-protobuf.git", "state": { "branch": null, - "revision": "0af9125c4eae12a4973fb66574c53a54962a9e1e", - "version": "1.21.0" + "revision": "ce20dc083ee485524b802669890291c0d8090170", + "version": "1.22.1" } } ] diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index e102a66519..1d014197e1 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -132,6 +132,7 @@ tree-sitter-racket.workspace = true tree-sitter-yaml.workspace = true tree-sitter-lua.workspace = true tree-sitter-nix.workspace = true +tree-sitter-nu.workspace = true url = "2.2" urlencoding = "2.1.2" diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 3fbb5aa14f..0b1fa750c0 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -170,6 +170,7 @@ pub fn init(languages: Arc, node_runtime: Arc language("elm", tree_sitter_elm::language(), vec![]); language("glsl", tree_sitter_glsl::language(), vec![]); language("nix", tree_sitter_nix::language(), vec![]); + language("nu", tree_sitter_nu::language(), vec![]); } #[cfg(any(test, feature = "test-support"))] diff --git a/crates/zed/src/languages/nushell.rs b/crates/zed/src/languages/nushell.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/zed/src/languages/nushell/config.toml b/crates/zed/src/languages/nushell/config.toml new file mode 100644 index 0000000000..d382b0705a --- /dev/null +++ b/crates/zed/src/languages/nushell/config.toml @@ -0,0 +1,9 @@ +name = "Nu" +path_suffixes = ["nu"] +line_comment = "# " +autoclose_before = ";:.,=}])>` \n\t\"" +brackets = [ + { start = "{", end = "}", close = true, newline = true }, + { start = "[", end = "]", close = true, newline = true }, + { start = "(", end = ")", close = true, newline = true }, +] diff --git a/crates/zed/src/languages/nushell/highlights.scm b/crates/zed/src/languages/nushell/highlights.scm new file mode 100644 index 0000000000..b97ed9836e --- /dev/null +++ b/crates/zed/src/languages/nushell/highlights.scm @@ -0,0 +1,61 @@ +(string) @string +(type) @type +(value_path) @variable +(comment) @comment + +(number_literal) @number +(range from: (number_literal) @number) +(range to: (number_literal) @number) + +(command cmd_name: (identifier) @function) +(function_definition func_name: (identifier) @function) + +[ + (variable_declaration name: (identifier)) + (parameter (identifier)) + (flag (flag_name)) + (flag (flag_shorthand_name)) + (record_entry entry_name: (identifier)) + (block_args block_param: (identifier)) +] @property +; (parameter (identifier) @variable.parameter) ; -- alternative highlighting group? + +(cmd_invocation) @embedded + + +((identifier) @constant + (.match? @constant "^[A-Z][A-Z\\d_]*$")) + +[ + "if" + "else" + "not" + "let" + "def" + "def-env" + "export" + "true" + "false" + "and" + "or" +] @keyword + +[ + ; "/" Not making / an operator may lead to better highlighting? + "$" + "|" + "+" + "-" + "*" + "=" + "!=" + "and" + "or" + "==" + ">" +] @operator + +["." + "," + ";" +] @delimiter From 2be34ef254f27ee22e3a5e586cc611db1732b629 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Sat, 9 Sep 2023 14:44:52 -0700 Subject: [PATCH 08/10] Add syntax highlighting for nu scripts --- Cargo.lock | 2 +- Cargo.toml | 2 +- crates/zed/src/languages/nu/brackets.scm | 4 + .../src/languages/{nushell => nu}/config.toml | 0 crates/zed/src/languages/nu/highlights.scm | 302 ++++++++++++++++++ crates/zed/src/languages/nu/indents.scm | 3 + crates/zed/src/languages/nushell.rs | 0 .../zed/src/languages/nushell/highlights.scm | 61 ---- 8 files changed, 311 insertions(+), 63 deletions(-) create mode 100644 crates/zed/src/languages/nu/brackets.scm rename crates/zed/src/languages/{nushell => nu}/config.toml (100%) create mode 100644 crates/zed/src/languages/nu/highlights.scm create mode 100644 crates/zed/src/languages/nu/indents.scm delete mode 100644 crates/zed/src/languages/nushell.rs delete mode 100644 crates/zed/src/languages/nushell/highlights.scm diff --git a/Cargo.lock b/Cargo.lock index 4bbe29742c..d1cd868eaa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8409,7 +8409,7 @@ dependencies = [ [[package]] name = "tree-sitter-nu" version = "0.0.1" -source = "git+https://github.com/mikayla-maki/tree-sitter-nu/?rev=6cf9ee39ceb3da79501de7646b10e5e1da800ab8#6cf9ee39ceb3da79501de7646b10e5e1da800ab8" +source = "git+https://github.com/nushell/tree-sitter-nu?rev=786689b0562b9799ce53e824cb45a1a2a04dc673#786689b0562b9799ce53e824cb45a1a2a04dc673" dependencies = [ "cc", "tree-sitter", diff --git a/Cargo.toml b/Cargo.toml index 01f42c3062..c233c74b55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -141,7 +141,7 @@ tree-sitter-racket = { git = "https://github.com/zed-industries/tree-sitter-rack tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "f545a41f57502e1b5ddf2a6668896c1b0620f930"} tree-sitter-lua = "0.0.14" tree-sitter-nix = { git = "https://github.com/nix-community/tree-sitter-nix", rev = "66e3e9ce9180ae08fc57372061006ef83f0abde7" } -tree-sitter-nu = { git = "https://github.com/mikayla-maki/tree-sitter-nu/", rev = "6cf9ee39ceb3da79501de7646b10e5e1da800ab8"} +tree-sitter-nu = { git = "https://github.com/nushell/tree-sitter-nu", rev = "786689b0562b9799ce53e824cb45a1a2a04dc673"} [patch.crates-io] tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "35a6052fbcafc5e5fc0f9415b8652be7dcaf7222" } diff --git a/crates/zed/src/languages/nu/brackets.scm b/crates/zed/src/languages/nu/brackets.scm new file mode 100644 index 0000000000..7ede7a6192 --- /dev/null +++ b/crates/zed/src/languages/nu/brackets.scm @@ -0,0 +1,4 @@ +("(" @open ")" @close) +("[" @open "]" @close) +("{" @open "}" @close) +(parameter_pipes "|" @open "|" @close) diff --git a/crates/zed/src/languages/nushell/config.toml b/crates/zed/src/languages/nu/config.toml similarity index 100% rename from crates/zed/src/languages/nushell/config.toml rename to crates/zed/src/languages/nu/config.toml diff --git a/crates/zed/src/languages/nu/highlights.scm b/crates/zed/src/languages/nu/highlights.scm new file mode 100644 index 0000000000..97f46d3879 --- /dev/null +++ b/crates/zed/src/languages/nu/highlights.scm @@ -0,0 +1,302 @@ +;;; --- +;;; keywords +[ + "def" + "def-env" + "alias" + "export-env" + "export" + "extern" + "module" + + "let" + "let-env" + "mut" + "const" + + "hide-env" + + "source" + "source-env" + + "overlay" + "register" + + "loop" + "while" + "error" + + "do" + "if" + "else" + "try" + "catch" + "match" + + "break" + "continue" + "return" + +] @keyword + +(hide_mod "hide" @keyword) +(decl_use "use" @keyword) + +(ctrl_for + "for" @keyword + "in" @keyword +) +(overlay_list "list" @keyword) +(overlay_hide "hide" @keyword) +(overlay_new "new" @keyword) +(overlay_use + "use" @keyword + "as" @keyword +) +(ctrl_error "make" @keyword) + +;;; --- +;;; literals +(val_number) @constant +(val_duration + unit: [ + "ns" "µs" "us" "ms" "sec" "min" "hr" "day" "wk" + ] @variable +) +(val_filesize + unit: [ + "b" "B" + + "kb" "kB" "Kb" "KB" + "mb" "mB" "Mb" "MB" + "gb" "gB" "Gb" "GB" + "tb" "tB" "Tb" "TB" + "pb" "pB" "Pb" "PB" + "eb" "eB" "Eb" "EB" + "zb" "zB" "Zb" "ZB" + + "kib" "kiB" "kIB" "kIb" "Kib" "KIb" "KIB" + "mib" "miB" "mIB" "mIb" "Mib" "MIb" "MIB" + "gib" "giB" "gIB" "gIb" "Gib" "GIb" "GIB" + "tib" "tiB" "tIB" "tIb" "Tib" "TIb" "TIB" + "pib" "piB" "pIB" "pIb" "Pib" "PIb" "PIB" + "eib" "eiB" "eIB" "eIb" "Eib" "EIb" "EIB" + "zib" "ziB" "zIB" "zIb" "Zib" "ZIb" "ZIB" + ] @variable +) +(val_binary + [ + "0b" + "0o" + "0x" + ] @constant + "[" @punctuation.bracket + digit: [ + "," @punctuation.delimiter + (hex_digit) @constant + ] + "]" @punctuation.bracket +) @constant +(val_bool) @constant.builtin +(val_nothing) @constant.builtin +(val_string) @string +(val_date) @constant +(inter_escape_sequence) @constant +(escape_sequence) @constant +(val_interpolated [ + "$\"" + "$\'" + "\"" + "\'" +] @string) +(unescaped_interpolated_content) @string +(escaped_interpolated_content) @string +(expr_interpolated ["(" ")"] @variable) + +;;; --- +;;; operators +(expr_binary [ + "+" + "-" + "*" + "/" + "mod" + "//" + "++" + "**" + "==" + "!=" + "<" + "<=" + ">" + ">=" + "=~" + "!~" + "and" + "or" + "xor" + "bit-or" + "bit-xor" + "bit-and" + "bit-shl" + "bit-shr" + "in" + "not-in" + "starts-with" + "ends-with" +] @operator) + +(expr_binary opr: ([ + "and" + "or" + "xor" + "bit-or" + "bit-xor" + "bit-and" + "bit-shl" + "bit-shr" + "in" + "not-in" + "starts-with" + "ends-with" +]) @keyword) + +(where_command [ + "+" + "-" + "*" + "/" + "mod" + "//" + "++" + "**" + "==" + "!=" + "<" + "<=" + ">" + ">=" + "=~" + "!~" + "and" + "or" + "xor" + "bit-or" + "bit-xor" + "bit-and" + "bit-shl" + "bit-shr" + "in" + "not-in" + "starts-with" + "ends-with" +] @operator) + +(assignment [ + "=" + "+=" + "-=" + "*=" + "/=" + "++=" +] @operator) + +(expr_unary ["not" "-"] @operator) + +(val_range [ + ".." + "..=" + "..<" +] @operator) + +["=>" "=" "|"] @operator + +[ + "o>" "out>" + "e>" "err>" + "e+o>" "err+out>" + "o+e>" "out+err>" +] @special + +;;; --- +;;; punctuation +[ + "," + ";" +] @punctuation.delimiter + +(param_short_flag "-" @punctuation.delimiter) +(param_long_flag ["--"] @punctuation.delimiter) +(long_flag ["--"] @punctuation.delimiter) +(param_rest "..." @punctuation.delimiter) +(param_type [":"] @punctuation.special) +(param_value ["="] @punctuation.special) +(param_cmd ["@"] @punctuation.special) +(param_opt ["?"] @punctuation.special) + +[ + "(" ")" + "{" "}" + "[" "]" +] @punctuation.bracket + +(val_record + (record_entry ":" @punctuation.delimiter)) +;;; --- +;;; identifiers +(param_rest + name: (_) @variable) +(param_opt + name: (_) @variable) +(parameter + param_name: (_) @variable) +(param_cmd + (cmd_identifier) @string) +(param_long_flag) @variable +(param_short_flag) @variable + +(short_flag) @variable +(long_flag) @variable + +(scope_pattern [(wild_card) @function]) + +(cmd_identifier) @function + +(command + "^" @punctuation.delimiter + head: (_) @function +) + +"where" @function + +(path + ["." "?"] @punctuation.delimiter +) @variable + +(val_variable + "$" @operator + [ + (identifier) @variable + "in" @type.builtin + "nu" @type.builtin + "env" @type.builtin + "nothing" @type.builtin + ] ; If we have a special styling, use it here +) +;;; --- +;;; types +(flat_type) @type.builtin +(list_type + "list" @type + ["<" ">"] @punctuation.bracket +) +(collection_type + ["record" "table"] @type + "<" @punctuation.bracket + key: (_) @variable + ["," ":"] @punctuation.delimiter + ">" @punctuation.bracket +) + +(shebang) @comment +(comment) @comment diff --git a/crates/zed/src/languages/nu/indents.scm b/crates/zed/src/languages/nu/indents.scm new file mode 100644 index 0000000000..112b414aa4 --- /dev/null +++ b/crates/zed/src/languages/nu/indents.scm @@ -0,0 +1,3 @@ +(_ "[" "]" @end) @indent +(_ "{" "}" @end) @indent +(_ "(" ")" @end) @indent diff --git a/crates/zed/src/languages/nushell.rs b/crates/zed/src/languages/nushell.rs deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/crates/zed/src/languages/nushell/highlights.scm b/crates/zed/src/languages/nushell/highlights.scm deleted file mode 100644 index b97ed9836e..0000000000 --- a/crates/zed/src/languages/nushell/highlights.scm +++ /dev/null @@ -1,61 +0,0 @@ -(string) @string -(type) @type -(value_path) @variable -(comment) @comment - -(number_literal) @number -(range from: (number_literal) @number) -(range to: (number_literal) @number) - -(command cmd_name: (identifier) @function) -(function_definition func_name: (identifier) @function) - -[ - (variable_declaration name: (identifier)) - (parameter (identifier)) - (flag (flag_name)) - (flag (flag_shorthand_name)) - (record_entry entry_name: (identifier)) - (block_args block_param: (identifier)) -] @property -; (parameter (identifier) @variable.parameter) ; -- alternative highlighting group? - -(cmd_invocation) @embedded - - -((identifier) @constant - (.match? @constant "^[A-Z][A-Z\\d_]*$")) - -[ - "if" - "else" - "not" - "let" - "def" - "def-env" - "export" - "true" - "false" - "and" - "or" -] @keyword - -[ - ; "/" Not making / an operator may lead to better highlighting? - "$" - "|" - "+" - "-" - "*" - "=" - "!=" - "and" - "or" - "==" - ">" -] @operator - -["." - "," - ";" -] @delimiter From 7df21f86ddcbcbdc6eea31f898d14c28ceb5e92c Mon Sep 17 00:00:00 2001 From: KCaverly Date: Mon, 11 Sep 2023 10:11:40 -0400 Subject: [PATCH 09/10] move cx notify observe for rate_limit_expiry into ProjectState in the semantic index Co-authored-by: Antonio --- crates/search/src/project_search.rs | 31 +++++++++++++++++---- crates/semantic_index/src/semantic_index.rs | 21 ++++---------- 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 5a1d5992a6..b85d0b9b40 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -34,7 +34,7 @@ use std::{ ops::{Not, Range}, path::PathBuf, sync::Arc, - time::SystemTime, + time::{Duration, SystemTime}, }; use util::ResultExt as _; use workspace::{ @@ -131,6 +131,7 @@ pub struct ProjectSearchView { struct SemanticState { index_status: SemanticIndexStatus, + maintain_rate_limit: Option>, _subscription: Subscription, } @@ -322,14 +323,14 @@ impl View for ProjectSearchView { SemanticIndexStatus::Indexed => Some("Indexing complete".to_string()), SemanticIndexStatus::Indexing { remaining_files, - rate_limit_expiration_time, + rate_limit_expiry, } => { if remaining_files == 0 { Some(format!("Indexing...")) } else { - if let Some(rate_limit_expiration_time) = rate_limit_expiration_time { + if let Some(rate_limit_expiry) = rate_limit_expiry { if let Ok(remaining_seconds) = - rate_limit_expiration_time.duration_since(SystemTime::now()) + rate_limit_expiry.duration_since(SystemTime::now()) { Some(format!( "Remaining files to index (rate limit resets in {}s): {}", @@ -669,9 +670,10 @@ impl ProjectSearchView { self.semantic_state = Some(SemanticState { index_status: semantic_index.read(cx).status(&project), + maintain_rate_limit: None, _subscription: cx.observe(&semantic_index, Self::semantic_index_changed), }); - cx.notify(); + self.semantic_index_changed(semantic_index, cx); } } @@ -682,8 +684,25 @@ impl ProjectSearchView { ) { let project = self.model.read(cx).project.clone(); if let Some(semantic_state) = self.semantic_state.as_mut() { - semantic_state.index_status = semantic_index.read(cx).status(&project); cx.notify(); + semantic_state.index_status = semantic_index.read(cx).status(&project); + if let SemanticIndexStatus::Indexing { + rate_limit_expiry: Some(_), + .. + } = &semantic_state.index_status + { + if semantic_state.maintain_rate_limit.is_none() { + semantic_state.maintain_rate_limit = + Some(cx.spawn(|this, mut cx| async move { + loop { + cx.background().timer(Duration::from_secs(1)).await; + this.update(&mut cx, |_, cx| cx.notify()).log_err(); + } + })); + return; + } + } + semantic_state.maintain_rate_limit = None; } } diff --git a/crates/semantic_index/src/semantic_index.rs b/crates/semantic_index/src/semantic_index.rs index 92b11f00d1..efcc1ba242 100644 --- a/crates/semantic_index/src/semantic_index.rs +++ b/crates/semantic_index/src/semantic_index.rs @@ -112,7 +112,7 @@ pub enum SemanticIndexStatus { Indexed, Indexing { remaining_files: usize, - rate_limit_expiration_time: Option, + rate_limit_expiry: Option, }, } @@ -232,20 +232,9 @@ impl ProjectState { _observe_pending_file_count: cx.spawn_weak({ let mut pending_file_count_rx = pending_file_count_rx.clone(); |this, mut cx| async move { - loop { - let mut timer = cx.background().timer(Duration::from_millis(350)).fuse(); - let mut pending_file_count = pending_file_count_rx.next().fuse(); - futures::select_biased! { - _ = pending_file_count => { - if let Some(this) = this.upgrade(&cx) { - this.update(&mut cx, |_, cx| cx.notify()); - } - }, - _ = timer => { - if let Some(this) = this.upgrade(&cx) { - this.update(&mut cx, |_, cx| cx.notify()); - } - } + while let Some(_) = pending_file_count_rx.next().await { + if let Some(this) = this.upgrade(&cx) { + this.update(&mut cx, |_, cx| cx.notify()); } } } @@ -304,7 +293,7 @@ impl SemanticIndex { } else { SemanticIndexStatus::Indexing { remaining_files: project_state.pending_file_count_rx.borrow().clone(), - rate_limit_expiration_time: self.embedding_provider.rate_limit_expiration(), + rate_limit_expiry: self.embedding_provider.rate_limit_expiration(), } } } else { From e678c7d9ee29cd7de6ab788c866c8a950b843306 Mon Sep 17 00:00:00 2001 From: KCaverly Date: Mon, 11 Sep 2023 10:26:14 -0400 Subject: [PATCH 10/10] swap SystemTime for Instant throughout rate_limit_expiry tracking --- crates/search/src/project_search.rs | 8 ++++---- crates/semantic_index/src/embedding.rs | 18 +++++++++--------- crates/semantic_index/src/semantic_index.rs | 2 +- .../semantic_index/src/semantic_index_tests.rs | 4 ++-- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index b85d0b9b40..6ca4928803 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -34,7 +34,7 @@ use std::{ ops::{Not, Range}, path::PathBuf, sync::Arc, - time::{Duration, SystemTime}, + time::{Duration, Instant}, }; use util::ResultExt as _; use workspace::{ @@ -329,9 +329,9 @@ impl View for ProjectSearchView { Some(format!("Indexing...")) } else { if let Some(rate_limit_expiry) = rate_limit_expiry { - if let Ok(remaining_seconds) = - rate_limit_expiry.duration_since(SystemTime::now()) - { + let remaining_seconds = + rate_limit_expiry.duration_since(Instant::now()); + if remaining_seconds > Duration::from_secs(0) { Some(format!( "Remaining files to index (rate limit resets in {}s): {}", remaining_seconds.as_secs(), diff --git a/crates/semantic_index/src/embedding.rs b/crates/semantic_index/src/embedding.rs index 7bac809c97..42d90f0fdb 100644 --- a/crates/semantic_index/src/embedding.rs +++ b/crates/semantic_index/src/embedding.rs @@ -16,7 +16,7 @@ use serde::{Deserialize, Serialize}; use std::env; use std::ops::Add; use std::sync::Arc; -use std::time::{Duration, SystemTime}; +use std::time::{Duration, Instant}; use tiktoken_rs::{cl100k_base, CoreBPE}; use util::http::{HttpClient, Request}; @@ -85,8 +85,8 @@ impl ToSql for Embedding { pub struct OpenAIEmbeddings { pub client: Arc, pub executor: Arc, - rate_limit_count_rx: watch::Receiver>, - rate_limit_count_tx: Arc>>>, + rate_limit_count_rx: watch::Receiver>, + rate_limit_count_tx: Arc>>>, } #[derive(Serialize)] @@ -119,14 +119,14 @@ pub trait EmbeddingProvider: Sync + Send { async fn embed_batch(&self, spans: Vec) -> Result>; fn max_tokens_per_batch(&self) -> usize; fn truncate(&self, span: &str) -> (String, usize); - fn rate_limit_expiration(&self) -> Option; + fn rate_limit_expiration(&self) -> Option; } pub struct DummyEmbeddings {} #[async_trait] impl EmbeddingProvider for DummyEmbeddings { - fn rate_limit_expiration(&self) -> Option { + fn rate_limit_expiration(&self) -> Option { None } async fn embed_batch(&self, spans: Vec) -> Result> { @@ -174,7 +174,7 @@ impl OpenAIEmbeddings { let reset_time = *self.rate_limit_count_tx.lock().borrow(); if let Some(reset_time) = reset_time { - if SystemTime::now() >= reset_time { + if Instant::now() >= reset_time { *self.rate_limit_count_tx.lock().borrow_mut() = None } } @@ -185,7 +185,7 @@ impl OpenAIEmbeddings { ); } - fn update_reset_time(&self, reset_time: SystemTime) { + fn update_reset_time(&self, reset_time: Instant) { let original_time = *self.rate_limit_count_tx.lock().borrow(); let updated_time = if let Some(original_time) = original_time { @@ -232,7 +232,7 @@ impl EmbeddingProvider for OpenAIEmbeddings { 50000 } - fn rate_limit_expiration(&self) -> Option { + fn rate_limit_expiration(&self) -> Option { *self.rate_limit_count_rx.borrow() } fn truncate(&self, span: &str) -> (String, usize) { @@ -319,7 +319,7 @@ impl EmbeddingProvider for OpenAIEmbeddings { }; // If we've previously rate limited, increment the duration but not the count - let reset_time = SystemTime::now().add(delay_duration); + let reset_time = Instant::now().add(delay_duration); self.update_reset_time(reset_time); log::trace!( diff --git a/crates/semantic_index/src/semantic_index.rs b/crates/semantic_index/src/semantic_index.rs index efcc1ba242..115bf5d7a8 100644 --- a/crates/semantic_index/src/semantic_index.rs +++ b/crates/semantic_index/src/semantic_index.rs @@ -112,7 +112,7 @@ pub enum SemanticIndexStatus { Indexed, Indexing { remaining_files: usize, - rate_limit_expiry: Option, + rate_limit_expiry: Option, }, } diff --git a/crates/semantic_index/src/semantic_index_tests.rs b/crates/semantic_index/src/semantic_index_tests.rs index 4bc95bec62..9035327b2e 100644 --- a/crates/semantic_index/src/semantic_index_tests.rs +++ b/crates/semantic_index/src/semantic_index_tests.rs @@ -21,7 +21,7 @@ use std::{ atomic::{self, AtomicUsize}, Arc, }, - time::SystemTime, + time::{Instant, SystemTime}, }; use unindent::Unindent; use util::RandomCharIter; @@ -1275,7 +1275,7 @@ impl EmbeddingProvider for FakeEmbeddingProvider { 200 } - fn rate_limit_expiration(&self) -> Option { + fn rate_limit_expiration(&self) -> Option { None }