diff --git a/assets/fonts/plex/IBMPlexSans-Bold.ttf b/assets/fonts/plex/IBMPlexSans-Bold.ttf deleted file mode 100644 index 72b190a849c9c275b44de7466c5bbd101e4e0403..0000000000000000000000000000000000000000 Binary files a/assets/fonts/plex/IBMPlexSans-Bold.ttf and /dev/null differ diff --git a/assets/fonts/plex/IBMPlexSans-Italic.ttf b/assets/fonts/plex/IBMPlexSans-Italic.ttf deleted file mode 100644 index a997a20562517421ed0b19885bca198a86f9597c..0000000000000000000000000000000000000000 Binary files a/assets/fonts/plex/IBMPlexSans-Italic.ttf and /dev/null differ diff --git a/assets/fonts/plex/IBMPlexSans-Regular.ttf b/assets/fonts/plex/IBMPlexSans-Regular.ttf deleted file mode 100644 index 9e4b0a754a9e211c119c3727d330f5a46120fc67..0000000000000000000000000000000000000000 Binary files a/assets/fonts/plex/IBMPlexSans-Regular.ttf and /dev/null differ diff --git a/assets/fonts/plex/LICENSE.txt b/assets/fonts/plex/LICENSE.txt deleted file mode 100644 index c35c4c618fab33da8695177b3a6cefe0810b7b28..0000000000000000000000000000000000000000 --- a/assets/fonts/plex/LICENSE.txt +++ /dev/null @@ -1,93 +0,0 @@ -Copyright © 2017 IBM Corp. with Reserved Font Name "Plex" - -This Font Software is licensed under the SIL Open Font License, Version 1.1. - -This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL - - ------------------------------------------------------------ -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ------------------------------------------------------------ - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font creation -efforts of academic and linguistic communities, and to provide a free and -open framework in which fonts may be shared and improved in partnership -with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply -to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components as -distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to a -new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, -in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the corresponding -Copyright Holder. This restriction only applies to the primary font name as -presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created -using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/crates/ai/src/auth.rs b/crates/ai/src/auth.rs index 1ea49bd615999a7f0318d3e205d3f86cee9c64a8..62556d718360a92bdd21cd20155c2ea8ca5e5c57 100644 --- a/crates/ai/src/auth.rs +++ b/crates/ai/src/auth.rs @@ -1,3 +1,4 @@ +use futures::future::BoxFuture; use gpui::AppContext; #[derive(Clone, Debug)] @@ -9,7 +10,14 @@ pub enum ProviderCredential { pub trait CredentialProvider: Send + Sync { fn has_credentials(&self) -> bool; - fn retrieve_credentials(&self, cx: &mut AppContext) -> ProviderCredential; - fn save_credentials(&self, cx: &mut AppContext, credential: ProviderCredential); - fn delete_credentials(&self, cx: &mut AppContext); + #[must_use] + fn retrieve_credentials(&self, cx: &mut AppContext) -> BoxFuture; + #[must_use] + fn save_credentials( + &self, + cx: &mut AppContext, + credential: ProviderCredential, + ) -> BoxFuture<()>; + #[must_use] + fn delete_credentials(&self, cx: &mut AppContext) -> BoxFuture<()>; } diff --git a/crates/ai/src/providers/open_ai/completion.rs b/crates/ai/src/providers/open_ai/completion.rs index 0e325ee62489b41fb599a75b7b64efcbe864d269..aa5895011323e67ea4618bbb2fb4164737dc53a2 100644 --- a/crates/ai/src/providers/open_ai/completion.rs +++ b/crates/ai/src/providers/open_ai/completion.rs @@ -201,8 +201,10 @@ pub struct OpenAICompletionProvider { } impl OpenAICompletionProvider { - pub fn new(model_name: &str, executor: BackgroundExecutor) -> Self { - let model = OpenAILanguageModel::load(model_name); + pub async fn new(model_name: String, executor: BackgroundExecutor) -> Self { + let model = executor + .spawn(async move { OpenAILanguageModel::load(&model_name) }) + .await; let credential = Arc::new(RwLock::new(ProviderCredential::NoCredentials)); Self { model, @@ -220,45 +222,70 @@ impl CredentialProvider for OpenAICompletionProvider { } } - fn retrieve_credentials(&self, cx: &mut AppContext) -> ProviderCredential { + fn retrieve_credentials(&self, cx: &mut AppContext) -> BoxFuture { let existing_credential = self.credential.read().clone(); let retrieved_credential = match existing_credential { - ProviderCredential::Credentials { .. } => existing_credential.clone(), + ProviderCredential::Credentials { .. } => { + return async move { existing_credential }.boxed() + } _ => { if let Some(api_key) = env::var("OPENAI_API_KEY").log_err() { - ProviderCredential::Credentials { api_key } - } else if let Some(Some((_, api_key))) = - cx.read_credentials(OPENAI_API_URL).log_err() - { - if let Some(api_key) = String::from_utf8(api_key).log_err() { - ProviderCredential::Credentials { api_key } - } else { - ProviderCredential::NoCredentials - } + async move { ProviderCredential::Credentials { api_key } }.boxed() } else { - ProviderCredential::NoCredentials + let credentials = cx.read_credentials(OPENAI_API_URL); + async move { + if let Some(Some((_, api_key))) = credentials.await.log_err() { + if let Some(api_key) = String::from_utf8(api_key).log_err() { + ProviderCredential::Credentials { api_key } + } else { + ProviderCredential::NoCredentials + } + } else { + ProviderCredential::NoCredentials + } + } + .boxed() } } }; - *self.credential.write() = retrieved_credential.clone(); - retrieved_credential + + async move { + let retrieved_credential = retrieved_credential.await; + *self.credential.write() = retrieved_credential.clone(); + retrieved_credential + } + .boxed() } - fn save_credentials(&self, cx: &mut AppContext, credential: ProviderCredential) { + fn save_credentials( + &self, + cx: &mut AppContext, + credential: ProviderCredential, + ) -> BoxFuture<()> { *self.credential.write() = credential.clone(); let credential = credential.clone(); - match credential { + let write_credentials = match credential { ProviderCredential::Credentials { api_key } => { - cx.write_credentials(OPENAI_API_URL, "Bearer", api_key.as_bytes()) - .log_err(); + Some(cx.write_credentials(OPENAI_API_URL, "Bearer", api_key.as_bytes())) + } + _ => None, + }; + + async move { + if let Some(write_credentials) = write_credentials { + write_credentials.await.log_err(); } - _ => {} } + .boxed() } - fn delete_credentials(&self, cx: &mut AppContext) { - cx.delete_credentials(OPENAI_API_URL).log_err(); + fn delete_credentials(&self, cx: &mut AppContext) -> BoxFuture<()> { *self.credential.write() = ProviderCredential::NoCredentials; + let delete_credentials = cx.delete_credentials(OPENAI_API_URL); + async move { + delete_credentials.await.log_err(); + } + .boxed() } } diff --git a/crates/ai/src/providers/open_ai/embedding.rs b/crates/ai/src/providers/open_ai/embedding.rs index 0a9b6ba969c7c519d337ae27db45af12252efa0b..89aebb1b7616643c683cdd419ec0ebd1d51b5142 100644 --- a/crates/ai/src/providers/open_ai/embedding.rs +++ b/crates/ai/src/providers/open_ai/embedding.rs @@ -1,6 +1,8 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; +use futures::future::BoxFuture; use futures::AsyncReadExt; +use futures::FutureExt; use gpui::AppContext; use gpui::BackgroundExecutor; use isahc::http::StatusCode; @@ -67,11 +69,14 @@ struct OpenAIEmbeddingUsage { } impl OpenAIEmbeddingProvider { - pub fn new(client: Arc, executor: BackgroundExecutor) -> Self { + pub async fn new(client: Arc, executor: BackgroundExecutor) -> Self { 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)); - let model = OpenAILanguageModel::load("text-embedding-ada-002"); + // Loading the model is expensive, so ensure this runs off the main thread. + let model = executor + .spawn(async move { OpenAILanguageModel::load("text-embedding-ada-002") }) + .await; let credential = Arc::new(RwLock::new(ProviderCredential::NoCredentials)); OpenAIEmbeddingProvider { @@ -154,46 +159,71 @@ impl CredentialProvider for OpenAIEmbeddingProvider { _ => false, } } - fn retrieve_credentials(&self, cx: &mut AppContext) -> ProviderCredential { - let existing_credential = self.credential.read().clone(); + fn retrieve_credentials(&self, cx: &mut AppContext) -> BoxFuture { + let existing_credential = self.credential.read().clone(); let retrieved_credential = match existing_credential { - ProviderCredential::Credentials { .. } => existing_credential.clone(), + ProviderCredential::Credentials { .. } => { + return async move { existing_credential }.boxed() + } _ => { if let Some(api_key) = env::var("OPENAI_API_KEY").log_err() { - ProviderCredential::Credentials { api_key } - } else if let Some(Some((_, api_key))) = - cx.read_credentials(OPENAI_API_URL).log_err() - { - if let Some(api_key) = String::from_utf8(api_key).log_err() { - ProviderCredential::Credentials { api_key } - } else { - ProviderCredential::NoCredentials - } + async move { ProviderCredential::Credentials { api_key } }.boxed() } else { - ProviderCredential::NoCredentials + let credentials = cx.read_credentials(OPENAI_API_URL); + async move { + if let Some(Some((_, api_key))) = credentials.await.log_err() { + if let Some(api_key) = String::from_utf8(api_key).log_err() { + ProviderCredential::Credentials { api_key } + } else { + ProviderCredential::NoCredentials + } + } else { + ProviderCredential::NoCredentials + } + } + .boxed() } } }; - *self.credential.write() = retrieved_credential.clone(); - retrieved_credential + async move { + let retrieved_credential = retrieved_credential.await; + *self.credential.write() = retrieved_credential.clone(); + retrieved_credential + } + .boxed() } - fn save_credentials(&self, cx: &mut AppContext, credential: ProviderCredential) { + fn save_credentials( + &self, + cx: &mut AppContext, + credential: ProviderCredential, + ) -> BoxFuture<()> { *self.credential.write() = credential.clone(); - match credential { + let credential = credential.clone(); + let write_credentials = match credential { ProviderCredential::Credentials { api_key } => { - cx.write_credentials(OPENAI_API_URL, "Bearer", api_key.as_bytes()) - .log_err(); + Some(cx.write_credentials(OPENAI_API_URL, "Bearer", api_key.as_bytes())) + } + _ => None, + }; + + async move { + if let Some(write_credentials) = write_credentials { + write_credentials.await.log_err(); } - _ => {} } + .boxed() } - fn delete_credentials(&self, cx: &mut AppContext) { - cx.delete_credentials(OPENAI_API_URL).log_err(); + fn delete_credentials(&self, cx: &mut AppContext) -> BoxFuture<()> { *self.credential.write() = ProviderCredential::NoCredentials; + let delete_credentials = cx.delete_credentials(OPENAI_API_URL); + async move { + delete_credentials.await.log_err(); + } + .boxed() } } diff --git a/crates/ai/src/test.rs b/crates/ai/src/test.rs index 3d59febbe912b42f7be11625ff0fbc2952184291..89edc71b0bc51f4d08c8f5fba8c3621ddb6ce000 100644 --- a/crates/ai/src/test.rs +++ b/crates/ai/src/test.rs @@ -104,11 +104,22 @@ impl CredentialProvider for FakeEmbeddingProvider { fn has_credentials(&self) -> bool { true } - fn retrieve_credentials(&self, _cx: &mut AppContext) -> ProviderCredential { - ProviderCredential::NotNeeded + + fn retrieve_credentials(&self, _cx: &mut AppContext) -> BoxFuture { + async { ProviderCredential::NotNeeded }.boxed() + } + + fn save_credentials( + &self, + _cx: &mut AppContext, + _credential: ProviderCredential, + ) -> BoxFuture<()> { + async {}.boxed() + } + + fn delete_credentials(&self, _cx: &mut AppContext) -> BoxFuture<()> { + async {}.boxed() } - fn save_credentials(&self, _cx: &mut AppContext, _credential: ProviderCredential) {} - fn delete_credentials(&self, _cx: &mut AppContext) {} } #[async_trait] @@ -165,11 +176,22 @@ impl CredentialProvider for FakeCompletionProvider { fn has_credentials(&self) -> bool { true } - fn retrieve_credentials(&self, _cx: &mut AppContext) -> ProviderCredential { - ProviderCredential::NotNeeded + + fn retrieve_credentials(&self, _cx: &mut AppContext) -> BoxFuture { + async { ProviderCredential::NotNeeded }.boxed() + } + + fn save_credentials( + &self, + _cx: &mut AppContext, + _credential: ProviderCredential, + ) -> BoxFuture<()> { + async {}.boxed() + } + + fn delete_credentials(&self, _cx: &mut AppContext) -> BoxFuture<()> { + async {}.boxed() } - fn save_credentials(&self, _cx: &mut AppContext, _credential: ProviderCredential) {} - fn delete_credentials(&self, _cx: &mut AppContext) {} } impl CompletionProvider for FakeCompletionProvider { diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 1f57e52032b1e4e76f7297f577d4db82cf970eb3..b2c539fcc21dc1ccb05532e5a5aa7e9ca7012b0a 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -6,14 +6,12 @@ use crate::{ NewConversation, QuoteSelection, ResetKey, Role, SavedConversation, SavedConversationMetadata, SavedMessage, Split, ToggleFocus, ToggleIncludeConversation, ToggleRetrieveContext, }; - +use ai::prompts::repository_context::PromptCodeSnippet; use ai::{ auth::ProviderCredential, completion::{CompletionProvider, CompletionRequest}, providers::open_ai::{OpenAICompletionProvider, OpenAIRequest, RequestMessage}, }; - -use ai::prompts::repository_context::PromptCodeSnippet; use anyhow::{anyhow, Result}; use chrono::{DateTime, Local}; use client::telemetry::AssistantKind; @@ -31,9 +29,9 @@ use fs::Fs; use futures::StreamExt; use gpui::{ canvas, div, point, relative, rems, uniform_list, Action, AnyElement, AppContext, - AsyncWindowContext, AvailableSpace, ClipboardItem, Context, EventEmitter, FocusHandle, - FocusableView, FontStyle, FontWeight, HighlightStyle, InteractiveElement, IntoElement, Model, - ModelContext, ParentElement, Pixels, PromptLevel, Render, SharedString, + AsyncAppContext, AsyncWindowContext, AvailableSpace, ClipboardItem, Context, EventEmitter, + FocusHandle, FocusableView, FontStyle, FontWeight, HighlightStyle, InteractiveElement, + IntoElement, Model, ModelContext, ParentElement, Pixels, PromptLevel, Render, SharedString, StatefulInteractiveElement, Styled, Subscription, Task, TextStyle, UniformListScrollHandle, View, ViewContext, VisualContext, WeakModel, WeakView, WhiteSpace, WindowContext, }; @@ -123,6 +121,10 @@ impl AssistantPanel { .await .log_err() .unwrap_or_default(); + // Defaulting currently to GPT4, allow for this to be set via config. + let completion_provider = + OpenAICompletionProvider::new("gpt-4".into(), cx.background_executor().clone()) + .await; // TODO: deserialize state. let workspace_handle = workspace.clone(); @@ -156,11 +158,6 @@ impl AssistantPanel { }); let semantic_index = SemanticIndex::global(cx); - // Defaulting currently to GPT4, allow for this to be set via config. - let completion_provider = Arc::new(OpenAICompletionProvider::new( - "gpt-4", - cx.background_executor().clone(), - )); let focus_handle = cx.focus_handle(); cx.on_focus_in(&focus_handle, Self::focus_in).detach(); @@ -176,7 +173,7 @@ impl AssistantPanel { zoomed: false, focus_handle, toolbar, - completion_provider, + completion_provider: Arc::new(completion_provider), api_key_editor: None, languages: workspace.app_state().languages.clone(), fs: workspace.app_state().fs.clone(), @@ -221,23 +218,9 @@ impl AssistantPanel { _: &InlineAssist, cx: &mut ViewContext, ) { - let this = if let Some(this) = workspace.panel::(cx) { - if this.update(cx, |assistant, cx| { - if !assistant.has_credentials() { - assistant.load_credentials(cx); - }; - - assistant.has_credentials() - }) { - this - } else { - workspace.focus_panel::(cx); - return; - } - } else { + let Some(assistant) = workspace.panel::(cx) else { return; }; - let active_editor = if let Some(active_editor) = workspace .active_item(cx) .and_then(|item| item.act_as::(cx)) @@ -246,12 +229,32 @@ impl AssistantPanel { } else { return; }; + let project = workspace.project().clone(); - let project = workspace.project(); + if assistant.update(cx, |assistant, _| assistant.has_credentials()) { + assistant.update(cx, |assistant, cx| { + assistant.new_inline_assist(&active_editor, cx, &project) + }); + } else { + let assistant = assistant.downgrade(); + cx.spawn(|workspace, mut cx| async move { + assistant + .update(&mut cx, |assistant, cx| assistant.load_credentials(cx))? + .await; + if assistant.update(&mut cx, |assistant, _| assistant.has_credentials())? { + assistant.update(&mut cx, |assistant, cx| { + assistant.new_inline_assist(&active_editor, cx, &project) + })?; + } else { + workspace.update(&mut cx, |workspace, cx| { + workspace.focus_panel::(cx) + })?; + } - this.update(cx, |assistant, cx| { - assistant.new_inline_assist(&active_editor, cx, project) - }); + anyhow::Ok(()) + }) + .detach_and_log_err(cx) + } } fn new_inline_assist( @@ -291,9 +294,6 @@ impl AssistantPanel { let inline_assist_id = post_inc(&mut self.next_inline_assist_id); let provider = self.completion_provider.clone(); - // Retrieve Credentials Authenticates the Provider - provider.retrieve_credentials(cx); - let codegen = cx.new_model(|cx| { Codegen::new(editor.read(cx).buffer().clone(), codegen_kind, provider, cx) }); @@ -846,11 +846,18 @@ impl AssistantPanel { api_key: api_key.clone(), }; - self.completion_provider.save_credentials(cx, credential); + let completion_provider = self.completion_provider.clone(); + cx.spawn(|this, mut cx| async move { + cx.update(|cx| completion_provider.save_credentials(cx, credential))? + .await; - self.api_key_editor.take(); - self.focus_handle.focus(cx); - cx.notify(); + this.update(&mut cx, |this, cx| { + this.api_key_editor.take(); + this.focus_handle.focus(cx); + cx.notify(); + }) + }) + .detach_and_log_err(cx); } } else { cx.propagate(); @@ -858,10 +865,17 @@ impl AssistantPanel { } fn reset_credentials(&mut self, _: &ResetKey, cx: &mut ViewContext) { - self.completion_provider.delete_credentials(cx); - self.api_key_editor = Some(build_api_key_editor(cx)); - self.focus_handle.focus(cx); - cx.notify(); + let completion_provider = self.completion_provider.clone(); + cx.spawn(|this, mut cx| async move { + cx.update(|cx| completion_provider.delete_credentials(cx))? + .await; + this.update(&mut cx, |this, cx| { + this.api_key_editor = Some(build_api_key_editor(cx)); + this.focus_handle.focus(cx); + cx.notify(); + }) + }) + .detach_and_log_err(cx); } fn toggle_zoom(&mut self, _: &workspace::ToggleZoom, cx: &mut ViewContext) { @@ -1079,9 +1093,9 @@ impl AssistantPanel { cx.spawn(|this, mut cx| async move { let saved_conversation = fs.load(&path).await?; let saved_conversation = serde_json::from_str(&saved_conversation)?; - let conversation = cx.new_model(|cx| { - Conversation::deserialize(saved_conversation, path.clone(), languages, cx) - })?; + let conversation = + Conversation::deserialize(saved_conversation, path.clone(), languages, &mut cx) + .await?; this.update(&mut cx, |this, cx| { // If, by the time we've loaded the conversation, the user has already opened // the same conversation, we don't want to open it again. @@ -1108,8 +1122,16 @@ impl AssistantPanel { self.completion_provider.has_credentials() } - fn load_credentials(&mut self, cx: &mut ViewContext) { - self.completion_provider.retrieve_credentials(cx); + fn load_credentials(&mut self, cx: &mut ViewContext) -> Task<()> { + let completion_provider = self.completion_provider.clone(); + cx.spawn(|_, mut cx| async move { + if let Some(retrieve_credentials) = cx + .update(|cx| completion_provider.retrieve_credentials(cx)) + .log_err() + { + retrieve_credentials.await; + } + }) } } @@ -1315,11 +1337,16 @@ impl Panel for AssistantPanel { fn set_active(&mut self, active: bool, cx: &mut ViewContext) { if active { - self.load_credentials(cx); - - if self.editors.is_empty() { - self.new_conversation(cx); - } + let load_credentials = self.load_credentials(cx); + cx.spawn(|this, mut cx| async move { + load_credentials.await; + this.update(&mut cx, |this, cx| { + if this.editors.is_empty() { + this.new_conversation(cx); + } + }) + }) + .detach_and_log_err(cx); } } @@ -1462,21 +1489,25 @@ impl Conversation { } } - fn deserialize( + async fn deserialize( saved_conversation: SavedConversation, path: PathBuf, language_registry: Arc, - cx: &mut ModelContext, - ) -> Self { + cx: &mut AsyncAppContext, + ) -> Result> { let id = match saved_conversation.id { Some(id) => Some(id), None => Some(Uuid::new_v4().to_string()), }; let model = saved_conversation.model; let completion_provider: Arc = Arc::new( - OpenAICompletionProvider::new(model.full_name(), cx.background_executor().clone()), + OpenAICompletionProvider::new( + model.full_name().into(), + cx.background_executor().clone(), + ) + .await, ); - completion_provider.retrieve_credentials(cx); + cx.update(|cx| completion_provider.retrieve_credentials(cx))?; let markdown = language_registry.language_for_name("Markdown"); let mut message_anchors = Vec::new(); let mut next_message_id = MessageId(0); @@ -1499,32 +1530,34 @@ impl Conversation { }) .detach_and_log_err(cx); buffer - }); - - let mut this = Self { - id, - message_anchors, - messages_metadata: saved_conversation.message_metadata, - next_message_id, - summary: Some(Summary { - text: saved_conversation.summary, - done: true, - }), - pending_summary: Task::ready(None), - completion_count: Default::default(), - pending_completions: Default::default(), - token_count: None, - max_token_count: tiktoken_rs::model::get_context_size(&model.full_name()), - pending_token_count: Task::ready(None), - model, - _subscriptions: vec![cx.subscribe(&buffer, Self::handle_buffer_event)], - pending_save: Task::ready(Ok(())), - path: Some(path), - buffer, - completion_provider, - }; - this.count_remaining_tokens(cx); - this + })?; + + cx.new_model(|cx| { + let mut this = Self { + id, + message_anchors, + messages_metadata: saved_conversation.message_metadata, + next_message_id, + summary: Some(Summary { + text: saved_conversation.summary, + done: true, + }), + pending_summary: Task::ready(None), + completion_count: Default::default(), + pending_completions: Default::default(), + token_count: None, + max_token_count: tiktoken_rs::model::get_context_size(&model.full_name()), + pending_token_count: Task::ready(None), + model, + _subscriptions: vec![cx.subscribe(&buffer, Self::handle_buffer_event)], + pending_save: Task::ready(Ok(())), + path: Some(path), + buffer, + completion_provider, + }; + this.count_remaining_tokens(cx); + this + }) } fn handle_buffer_event( @@ -3169,7 +3202,7 @@ mod tests { use super::*; use crate::MessageId; use ai::test::FakeCompletionProvider; - use gpui::AppContext; + use gpui::{AppContext, TestAppContext}; use settings::SettingsStore; #[gpui::test] @@ -3487,16 +3520,17 @@ mod tests { } #[gpui::test] - fn test_serialization(cx: &mut AppContext) { - let settings_store = SettingsStore::test(cx); + async fn test_serialization(cx: &mut TestAppContext) { + let settings_store = cx.update(SettingsStore::test); cx.set_global(settings_store); - init(cx); + cx.update(init); let registry = Arc::new(LanguageRegistry::test()); let completion_provider = Arc::new(FakeCompletionProvider::new()); let conversation = cx.new_model(|cx| Conversation::new(registry.clone(), cx, completion_provider)); - let buffer = conversation.read(cx).buffer.clone(); - let message_0 = conversation.read(cx).message_anchors[0].id; + let buffer = conversation.read_with(cx, |conversation, _| conversation.buffer.clone()); + let message_0 = + conversation.read_with(cx, |conversation, _| conversation.message_anchors[0].id); let message_1 = conversation.update(cx, |conversation, cx| { conversation .insert_message_after(message_0, Role::Assistant, MessageStatus::Done, cx) @@ -3517,9 +3551,9 @@ mod tests { .unwrap() }); buffer.update(cx, |buffer, cx| buffer.undo(cx)); - assert_eq!(buffer.read(cx).text(), "a\nb\nc\n"); + assert_eq!(buffer.read_with(cx, |buffer, _| buffer.text()), "a\nb\nc\n"); assert_eq!( - messages(&conversation, cx), + cx.read(|cx| messages(&conversation, cx)), [ (message_0, Role::User, 0..2), (message_1.id, Role::Assistant, 2..6), @@ -3527,18 +3561,22 @@ mod tests { ] ); - let deserialized_conversation = cx.new_model(|cx| { - Conversation::deserialize( - conversation.read(cx).serialize(cx), - Default::default(), - registry.clone(), - cx, - ) - }); - let deserialized_buffer = deserialized_conversation.read(cx).buffer.clone(); - assert_eq!(deserialized_buffer.read(cx).text(), "a\nb\nc\n"); + let deserialized_conversation = Conversation::deserialize( + conversation.read_with(cx, |conversation, cx| conversation.serialize(cx)), + Default::default(), + registry.clone(), + &mut cx.to_async(), + ) + .await + .unwrap(); + let deserialized_buffer = + deserialized_conversation.read_with(cx, |conversation, _| conversation.buffer.clone()); + assert_eq!( + deserialized_buffer.read_with(cx, |buffer, _| buffer.text()), + "a\nb\nc\n" + ); assert_eq!( - messages(&deserialized_conversation, cx), + cx.read(|cx| messages(&deserialized_conversation, cx)), [ (message_0, Role::User, 0..2), (message_1.id, Role::Assistant, 2..6), diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 844c9a5eb66a89751a632ef311a79e9419521a58..370a2ba6ffdb8ab13d8279c19e9207990b160b20 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -700,8 +700,8 @@ impl Client { } } - pub fn has_keychain_credentials(&self, cx: &AsyncAppContext) -> bool { - read_credentials_from_keychain(cx).is_some() + pub async fn has_keychain_credentials(&self, cx: &AsyncAppContext) -> bool { + read_credentials_from_keychain(cx).await.is_some() } #[async_recursion(?Send)] @@ -732,7 +732,7 @@ impl Client { let mut read_from_keychain = false; let mut credentials = self.state.read().credentials.clone(); if credentials.is_none() && try_keychain { - credentials = read_credentials_from_keychain(cx); + credentials = read_credentials_from_keychain(cx).await; read_from_keychain = credentials.is_some(); } if credentials.is_none() { @@ -770,7 +770,7 @@ impl Client { Ok(conn) => { self.state.write().credentials = Some(credentials.clone()); if !read_from_keychain && IMPERSONATE_LOGIN.is_none() { - write_credentials_to_keychain(credentials, cx).log_err(); + write_credentials_to_keychain(credentials, cx).await.log_err(); } futures::select_biased! { @@ -784,7 +784,7 @@ impl Client { Err(EstablishConnectionError::Unauthorized) => { self.state.write().credentials.take(); if read_from_keychain { - delete_credentials_from_keychain(cx).log_err(); + delete_credentials_from_keychain(cx).await.log_err(); self.set_status(Status::SignedOut, cx); self.authenticate_and_connect(false, cx).await } else { @@ -1350,14 +1350,16 @@ impl Client { } } -fn read_credentials_from_keychain(cx: &AsyncAppContext) -> Option { +async fn read_credentials_from_keychain(cx: &AsyncAppContext) -> Option { if IMPERSONATE_LOGIN.is_some() { return None; } let (user_id, access_token) = cx - .update(|cx| cx.read_credentials(&ZED_SERVER_URL).log_err().flatten()) - .ok()??; + .update(|cx| cx.read_credentials(&ZED_SERVER_URL)) + .log_err()? + .await + .log_err()??; Some(Credentials { user_id: user_id.parse().ok()?, @@ -1365,7 +1367,10 @@ fn read_credentials_from_keychain(cx: &AsyncAppContext) -> Option { }) } -fn write_credentials_to_keychain(credentials: Credentials, cx: &AsyncAppContext) -> Result<()> { +async fn write_credentials_to_keychain( + credentials: Credentials, + cx: &AsyncAppContext, +) -> Result<()> { cx.update(move |cx| { cx.write_credentials( &ZED_SERVER_URL, @@ -1373,10 +1378,12 @@ fn write_credentials_to_keychain(credentials: Credentials, cx: &AsyncAppContext) credentials.access_token.as_bytes(), ) })? + .await } -fn delete_credentials_from_keychain(cx: &AsyncAppContext) -> Result<()> { +async fn delete_credentials_from_keychain(cx: &AsyncAppContext) -> Result<()> { cx.update(move |cx| cx.delete_credentials(&ZED_SERVER_URL))? + .await } const WORKTREE_URL_PREFIX: &str = "zed://worktrees/"; diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index dfecffb80a26461cc16d40489213988494229672..6f75eafaf2a898ecc7d33a4d96365a42e41e712f 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -523,17 +523,22 @@ impl AppContext { } /// Writes credentials to the platform keychain. - pub fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> { + pub fn write_credentials( + &self, + url: &str, + username: &str, + password: &[u8], + ) -> Task> { self.platform.write_credentials(url, username, password) } /// Reads credentials from the platform keychain. - pub fn read_credentials(&self, url: &str) -> Result)>> { + pub fn read_credentials(&self, url: &str) -> Task)>>> { self.platform.read_credentials(url) } /// Deletes credentials from the platform keychain. - pub fn delete_credentials(&self, url: &str) -> Result<()> { + pub fn delete_credentials(&self, url: &str) -> Task> { self.platform.delete_credentials(url) } diff --git a/crates/gpui/src/elements/img.rs b/crates/gpui/src/elements/img.rs index 191cadf2939834eb68c4b70abf18b92c8bdc6ff8..e7377373fe18cd7d6d26a610c2d5b776e02874fc 100644 --- a/crates/gpui/src/elements/img.rs +++ b/crates/gpui/src/elements/img.rs @@ -104,7 +104,7 @@ impl Element for Img { cx.with_z_index(1, |cx| { match source { ImageSource::Uri(uri) => { - let image_future = cx.image_cache.get(uri.clone()); + let image_future = cx.image_cache.get(uri.clone(), cx); if let Some(data) = image_future .clone() .now_or_never() diff --git a/crates/gpui/src/image_cache.rs b/crates/gpui/src/image_cache.rs index dd7b7b571e43c20609e6920f0da59093b5aa010f..95b41c3b2c0619c24deb4207e41b01d23e615b21 100644 --- a/crates/gpui/src/image_cache.rs +++ b/crates/gpui/src/image_cache.rs @@ -1,9 +1,6 @@ -use crate::{ImageData, ImageId, SharedUrl}; +use crate::{AppContext, ImageData, ImageId, SharedUrl, Task}; use collections::HashMap; -use futures::{ - future::{BoxFuture, Shared}, - AsyncReadExt, FutureExt, TryFutureExt, -}; +use futures::{future::Shared, AsyncReadExt, FutureExt, TryFutureExt}; use image::ImageError; use parking_lot::Mutex; use std::sync::Arc; @@ -44,10 +41,10 @@ impl From for Error { pub(crate) struct ImageCache { client: Arc, - images: Arc>>, + images: Arc>>, } -type FetchImageFuture = Shared, Error>>>; +type FetchImageTask = Shared, Error>>>; impl ImageCache { pub fn new(client: Arc) -> Self { @@ -57,10 +54,7 @@ impl ImageCache { } } - pub fn get( - &self, - uri: impl Into, - ) -> Shared, Error>>> { + pub fn get(&self, uri: impl Into, cx: &AppContext) -> FetchImageTask { let uri = uri.into(); let mut images = self.images.lock(); @@ -68,36 +62,39 @@ impl ImageCache { Some(future) => future.clone(), None => { let client = self.client.clone(); - let future = { - let uri = uri.clone(); - async move { - let mut response = client.get(uri.as_ref(), ().into(), true).await?; - let mut body = Vec::new(); - response.body_mut().read_to_end(&mut body).await?; + let future = cx + .background_executor() + .spawn( + { + let uri = uri.clone(); + async move { + let mut response = + client.get(uri.as_ref(), ().into(), true).await?; + let mut body = Vec::new(); + response.body_mut().read_to_end(&mut body).await?; - if !response.status().is_success() { - return Err(Error::BadStatus { - status: response.status(), - body: String::from_utf8_lossy(&body).into_owned(), - }); - } - - let format = image::guess_format(&body)?; - let image = - image::load_from_memory_with_format(&body, format)?.into_bgra8(); - Ok(Arc::new(ImageData::new(image))) - } - } - .map_err({ - let uri = uri.clone(); + if !response.status().is_success() { + return Err(Error::BadStatus { + status: response.status(), + body: String::from_utf8_lossy(&body).into_owned(), + }); + } - move |error| { - log::log!(log::Level::Error, "{:?} {:?}", &uri, &error); - error - } - }) - .boxed() - .shared(); + let format = image::guess_format(&body)?; + let image = image::load_from_memory_with_format(&body, format)? + .into_bgra8(); + Ok(Arc::new(ImageData::new(image))) + } + } + .map_err({ + let uri = uri.clone(); + move |error| { + log::log!(log::Level::Error, "{:?} {:?}", &uri, &error); + error + } + }), + ) + .shared(); images.insert(uri, future.clone()); future diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 900b17bb2c565a80b4095090478b3d2a06c8423a..3d2679dd7ecabe5d92e31038fa9c5d4a02f9d55c 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -9,7 +9,7 @@ use crate::{ Action, AnyWindowHandle, AsyncWindowContext, BackgroundExecutor, Bounds, DevicePixels, Font, FontId, FontMetrics, FontRun, ForegroundExecutor, GlobalPixels, GlyphId, Keymap, LineLayout, Pixels, PlatformInput, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result, - Scene, SharedString, Size, TaskLabel, WindowContext, + Scene, SharedString, Size, Task, TaskLabel, WindowContext, }; use anyhow::anyhow; use async_task::Runnable; @@ -108,9 +108,9 @@ pub(crate) trait Platform: 'static { fn write_to_clipboard(&self, item: ClipboardItem); fn read_from_clipboard(&self) -> Option; - fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()>; - fn read_credentials(&self, url: &str) -> Result)>>; - fn delete_credentials(&self, url: &str) -> Result<()>; + fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task>; + fn read_credentials(&self, url: &str) -> Task)>>>; + fn delete_credentials(&self, url: &str) -> Task>; } /// A handle to a platform's display, e.g. a monitor or laptop screen. diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index e4a688c2fdb35c1dd3adec829fd66a4baf1e4441..58a759865ead18dda81d4a8f2090dbab6c05e379 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -3,7 +3,7 @@ use crate::{ Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, ForegroundExecutor, Keymap, MacDispatcher, MacDisplay, MacDisplayLinker, MacTextSystem, MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay, PlatformInput, - PlatformTextSystem, PlatformWindow, Result, SemanticVersion, WindowOptions, + PlatformTextSystem, PlatformWindow, Result, SemanticVersion, Task, WindowOptions, }; use anyhow::anyhow; use block::ConcreteBlock; @@ -856,104 +856,115 @@ impl Platform for MacPlatform { } } - fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> { - let url = CFString::from(url); - let username = CFString::from(username); - let password = CFData::from_buffer(password); - - unsafe { - use security::*; - - // First, check if there are already credentials for the given server. If so, then - // update the username and password. - let mut verb = "updating"; - let mut query_attrs = CFMutableDictionary::with_capacity(2); - query_attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _); - query_attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef()); - - let mut attrs = CFMutableDictionary::with_capacity(4); - attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _); - attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef()); - attrs.set(kSecAttrAccount as *const _, username.as_CFTypeRef()); - attrs.set(kSecValueData as *const _, password.as_CFTypeRef()); - - let mut status = SecItemUpdate( - query_attrs.as_concrete_TypeRef(), - attrs.as_concrete_TypeRef(), - ); + fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task> { + let url = url.to_string(); + let username = username.to_string(); + let password = password.to_vec(); + self.background_executor().spawn(async move { + unsafe { + use security::*; + + let url = CFString::from(url.as_str()); + let username = CFString::from(username.as_str()); + let password = CFData::from_buffer(&password); + + // First, check if there are already credentials for the given server. If so, then + // update the username and password. + let mut verb = "updating"; + let mut query_attrs = CFMutableDictionary::with_capacity(2); + query_attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _); + query_attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef()); + + let mut attrs = CFMutableDictionary::with_capacity(4); + attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _); + attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef()); + attrs.set(kSecAttrAccount as *const _, username.as_CFTypeRef()); + attrs.set(kSecValueData as *const _, password.as_CFTypeRef()); + + let mut status = SecItemUpdate( + query_attrs.as_concrete_TypeRef(), + attrs.as_concrete_TypeRef(), + ); - // If there were no existing credentials for the given server, then create them. - if status == errSecItemNotFound { - verb = "creating"; - status = SecItemAdd(attrs.as_concrete_TypeRef(), ptr::null_mut()); - } + // If there were no existing credentials for the given server, then create them. + if status == errSecItemNotFound { + verb = "creating"; + status = SecItemAdd(attrs.as_concrete_TypeRef(), ptr::null_mut()); + } - if status != errSecSuccess { - return Err(anyhow!("{} password failed: {}", verb, status)); + if status != errSecSuccess { + return Err(anyhow!("{} password failed: {}", verb, status)); + } } - } - Ok(()) - } - - fn read_credentials(&self, url: &str) -> Result)>> { - let url = CFString::from(url); - let cf_true = CFBoolean::true_value().as_CFTypeRef(); + Ok(()) + }) + } + + fn read_credentials(&self, url: &str) -> Task)>>> { + let url = url.to_string(); + self.background_executor().spawn(async move { + let url = CFString::from(url.as_str()); + let cf_true = CFBoolean::true_value().as_CFTypeRef(); + + unsafe { + use security::*; + + // Find any credentials for the given server URL. + let mut attrs = CFMutableDictionary::with_capacity(5); + attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _); + attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef()); + attrs.set(kSecReturnAttributes as *const _, cf_true); + attrs.set(kSecReturnData as *const _, cf_true); + + let mut result = CFTypeRef::from(ptr::null()); + let status = SecItemCopyMatching(attrs.as_concrete_TypeRef(), &mut result); + match status { + security::errSecSuccess => {} + security::errSecItemNotFound | security::errSecUserCanceled => return Ok(None), + _ => return Err(anyhow!("reading password failed: {}", status)), + } - unsafe { - use security::*; - - // Find any credentials for the given server URL. - let mut attrs = CFMutableDictionary::with_capacity(5); - attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _); - attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef()); - attrs.set(kSecReturnAttributes as *const _, cf_true); - attrs.set(kSecReturnData as *const _, cf_true); - - let mut result = CFTypeRef::from(ptr::null()); - let status = SecItemCopyMatching(attrs.as_concrete_TypeRef(), &mut result); - match status { - security::errSecSuccess => {} - security::errSecItemNotFound | security::errSecUserCanceled => return Ok(None), - _ => return Err(anyhow!("reading password failed: {}", status)), + let result = CFType::wrap_under_create_rule(result) + .downcast::() + .ok_or_else(|| anyhow!("keychain item was not a dictionary"))?; + let username = result + .find(kSecAttrAccount as *const _) + .ok_or_else(|| anyhow!("account was missing from keychain item"))?; + let username = CFType::wrap_under_get_rule(*username) + .downcast::() + .ok_or_else(|| anyhow!("account was not a string"))?; + let password = result + .find(kSecValueData as *const _) + .ok_or_else(|| anyhow!("password was missing from keychain item"))?; + let password = CFType::wrap_under_get_rule(*password) + .downcast::() + .ok_or_else(|| anyhow!("password was not a string"))?; + + Ok(Some((username.to_string(), password.bytes().to_vec()))) } - - let result = CFType::wrap_under_create_rule(result) - .downcast::() - .ok_or_else(|| anyhow!("keychain item was not a dictionary"))?; - let username = result - .find(kSecAttrAccount as *const _) - .ok_or_else(|| anyhow!("account was missing from keychain item"))?; - let username = CFType::wrap_under_get_rule(*username) - .downcast::() - .ok_or_else(|| anyhow!("account was not a string"))?; - let password = result - .find(kSecValueData as *const _) - .ok_or_else(|| anyhow!("password was missing from keychain item"))?; - let password = CFType::wrap_under_get_rule(*password) - .downcast::() - .ok_or_else(|| anyhow!("password was not a string"))?; - - Ok(Some((username.to_string(), password.bytes().to_vec()))) - } + }) } - fn delete_credentials(&self, url: &str) -> Result<()> { - let url = CFString::from(url); + fn delete_credentials(&self, url: &str) -> Task> { + let url = url.to_string(); - unsafe { - use security::*; + self.background_executor().spawn(async move { + unsafe { + use security::*; - let mut query_attrs = CFMutableDictionary::with_capacity(2); - query_attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _); - query_attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef()); + let url = CFString::from(url.as_str()); + let mut query_attrs = CFMutableDictionary::with_capacity(2); + query_attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _); + query_attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef()); - let status = SecItemDelete(query_attrs.as_concrete_TypeRef()); + let status = SecItemDelete(query_attrs.as_concrete_TypeRef()); - if status != errSecSuccess { - return Err(anyhow!("delete password failed: {}", status)); + if status != errSecSuccess { + return Err(anyhow!("delete password failed: {}", status)); + } } - } - Ok(()) + Ok(()) + }) } } diff --git a/crates/gpui/src/platform/test/platform.rs b/crates/gpui/src/platform/test/platform.rs index 9a33b4b3b58bf67591746d1c1f000089db315098..5aadc4b760aeacf9bb03ee3bef4694a4d73fea4d 100644 --- a/crates/gpui/src/platform/test/platform.rs +++ b/crates/gpui/src/platform/test/platform.rs @@ -1,6 +1,7 @@ use crate::{ AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, ForegroundExecutor, - Keymap, Platform, PlatformDisplay, PlatformTextSystem, TestDisplay, TestWindow, WindowOptions, + Keymap, Platform, PlatformDisplay, PlatformTextSystem, Task, TestDisplay, TestWindow, + WindowOptions, }; use anyhow::{anyhow, Result}; use collections::VecDeque; @@ -280,16 +281,16 @@ impl Platform for TestPlatform { self.current_clipboard_item.lock().clone() } - fn write_credentials(&self, _url: &str, _username: &str, _password: &[u8]) -> Result<()> { - Ok(()) + fn write_credentials(&self, _url: &str, _username: &str, _password: &[u8]) -> Task> { + Task::ready(Ok(())) } - fn read_credentials(&self, _url: &str) -> Result)>> { - Ok(None) + fn read_credentials(&self, _url: &str) -> Task)>>> { + Task::ready(Ok(None)) } - fn delete_credentials(&self, _url: &str) -> Result<()> { - Ok(()) + fn delete_credentials(&self, _url: &str) -> Task> { + Task::ready(Ok(())) } fn double_click_interval(&self) -> std::time::Duration { diff --git a/crates/semantic_index/src/semantic_index.rs b/crates/semantic_index/src/semantic_index.rs index a556986f9b1f9bda64706a8691cd329ead136086..62773cced8e74c59ebffadc03d4c0efa9ab47c2e 100644 --- a/crates/semantic_index/src/semantic_index.rs +++ b/crates/semantic_index/src/semantic_index.rs @@ -90,13 +90,12 @@ pub fn init( .detach(); cx.spawn(move |cx| async move { + let embedding_provider = + OpenAIEmbeddingProvider::new(http_client, cx.background_executor().clone()).await; let semantic_index = SemanticIndex::new( fs, db_file_path, - Arc::new(OpenAIEmbeddingProvider::new( - http_client, - cx.background_executor().clone(), - )), + Arc::new(embedding_provider), language_registry, cx.clone(), ) @@ -279,14 +278,22 @@ impl SemanticIndex { .map(|semantic_index| semantic_index.clone()) } - pub fn authenticate(&mut self, cx: &mut AppContext) -> bool { + pub fn authenticate(&mut self, cx: &mut AppContext) -> Task { if !self.embedding_provider.has_credentials() { - self.embedding_provider.retrieve_credentials(cx); + let embedding_provider = self.embedding_provider.clone(); + cx.spawn(|cx| async move { + if let Some(retrieve_credentials) = cx + .update(|cx| embedding_provider.retrieve_credentials(cx)) + .log_err() + { + retrieve_credentials.await; + } + + embedding_provider.has_credentials() + }) } else { - return true; + Task::ready(true) } - - self.embedding_provider.has_credentials() } pub fn is_authenticated(&self) -> bool { @@ -1006,12 +1013,26 @@ impl SemanticIndex { project: Model, cx: &mut ModelContext, ) -> Task> { - if !self.is_authenticated() { - if !self.authenticate(cx) { - return Task::ready(Err(anyhow!("user is not authenticated"))); - } + if self.is_authenticated() { + self.index_project_internal(project, cx) + } else { + let authenticate = self.authenticate(cx); + cx.spawn(|this, mut cx| async move { + if authenticate.await { + this.update(&mut cx, |this, cx| this.index_project_internal(project, cx))? + .await + } else { + Err(anyhow!("user is not authenticated")) + } + }) } + } + fn index_project_internal( + &mut self, + project: Model, + cx: &mut ModelContext, + ) -> Task> { if !self.projects.contains_key(&project.downgrade()) { let subscription = cx.subscribe(&project, |this, project, event, cx| match event { project::Event::WorktreeAdded | project::Event::WorktreeRemoved(_) => { diff --git a/crates/workspace/src/persistence/model.rs b/crates/workspace/src/persistence/model.rs index c75ea2f52f41037f13bd6241825bb9a261e36953..73ae0f9b7e877b0bc49b26238fdb5156563d6338 100644 --- a/crates/workspace/src/persistence/model.rs +++ b/crates/workspace/src/persistence/model.rs @@ -233,24 +233,28 @@ impl SerializedPane { workspace: WeakView, cx: &mut AsyncWindowContext, ) -> Result>>> { - let mut items = Vec::new(); + let mut item_tasks = Vec::new(); let mut active_item_index = None; for (index, item) in self.children.iter().enumerate() { let project = project.clone(); - let item_handle = pane - .update(cx, |_, cx| { - if let Some(deserializer) = cx.global::().get(&item.kind) { - deserializer(project, workspace.clone(), workspace_id, item.item_id, cx) - } else { - Task::ready(Err(anyhow::anyhow!( - "Deserializer does not exist for item kind: {}", - item.kind - ))) - } - })? - .await - .log_err(); + item_tasks.push(pane.update(cx, |_, cx| { + if let Some(deserializer) = cx.global::().get(&item.kind) { + deserializer(project, workspace.clone(), workspace_id, item.item_id, cx) + } else { + Task::ready(Err(anyhow::anyhow!( + "Deserializer does not exist for item kind: {}", + item.kind + ))) + } + })?); + if item.active { + active_item_index = Some(index); + } + } + let mut items = Vec::new(); + for item_handle in futures::future::join_all(item_tasks).await { + let item_handle = item_handle.log_err(); items.push(item_handle.clone()); if let Some(item_handle) = item_handle { @@ -258,10 +262,6 @@ impl SerializedPane { pane.add_item(item_handle.clone(), true, true, None, cx); })?; } - - if item.active { - active_item_index = Some(index); - } } if let Some(active_item_index) = active_item_index { diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 6486fc5db45bff24e1697aa72fc3aa0483b06155..5abb046165c224f9a11fc5f9166d46eee73235b7 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -376,7 +376,7 @@ async fn authenticate(client: Arc, cx: &AsyncAppContext) -> Result<()> { if client::IMPERSONATE_LOGIN.is_some() { client.authenticate_and_connect(false, &cx).await?; } - } else if client.has_keychain_credentials(&cx) { + } else if client.has_keychain_credentials(&cx).await { client.authenticate_and_connect(true, &cx).await?; } Ok::<_, anyhow::Error>(()) diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index b94881d1b06c7a9d9c9f3ced83c2d09b77a3bbd0..3c714ad5da131da46054703e992d071ab96dc1bb 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -2705,11 +2705,6 @@ mod tests { .unwrap() .to_vec() .into(), - Assets - .load("fonts/plex/IBMPlexSans-Regular.ttf") - .unwrap() - .to_vec() - .into(), ]) .unwrap(); let themes = ThemeRegistry::default();