History

nightly url setting (#7037)

Release Notes: - Added the ability to set settings per-release stage - Added a `"server_url"` setting

Conrad Irwin committed 2 years ago

Commit dfbcaf31 parent c07355212 files · +217 −76Browse files

Unified · Split

12 files
  1. Cargo.lock +1 −0
  2. assets/settings/default.json +23 −0
  3. crates/auto_update/src/auto_update.rs +16 −22
  4. crates/channel/src/channel_store_tests.rs +1 −0
  5. crates/client/src/client.rs +57 −23
  6. crates/client/src/telemetry.rs +5 −12
  7. crates/collab/src/tests/test_server.rs +1 −0
  8. crates/feedback/src/feedback_modal.rs +3 −3
  9. crates/settings/src/settings_store.rs +40 −2
  10. crates/util/Cargo.toml +1 −0
  11. crates/util/src/http.rs +45 −5
  12. crates/zed/src/main.rs +24 −9
Cargo.lock+1−0
@@ -9025,6 +9025,7 @@ dependencies = [ · 9024 unmodified lines
90259025 "isahc",
90269026 "lazy_static",
90279027 "log",
9028+ "parking_lot 0.11.2",
90289029 "rand 0.8.5",
90299030 "rust-embed",
90309031 "serde",
assets/settings/default.json+23−0
@@ -503,5 +503,28 @@ · 502 unmodified lines
503503 // }
504504 // }
505505 // }
506+ },
507+ // The server to connect to. If the environment variable
508+ // ZED_SERVER_URL is set, it will override this setting.
509+ "server_url": "https://zed.dev",
510+ // Settings overrides to use when using Zed Preview.
511+ // Mostly useful for developers who are managing multiple instances of Zed.
512+ "preview": {
513+ // "theme": "Andromeda"
514+ },
515+ // Settings overrides to use when using Zed Nightly.
516+ // Mostly useful for developers who are managing multiple instances of Zed.
517+ "nightly": {
518+ // "theme": "Andromeda"
519+ },
520+ // Settings overrides to use when using Zed Stable.
521+ // Mostly useful for developers who are managing multiple instances of Zed.
522+ "stable": {
523+ // "theme": "Andromeda"
524+ },
525+ // Settings overrides to use when using Zed Stable.
526+ // Mostly useful for developers who are managing multiple instances of Zed.
527+ "dev": {
528+ // "theme": "Andromeda"
506529 }
507530 }
crates/auto_update/src/auto_update.rs+16−22
@@ -25,8 +25,11 @@ use std::{ · 24 unmodified lines
2525 time::Duration,
2626 };
2727 use update_notification::UpdateNotification;
28−use util::channel::{AppCommitSha, ReleaseChannel};
2928 use util::http::HttpClient;
29+use util::{
30+ channel::{AppCommitSha, ReleaseChannel},
31+ http::ZedHttpClient,
32+};
3033 use workspace::Workspace;
3134
3235 const SHOULD_SHOW_UPDATE_NOTIFICATION_KEY: &str = "auto-updater-should-show-updated-notification";
@@ -54,9 +57,8 @@ pub enum AutoUpdateStatus { · 21 unmodified lines
5457 pub struct AutoUpdater {
5558 status: AutoUpdateStatus,
5659 current_version: SemanticVersion,
57− http_client: Arc<dyn HttpClient>,
60+ http_client: Arc<ZedHttpClient>,
5861 pending_poll: Option<Task<Option<()>>>,
59− server_url: String,
6062 }
6163
6264 #[derive(Deserialize)]
@@ -92,7 +94,7 @@ impl Settings for AutoUpdateSetting { · 29 unmodified lines
9294 }
9395 }
9496
95−pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppContext) {
97+pub fn init(http_client: Arc<ZedHttpClient>, cx: &mut AppContext) {
9698 AutoUpdateSetting::register(cx);
9799
98100 cx.observe_new_views(|workspace: &mut Workspace, _cx| {
@@ -106,7 +108,7 @@ pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppCo · 7 unmodified lines
106108
107109 if let Some(version) = ZED_APP_VERSION.or_else(|| cx.app_metadata().app_version) {
108110 let auto_updater = cx.new_model(|cx| {
109− let updater = AutoUpdater::new(version, http_client, server_url);
111+ let updater = AutoUpdater::new(version, http_client);
110112
111113 let mut update_subscription = AutoUpdateSetting::get_global(cx)
112114 .0
@@ -151,10 +153,11 @@ pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) -> Option<( · 38 unmodified lines
151153 ReleaseChannel::Stable | ReleaseChannel::Preview
152154 ) {
153155 let auto_updater = auto_updater.read(cx);
154− let server_url = &auto_updater.server_url;
155156 let release_channel = release_channel.dev_name();
156157 let current_version = auto_updater.current_version;
157− let url = format!("{server_url}/releases/{release_channel}/{current_version}");
158+ let url = &auto_updater
159+ .http_client
160+ .zed_url(&format!("/releases/{release_channel}/{current_version}"));
158161 cx.open_url(&url);
159162 }
160163
@@ -191,16 +194,11 @@ impl AutoUpdater { · 30 unmodified lines
191194 cx.default_global::<Option<Model<Self>>>().clone()
192195 }
193196
194− fn new(
195− current_version: SemanticVersion,
196− http_client: Arc<dyn HttpClient>,
197− server_url: String,
198− ) -> Self {
197+ fn new(current_version: SemanticVersion, http_client: Arc<ZedHttpClient>) -> Self {
199198 Self {
200199 status: AutoUpdateStatus::Idle,
201200 current_version,
202201 http_client,
203− server_url,
204202 pending_poll: None,
205203 }
206204 }
@@ -246,18 +244,14 @@ impl AutoUpdater { · 39 unmodified lines
246244 }
247245
248246 async fn update(this: Model<Self>, mut cx: AsyncAppContext) -> Result<()> {
249− let (client, server_url, current_version) = this.read_with(&cx, |this, _| {
250− (
251− this.http_client.clone(),
252− this.server_url.clone(),
253− this.current_version,
254− )
247+ let (client, current_version) = this.read_with(&cx, |this, _| {
248+ (this.http_client.clone(), this.current_version)
255249 })?;
256250
257− let mut url_string = format!(
258− "{server_url}/api/releases/latest?asset=Zed.dmg&os={}&arch={}",
251+ let mut url_string = client.zed_url(&format!(
252+ "/api/releases/latest?asset=Zed.dmg&os={}&arch={}",
259253 OS, ARCH
260− );
254+ ));
261255 cx.update(|cx| {
262256 if let Some(param) = cx
263257 .try_global::<ReleaseChannel>()
crates/channel/src/channel_store_tests.rs+1−0
@@ -329,6 +329,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) { · 328 unmodified lines
329329 fn init_test(cx: &mut AppContext) -> Model<ChannelStore> {
330330 let settings_store = SettingsStore::test(cx);
331331 cx.set_global(settings_store);
332+ client::init_settings(cx);
332333
333334 let http = FakeHttpClient::with_404_response();
334335 let client = Client::new(http.clone(), cx);
crates/client/src/client.rs+57−23
@@ -26,7 +26,7 @@ use rpc::proto::{AnyTypedEnvelope, EntityMessage, EnvelopedMessage, PeerId, Requ · 25 unmodified lines
2626 use schemars::JsonSchema;
2727 use serde::{Deserialize, Serialize};
2828 use serde_json;
29−use settings::Settings;
29+use settings::{Settings, SettingsStore};
3030 use std::{
3131 any::TypeId,
3232 collections::HashMap,
@@ -41,8 +41,8 @@ use std::{ · 8 unmodified lines
4141 use telemetry::Telemetry;
4242 use thiserror::Error;
4343 use url::Url;
44−use util::channel::ReleaseChannel;
4544 use util::http::HttpClient;
45+use util::{channel::ReleaseChannel, http::ZedHttpClient};
4646 use util::{ResultExt, TryFutureExt};
4747
4848 pub use rpc::*;
@@ -50,9 +50,8 @@ pub use telemetry::Event; · 1 unmodified line
5050 pub use user::*;
5151
5252 lazy_static! {
53− pub static ref ZED_SERVER_URL: String =
54− std::env::var("ZED_SERVER_URL").unwrap_or_else(|_| "https://zed.dev".to_string());
55− pub static ref ZED_RPC_URL: Option<String> = std::env::var("ZED_RPC_URL").ok();
53+ static ref ZED_SERVER_URL: Option<String> = std::env::var("ZED_SERVER_URL").ok();
54+ static ref ZED_RPC_URL: Option<String> = std::env::var("ZED_RPC_URL").ok();
5655 pub static ref IMPERSONATE_LOGIN: Option<String> = std::env::var("ZED_IMPERSONATE")
5756 .ok()
5857 .and_then(|s| if s.is_empty() { None } else { Some(s) });
@@ -73,13 +72,45 @@ pub const CONNECTION_TIMEOUT: Duration = Duration::from_secs(5); · 14 unmodified lines
7372
7473 actions!(client, [SignIn, SignOut, Reconnect]);
7574
75+#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
76+pub struct ClientSettingsContent {
77+ server_url: Option<String>,
78+}
79+
80+#[derive(Deserialize)]
81+pub struct ClientSettings {
82+ pub server_url: String,
83+}
84+
85+impl Settings for ClientSettings {
86+ const KEY: Option<&'static str> = None;
87+
88+ type FileContent = ClientSettingsContent;
89+
90+ fn load(
91+ default_value: &Self::FileContent,
92+ user_values: &[&Self::FileContent],
93+ _: &mut AppContext,
94+ ) -> Result<Self>
95+ where
96+ Self: Sized,
97+ {
98+ let mut result = Self::load_via_json_merge(default_value, user_values)?;
99+ if let Some(server_url) = &*ZED_SERVER_URL {
100+ result.server_url = server_url.clone()
101+ }
102+ Ok(result)
103+ }
104+}
105+
76106 pub fn init_settings(cx: &mut AppContext) {
77107 TelemetrySettings::register(cx);
108+ cx.update_global(|store: &mut SettingsStore, cx| {
109+ store.register_setting::<ClientSettings>(cx);
110+ });
78111 }
79112
80113 pub fn init(client: &Arc<Client>, cx: &mut AppContext) {
81− init_settings(cx);
82−
83114 let client = Arc::downgrade(client);
84115 cx.on_action({
85116 let client = client.clone();
@@ -121,7 +152,7 @@ pub fn init(client: &Arc<Client>, cx: &mut AppContext) { · 35 unmodified lines
121152 pub struct Client {
122153 id: AtomicU64,
123154 peer: Arc<Peer>,
124− http: Arc<dyn HttpClient>,
155+ http: Arc<ZedHttpClient>,
125156 telemetry: Arc<Telemetry>,
126157 state: RwLock<ClientState>,
127158
@@ -390,8 +421,8 @@ impl settings::Settings for TelemetrySettings { · 262 unmodified lines
390421 }
391422
392423 impl Client {
393− pub fn new(http: Arc<dyn HttpClient>, cx: &mut AppContext) -> Arc<Self> {
394− Arc::new(Self {
424+ pub fn new(http: Arc<ZedHttpClient>, cx: &mut AppContext) -> Arc<Self> {
425+ let client = Arc::new(Self {
395426 id: AtomicU64::new(0),
396427 peer: Peer::new(0),
397428 telemetry: Telemetry::new(http.clone(), cx),
@@ -402,14 +433,16 @@ impl Client { · 4 unmodified lines
402433 authenticate: Default::default(),
403434 #[cfg(any(test, feature = "test-support"))]
404435 establish_connection: Default::default(),
405− })
436+ });
437+
438+ client
406439 }
407440
408441 pub fn id(&self) -> u64 {
409442 self.id.load(std::sync::atomic::Ordering::SeqCst)
410443 }
411444
412− pub fn http_client(&self) -> Arc<dyn HttpClient> {
445+ pub fn http_client(&self) -> Arc<ZedHttpClient> {
413446 self.http.clone()
414447 }
415448
@@ -925,14 +958,14 @@ impl Client { · 509 unmodified lines
925958 }
926959
927960 async fn get_rpc_url(
928− http: Arc<dyn HttpClient>,
961+ http: Arc<ZedHttpClient>,
929962 release_channel: Option<ReleaseChannel>,
930963 ) -> Result<Url> {
931964 if let Some(url) = &*ZED_RPC_URL {
932965 return Url::parse(url).context("invalid rpc url");
933966 }
934967
935− let mut url = format!("{}/rpc", *ZED_SERVER_URL);
968+ let mut url = http.zed_url("/rpc");
936969 if let Some(preview_param) =
937970 release_channel.and_then(|channel| channel.release_query_param())
938971 {
@@ -1053,10 +1086,10 @@ impl Client { · 114 unmodified lines
10531086
10541087 // Open the Zed sign-in page in the user's browser, with query parameters that indicate
10551088 // that the user is signing in from a Zed app running on the same device.
1056− let mut url = format!(
1057− "{}/native_app_signin?native_app_port={}&native_app_public_key={}",
1058− *ZED_SERVER_URL, port, public_key_string
1059− );
1089+ let mut url = http.zed_url(&format!(
1090+ "/native_app_signin?native_app_port={}&native_app_public_key={}",
1091+ port, public_key_string
1092+ ));
10601093
10611094 if let Some(impersonate_login) = IMPERSONATE_LOGIN.as_ref() {
10621095 log::info!("impersonating user @{}", impersonate_login);
@@ -1088,7 +1121,7 @@ impl Client { · 25 unmodified lines
10881121 }
10891122
10901123 let post_auth_url =
1091− format!("{}/native_app_signin_succeeded", *ZED_SERVER_URL);
1124+ http.zed_url("/native_app_signin_succeeded");
10921125 req.respond(
10931126 tiny_http::Response::empty(302).with_header(
10941127 tiny_http::Header::from_bytes(
@@ -1130,7 +1163,7 @@ impl Client { · 35 unmodified lines
11301163 }
11311164
11321165 async fn authenticate_as_admin(
1133− http: Arc<dyn HttpClient>,
1166+ http: Arc<ZedHttpClient>,
11341167 login: String,
11351168 mut api_token: String,
11361169 ) -> Result<Credentials> {
@@ -1351,7 +1384,7 @@ async fn read_credentials_from_keychain(cx: &AsyncAppContext) -> Option<Credenti · 214 unmodified lines
13511384 }
13521385
13531386 let (user_id, access_token) = cx
1354− .update(|cx| cx.read_credentials(&ZED_SERVER_URL))
1387+ .update(|cx| cx.read_credentials(&ClientSettings::get_global(cx).server_url))
13551388 .log_err()?
13561389 .await
13571390 .log_err()??;
@@ -1368,7 +1401,7 @@ async fn write_credentials_to_keychain( · 10 unmodified lines
13681401 ) -> Result<()> {
13691402 cx.update(move |cx| {
13701403 cx.write_credentials(
1371− &ZED_SERVER_URL,
1404+ &ClientSettings::get_global(cx).server_url,
13721405 &credentials.user_id.to_string(),
13731406 credentials.access_token.as_bytes(),
13741407 )
@@ -1377,7 +1410,7 @@ async fn write_credentials_to_keychain( · 2 unmodified lines
13771410 }
13781411
13791412 async fn delete_credentials_from_keychain(cx: &AsyncAppContext) -> Result<()> {
1380− cx.update(move |cx| cx.delete_credentials(&ZED_SERVER_URL))?
1413+ cx.update(move |cx| cx.delete_credentials(&ClientSettings::get_global(cx).server_url))?
13811414 .await
13821415 }
13831416
@@ -1684,6 +1717,7 @@ mod tests { · 300 unmodified lines
16841717 cx.update(|cx| {
16851718 let settings_store = SettingsStore::test(cx);
16861719 cx.set_global(settings_store);
1720+ init_settings(cx);
16871721 });
16881722 }
16891723 }
crates/client/src/telemetry.rs+5−12
@@ -1,10 +1,9 @@
11 mod event_coalescer;
22
3−use crate::{TelemetrySettings, ZED_SERVER_URL};
3+use crate::TelemetrySettings;
44 use chrono::{DateTime, Utc};
55 use futures::Future;
66 use gpui::{AppContext, AppMetadata, BackgroundExecutor, Task};
7−use lazy_static::lazy_static;
87 use parking_lot::Mutex;
98 use serde::Serialize;
109 use settings::{Settings, SettingsStore};
@@ -13,7 +12,7 @@ use sysinfo::{ · 2 unmodified lines
1312 CpuRefreshKind, Pid, PidExt, ProcessExt, ProcessRefreshKind, RefreshKind, System, SystemExt,
1413 };
1514 use tempfile::NamedTempFile;
16−use util::http::HttpClient;
15+use util::http::{HttpClient, ZedHttpClient};
1716 #[cfg(not(debug_assertions))]
1817 use util::ResultExt;
1918 use util::{channel::ReleaseChannel, TryFutureExt};
@@ -21,7 +20,7 @@ use util::{channel::ReleaseChannel, TryFutureExt}; · 1 unmodified line
2120 use self::event_coalescer::EventCoalescer;
2221
2322 pub struct Telemetry {
24− http_client: Arc<dyn HttpClient>,
23+ http_client: Arc<ZedHttpClient>,
2524 executor: BackgroundExecutor,
2625 state: Arc<Mutex<TelemetryState>>,
2726 }
@@ -43,12 +42,6 @@ struct TelemetryState { · 15 unmodified lines
4342 max_queue_size: usize,
4443 }
4544
46−const EVENTS_URL_PATH: &'static str = "/api/events";
47−
48−lazy_static! {
49− static ref EVENTS_URL: String = format!("{}{}", *ZED_SERVER_URL, EVENTS_URL_PATH);
50−}
51−
5245 #[derive(Serialize, Debug)]
5346 struct EventRequestBody {
5447 installation_id: Option<Arc<str>>,
@@ -149,7 +142,7 @@ const FLUSH_INTERVAL: Duration = Duration::from_secs(1); · 94 unmodified lines
149142 const FLUSH_INTERVAL: Duration = Duration::from_secs(60 * 5);
150143
151144 impl Telemetry {
152− pub fn new(client: Arc<dyn HttpClient>, cx: &mut AppContext) -> Arc<Self> {
145+ pub fn new(client: Arc<ZedHttpClient>, cx: &mut AppContext) -> Arc<Self> {
153146 let release_channel = cx
154147 .try_global::<ReleaseChannel>()
155148 .map(|release_channel| release_channel.display_name());
@@ -548,7 +541,7 @@ impl Telemetry { · 392 unmodified lines
548541 }
549542
550543 this.http_client
551− .post_json(EVENTS_URL.as_str(), json_bytes.into())
544+ .post_json(&this.http_client.zed_url("/api/events"), json_bytes.into())
552545 .await?;
553546 anyhow::Ok(())
554547 }
crates/collab/src/tests/test_server.rs+1−0
@@ -153,6 +153,7 @@ impl TestServer { · 152 unmodified lines
153153 }
154154 let settings = SettingsStore::test(cx);
155155 cx.set_global(settings);
156+ client::init_settings(cx);
156157 });
157158
158159 let http = FakeHttpClient::with_404_response();
crates/feedback/src/feedback_modal.rs+3−3
@@ -2,7 +2,7 @@ use std::{ops::RangeInclusive, sync::Arc, time::Duration}; · 1 unmodified line
22
33 use anyhow::{anyhow, bail};
44 use bitflags::bitflags;
5−use client::{Client, ZED_SERVER_URL};
5+use client::Client;
66 use db::kvp::KEY_VALUE_STORE;
77 use editor::{Editor, EditorEvent};
88 use futures::AsyncReadExt;
@@ -16,7 +16,7 @@ use project::Project; · 7 unmodified lines
1616 use regex::Regex;
1717 use serde_derive::Serialize;
1818 use ui::{prelude::*, Button, ButtonStyle, IconPosition, Tooltip};
19−use util::ResultExt;
19+use util::{http::HttpClient, ResultExt};
2020 use workspace::{ModalView, Toast, Workspace};
2121
2222 use crate::{system_specs::SystemSpecs, GiveFeedback, OpenZedRepo};
@@ -293,12 +293,12 @@ impl FeedbackModal { · 270 unmodified lines
293293 }
294294 }
295295
296− let feedback_endpoint = format!("{}/api/feedback", *ZED_SERVER_URL);
297296 let telemetry = zed_client.telemetry();
298297 let metrics_id = telemetry.metrics_id();
299298 let installation_id = telemetry.installation_id();
300299 let is_staff = telemetry.is_staff();
301300 let http_client = zed_client.http_client();
301+ let feedback_endpoint = http_client.zed_url("/api/feedback");
302302 let request = FeedbackRequestBody {
303303 feedback_text: &feedback_text,
304304 email,
crates/settings/src/settings_store.rs+40−2
@@ -1,6 +1,6 @@
11 use anyhow::{anyhow, Context, Result};
22 use collections::{btree_map, hash_map, BTreeMap, HashMap};
3−use gpui::AppContext;
3+use gpui::{AppContext, AsyncAppContext};
44 use lazy_static::lazy_static;
55 use schemars::{gen::SchemaGenerator, schema::RootSchema, JsonSchema};
66 use serde::{de::DeserializeOwned, Deserialize as _, Serialize};
@@ -13,7 +13,9 @@ use std::{ · 6 unmodified lines
1313 str,
1414 sync::Arc,
1515 };
16−use util::{merge_non_null_json_value_into, RangeExt, ResultExt as _};
16+use util::{
17+ channel::RELEASE_CHANNEL_NAME, merge_non_null_json_value_into, RangeExt, ResultExt as _,
18+};
1719
1820 /// A value that can be defined as a user setting.
1921 ///
@@ -102,6 +104,14 @@ pub trait Settings: 'static + Send + Sync { · 82 unmodified lines
102104 cx.global::<SettingsStore>().get(None)
103105 }
104106
107+ #[track_caller]
108+ fn try_read_global<'a, R>(cx: &'a AsyncAppContext, f: impl FnOnce(&Self) -> R) -> Option<R>
109+ where
110+ Self: Sized,
111+ {
112+ cx.try_read_global(|s: &SettingsStore, _| f(s.get(None)))
113+ }
114+
105115 #[track_caller]
106116 fn override_global<'a>(settings: Self, cx: &'a mut AppContext)
107117 where
@@ -197,6 +207,15 @@ impl SettingsStore { · 89 unmodified lines
197207 user_values_stack = vec![user_settings];
198208 }
199209
210+ if let Some(release_settings) = &self.raw_user_settings.get(&*RELEASE_CHANNEL_NAME) {
211+ if let Some(release_settings) = setting_value
212+ .deserialize_setting(&release_settings)
213+ .log_err()
214+ {
215+ user_values_stack.push(release_settings);
216+ }
217+ }
218+
200219 if let Some(setting) = setting_value
201220 .load_setting(&default_settings, &user_values_stack, cx)
202221 .context("A default setting must be added to the `default.json` file")
@@ -484,6 +503,15 @@ impl SettingsStore { · 281 unmodified lines
484503 }
485504 }
486505
506+ for release_stage in ["dev", "nightly", "stable", "preview"] {
507+ let schema = combined_schema.schema.clone();
508+ combined_schema
509+ .schema
510+ .object()
511+ .properties
512+ .insert(release_stage.to_string(), schema.into());
513+ }
514+
487515 serde_json::to_value(&combined_schema).unwrap()
488516 }
489517
@@ -509,6 +537,16 @@ impl SettingsStore { · 19 unmodified lines
509537 paths_stack.push(None);
510538 }
511539
540+ if let Some(release_settings) = &self.raw_user_settings.get(&*RELEASE_CHANNEL_NAME) {
541+ if let Some(release_settings) = setting_value
542+ .deserialize_setting(&release_settings)
543+ .log_err()
544+ {
545+ user_settings_stack.push(release_settings);
546+ paths_stack.push(None);
547+ }
548+ }
549+
512550 // If the global settings file changed, reload the global value for the field.
513551 if changed_local_path.is_none() {
514552 if let Some(value) = setting_value
crates/util/Cargo.toml+1−0
@@ -30,6 +30,7 @@ serde_json.workspace = true · 29 unmodified lines
3030 git2 = { workspace = true, optional = true }
3131 dirs = "3.0"
3232 take-until = "0.2.0"
33+parking_lot.workspace = true
3334
3435 [target.'cfg(windows)'.dependencies]
3536 tendril = "0.4.3"
crates/util/src/http.rs+45−5
@@ -6,12 +6,49 @@ pub use isahc::{ · 5 unmodified lines
66 Error,
77 };
88 pub use isahc::{AsyncBody, Request, Response};
9+use parking_lot::Mutex;
910 use smol::future::FutureExt;
1011 #[cfg(feature = "test-support")]
1112 use std::fmt;
1213 use std::{sync::Arc, time::Duration};
1314 pub use url::Url;
1415
16+pub struct ZedHttpClient {
17+ pub zed_host: Mutex<String>,
18+ client: Box<dyn HttpClient>,
19+}
20+
21+impl ZedHttpClient {
22+ pub fn zed_url(&self, path: &str) -> String {
23+ format!("{}{}", self.zed_host.lock(), path)
24+ }
25+}
26+
27+impl HttpClient for Arc<ZedHttpClient> {
28+ fn send(&self, req: Request<AsyncBody>) -> BoxFuture<Result<Response<AsyncBody>, Error>> {
29+ self.client.send(req)
30+ }
31+}
32+
33+impl HttpClient for ZedHttpClient {
34+ fn send(&self, req: Request<AsyncBody>) -> BoxFuture<Result<Response<AsyncBody>, Error>> {
35+ self.client.send(req)
36+ }
37+}
38+
39+pub fn zed_client(zed_host: &str) -> Arc<ZedHttpClient> {
40+ Arc::new(ZedHttpClient {
41+ zed_host: Mutex::new(zed_host.to_string()),
42+ client: Box::new(
43+ isahc::HttpClient::builder()
44+ .connect_timeout(Duration::from_secs(5))
45+ .low_speed_timeout(100, Duration::from_secs(5))
46+ .build()
47+ .unwrap(),
48+ ),
49+ })
50+}
51+
1552 pub trait HttpClient: Send + Sync {
1653 fn send(&self, req: Request<AsyncBody>) -> BoxFuture<Result<Response<AsyncBody>, Error>>;
1754
@@ -81,17 +118,20 @@ pub struct FakeHttpClient { · 63 unmodified lines
81118
82119 #[cfg(feature = "test-support")]
83120 impl FakeHttpClient {
84− pub fn create<Fut, F>(handler: F) -> Arc<dyn HttpClient>
121+ pub fn create<Fut, F>(handler: F) -> Arc<ZedHttpClient>
85122 where
86123 Fut: 'static + Send + futures::Future<Output = Result<Response<AsyncBody>, Error>>,
87124 F: 'static + Send + Sync + Fn(Request<AsyncBody>) -> Fut,
88125 {
89− Arc::new(Self {
90− handler: Box::new(move |req| Box::pin(handler(req))),
126+ Arc::new(ZedHttpClient {
127+ zed_host: Mutex::new("http://test.example".into()),
128+ client: Box::new(Self {
129+ handler: Box::new(move |req| Box::pin(handler(req))),
130+ }),
91131 })
92132 }
93133
94− pub fn with_404_response() -> Arc<dyn HttpClient> {
134+ pub fn with_404_response() -> Arc<ZedHttpClient> {
95135 Self::create(|_| async move {
96136 Ok(Response::builder()
97137 .status(404)
@@ -100,7 +140,7 @@ impl FakeHttpClient { · 2 unmodified lines
100140 })
101141 }
102142
103− pub fn with_200_response() -> Arc<dyn HttpClient> {
143+ pub fn with_200_response() -> Arc<ZedHttpClient> {
104144 Self::create(|_| async move {
105145 Ok(Response::builder()
106146 .status(200)
crates/zed/src/main.rs+24−9
@@ -42,7 +42,7 @@ use theme::{ActiveTheme, ThemeRegistry, ThemeSettings}; · 41 unmodified lines
4242 use util::{
4343 async_maybe,
4444 channel::{parse_zed_link, AppCommitSha, ReleaseChannel, RELEASE_CHANNEL},
45− http::{self, HttpClient},
45+ http::{self, HttpClient, ZedHttpClient},
4646 paths::{self, CRASHES_DIR, CRASHES_RETIRED_DIR},
4747 ResultExt,
4848 };
@@ -59,7 +59,6 @@ fn main() { · 10 unmodified lines
5959 menu::init();
6060 zed_actions::init();
6161
62− let http = http::client();
6362 init_paths();
6463 init_logger();
6564
@@ -132,6 +131,9 @@ fn main() { · 66 unmodified lines
132131 cx.set_global(store);
133132 handle_settings_file_changes(user_settings_file_rx, cx);
134133 handle_keymap_file_changes(user_keymap_file_rx, cx);
134+ client::init_settings(cx);
135+
136+ let http = http::zed_client(&client::ClientSettings::get_global(cx).server_url);
135137
136138 let client = client::Client::new(http.clone(), cx);
137139 let mut languages = LanguageRegistry::new(login_shell_env_loaded);
@@ -201,7 +203,20 @@ fn main() { · 63 unmodified lines
201203 languages.set_theme(cx.theme().clone());
202204 cx.observe_global::<SettingsStore>({
203205 let languages = languages.clone();
204− move |cx| languages.set_theme(cx.theme().clone())
206+ let http = http.clone();
207+ let client = client.clone();
208+
209+ move |cx| {
210+ languages.set_theme(cx.theme().clone());
211+ let new_host = &client::ClientSettings::get_global(cx).server_url;
212+ let mut host = http.zed_host.lock();
213+ if &*host != new_host {
214+ *host = new_host.clone();
215+ if client.status().borrow().is_connected() {
216+ client.reconnect(&cx.to_async());
217+ }
218+ }
219+ }
205220 })
206221 .detach();
207222
@@ -230,7 +245,7 @@ fn main() { · 22 unmodified lines
230245 cx.set_global(Arc::downgrade(&app_state));
231246
232247 audio::init(Assets, cx);
233− auto_update::init(http.clone(), client::ZED_SERVER_URL.clone(), cx);
248+ auto_update::init(http.clone(), cx);
234249
235250 workspace::init(app_state.clone(), cx);
236251 recent_projects::init(cx);
@@ -634,7 +649,7 @@ fn init_panic_hook(app: &App, installation_id: Option<String>, session_id: Strin · 397 unmodified lines
634649 }));
635650 }
636651
637−fn upload_panics_and_crashes(http: Arc<dyn HttpClient>, cx: &mut AppContext) {
652+fn upload_panics_and_crashes(http: Arc<ZedHttpClient>, cx: &mut AppContext) {
638653 let telemetry_settings = *client::TelemetrySettings::get_global(cx);
639654 cx.background_executor()
640655 .spawn(async move {
@@ -650,10 +665,10 @@ fn upload_panics_and_crashes(http: Arc<dyn HttpClient>, cx: &mut AppContext) { · 9 unmodified lines
650665
651666 /// upload panics to us (via zed.dev)
652667 async fn upload_previous_panics(
653− http: Arc<dyn HttpClient>,
668+ http: Arc<ZedHttpClient>,
654669 telemetry_settings: client::TelemetrySettings,
655670 ) -> Result<()> {
656− let panic_report_url = format!("{}/api/panic", &*client::ZED_SERVER_URL);
671+ let panic_report_url = http.zed_url("/api/panic");
657672 let mut children = smol::fs::read_dir(&*paths::LOGS_DIR).await?;
658673 while let Some(child) = children.next().await {
659674 let child = child?;
@@ -717,7 +732,7 @@ static LAST_CRASH_UPLOADED: &'static str = "LAST_CRASH_UPLOADED"; · 57 unmodified lines
717732 /// upload crashes from apple's diagnostic reports to our server.
718733 /// (only if telemetry is enabled)
719734 async fn upload_previous_crashes(
720− http: Arc<dyn HttpClient>,
735+ http: Arc<ZedHttpClient>,
721736 telemetry_settings: client::TelemetrySettings,
722737 ) -> Result<()> {
723738 if !telemetry_settings.diagnostics {
@@ -728,7 +743,7 @@ async fn upload_previous_crashes( · 4 unmodified lines
728743 .unwrap_or("zed-2024-01-17-221900.ips".to_string()); // don't upload old crash reports from before we had this.
729744 let mut uploaded = last_uploaded.clone();
730745
731− let crash_report_url = format!("{}/api/crash", &*client::ZED_SERVER_URL);
746+ let crash_report_url = http.zed_url("/api/crash");
732747
733748 for dir in [&*CRASHES_DIR, &*CRASHES_RETIRED_DIR] {
734749 let mut children = smol::fs::read_dir(&dir).await?;