mod copilot_edit_prediction_delegate;pub mod request; use crate::request::{ DidFocus, DidFocusParams, FormattingOptions, InlineCompletionContext, InlineCompletionTriggerKind, InlineCompletions, NextEditSuggestions,};use ::fs::Fs;use anyhow::{Context as _, Result, anyhow};use collections::{HashMap, HashSet};use command_palette_hooks::CommandPaletteFilter;use futures::future;use futures::{Future, FutureExt, TryFutureExt, channel::oneshot, future::Shared, select_biased};use gpui::{ App, AppContext as _, AsyncApp, Context, Entity, EntityId, EventEmitter, Global, Subscription, Task, WeakEntity, actions,};use language::language_settings::{AllLanguageSettings, CopilotSettings};use language::{ Anchor, Bias, Buffer, BufferSnapshot, Language, PointUtf16, ToPointUtf16, language_settings::{EditPredictionProvider, all_language_settings}, point_from_lsp, point_to_lsp,};use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId, LanguageServerName};use node_runtime::{NodeRuntime, VersionStrategy};use parking_lot::Mutex;use project::project_settings::ProjectSettings;use project::{DisableAiSettings, Project};use request::DidChangeStatus;use serde_json::json;use settings::{Settings, SettingsStore};use std::{ any::TypeId, collections::hash_map::Entry, env, ffi::OsString, mem, ops::Range, path::{Path, PathBuf}, sync::Arc,};use sum_tree::Dimensions;use util::{ResultExt, fs::remove_matching};use workspace::AppState; pub use crate::copilot_edit_prediction_delegate::CopilotEditPredictionDelegate; actions!( copilot, [ /// Requests a code completion suggestion from Copilot. Suggest, /// Cycles to the next Copilot suggestion. NextSuggestion, /// Cycles to the previous Copilot suggestion. PreviousSuggestion, /// Reinstalls the Copilot language server. Reinstall, /// Signs in to GitHub Copilot. SignIn, /// Signs out of GitHub Copilot. SignOut ]); enum CopilotServer { Disabled, Starting { task: Shared<Task<()>> }, Error(Arc<str>), Running(RunningCopilotServer),} impl CopilotServer { fn as_authenticated(&mut self) -> Result<&mut RunningCopilotServer> { let server = self.as_running()?; anyhow::ensure!( matches!(server.sign_in_status, SignInStatus::Authorized), "must sign in before using copilot" ); Ok(server) } fn as_running(&mut self) -> Result<&mut RunningCopilotServer> { match self { CopilotServer::Starting { .. } => anyhow::bail!("copilot is still starting"), CopilotServer::Disabled => anyhow::bail!("copilot is disabled"), CopilotServer::Error(error) => { anyhow::bail!("copilot was not started because of an error: {error}") } CopilotServer::Running(server) => Ok(server), } }} struct RunningCopilotServer { lsp: Arc<LanguageServer>, sign_in_status: SignInStatus, registered_buffers: HashMap<EntityId, RegisteredBuffer>,} #[derive(Clone, Debug)]enum SignInStatus { Authorized, Unauthorized, SigningIn { prompt: Option<request::PromptUserDeviceFlow>, task: Shared<Task<Result<(), Arc<anyhow::Error>>>>, }, SignedOut { awaiting_signing_in: bool, },} #[derive(Debug, Clone)]pub enum Status { Starting { task: Shared<Task<()>>, }, Error(Arc<str>), Disabled, SignedOut { awaiting_signing_in: bool, }, SigningIn { prompt: Option<request::PromptUserDeviceFlow>, }, Unauthorized, Authorized,} impl Status { pub fn is_authorized(&self) -> bool { matches!(self, Status::Authorized) } pub fn is_configured(&self) -> bool { matches!( self, Status::Starting { .. } | Status::Error(_) | Status::SigningIn { .. } | Status::Authorized ) }} struct RegisteredBuffer { uri: lsp::Uri, language_id: String, snapshot: BufferSnapshot, snapshot_version: i32, _subscriptions: [gpui::Subscription; 2], pending_buffer_change: Task<Option<()>>,} impl RegisteredBuffer { fn report_changes( &mut self, buffer: &Entity<Buffer>, cx: &mut Context<Copilot>, ) -> oneshot::Receiver<(i32, BufferSnapshot)> { let (done_tx, done_rx) = oneshot::channel(); if buffer.read(cx).version() == self.snapshot.version { let _ = done_tx.send((self.snapshot_version, self.snapshot.clone())); } else { let buffer = buffer.downgrade(); let id = buffer.entity_id(); let prev_pending_change = mem::replace(&mut self.pending_buffer_change, Task::ready(None)); self.pending_buffer_change = cx.spawn(async move |copilot, cx| { prev_pending_change.await; let old_version = copilot .update(cx, |copilot, _| { let server = copilot.server.as_authenticated().log_err()?; let buffer = server.registered_buffers.get_mut(&id)?; Some(buffer.snapshot.version.clone()) }) .ok()??; let new_snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot()).ok()?; let content_changes = cx .background_spawn({ let new_snapshot = new_snapshot.clone(); async move { new_snapshot .edits_since::<Dimensions<PointUtf16, usize>>(&old_version) .map(|edit| { let edit_start = edit.new.start.0; let edit_end = edit_start + (edit.old.end.0 - edit.old.start.0); let new_text = new_snapshot .text_for_range(edit.new.start.1..edit.new.end.1) .collect(); lsp::TextDocumentContentChangeEvent { range: Some(lsp::Range::new( point_to_lsp(edit_start), point_to_lsp(edit_end), )), range_length: None, text: new_text, } }) .collect::<Vec<_>>() } }) .await; copilot .update(cx, |copilot, _| { let server = copilot.server.as_authenticated().log_err()?; let buffer = server.registered_buffers.get_mut(&id)?; if !content_changes.is_empty() { buffer.snapshot_version += 1; buffer.snapshot = new_snapshot; server .lsp .notify::<lsp::notification::DidChangeTextDocument>( lsp::DidChangeTextDocumentParams { text_document: lsp::VersionedTextDocumentIdentifier::new( buffer.uri.clone(), buffer.snapshot_version, ), content_changes, }, ) .ok(); } let _ = done_tx.send((buffer.snapshot_version, buffer.snapshot.clone())); Some(()) }) .ok()?; Some(()) }); } done_rx }} #[derive(Debug)]pub struct Completion { pub uuid: String, pub range: Range<Anchor>, pub text: String,} pub struct Copilot { fs: Arc<dyn Fs>, node_runtime: NodeRuntime, server: CopilotServer, buffers: HashSet<WeakEntity<Buffer>>, server_id: LanguageServerId, _subscriptions: Vec<Subscription>,} pub enum Event { CopilotAuthSignedIn, CopilotAuthSignedOut,} impl EventEmitter<Event> for Copilot {} #[derive(Clone)]pub struct GlobalCopilotAuth(pub Entity<Copilot>); impl GlobalCopilotAuth { pub fn set_global( server_id: LanguageServerId, fs: Arc<dyn Fs>, node_runtime: NodeRuntime, cx: &mut App, ) -> GlobalCopilotAuth { let auth = GlobalCopilotAuth(cx.new(|cx| Copilot::new(None, server_id, fs, node_runtime, cx))); cx.set_global(auth.clone()); auth } pub fn try_global(cx: &mut App) -> Option<&GlobalCopilotAuth> { cx.try_global() } pub fn try_get_or_init(app_state: Arc<AppState>, cx: &mut App) -> Option<GlobalCopilotAuth> { let ai_enabled = !DisableAiSettings::get(None, cx).disable_ai; if let Some(copilot) = cx.try_global::<Self>().cloned() { if ai_enabled { Some(copilot) } else { cx.remove_global::<Self>(); None } } else if ai_enabled { Some(Self::set_global( app_state.languages.next_language_server_id(), app_state.fs.clone(), app_state.node_runtime.clone(), cx, )) } else { None } }}impl Global for GlobalCopilotAuth {} #[derive(Clone, Copy, Debug, PartialEq, Eq)]pub(crate) enum CompletionSource { NextEditSuggestion, InlineCompletion,} /// Copilot's NextEditSuggestion response, with coordinates converted to Anchors.#[derive(Clone)]pub(crate) struct CopilotEditPrediction { pub(crate) buffer: Entity<Buffer>, pub(crate) range: Range<Anchor>, pub(crate) text: String, pub(crate) command: Option<lsp::Command>, pub(crate) snapshot: BufferSnapshot, pub(crate) source: CompletionSource,} impl Copilot { pub fn new( project: Option<Entity<Project>>, new_server_id: LanguageServerId, fs: Arc<dyn Fs>, node_runtime: NodeRuntime, cx: &mut Context<Self>, ) -> Self { let send_focus_notification = project.map(|project| { cx.subscribe(&project, |this, project, e: &project::Event, cx| { if let project::Event::ActiveEntryChanged(new_entry) = e && let Ok(running) = this.server.as_authenticated() { let uri = new_entry .and_then(|id| project.read(cx).path_for_entry(id, cx)) .and_then(|entry| project.read(cx).absolute_path(&entry, cx)) .and_then(|abs_path| lsp::Uri::from_file_path(abs_path).ok()); _ = running.lsp.notify::<DidFocus>(DidFocusParams { uri }); } }) }); let global_authentication_events = cx.try_global::<GlobalCopilotAuth>().cloned().map(|auth| { cx.subscribe(&auth.0, |_, _, _: &Event, cx| { let request_timeout = ProjectSettings::get_global(cx) .global_lsp_settings .get_request_timeout(); cx.spawn(async move |this, cx| { let Some(server) = this .update(cx, |this, _| this.language_server().cloned()) .ok() .flatten() else { return; }; let status = server .request::<request::CheckStatus>( request::CheckStatusParams { local_checks_only: false, }, request_timeout, ) .await .into_response() .ok(); if let Some(status) = status { this.update(cx, |copilot, cx| { copilot.update_sign_in_status(status, cx); }) .ok(); } }) .detach() }) }); let _subscriptions = std::iter::once(cx.on_app_quit(Self::shutdown_language_server)) .chain(send_focus_notification) .chain(global_authentication_events) .collect(); let mut this = Self { server_id: new_server_id, fs, node_runtime, server: CopilotServer::Disabled, buffers: Default::default(), _subscriptions, }; this.start_copilot(true, false, cx); cx.observe_global::<SettingsStore>(move |this, cx| { let ai_disabled = DisableAiSettings::get_global(cx).disable_ai; if ai_disabled { // Stop the server if AI is disabled if !matches!(this.server, CopilotServer::Disabled) { let shutdown = match mem::replace(&mut this.server, CopilotServer::Disabled) { CopilotServer::Running(server) => { let shutdown_future = server.lsp.shutdown(); Some(cx.background_spawn(async move { if let Some(fut) = shutdown_future { fut.await; } })) } _ => None, }; if let Some(task) = shutdown { task.detach(); } cx.notify(); } } else { // Only start if AI is enabled this.start_copilot(true, false, cx); if let Ok(server) = this.server.as_running() { notify_did_change_config_to_server(&server.lsp, cx) .context("copilot setting change: did change configuration") .log_err(); } } this.update_action_visibilities(cx); }) .detach(); cx.observe_self(|copilot, cx| { copilot.update_action_visibilities(cx); }) .detach(); this } fn shutdown_language_server( &mut self, _cx: &mut Context<Self>, ) -> impl Future<Output = ()> + use<> { let shutdown = match mem::replace(&mut self.server, CopilotServer::Disabled) { CopilotServer::Running(server) => Some(Box::pin(async move { server.lsp.shutdown() })), _ => None, }; async move { if let Some(shutdown) = shutdown { shutdown.await; } } } pub fn start_copilot( &mut self, check_edit_prediction_provider: bool, awaiting_sign_in_after_start: bool, cx: &mut Context<Self>, ) { if DisableAiSettings::get_global(cx).disable_ai { return; } if !matches!(self.server, CopilotServer::Disabled) { return; } let language_settings = all_language_settings(None, cx); if check_edit_prediction_provider && language_settings.edit_predictions.provider != EditPredictionProvider::Copilot { return; } let server_id = self.server_id; let fs = self.fs.clone(); let node_runtime = self.node_runtime.clone(); let env = self.build_env(&language_settings.edit_predictions.copilot); let start_task = cx .spawn(async move |this, cx| { Self::start_language_server( server_id, fs, node_runtime, env, this, awaiting_sign_in_after_start, cx, ) .await }) .shared(); self.server = CopilotServer::Starting { task: start_task }; cx.notify(); } fn build_env(&self, copilot_settings: &CopilotSettings) -> Option<HashMap<String, String>> { let proxy_url = copilot_settings.proxy.clone()?; let no_verify = copilot_settings.proxy_no_verify; let http_or_https_proxy = if proxy_url.starts_with("http:") { Some("HTTP_PROXY") } else if proxy_url.starts_with("https:") { Some("HTTPS_PROXY") } else { log::error!( "Unsupported protocol scheme for language server proxy (must be http or https)"Lines 1–500 of 1901. Load more
mod copilot_edit_prediction_delegate; pub mod request; use crate::request::{ DidFocus, DidFocusParams, FormattingOptions, InlineCompletionContext, InlineCompletionTriggerKind, InlineCompletions, NextEditSuggestions, }; use ::fs::Fs; use anyhow::{Context as _, Result, anyhow}; use collections::{HashMap, HashSet}; use command_palette_hooks::CommandPaletteFilter; use futures::future; use futures::{Future, FutureExt, TryFutureExt, channel::oneshot, future::Shared, select_biased}; use gpui::{ App, AppContext as _, AsyncApp, Context, Entity, EntityId, EventEmitter, Global, Subscription, Task, WeakEntity, actions, }; use language::language_settings::{AllLanguageSettings, CopilotSettings}; use language::{ Anchor, Bias, Buffer, BufferSnapshot, Language, PointUtf16, ToPointUtf16, language_settings::{EditPredictionProvider, all_language_settings}, point_from_lsp, point_to_lsp, }; use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId, LanguageServerName}; use node_runtime::{NodeRuntime, VersionStrategy}; use parking_lot::Mutex; use project::project_settings::ProjectSettings; use project::{DisableAiSettings, Project}; use request::DidChangeStatus; use serde_json::json; use settings::{Settings, SettingsStore}; use std::{ any::TypeId, collections::hash_map::Entry, env, ffi::OsString, mem, ops::Range, path::{Path, PathBuf}, sync::Arc, }; use sum_tree::Dimensions; use util::{ResultExt, fs::remove_matching}; use workspace::AppState; pub use crate::copilot_edit_prediction_delegate::CopilotEditPredictionDelegate; actions!( copilot, [ /// Requests a code completion suggestion from Copilot. Suggest, /// Cycles to the next Copilot suggestion. NextSuggestion, /// Cycles to the previous Copilot suggestion. PreviousSuggestion, /// Reinstalls the Copilot language server. Reinstall, /// Signs in to GitHub Copilot. SignIn, /// Signs out of GitHub Copilot. SignOut ] ); enum CopilotServer { Disabled, Starting { task: Shared<Task<()>> }, Error(Arc<str>), Running(RunningCopilotServer), } impl CopilotServer { fn as_authenticated(&mut self) -> Result<&mut RunningCopilotServer> { let server = self.as_running()?; anyhow::ensure!( matches!(server.sign_in_status, SignInStatus::Authorized), "must sign in before using copilot" ); Ok(server) } fn as_running(&mut self) -> Result<&mut RunningCopilotServer> { match self { CopilotServer::Starting { .. } => anyhow::bail!("copilot is still starting"), CopilotServer::Disabled => anyhow::bail!("copilot is disabled"), CopilotServer::Error(error) => { anyhow::bail!("copilot was not started because of an error: {error}") } CopilotServer::Running(server) => Ok(server), } } } struct RunningCopilotServer { lsp: Arc<LanguageServer>, sign_in_status: SignInStatus, registered_buffers: HashMap<EntityId, RegisteredBuffer>, } #[derive(Clone, Debug)] enum SignInStatus { Authorized, Unauthorized, SigningIn { prompt: Option<request::PromptUserDeviceFlow>, task: Shared<Task<Result<(), Arc<anyhow::Error>>>>, }, SignedOut { awaiting_signing_in: bool, }, } #[derive(Debug, Clone)] pub enum Status { Starting { task: Shared<Task<()>>, }, Error(Arc<str>), Disabled, SignedOut { awaiting_signing_in: bool, }, SigningIn { prompt: Option<request::PromptUserDeviceFlow>, }, Unauthorized, Authorized, } impl Status { pub fn is_authorized(&self) -> bool { matches!(self, Status::Authorized) } pub fn is_configured(&self) -> bool { matches!( self, Status::Starting { .. } | Status::Error(_) | Status::SigningIn { .. } | Status::Authorized ) } } struct RegisteredBuffer { uri: lsp::Uri, language_id: String, snapshot: BufferSnapshot, snapshot_version: i32, _subscriptions: [gpui::Subscription; 2], pending_buffer_change: Task<Option<()>>, } impl RegisteredBuffer { fn report_changes( &mut self, buffer: &Entity<Buffer>, cx: &mut Context<Copilot>, ) -> oneshot::Receiver<(i32, BufferSnapshot)> { let (done_tx, done_rx) = oneshot::channel(); if buffer.read(cx).version() == self.snapshot.version { let _ = done_tx.send((self.snapshot_version, self.snapshot.clone())); } else { let buffer = buffer.downgrade(); let id = buffer.entity_id(); let prev_pending_change = mem::replace(&mut self.pending_buffer_change, Task::ready(None)); self.pending_buffer_change = cx.spawn(async move |copilot, cx| { prev_pending_change.await; let old_version = copilot .update(cx, |copilot, _| { let server = copilot.server.as_authenticated().log_err()?; let buffer = server.registered_buffers.get_mut(&id)?; Some(buffer.snapshot.version.clone()) }) .ok()??; let new_snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot()).ok()?; let content_changes = cx .background_spawn({ let new_snapshot = new_snapshot.clone(); async move { new_snapshot .edits_since::<Dimensions<PointUtf16, usize>>(&old_version) .map(|edit| { let edit_start = edit.new.start.0; let edit_end = edit_start + (edit.old.end.0 - edit.old.start.0); let new_text = new_snapshot .text_for_range(edit.new.start.1..edit.new.end.1) .collect(); lsp::TextDocumentContentChangeEvent { range: Some(lsp::Range::new( point_to_lsp(edit_start), point_to_lsp(edit_end), )), range_length: None, text: new_text, } }) .collect::<Vec<_>>() } }) .await; copilot .update(cx, |copilot, _| { let server = copilot.server.as_authenticated().log_err()?; let buffer = server.registered_buffers.get_mut(&id)?; if !content_changes.is_empty() { buffer.snapshot_version += 1; buffer.snapshot = new_snapshot; server .lsp .notify::<lsp::notification::DidChangeTextDocument>( lsp::DidChangeTextDocumentParams { text_document: lsp::VersionedTextDocumentIdentifier::new( buffer.uri.clone(), buffer.snapshot_version, ), content_changes, }, ) .ok(); } let _ = done_tx.send((buffer.snapshot_version, buffer.snapshot.clone())); Some(()) }) .ok()?; Some(()) }); } done_rx } } #[derive(Debug)] pub struct Completion { pub uuid: String, pub range: Range<Anchor>, pub text: String, } pub struct Copilot { fs: Arc<dyn Fs>, node_runtime: NodeRuntime, server: CopilotServer, buffers: HashSet<WeakEntity<Buffer>>, server_id: LanguageServerId, _subscriptions: Vec<Subscription>, } pub enum Event { CopilotAuthSignedIn, CopilotAuthSignedOut, } impl EventEmitter<Event> for Copilot {} #[derive(Clone)] pub struct GlobalCopilotAuth(pub Entity<Copilot>); impl GlobalCopilotAuth { pub fn set_global( server_id: LanguageServerId, fs: Arc<dyn Fs>, node_runtime: NodeRuntime, cx: &mut App, ) -> GlobalCopilotAuth { let auth = GlobalCopilotAuth(cx.new(|cx| Copilot::new(None, server_id, fs, node_runtime, cx))); cx.set_global(auth.clone()); auth } pub fn try_global(cx: &mut App) -> Option<&GlobalCopilotAuth> { cx.try_global() } pub fn try_get_or_init(app_state: Arc<AppState>, cx: &mut App) -> Option<GlobalCopilotAuth> { let ai_enabled = !DisableAiSettings::get(None, cx).disable_ai; if let Some(copilot) = cx.try_global::<Self>().cloned() { if ai_enabled { Some(copilot) } else { cx.remove_global::<Self>(); None } } else if ai_enabled { Some(Self::set_global( app_state.languages.next_language_server_id(), app_state.fs.clone(), app_state.node_runtime.clone(), cx, )) } else { None } } } impl Global for GlobalCopilotAuth {} #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub(crate) enum CompletionSource { NextEditSuggestion, InlineCompletion, } /// Copilot's NextEditSuggestion response, with coordinates converted to Anchors. #[derive(Clone)] pub(crate) struct CopilotEditPrediction { pub(crate) buffer: Entity<Buffer>, pub(crate) range: Range<Anchor>, pub(crate) text: String, pub(crate) command: Option<lsp::Command>, pub(crate) snapshot: BufferSnapshot, pub(crate) source: CompletionSource, } impl Copilot { pub fn new( project: Option<Entity<Project>>, new_server_id: LanguageServerId, fs: Arc<dyn Fs>, node_runtime: NodeRuntime, cx: &mut Context<Self>, ) -> Self { let send_focus_notification = project.map(|project| { cx.subscribe(&project, |this, project, e: &project::Event, cx| { if let project::Event::ActiveEntryChanged(new_entry) = e && let Ok(running) = this.server.as_authenticated() { let uri = new_entry .and_then(|id| project.read(cx).path_for_entry(id, cx)) .and_then(|entry| project.read(cx).absolute_path(&entry, cx)) .and_then(|abs_path| lsp::Uri::from_file_path(abs_path).ok()); _ = running.lsp.notify::<DidFocus>(DidFocusParams { uri }); } }) }); let global_authentication_events = cx.try_global::<GlobalCopilotAuth>().cloned().map(|auth| { cx.subscribe(&auth.0, |_, _, _: &Event, cx| { let request_timeout = ProjectSettings::get_global(cx) .global_lsp_settings .get_request_timeout(); cx.spawn(async move |this, cx| { let Some(server) = this .update(cx, |this, _| this.language_server().cloned()) .ok() .flatten() else { return; }; let status = server .request::<request::CheckStatus>( request::CheckStatusParams { local_checks_only: false, }, request_timeout, ) .await .into_response() .ok(); if let Some(status) = status { this.update(cx, |copilot, cx| { copilot.update_sign_in_status(status, cx); }) .ok(); } }) .detach() }) }); let _subscriptions = std::iter::once(cx.on_app_quit(Self::shutdown_language_server)) .chain(send_focus_notification) .chain(global_authentication_events) .collect(); let mut this = Self { server_id: new_server_id, fs, node_runtime, server: CopilotServer::Disabled, buffers: Default::default(), _subscriptions, }; this.start_copilot(true, false, cx); cx.observe_global::<SettingsStore>(move |this, cx| { let ai_disabled = DisableAiSettings::get_global(cx).disable_ai; if ai_disabled { // Stop the server if AI is disabled if !matches!(this.server, CopilotServer::Disabled) { let shutdown = match mem::replace(&mut this.server, CopilotServer::Disabled) { CopilotServer::Running(server) => { let shutdown_future = server.lsp.shutdown(); Some(cx.background_spawn(async move { if let Some(fut) = shutdown_future { fut.await; } })) } _ => None, }; if let Some(task) = shutdown { task.detach(); } cx.notify(); } } else { // Only start if AI is enabled this.start_copilot(true, false, cx); if let Ok(server) = this.server.as_running() { notify_did_change_config_to_server(&server.lsp, cx) .context("copilot setting change: did change configuration") .log_err(); } } this.update_action_visibilities(cx); }) .detach(); cx.observe_self(|copilot, cx| { copilot.update_action_visibilities(cx); }) .detach(); this } fn shutdown_language_server( &mut self, _cx: &mut Context<Self>, ) -> impl Future<Output = ()> + use<> { let shutdown = match mem::replace(&mut self.server, CopilotServer::Disabled) { CopilotServer::Running(server) => Some(Box::pin(async move { server.lsp.shutdown() })), _ => None, }; async move { if let Some(shutdown) = shutdown { shutdown.await; } } } pub fn start_copilot( &mut self, check_edit_prediction_provider: bool, awaiting_sign_in_after_start: bool, cx: &mut Context<Self>, ) { if DisableAiSettings::get_global(cx).disable_ai { return; } if !matches!(self.server, CopilotServer::Disabled) { return; } let language_settings = all_language_settings(None, cx); if check_edit_prediction_provider && language_settings.edit_predictions.provider != EditPredictionProvider::Copilot { return; } let server_id = self.server_id; let fs = self.fs.clone(); let node_runtime = self.node_runtime.clone(); let env = self.build_env(&language_settings.edit_predictions.copilot); let start_task = cx .spawn(async move |this, cx| { Self::start_language_server( server_id, fs, node_runtime, env, this, awaiting_sign_in_after_start, cx, ) .await }) .shared(); self.server = CopilotServer::Starting { task: start_task }; cx.notify(); } fn build_env(&self, copilot_settings: &CopilotSettings) -> Option<HashMap<String, String>> { let proxy_url = copilot_settings.proxy.clone()?; let no_verify = copilot_settings.proxy_no_verify; let http_or_https_proxy = if proxy_url.starts_with("http:") { Some("HTTP_PROXY") } else if proxy_url.starts_with("https:") { Some("HTTPS_PROXY") } else { log::error!( "Unsupported protocol scheme for language server proxy (must be http or https)"