Move interaction with keychain off the main thread
This commit is contained in:
parent
22046ef9a7
commit
25a7eb27d2
12 changed files with 370 additions and 214 deletions
|
@ -1,3 +1,4 @@
|
||||||
|
use futures::future::BoxFuture;
|
||||||
use gpui::AppContext;
|
use gpui::AppContext;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -9,7 +10,14 @@ pub enum ProviderCredential {
|
||||||
|
|
||||||
pub trait CredentialProvider: Send + Sync {
|
pub trait CredentialProvider: Send + Sync {
|
||||||
fn has_credentials(&self) -> bool;
|
fn has_credentials(&self) -> bool;
|
||||||
fn retrieve_credentials(&self, cx: &mut AppContext) -> ProviderCredential;
|
#[must_use]
|
||||||
fn save_credentials(&self, cx: &mut AppContext, credential: ProviderCredential);
|
fn retrieve_credentials(&self, cx: &mut AppContext) -> BoxFuture<ProviderCredential>;
|
||||||
fn delete_credentials(&self, cx: &mut AppContext);
|
#[must_use]
|
||||||
|
fn save_credentials(
|
||||||
|
&self,
|
||||||
|
cx: &mut AppContext,
|
||||||
|
credential: ProviderCredential,
|
||||||
|
) -> BoxFuture<()>;
|
||||||
|
#[must_use]
|
||||||
|
fn delete_credentials(&self, cx: &mut AppContext) -> BoxFuture<()>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -222,16 +222,19 @@ 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 existing_credential = self.credential.read().clone();
|
||||||
let retrieved_credential = match existing_credential {
|
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() {
|
if let Some(api_key) = env::var("OPENAI_API_KEY").log_err() {
|
||||||
ProviderCredential::Credentials { api_key }
|
async move { ProviderCredential::Credentials { api_key } }.boxed()
|
||||||
} else if let Some(Some((_, api_key))) =
|
} else {
|
||||||
cx.read_credentials(OPENAI_API_URL).log_err()
|
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() {
|
if let Some(api_key) = String::from_utf8(api_key).log_err() {
|
||||||
ProviderCredential::Credentials { api_key }
|
ProviderCredential::Credentials { api_key }
|
||||||
} else {
|
} else {
|
||||||
|
@ -241,26 +244,48 @@ impl CredentialProvider for OpenAICompletionProvider {
|
||||||
ProviderCredential::NoCredentials
|
ProviderCredential::NoCredentials
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async move {
|
||||||
|
let retrieved_credential = retrieved_credential.await;
|
||||||
*self.credential.write() = retrieved_credential.clone();
|
*self.credential.write() = retrieved_credential.clone();
|
||||||
retrieved_credential
|
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();
|
*self.credential.write() = credential.clone();
|
||||||
let credential = credential.clone();
|
let credential = credential.clone();
|
||||||
match credential {
|
let write_credentials = match credential {
|
||||||
ProviderCredential::Credentials { api_key } => {
|
ProviderCredential::Credentials { api_key } => {
|
||||||
cx.write_credentials(OPENAI_API_URL, "Bearer", api_key.as_bytes())
|
Some(cx.write_credentials(OPENAI_API_URL, "Bearer", api_key.as_bytes()))
|
||||||
.log_err();
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
async move {
|
||||||
|
if let Some(write_credentials) = write_credentials {
|
||||||
|
write_credentials.await.log_err();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
|
||||||
fn delete_credentials(&self, cx: &mut AppContext) {
|
fn delete_credentials(&self, cx: &mut AppContext) -> BoxFuture<()> {
|
||||||
cx.delete_credentials(OPENAI_API_URL).log_err();
|
|
||||||
*self.credential.write() = ProviderCredential::NoCredentials;
|
*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 anyhow::{anyhow, Result};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use futures::future::BoxFuture;
|
||||||
use futures::AsyncReadExt;
|
use futures::AsyncReadExt;
|
||||||
|
use futures::FutureExt;
|
||||||
use gpui::AppContext;
|
use gpui::AppContext;
|
||||||
use gpui::BackgroundExecutor;
|
use gpui::BackgroundExecutor;
|
||||||
use isahc::http::StatusCode;
|
use isahc::http::StatusCode;
|
||||||
|
@ -157,17 +159,20 @@ impl CredentialProvider for OpenAIEmbeddingProvider {
|
||||||
_ => false,
|
_ => 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 {
|
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() {
|
if let Some(api_key) = env::var("OPENAI_API_KEY").log_err() {
|
||||||
ProviderCredential::Credentials { api_key }
|
async move { ProviderCredential::Credentials { api_key } }.boxed()
|
||||||
} else if let Some(Some((_, api_key))) =
|
} else {
|
||||||
cx.read_credentials(OPENAI_API_URL).log_err()
|
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() {
|
if let Some(api_key) = String::from_utf8(api_key).log_err() {
|
||||||
ProviderCredential::Credentials { api_key }
|
ProviderCredential::Credentials { api_key }
|
||||||
} else {
|
} else {
|
||||||
|
@ -177,26 +182,48 @@ impl CredentialProvider for OpenAIEmbeddingProvider {
|
||||||
ProviderCredential::NoCredentials
|
ProviderCredential::NoCredentials
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async move {
|
||||||
|
let retrieved_credential = retrieved_credential.await;
|
||||||
*self.credential.write() = retrieved_credential.clone();
|
*self.credential.write() = retrieved_credential.clone();
|
||||||
retrieved_credential
|
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();
|
*self.credential.write() = credential.clone();
|
||||||
match credential {
|
let credential = credential.clone();
|
||||||
|
let write_credentials = match credential {
|
||||||
ProviderCredential::Credentials { api_key } => {
|
ProviderCredential::Credentials { api_key } => {
|
||||||
cx.write_credentials(OPENAI_API_URL, "Bearer", api_key.as_bytes())
|
Some(cx.write_credentials(OPENAI_API_URL, "Bearer", api_key.as_bytes()))
|
||||||
.log_err();
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
async move {
|
||||||
|
if let Some(write_credentials) = write_credentials {
|
||||||
|
write_credentials.await.log_err();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
|
||||||
fn delete_credentials(&self, cx: &mut AppContext) {
|
fn delete_credentials(&self, cx: &mut AppContext) -> BoxFuture<()> {
|
||||||
cx.delete_credentials(OPENAI_API_URL).log_err();
|
|
||||||
*self.credential.write() = ProviderCredential::NoCredentials;
|
*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 {
|
fn has_credentials(&self) -> bool {
|
||||||
true
|
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]
|
#[async_trait]
|
||||||
|
@ -165,11 +176,22 @@ impl CredentialProvider for FakeCompletionProvider {
|
||||||
fn has_credentials(&self) -> bool {
|
fn has_credentials(&self) -> bool {
|
||||||
true
|
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 {
|
impl CompletionProvider for FakeCompletionProvider {
|
||||||
|
|
|
@ -6,14 +6,12 @@ use crate::{
|
||||||
NewConversation, QuoteSelection, ResetKey, Role, SavedConversation, SavedConversationMetadata,
|
NewConversation, QuoteSelection, ResetKey, Role, SavedConversation, SavedConversationMetadata,
|
||||||
SavedMessage, Split, ToggleFocus, ToggleIncludeConversation, ToggleRetrieveContext,
|
SavedMessage, Split, ToggleFocus, ToggleIncludeConversation, ToggleRetrieveContext,
|
||||||
};
|
};
|
||||||
|
use ai::prompts::repository_context::PromptCodeSnippet;
|
||||||
use ai::{
|
use ai::{
|
||||||
auth::ProviderCredential,
|
auth::ProviderCredential,
|
||||||
completion::{CompletionProvider, CompletionRequest},
|
completion::{CompletionProvider, CompletionRequest},
|
||||||
providers::open_ai::{OpenAICompletionProvider, OpenAIRequest, RequestMessage},
|
providers::open_ai::{OpenAICompletionProvider, OpenAIRequest, RequestMessage},
|
||||||
};
|
};
|
||||||
|
|
||||||
use ai::prompts::repository_context::PromptCodeSnippet;
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use chrono::{DateTime, Local};
|
use chrono::{DateTime, Local};
|
||||||
use client::telemetry::AssistantKind;
|
use client::telemetry::AssistantKind;
|
||||||
|
@ -220,23 +218,9 @@ impl AssistantPanel {
|
||||||
_: &InlineAssist,
|
_: &InlineAssist,
|
||||||
cx: &mut ViewContext<Workspace>,
|
cx: &mut ViewContext<Workspace>,
|
||||||
) {
|
) {
|
||||||
let this = if let Some(this) = workspace.panel::<AssistantPanel>(cx) {
|
let Some(assistant) = workspace.panel::<AssistantPanel>(cx) else {
|
||||||
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 {
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let active_editor = if let Some(active_editor) = workspace
|
let active_editor = if let Some(active_editor) = workspace
|
||||||
.active_item(cx)
|
.active_item(cx)
|
||||||
.and_then(|item| item.act_as::<Editor>(cx))
|
.and_then(|item| item.act_as::<Editor>(cx))
|
||||||
|
@ -245,12 +229,32 @@ impl AssistantPanel {
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
let project = workspace.project().clone();
|
||||||
|
|
||||||
let project = workspace.project();
|
if assistant.update(cx, |assistant, _| assistant.has_credentials()) {
|
||||||
|
assistant.update(cx, |assistant, cx| {
|
||||||
this.update(cx, |assistant, cx| {
|
assistant.new_inline_assist(&active_editor, cx, &project)
|
||||||
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)
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
anyhow::Ok(())
|
||||||
|
})
|
||||||
|
.detach_and_log_err(cx)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_inline_assist(
|
fn new_inline_assist(
|
||||||
|
@ -290,9 +294,6 @@ impl AssistantPanel {
|
||||||
let inline_assist_id = post_inc(&mut self.next_inline_assist_id);
|
let inline_assist_id = post_inc(&mut self.next_inline_assist_id);
|
||||||
let provider = self.completion_provider.clone();
|
let provider = self.completion_provider.clone();
|
||||||
|
|
||||||
// Retrieve Credentials Authenticates the Provider
|
|
||||||
provider.retrieve_credentials(cx);
|
|
||||||
|
|
||||||
let codegen = cx.new_model(|cx| {
|
let codegen = cx.new_model(|cx| {
|
||||||
Codegen::new(editor.read(cx).buffer().clone(), codegen_kind, provider, cx)
|
Codegen::new(editor.read(cx).buffer().clone(), codegen_kind, provider, cx)
|
||||||
});
|
});
|
||||||
|
@ -845,11 +846,18 @@ impl AssistantPanel {
|
||||||
api_key: api_key.clone(),
|
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();
|
this.update(&mut cx, |this, cx| {
|
||||||
self.focus_handle.focus(cx);
|
this.api_key_editor.take();
|
||||||
|
this.focus_handle.focus(cx);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.detach_and_log_err(cx);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
cx.propagate();
|
cx.propagate();
|
||||||
|
@ -857,10 +865,17 @@ impl AssistantPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reset_credentials(&mut self, _: &ResetKey, cx: &mut ViewContext<Self>) {
|
fn reset_credentials(&mut self, _: &ResetKey, cx: &mut ViewContext<Self>) {
|
||||||
self.completion_provider.delete_credentials(cx);
|
let completion_provider = self.completion_provider.clone();
|
||||||
self.api_key_editor = Some(build_api_key_editor(cx));
|
cx.spawn(|this, mut cx| async move {
|
||||||
self.focus_handle.focus(cx);
|
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();
|
cx.notify();
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.detach_and_log_err(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_zoom(&mut self, _: &workspace::ToggleZoom, cx: &mut ViewContext<Self>) {
|
fn toggle_zoom(&mut self, _: &workspace::ToggleZoom, cx: &mut ViewContext<Self>) {
|
||||||
|
@ -1107,8 +1122,16 @@ impl AssistantPanel {
|
||||||
self.completion_provider.has_credentials()
|
self.completion_provider.has_credentials()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_credentials(&mut self, cx: &mut ViewContext<Self>) {
|
fn load_credentials(&mut self, cx: &mut ViewContext<Self>) -> Task<()> {
|
||||||
self.completion_provider.retrieve_credentials(cx);
|
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;
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1314,11 +1337,16 @@ impl Panel for AssistantPanel {
|
||||||
|
|
||||||
fn set_active(&mut self, active: bool, cx: &mut ViewContext<Self>) {
|
fn set_active(&mut self, active: bool, cx: &mut ViewContext<Self>) {
|
||||||
if active {
|
if active {
|
||||||
self.load_credentials(cx);
|
let load_credentials = self.load_credentials(cx);
|
||||||
|
cx.spawn(|this, mut cx| async move {
|
||||||
if self.editors.is_empty() {
|
load_credentials.await;
|
||||||
self.new_conversation(cx);
|
this.update(&mut cx, |this, cx| {
|
||||||
|
if this.editors.is_empty() {
|
||||||
|
this.new_conversation(cx);
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.detach_and_log_err(cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -700,8 +700,8 @@ impl Client {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_keychain_credentials(&self, cx: &AsyncAppContext) -> bool {
|
pub async fn has_keychain_credentials(&self, cx: &AsyncAppContext) -> bool {
|
||||||
read_credentials_from_keychain(cx).is_some()
|
read_credentials_from_keychain(cx).await.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_recursion(?Send)]
|
#[async_recursion(?Send)]
|
||||||
|
@ -732,7 +732,7 @@ impl Client {
|
||||||
let mut read_from_keychain = false;
|
let mut read_from_keychain = false;
|
||||||
let mut credentials = self.state.read().credentials.clone();
|
let mut credentials = self.state.read().credentials.clone();
|
||||||
if credentials.is_none() && try_keychain {
|
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();
|
read_from_keychain = credentials.is_some();
|
||||||
}
|
}
|
||||||
if credentials.is_none() {
|
if credentials.is_none() {
|
||||||
|
@ -770,7 +770,7 @@ impl Client {
|
||||||
Ok(conn) => {
|
Ok(conn) => {
|
||||||
self.state.write().credentials = Some(credentials.clone());
|
self.state.write().credentials = Some(credentials.clone());
|
||||||
if !read_from_keychain && IMPERSONATE_LOGIN.is_none() {
|
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! {
|
futures::select_biased! {
|
||||||
|
@ -784,7 +784,7 @@ impl Client {
|
||||||
Err(EstablishConnectionError::Unauthorized) => {
|
Err(EstablishConnectionError::Unauthorized) => {
|
||||||
self.state.write().credentials.take();
|
self.state.write().credentials.take();
|
||||||
if read_from_keychain {
|
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.set_status(Status::SignedOut, cx);
|
||||||
self.authenticate_and_connect(false, cx).await
|
self.authenticate_and_connect(false, cx).await
|
||||||
} else {
|
} 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() {
|
if IMPERSONATE_LOGIN.is_some() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let (user_id, access_token) = cx
|
let (user_id, access_token) = cx
|
||||||
.update(|cx| cx.read_credentials(&ZED_SERVER_URL).log_err().flatten())
|
.update(|cx| cx.read_credentials(&ZED_SERVER_URL))
|
||||||
.ok()??;
|
.log_err()?
|
||||||
|
.await
|
||||||
|
.log_err()??;
|
||||||
|
|
||||||
Some(Credentials {
|
Some(Credentials {
|
||||||
user_id: user_id.parse().ok()?,
|
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.update(move |cx| {
|
||||||
cx.write_credentials(
|
cx.write_credentials(
|
||||||
&ZED_SERVER_URL,
|
&ZED_SERVER_URL,
|
||||||
|
@ -1373,10 +1378,12 @@ fn write_credentials_to_keychain(credentials: Credentials, cx: &AsyncAppContext)
|
||||||
credentials.access_token.as_bytes(),
|
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))?
|
cx.update(move |cx| cx.delete_credentials(&ZED_SERVER_URL))?
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
const WORKTREE_URL_PREFIX: &str = "zed://worktrees/";
|
const WORKTREE_URL_PREFIX: &str = "zed://worktrees/";
|
||||||
|
|
|
@ -523,17 +523,22 @@ impl AppContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Writes credentials to the platform keychain.
|
/// 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)
|
self.platform.write_credentials(url, username, password)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads credentials from the platform keychain.
|
/// 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)
|
self.platform.read_credentials(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deletes credentials from the platform keychain.
|
/// 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)
|
self.platform.delete_credentials(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ use crate::{
|
||||||
Action, AnyWindowHandle, AsyncWindowContext, BackgroundExecutor, Bounds, DevicePixels, Font,
|
Action, AnyWindowHandle, AsyncWindowContext, BackgroundExecutor, Bounds, DevicePixels, Font,
|
||||||
FontId, FontMetrics, FontRun, ForegroundExecutor, GlobalPixels, GlyphId, Keymap, LineLayout,
|
FontId, FontMetrics, FontRun, ForegroundExecutor, GlobalPixels, GlyphId, Keymap, LineLayout,
|
||||||
Pixels, PlatformInput, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result,
|
Pixels, PlatformInput, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result,
|
||||||
Scene, SharedString, Size, TaskLabel, WindowContext,
|
Scene, SharedString, Size, Task, TaskLabel, WindowContext,
|
||||||
};
|
};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use async_task::Runnable;
|
use async_task::Runnable;
|
||||||
|
@ -108,9 +108,9 @@ pub(crate) trait Platform: 'static {
|
||||||
fn write_to_clipboard(&self, item: ClipboardItem);
|
fn write_to_clipboard(&self, item: ClipboardItem);
|
||||||
fn read_from_clipboard(&self) -> Option<ClipboardItem>;
|
fn read_from_clipboard(&self) -> Option<ClipboardItem>;
|
||||||
|
|
||||||
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()>;
|
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>>;
|
||||||
fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>>;
|
fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>>;
|
||||||
fn delete_credentials(&self, url: &str) -> Result<()>;
|
fn delete_credentials(&self, url: &str) -> Task<Result<()>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A handle to a platform's display, e.g. a monitor or laptop screen.
|
/// 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,
|
Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
|
||||||
ForegroundExecutor, Keymap, MacDispatcher, MacDisplay, MacDisplayLinker, MacTextSystem,
|
ForegroundExecutor, Keymap, MacDispatcher, MacDisplay, MacDisplayLinker, MacTextSystem,
|
||||||
MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay, PlatformInput,
|
MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay, PlatformInput,
|
||||||
PlatformTextSystem, PlatformWindow, Result, SemanticVersion, WindowOptions,
|
PlatformTextSystem, PlatformWindow, Result, SemanticVersion, Task, WindowOptions,
|
||||||
};
|
};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use block::ConcreteBlock;
|
use block::ConcreteBlock;
|
||||||
|
@ -856,14 +856,18 @@ impl Platform for MacPlatform {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> {
|
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>> {
|
||||||
let url = CFString::from(url);
|
let url = url.to_string();
|
||||||
let username = CFString::from(username);
|
let username = username.to_string();
|
||||||
let password = CFData::from_buffer(password);
|
let password = password.to_vec();
|
||||||
|
self.background_executor().spawn(async move {
|
||||||
unsafe {
|
unsafe {
|
||||||
use security::*;
|
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
|
// First, check if there are already credentials for the given server. If so, then
|
||||||
// update the username and password.
|
// update the username and password.
|
||||||
let mut verb = "updating";
|
let mut verb = "updating";
|
||||||
|
@ -893,10 +897,13 @@ impl Platform for MacPlatform {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>> {
|
fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>> {
|
||||||
let url = CFString::from(url);
|
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();
|
let cf_true = CFBoolean::true_value().as_CFTypeRef();
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -935,14 +942,17 @@ impl Platform for MacPlatform {
|
||||||
|
|
||||||
Ok(Some((username.to_string(), password.bytes().to_vec())))
|
Ok(Some((username.to_string(), password.bytes().to_vec())))
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete_credentials(&self, url: &str) -> Result<()> {
|
fn delete_credentials(&self, url: &str) -> Task<Result<()>> {
|
||||||
let url = CFString::from(url);
|
let url = url.to_string();
|
||||||
|
|
||||||
|
self.background_executor().spawn(async move {
|
||||||
unsafe {
|
unsafe {
|
||||||
use security::*;
|
use security::*;
|
||||||
|
|
||||||
|
let url = CFString::from(url.as_str());
|
||||||
let mut query_attrs = CFMutableDictionary::with_capacity(2);
|
let mut query_attrs = CFMutableDictionary::with_capacity(2);
|
||||||
query_attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
|
query_attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
|
||||||
query_attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
|
query_attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
|
||||||
|
@ -954,6 +964,7 @@ impl Platform for MacPlatform {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, ForegroundExecutor,
|
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 anyhow::{anyhow, Result};
|
||||||
use collections::VecDeque;
|
use collections::VecDeque;
|
||||||
|
@ -280,16 +281,16 @@ impl Platform for TestPlatform {
|
||||||
self.current_clipboard_item.lock().clone()
|
self.current_clipboard_item.lock().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_credentials(&self, _url: &str, _username: &str, _password: &[u8]) -> Result<()> {
|
fn write_credentials(&self, _url: &str, _username: &str, _password: &[u8]) -> Task<Result<()>> {
|
||||||
Ok(())
|
Task::ready(Ok(()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_credentials(&self, _url: &str) -> Result<Option<(String, Vec<u8>)>> {
|
fn read_credentials(&self, _url: &str) -> Task<Result<Option<(String, Vec<u8>)>>> {
|
||||||
Ok(None)
|
Task::ready(Ok(None))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete_credentials(&self, _url: &str) -> Result<()> {
|
fn delete_credentials(&self, _url: &str) -> Task<Result<()>> {
|
||||||
Ok(())
|
Task::ready(Ok(()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn double_click_interval(&self) -> std::time::Duration {
|
fn double_click_interval(&self) -> std::time::Duration {
|
||||||
|
|
|
@ -278,14 +278,22 @@ impl SemanticIndex {
|
||||||
.map(|semantic_index| semantic_index.clone())
|
.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() {
|
if !self.embedding_provider.has_credentials() {
|
||||||
self.embedding_provider.retrieve_credentials(cx);
|
let embedding_provider = self.embedding_provider.clone();
|
||||||
} else {
|
cx.spawn(|cx| async move {
|
||||||
return true;
|
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 {
|
pub fn is_authenticated(&self) -> bool {
|
||||||
|
@ -1005,12 +1013,26 @@ impl SemanticIndex {
|
||||||
project: Model<Project>,
|
project: Model<Project>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<()>> {
|
) -> Task<Result<()>> {
|
||||||
if !self.is_authenticated() {
|
if self.is_authenticated() {
|
||||||
if !self.authenticate(cx) {
|
self.index_project_internal(project, cx)
|
||||||
return Task::ready(Err(anyhow!("user is not authenticated")));
|
} 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()) {
|
if !self.projects.contains_key(&project.downgrade()) {
|
||||||
let subscription = cx.subscribe(&project, |this, project, event, cx| match event {
|
let subscription = cx.subscribe(&project, |this, project, event, cx| match event {
|
||||||
project::Event::WorktreeAdded | project::Event::WorktreeRemoved(_) => {
|
project::Event::WorktreeAdded | project::Event::WorktreeRemoved(_) => {
|
||||||
|
|
|
@ -376,7 +376,7 @@ async fn authenticate(client: Arc<Client>, cx: &AsyncAppContext) -> Result<()> {
|
||||||
if client::IMPERSONATE_LOGIN.is_some() {
|
if client::IMPERSONATE_LOGIN.is_some() {
|
||||||
client.authenticate_and_connect(false, &cx).await?;
|
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?;
|
client.authenticate_and_connect(true, &cx).await?;
|
||||||
}
|
}
|
||||||
Ok::<_, anyhow::Error>(())
|
Ok::<_, anyhow::Error>(())
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue