pub mod copilot_chat;mod copilot_edit_prediction_delegate;pub mod copilot_responses;pub mod request;mod sign_in; use crate::request::NextEditSuggestions;use crate::sign_in::initiate_sign_out;use ::fs::Fs;use anyhow::{Context as _, Result, anyhow};use collections::{HashMap, HashSet};use command_palette_hooks::CommandPaletteFilter;use futures::{Future, FutureExt, TryFutureExt, channel::oneshot, future::Shared};use gpui::{ App, AppContext as _, AsyncApp, Context, Entity, EntityId, EventEmitter, Global, Task, WeakEntity, actions,};use http_client::HttpClient;use language::language_settings::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::DisableAiSettings;use request::StatusNotification;use semver::Version;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::Workspace; pub use crate::copilot_edit_prediction_delegate::CopilotEditPredictionDelegate;pub use crate::sign_in::{ ConfigurationMode, ConfigurationView, CopilotCodeVerification, initiate_sign_in, reinstall_and_sign_in,}; 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 ]); pub fn init( new_server_id: LanguageServerId, fs: Arc<dyn Fs>, http: Arc<dyn HttpClient>, node_runtime: NodeRuntime, cx: &mut App,) { let language_settings = all_language_settings(None, cx); let configuration = copilot_chat::CopilotChatConfiguration { enterprise_uri: language_settings .edit_predictions .copilot .enterprise_uri .clone(), }; copilot_chat::init(fs.clone(), http.clone(), configuration, cx); let copilot = cx.new(move |cx| Copilot::start(new_server_id, fs, node_runtime, cx)); Copilot::set_global(copilot.clone(), cx); cx.observe(&copilot, |copilot, cx| { copilot.update(cx, |copilot, cx| copilot.update_action_visibilities(cx)); }) .detach(); cx.observe_global::<SettingsStore>(|cx| { if let Some(copilot) = Copilot::global(cx) { copilot.update(cx, |copilot, cx| copilot.update_action_visibilities(cx)); } }) .detach(); cx.observe_new(|workspace: &mut Workspace, _window, _cx| { workspace.register_action(|_, _: &SignIn, window, cx| { initiate_sign_in(window, cx); }); workspace.register_action(|_, _: &Reinstall, window, cx| { reinstall_and_sign_in(window, cx); }); workspace.register_action(|_, _: &SignOut, window, cx| { initiate_sign_out(window, cx); }); }) .detach();} 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, _subscription: gpui::Subscription,} pub enum Event { CopilotLanguageServerStarted, CopilotAuthSignedIn, CopilotAuthSignedOut,} impl EventEmitter<Event> for Copilot {} struct GlobalCopilot(Entity<Copilot>); impl Global for GlobalCopilot {} /// Copilot's NextEditSuggestion response, with coordinates converted to Anchors.struct CopilotEditPrediction { buffer: Entity<Buffer>, range: Range<Anchor>, text: String, command: Option<lsp::Command>, snapshot: BufferSnapshot,} impl Copilot { pub fn global(cx: &App) -> Option<Entity<Self>> { cx.try_global::<GlobalCopilot>() .map(|model| model.0.clone()) } pub fn set_global(copilot: Entity<Self>, cx: &mut App) { cx.set_global(GlobalCopilot(copilot)); } fn start( new_server_id: LanguageServerId, fs: Arc<dyn Fs>, node_runtime: NodeRuntime, cx: &mut Context<Self>, ) -> Self { let mut this = Self { server_id: new_server_id, fs, node_runtime, server: CopilotServer::Disabled, buffers: Default::default(), _subscription: cx.on_app_quit(Self::shutdown_language_server), }; this.start_copilot(true, false, cx); cx.observe_global::<SettingsStore>(move |this, cx| { 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(); } }) .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 !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)" ); None }; let mut env = HashMap::default(); if let Some(proxy_type) = http_or_https_proxy { env.insert(proxy_type.to_string(), proxy_url); if let Some(true) = no_verify { env.insert("NODE_TLS_REJECT_UNAUTHORIZED".to_string(), "0".to_string()); }; } if let Ok(oauth_token) = env::var(copilot_chat::COPILOT_OAUTH_ENV_VAR) { env.insert(copilot_chat::COPILOT_OAUTH_ENV_VAR.to_string(), oauth_token); } if env.is_empty() { None } else { Some(env) } } #[cfg(any(test, feature = "test-support"))] pub fn fake(cx: &mut gpui::TestAppContext) -> (Entity<Self>, lsp::FakeLanguageServer) { use fs::FakeFs; use lsp::FakeLanguageServer; use node_runtime::NodeRuntime; let (server, fake_server) = FakeLanguageServer::new( LanguageServerId(0), LanguageServerBinary { path: "path/to/copilot".into(), arguments: vec![], env: None, }, "copilot".into(), Default::default(), &mut cx.to_async(), ); let node_runtime = NodeRuntime::unavailable(); let this = cx.new(|cx| Self { server_id: LanguageServerId(0), fs: FakeFs::new(cx.background_executor().clone()), node_runtime, server: CopilotServer::Running(RunningCopilotServer { lsp: Arc::new(server), sign_in_status: SignInStatus::Authorized, registered_buffers: Default::default(), }), _subscription: cx.on_app_quit(Self::shutdown_language_server), buffers: Default::default(), }); (this, fake_server) } async fn start_language_server( new_server_id: LanguageServerId, fs: Arc<dyn Fs>, node_runtime: NodeRuntime, env: Option<HashMap<String, String>>, this: WeakEntity<Self>, awaiting_sign_in_after_start: bool, cx: &mut AsyncApp, ) { let start_language_server = async { let server_path = get_copilot_lsp(fs, node_runtime.clone()).await?; let node_path = node_runtime.binary_path().await?; ensure_node_version_for_copilot(&node_path).await?; let arguments: Vec<OsString> = vec![ "--experimental-sqlite".into(), server_path.into(), "--stdio".into(), ]; let binary = LanguageServerBinary {Lines 1–500 of 1432. Load more
pub mod copilot_chat; mod copilot_edit_prediction_delegate; pub mod copilot_responses; pub mod request; mod sign_in; use crate::request::NextEditSuggestions; use crate::sign_in::initiate_sign_out; use ::fs::Fs; use anyhow::{Context as _, Result, anyhow}; use collections::{HashMap, HashSet}; use command_palette_hooks::CommandPaletteFilter; use futures::{Future, FutureExt, TryFutureExt, channel::oneshot, future::Shared}; use gpui::{ App, AppContext as _, AsyncApp, Context, Entity, EntityId, EventEmitter, Global, Task, WeakEntity, actions, }; use http_client::HttpClient; use language::language_settings::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::DisableAiSettings; use request::StatusNotification; use semver::Version; 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::Workspace; pub use crate::copilot_edit_prediction_delegate::CopilotEditPredictionDelegate; pub use crate::sign_in::{ ConfigurationMode, ConfigurationView, CopilotCodeVerification, initiate_sign_in, reinstall_and_sign_in, }; 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 ] ); pub fn init( new_server_id: LanguageServerId, fs: Arc<dyn Fs>, http: Arc<dyn HttpClient>, node_runtime: NodeRuntime, cx: &mut App, ) { let language_settings = all_language_settings(None, cx); let configuration = copilot_chat::CopilotChatConfiguration { enterprise_uri: language_settings .edit_predictions .copilot .enterprise_uri .clone(), }; copilot_chat::init(fs.clone(), http.clone(), configuration, cx); let copilot = cx.new(move |cx| Copilot::start(new_server_id, fs, node_runtime, cx)); Copilot::set_global(copilot.clone(), cx); cx.observe(&copilot, |copilot, cx| { copilot.update(cx, |copilot, cx| copilot.update_action_visibilities(cx)); }) .detach(); cx.observe_global::<SettingsStore>(|cx| { if let Some(copilot) = Copilot::global(cx) { copilot.update(cx, |copilot, cx| copilot.update_action_visibilities(cx)); } }) .detach(); cx.observe_new(|workspace: &mut Workspace, _window, _cx| { workspace.register_action(|_, _: &SignIn, window, cx| { initiate_sign_in(window, cx); }); workspace.register_action(|_, _: &Reinstall, window, cx| { reinstall_and_sign_in(window, cx); }); workspace.register_action(|_, _: &SignOut, window, cx| { initiate_sign_out(window, cx); }); }) .detach(); } 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, _subscription: gpui::Subscription, } pub enum Event { CopilotLanguageServerStarted, CopilotAuthSignedIn, CopilotAuthSignedOut, } impl EventEmitter<Event> for Copilot {} struct GlobalCopilot(Entity<Copilot>); impl Global for GlobalCopilot {} /// Copilot's NextEditSuggestion response, with coordinates converted to Anchors. struct CopilotEditPrediction { buffer: Entity<Buffer>, range: Range<Anchor>, text: String, command: Option<lsp::Command>, snapshot: BufferSnapshot, } impl Copilot { pub fn global(cx: &App) -> Option<Entity<Self>> { cx.try_global::<GlobalCopilot>() .map(|model| model.0.clone()) } pub fn set_global(copilot: Entity<Self>, cx: &mut App) { cx.set_global(GlobalCopilot(copilot)); } fn start( new_server_id: LanguageServerId, fs: Arc<dyn Fs>, node_runtime: NodeRuntime, cx: &mut Context<Self>, ) -> Self { let mut this = Self { server_id: new_server_id, fs, node_runtime, server: CopilotServer::Disabled, buffers: Default::default(), _subscription: cx.on_app_quit(Self::shutdown_language_server), }; this.start_copilot(true, false, cx); cx.observe_global::<SettingsStore>(move |this, cx| { 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(); } }) .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 !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)" ); None }; let mut env = HashMap::default(); if let Some(proxy_type) = http_or_https_proxy { env.insert(proxy_type.to_string(), proxy_url); if let Some(true) = no_verify { env.insert("NODE_TLS_REJECT_UNAUTHORIZED".to_string(), "0".to_string()); }; } if let Ok(oauth_token) = env::var(copilot_chat::COPILOT_OAUTH_ENV_VAR) { env.insert(copilot_chat::COPILOT_OAUTH_ENV_VAR.to_string(), oauth_token); } if env.is_empty() { None } else { Some(env) } } #[cfg(any(test, feature = "test-support"))] pub fn fake(cx: &mut gpui::TestAppContext) -> (Entity<Self>, lsp::FakeLanguageServer) { use fs::FakeFs; use lsp::FakeLanguageServer; use node_runtime::NodeRuntime; let (server, fake_server) = FakeLanguageServer::new( LanguageServerId(0), LanguageServerBinary { path: "path/to/copilot".into(), arguments: vec![], env: None, }, "copilot".into(), Default::default(), &mut cx.to_async(), ); let node_runtime = NodeRuntime::unavailable(); let this = cx.new(|cx| Self { server_id: LanguageServerId(0), fs: FakeFs::new(cx.background_executor().clone()), node_runtime, server: CopilotServer::Running(RunningCopilotServer { lsp: Arc::new(server), sign_in_status: SignInStatus::Authorized, registered_buffers: Default::default(), }), _subscription: cx.on_app_quit(Self::shutdown_language_server), buffers: Default::default(), }); (this, fake_server) } async fn start_language_server( new_server_id: LanguageServerId, fs: Arc<dyn Fs>, node_runtime: NodeRuntime, env: Option<HashMap<String, String>>, this: WeakEntity<Self>, awaiting_sign_in_after_start: bool, cx: &mut AsyncApp, ) { let start_language_server = async { let server_path = get_copilot_lsp(fs, node_runtime.clone()).await?; let node_path = node_runtime.binary_path().await?; ensure_node_version_for_copilot(&node_path).await?; let arguments: Vec<OsString> = vec![ "--experimental-sqlite".into(), server_path.into(), "--stdio".into(), ]; let binary = LanguageServerBinary {