diff --git a/assets/settings/default.json b/assets/settings/default.json index 1430458380..1d36c47f4b 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -797,5 +797,17 @@ // - `short`: "2 s, 15 l, 32 c" // - `long`: "2 selections, 15 lines, 32 characters" // Default: long - "line_indicator_format": "long" -} + "line_indicator_format": "long", + // Set a proxy to use. The proxy protocol is specified by the URI scheme. + // + // Supported URI scheme: `http`, `https`, `socks4`, `socks4a`, `socks5`, + // `socks5h`. `http` will be used when no scheme is specified. + // + // By default no proxy will be used, or Zed will try get proxy settings from + // environment variables. + // + // Examples: + // - "proxy" = "socks5://localhost:10808" + // - "proxy" = "http://127.0.0.1:10809" + "proxy": null +} \ No newline at end of file diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 9b453696df..da895de97a 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -114,10 +114,36 @@ impl Settings for ClientSettings { } } +#[derive(Default, Clone, Serialize, Deserialize, JsonSchema)] +pub struct ProxySettingsContent { + proxy: Option, +} + +#[derive(Deserialize, Default)] +pub struct ProxySettings { + pub proxy: Option, +} + +impl Settings for ProxySettings { + const KEY: Option<&'static str> = None; + + type FileContent = ProxySettingsContent; + + fn load(sources: SettingsSources, _: &mut AppContext) -> Result { + Ok(Self { + proxy: sources + .user + .and_then(|value| value.proxy.clone()) + .or(sources.default.proxy.clone()), + }) + } +} + pub fn init_settings(cx: &mut AppContext) { TelemetrySettings::register(cx); cx.update_global(|store: &mut SettingsStore, cx| { store.register_setting::(cx); + store.register_setting::(cx); }); } @@ -512,6 +538,7 @@ impl Client { let clock = Arc::new(clock::RealSystemClock); let http = Arc::new(HttpClientWithUrl::new( &ClientSettings::get_global(cx).server_url, + ProxySettings::get_global(cx).proxy.clone(), )); Self::new(clock, http.clone(), cx) } diff --git a/crates/extension/src/extension_builder.rs b/crates/extension/src/extension_builder.rs index 6a58b9f0d3..3c5357950d 100644 --- a/crates/extension/src/extension_builder.rs +++ b/crates/extension/src/extension_builder.rs @@ -67,7 +67,7 @@ impl ExtensionBuilder { pub fn new(cache_dir: PathBuf) -> Self { Self { cache_dir, - http: http::client(), + http: http::client(None), } } diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 0a234e4359..3ac9587930 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -115,7 +115,7 @@ impl App { Self(AppContext::new( current_platform(), Arc::new(()), - http::client(), + http::client(None), )) } @@ -651,6 +651,11 @@ impl AppContext { self.platform.local_timezone() } + /// Updates the http client assigned to GPUI + pub fn update_http_client(&mut self, new_client: Arc) { + self.http_client = new_client; + } + /// Returns the http client assigned to GPUI pub fn http_client(&self) -> Arc { self.http_client.clone() diff --git a/crates/http/src/http.rs b/crates/http/src/http.rs index acb725952d..2c1b879bdc 100644 --- a/crates/http/src/http.rs +++ b/crates/http/src/http.rs @@ -16,7 +16,7 @@ use std::{ }; pub use url::Url; -fn http_proxy_from_env() -> Option { +fn get_proxy(proxy: Option) -> Option { macro_rules! try_env { ($($env:literal),+) => { $( @@ -27,29 +27,42 @@ fn http_proxy_from_env() -> Option { }; } - try_env!( - "ALL_PROXY", - "all_proxy", - "HTTPS_PROXY", - "https_proxy", - "HTTP_PROXY", - "http_proxy" - ); - None + proxy + .and_then(|input| { + input + .parse::() + .inspect_err(|e| log::error!("Error parsing proxy settings: {}", e)) + .ok() + }) + .or_else(|| { + try_env!( + "ALL_PROXY", + "all_proxy", + "HTTPS_PROXY", + "https_proxy", + "HTTP_PROXY", + "http_proxy" + ); + None + }) } /// An [`HttpClient`] that has a base URL. pub struct HttpClientWithUrl { base_url: Mutex, client: Arc, + proxy: Option, } impl HttpClientWithUrl { /// Returns a new [`HttpClientWithUrl`] with the given base URL. - pub fn new(base_url: impl Into) -> Self { + pub fn new(base_url: impl Into, unparsed_proxy: Option) -> Self { + let parsed_proxy = get_proxy(unparsed_proxy); + let proxy_string = parsed_proxy.as_ref().map(|p| p.to_string()); Self { base_url: Mutex::new(base_url.into()), - client: client(), + client: client(parsed_proxy), + proxy: proxy_string, } } @@ -100,6 +113,10 @@ impl HttpClient for Arc { ) -> BoxFuture<'static, Result, Error>> { self.client.send(req) } + + fn proxy(&self) -> Option<&str> { + self.proxy.as_deref() + } } impl HttpClient for HttpClientWithUrl { @@ -109,6 +126,10 @@ impl HttpClient for HttpClientWithUrl { ) -> BoxFuture<'static, Result, Error>> { self.client.send(req) } + + fn proxy(&self) -> Option<&str> { + self.proxy.as_deref() + } } pub trait HttpClient: Send + Sync { @@ -153,14 +174,16 @@ pub trait HttpClient: Send + Sync { Err(error) => async move { Err(error.into()) }.boxed(), } } + + fn proxy(&self) -> Option<&str>; } -pub fn client() -> Arc { +pub fn client(proxy: Option) -> Arc { Arc::new( isahc::HttpClient::builder() .connect_timeout(Duration::from_secs(5)) .low_speed_timeout(100, Duration::from_secs(5)) - .proxy(http_proxy_from_env()) + .proxy(proxy) .build() .unwrap(), ) @@ -174,6 +197,10 @@ impl HttpClient for isahc::HttpClient { let client = self.clone(); Box::pin(async move { client.send_async(req).await }) } + + fn proxy(&self) -> Option<&str> { + None + } } #[cfg(feature = "test-support")] @@ -201,6 +228,7 @@ impl FakeHttpClient { client: Arc::new(Self { handler: Box::new(move |req| Box::pin(handler(req))), }), + proxy: None, }) } @@ -239,4 +267,8 @@ impl HttpClient for FakeHttpClient { let future = (self.handler)(req); Box::pin(async move { future.await.map(Into::into) }) } + + fn proxy(&self) -> Option<&str> { + None + } } diff --git a/crates/node_runtime/src/node_runtime.rs b/crates/node_runtime/src/node_runtime.rs index f6c20ca17a..426b5c4d3c 100644 --- a/crates/node_runtime/src/node_runtime.rs +++ b/crates/node_runtime/src/node_runtime.rs @@ -266,8 +266,21 @@ impl NodeRuntime for RealNodeRuntime { command.args(["--prefix".into(), directory.to_path_buf()]); } + if let Some(proxy) = self.http.proxy() { + command.args(["--proxy", proxy]); + } + #[cfg(windows)] - command.creation_flags(windows::Win32::System::Threading::CREATE_NO_WINDOW.0); + { + // SYSTEMROOT is a critical environment variables for Windows. + if let Some(val) = std::env::var("SYSTEMROOT") + .context("Missing environment variable: SYSTEMROOT!") + .log_err() + { + command.env("SYSTEMROOT", val); + } + command.creation_flags(windows::Win32::System::Threading::CREATE_NO_WINDOW.0); + } command.output().await.map_err(|e| anyhow!("{e}")) }; diff --git a/crates/semantic_index/examples/index.rs b/crates/semantic_index/examples/index.rs index 105985a80c..f994952d81 100644 --- a/crates/semantic_index/examples/index.rs +++ b/crates/semantic_index/examples/index.rs @@ -26,7 +26,7 @@ fn main() { }); let clock = Arc::new(FakeSystemClock::default()); - let http = Arc::new(HttpClientWithUrl::new("http://localhost:11434")); + let http = Arc::new(HttpClientWithUrl::new("http://localhost:11434", None)); let client = client::Client::new(clock, http.clone(), cx); Client::set_global(client.clone(), cx); diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 8634649f7c..513a4a52c0 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -343,6 +343,7 @@ fn main() { client::init_settings(cx); let client = Client::production(cx); + cx.update_http_client(client.http_client().clone()); let mut languages = LanguageRegistry::new(login_shell_env_loaded, cx.background_executor().clone()); languages.set_language_server_download_dir(paths::LANGUAGES_DIR.clone());