mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-28 20:01:33 +00:00
Improve startup time (#4248)
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.
This commit is contained in:
commit
4665b9afb6
20 changed files with 508 additions and 439 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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
|
||||
}
|
||||
|
||||
fn save_credentials(&self, cx: &mut AppContext, credential: ProviderCredential) {
|
||||
*self.credential.write() = credential.clone();
|
||||
match credential {
|
||||
ProviderCredential::Credentials { api_key } => {
|
||||
cx.write_credentials(OPENAI_API_URL, "Bearer", api_key.as_bytes())
|
||||
.log_err();
|
||||
}
|
||||
_ => {}
|
||||
async move {
|
||||
let retrieved_credential = retrieved_credential.await;
|
||||
*self.credential.write() = retrieved_credential.clone();
|
||||
retrieved_credential
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn delete_credentials(&self, cx: &mut AppContext) {
|
||||
cx.delete_credentials(OPENAI_API_URL).log_err();
|
||||
fn save_credentials(
|
||||
&self,
|
||||
cx: &mut AppContext,
|
||||
credential: ProviderCredential,
|
||||
) -> BoxFuture<()> {
|
||||
*self.credential.write() = credential.clone();
|
||||
let credential = credential.clone();
|
||||
let write_credentials = match credential {
|
||||
ProviderCredential::Credentials { api_key } => {
|
||||
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) -> 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!(
|
||||
messages(&deserialized_conversation, cx),
|
||||
deserialized_buffer.read_with(cx, |buffer, _| buffer.text()),
|
||||
"a\nb\nc\n"
|
||||
);
|
||||
assert_eq!(
|
||||
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(),
|
||||
});
|
||||
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)))
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
})
|
||||
.boxed()
|
||||
.shared();
|
||||
.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);
|
||||
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::*;
|
||||
|
||||
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());
|
||||
// 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 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(),
|
||||
);
|
||||
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(())
|
||||
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();
|
||||
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::*;
|
||||
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);
|
||||
// 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 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);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
self.embedding_provider.has_credentials()
|
||||
embedding_provider.has_credentials()
|
||||
})
|
||||
} else {
|
||||
Task::ready(true)
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
|
Loading…
Reference in a new issue