assets/fonts/plex/IBMPlexSans-Bold.ttf 🔗
Antonio Scandurra created
Since our last measurements, startup time in Zed had regressed quite
significantly. This was due to several issues:
- We were loading IBMPlex, which we're not really using in the UI.
- Images were being parsed in the foreground, thus blocking the main
thread
- Language models (for semantic index and assistant) were being loaded
in the foreground, thus blocking the main thread
- Interaction with the keychain was blocking the main thread
In addition to these, with this pull request we will now deserialize the
items for a pane in parallel, as opposed to doing so sequentially.
All combined, when running the zed binary directly this brings startup
time from ~350ms to ~210ms on my machine.
Release Notes:
- Improved startup time.
assets/fonts/plex/IBMPlexSans-Bold.ttf | 0
assets/fonts/plex/IBMPlexSans-Italic.ttf | 0
assets/fonts/plex/IBMPlexSans-Regular.ttf | 0
assets/fonts/plex/LICENSE.txt | 93 --------
crates/ai/src/auth.rs | 14
crates/ai/src/providers/open_ai/completion.rs | 73 ++++-
crates/ai/src/providers/open_ai/embedding.rs | 78 ++++--
crates/ai/src/test.rs | 38 ++
crates/assistant/src/assistant_panel.rs | 242 ++++++++++++--------
crates/client/src/client.rs | 27 +
crates/gpui/src/app.rs | 11
crates/gpui/src/elements/img.rs | 2
crates/gpui/src/image_cache.rs | 75 +++---
crates/gpui/src/platform.rs | 8
crates/gpui/src/platform/mac/platform.rs | 185 ++++++++-------
crates/gpui/src/platform/test/platform.rs | 15
crates/semantic_index/src/semantic_index.rs | 47 ++-
crates/workspace/src/persistence/model.rs | 36 +-
crates/zed/src/main.rs | 2
crates/zed/src/zed.rs | 5
20 files changed, 510 insertions(+), 441 deletions(-)
@@ -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.
@@ -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<ProviderCredential>;
+ #[must_use]
+ fn save_credentials(
+ &self,
+ cx: &mut AppContext,
+ credential: ProviderCredential,
+ ) -> BoxFuture<()>;
+ #[must_use]
+ fn delete_credentials(&self, cx: &mut AppContext) -> BoxFuture<()>;
}
@@ -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<ProviderCredential> {
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()
}
}
@@ -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<dyn HttpClient>, executor: BackgroundExecutor) -> Self {
+ pub async fn new(client: Arc<dyn HttpClient>, 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<ProviderCredential> {
+ 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()
}
}
@@ -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<ProviderCredential> {
+ 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<ProviderCredential> {
+ 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 {
@@ -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<Workspace>,
) {
- let this = if let Some(this) = workspace.panel::<AssistantPanel>(cx) {
- if this.update(cx, |assistant, cx| {
- if !assistant.has_credentials() {
- assistant.load_credentials(cx);
- };
-
- assistant.has_credentials()
- }) {
- this
- } else {
- workspace.focus_panel::<AssistantPanel>(cx);
- return;
- }
- } else {
+ let Some(assistant) = workspace.panel::<AssistantPanel>(cx) else {
return;
};
-
let active_editor = if let Some(active_editor) = workspace
.active_item(cx)
.and_then(|item| item.act_as::<Editor>(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::<AssistantPanel>(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>) {
- 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<Self>) {
@@ -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>) {
- self.completion_provider.retrieve_credentials(cx);
+ fn load_credentials(&mut self, cx: &mut ViewContext<Self>) -> 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<Self>) {
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<LanguageRegistry>,
- cx: &mut ModelContext<Self>,
- ) -> Self {
+ cx: &mut AsyncAppContext,
+ ) -> Result<Model<Self>> {
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<dyn CompletionProvider> = 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),
@@ -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<Credentials> {
+async fn read_credentials_from_keychain(cx: &AsyncAppContext) -> Option<Credentials> {
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<Credentials> {
})
}
-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/";
@@ -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<Result<()>> {
self.platform.write_credentials(url, username, password)
}
/// Reads credentials from the platform keychain.
- pub fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>> {
+ pub fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>> {
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<Result<()>> {
self.platform.delete_credentials(url)
}
@@ -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()
@@ -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<ImageError> for Error {
pub(crate) struct ImageCache {
client: Arc<dyn HttpClient>,
- images: Arc<Mutex<HashMap<SharedUrl, FetchImageFuture>>>,
+ images: Arc<Mutex<HashMap<SharedUrl, FetchImageTask>>>,
}
-type FetchImageFuture = Shared<BoxFuture<'static, Result<Arc<ImageData>, Error>>>;
+type FetchImageTask = Shared<Task<Result<Arc<ImageData>, Error>>>;
impl ImageCache {
pub fn new(client: Arc<dyn HttpClient>) -> Self {
@@ -57,10 +54,7 @@ impl ImageCache {
}
}
- pub fn get(
- &self,
- uri: impl Into<SharedUrl>,
- ) -> Shared<BoxFuture<'static, Result<Arc<ImageData>, Error>>> {
+ pub fn get(&self, uri: impl Into<SharedUrl>, 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
@@ -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<ClipboardItem>;
- fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()>;
- fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>>;
- fn delete_credentials(&self, url: &str) -> Result<()>;
+ fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>>;
+ fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>>;
+ fn delete_credentials(&self, url: &str) -> Task<Result<()>>;
}
/// A handle to a platform's display, e.g. a monitor or laptop screen.
@@ -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<Result<()>> {
+ 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<Option<(String, Vec<u8>)>> {
- let url = CFString::from(url);
- let cf_true = CFBoolean::true_value().as_CFTypeRef();
+ Ok(())
+ })
+ }
+
+ fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>> {
+ 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::<CFDictionary>()
+ .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::<CFString>()
+ .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::<CFData>()
+ .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::<CFDictionary>()
- .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::<CFString>()
- .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::<CFData>()
- .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<Result<()>> {
+ 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(())
+ })
}
}
@@ -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<Result<()>> {
+ Task::ready(Ok(()))
}
- fn read_credentials(&self, _url: &str) -> Result<Option<(String, Vec<u8>)>> {
- Ok(None)
+ fn read_credentials(&self, _url: &str) -> Task<Result<Option<(String, Vec<u8>)>>> {
+ Task::ready(Ok(None))
}
- fn delete_credentials(&self, _url: &str) -> Result<()> {
- Ok(())
+ fn delete_credentials(&self, _url: &str) -> Task<Result<()>> {
+ Task::ready(Ok(()))
}
fn double_click_interval(&self) -> std::time::Duration {
@@ -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<bool> {
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<Project>,
cx: &mut ModelContext<Self>,
) -> Task<Result<()>> {
- 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<Project>,
+ cx: &mut ModelContext<Self>,
+ ) -> Task<Result<()>> {
if !self.projects.contains_key(&project.downgrade()) {
let subscription = cx.subscribe(&project, |this, project, event, cx| match event {
project::Event::WorktreeAdded | project::Event::WorktreeRemoved(_) => {
@@ -233,24 +233,28 @@ impl SerializedPane {
workspace: WeakView<Workspace>,
cx: &mut AsyncWindowContext,
) -> Result<Vec<Option<Box<dyn ItemHandle>>>> {
- 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::<ItemDeserializers>().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::<ItemDeserializers>().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 {
@@ -376,7 +376,7 @@ async fn authenticate(client: Arc<Client>, 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>(())
@@ -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();