diff --git a/Cargo.lock b/Cargo.lock index 461164251b..537c8f9e67 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3112,7 +3112,9 @@ dependencies = [ "clock", "collections", "command_palette_hooks", + "ctor", "editor", + "env_logger 0.11.6", "fs", "futures 0.3.31", "gpui", @@ -3120,6 +3122,7 @@ dependencies = [ "indoc", "inline_completion", "language", + "log", "lsp", "menu", "node_runtime", diff --git a/crates/copilot/Cargo.toml b/crates/copilot/Cargo.toml index d3c2108400..867e8fd3bb 100644 --- a/crates/copilot/Cargo.toml +++ b/crates/copilot/Cargo.toml @@ -38,6 +38,7 @@ gpui.workspace = true http_client.workspace = true inline_completion.workspace = true language.workspace = true +log.workspace = true lsp.workspace = true menu.workspace = true node_runtime.workspace = true @@ -62,7 +63,9 @@ async-std = { version = "1.12.0", features = ["unstable"] } client = { workspace = true, features = ["test-support"] } clock = { workspace = true, features = ["test-support"] } collections = { workspace = true, features = ["test-support"] } +ctor.workspace = true editor = { workspace = true, features = ["test-support"] } +env_logger.workspace = true fs = { workspace = true, features = ["test-support"] } gpui = { workspace = true, features = ["test-support"] } http_client = { workspace = true, features = ["test-support"] } diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index 5edc0d5954..ff54ce1cef 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -16,6 +16,7 @@ use gpui::{ }; use http_client::github::get_release_by_tag_name; use http_client::HttpClient; +use language::language_settings::CopilotSettings; use language::{ language_settings::{all_language_settings, language_settings, EditPredictionProvider}, point_from_lsp, point_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, Language, PointUtf16, @@ -367,13 +368,13 @@ impl Copilot { let server_id = self.server_id; let http = self.http.clone(); let node_runtime = self.node_runtime.clone(); - if all_language_settings(None, cx).edit_predictions.provider - == EditPredictionProvider::Copilot - { + let language_settings = all_language_settings(None, cx); + if language_settings.edit_predictions.provider == EditPredictionProvider::Copilot { if matches!(self.server, CopilotServer::Disabled) { + let env = self.build_env(&language_settings.edit_predictions.copilot); let start_task = cx .spawn(move |this, cx| { - Self::start_language_server(server_id, http, node_runtime, this, cx) + Self::start_language_server(server_id, http, node_runtime, env, this, cx) }) .shared(); self.server = CopilotServer::Starting { task: start_task }; @@ -385,6 +386,30 @@ impl Copilot { } } + fn build_env(&self, copilot_settings: &CopilotSettings) -> Option> { + 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:") { + "HTTP_PROXY" + } else if proxy_url.starts_with("https:") { + "HTTPS_PROXY" + } else { + log::error!( + "Unsupported protocol scheme for language server proxy (must be http or https)" + ); + return None; + }; + + let mut env = HashMap::default(); + env.insert(http_or_https_proxy.to_string(), proxy_url); + + if let Some(true) = no_verify { + env.insert("NODE_TLS_REJECT_UNAUTHORIZED".to_string(), "0".to_string()); + }; + + Some(env) + } + #[cfg(any(test, feature = "test-support"))] pub fn fake(cx: &mut gpui::TestAppContext) -> (Entity, lsp::FakeLanguageServer) { use lsp::FakeLanguageServer; @@ -422,6 +447,7 @@ impl Copilot { new_server_id: LanguageServerId, http: Arc, node_runtime: NodeRuntime, + env: Option>, this: WeakEntity, mut cx: AsyncApp, ) { @@ -432,8 +458,7 @@ impl Copilot { let binary = LanguageServerBinary { path: node_path, arguments, - // TODO: We could set HTTP_PROXY etc here and fix the copilot issue. - env: None, + env, }; let root_path = if cfg!(target_os = "windows") { @@ -611,6 +636,8 @@ impl Copilot { } pub fn reinstall(&mut self, cx: &mut Context) -> Task<()> { + let language_settings = all_language_settings(None, cx); + let env = self.build_env(&language_settings.edit_predictions.copilot); let start_task = cx .spawn({ let http = self.http.clone(); @@ -618,7 +645,7 @@ impl Copilot { let server_id = self.server_id; move |this, cx| async move { clear_copilot_dir().await; - Self::start_language_server(server_id, http, node_runtime, this, cx).await + Self::start_language_server(server_id, http, node_runtime, env, this, cx).await } }) .shared(); @@ -1279,3 +1306,11 @@ mod tests { } } } + +#[cfg(test)] +#[ctor::ctor] +fn init_logger() { + if std::env::var("RUST_LOG").is_ok() { + env_logger::init(); + } +} diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index c038458977..58ede4e4bc 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -234,6 +234,8 @@ pub struct EditPredictionSettings { pub disabled_globs: Vec, /// Configures how edit predictions are displayed in the buffer. pub mode: EditPredictionsMode, + /// Settings specific to GitHub Copilot. + pub copilot: CopilotSettings, } /// The mode in which edit predictions should be displayed. @@ -248,6 +250,14 @@ pub enum EditPredictionsMode { EagerPreview, } +#[derive(Clone, Debug, Default)] +pub struct CopilotSettings { + /// HTTP/HTTPS proxy to use for Copilot. + pub proxy: Option, + /// Disable certificate verification for proxy (not recommended). + pub proxy_no_verify: Option, +} + /// The settings for all languages. #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)] pub struct AllLanguageSettingsContent { @@ -465,6 +475,23 @@ pub struct EditPredictionSettingsContent { /// Provider support required. #[serde(default)] pub mode: EditPredictionsMode, + /// Settings specific to GitHub Copilot. + #[serde(default)] + pub copilot: CopilotSettingsContent, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq)] +pub struct CopilotSettingsContent { + /// HTTP/HTTPS proxy to use for Copilot. + /// + /// Default: none + #[serde(default)] + pub proxy: Option, + /// Disable certificate verification for the proxy (not recommended). + /// + /// Default: false + #[serde(default)] + pub proxy_no_verify: Option, } /// The settings for enabling/disabling features. @@ -1064,6 +1091,16 @@ impl settings::Settings for AllLanguageSettings { .map(|globs| globs.iter().collect()) .ok_or_else(Self::missing_default)?; + let mut copilot_settings = default_value + .edit_predictions + .as_ref() + .map(|settings| settings.copilot.clone()) + .map(|copilot| CopilotSettings { + proxy: copilot.proxy, + proxy_no_verify: copilot.proxy_no_verify, + }) + .unwrap_or_default(); + let mut file_types: HashMap, GlobSet> = HashMap::default(); for (language, suffixes) in &default_value.file_types { @@ -1096,6 +1133,22 @@ impl settings::Settings for AllLanguageSettings { } } + if let Some(proxy) = user_settings + .edit_predictions + .as_ref() + .and_then(|settings| settings.copilot.proxy.clone()) + { + copilot_settings.proxy = Some(proxy); + } + + if let Some(proxy_no_verify) = user_settings + .edit_predictions + .as_ref() + .and_then(|settings| settings.copilot.proxy_no_verify) + { + copilot_settings.proxy_no_verify = Some(proxy_no_verify); + } + // A user's global settings override the default global settings and // all default language-specific settings. merge_settings(&mut defaults, &user_settings.defaults); @@ -1147,6 +1200,7 @@ impl settings::Settings for AllLanguageSettings { .filter_map(|g| Some(globset::Glob::new(g).ok()?.compile_matcher())) .collect(), mode: edit_predictions_mode, + copilot: copilot_settings, }, defaults, languages,