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
9025 "isahc",9025 "isahc",
9026 "lazy_static",9026 "lazy_static",
9027 "log",9027 "log",
9028 "parking_lot 0.11.2",
9028 "rand 0.8.5",9029 "rand 0.8.5",
9029 "rust-embed",9030 "rust-embed",
9030 "serde",9031 "serde",
assets/settings/default.json+23−0
@@ -503,5 +503,28 @@ · 502 unmodified lines
503 // }503 // }
504 // }504 // }
505 // }505 // }
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"
506 }529 }
507}530}
crates/auto_update/src/auto_update.rs+16−22
@@ -25,8 +25,11 @@ use std::{ · 24 unmodified lines
25 time::Duration,25 time::Duration,
26};26};
27use update_notification::UpdateNotification;27use update_notification::UpdateNotification;
28use util::channel::{AppCommitSha, ReleaseChannel};
29use util::http::HttpClient;28use util::http::HttpClient;
29use util::{
30 channel::{AppCommitSha, ReleaseChannel},
31 http::ZedHttpClient,
32};
30use workspace::Workspace;33use workspace::Workspace;
31 34
32const SHOULD_SHOW_UPDATE_NOTIFICATION_KEY: &str = "auto-updater-should-show-updated-notification";35const SHOULD_SHOW_UPDATE_NOTIFICATION_KEY: &str = "auto-updater-should-show-updated-notification";
@@ -54,9 +57,8 @@ pub enum AutoUpdateStatus { · 21 unmodified lines
54pub struct AutoUpdater {57pub struct AutoUpdater {
55 status: AutoUpdateStatus,58 status: AutoUpdateStatus,
56 current_version: SemanticVersion,59 current_version: SemanticVersion,
57 http_client: Arc<dyn HttpClient>,60 http_client: Arc<ZedHttpClient>,
58 pending_poll: Option<Task<Option<()>>>,61 pending_poll: Option<Task<Option<()>>>,
59 server_url: String,
60}62}
61 63
62#[derive(Deserialize)]64#[derive(Deserialize)]
@@ -92,7 +94,7 @@ impl Settings for AutoUpdateSetting { · 29 unmodified lines
92 }94 }
93}95}
94 96
95pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppContext) {97pub fn init(http_client: Arc<ZedHttpClient>, cx: &mut AppContext) {
96 AutoUpdateSetting::register(cx);98 AutoUpdateSetting::register(cx);
97 99
98 cx.observe_new_views(|workspace: &mut Workspace, _cx| {100 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
106 108
107 if let Some(version) = ZED_APP_VERSION.or_else(|| cx.app_metadata().app_version) {109 if let Some(version) = ZED_APP_VERSION.or_else(|| cx.app_metadata().app_version) {
108 let auto_updater = cx.new_model(|cx| {110 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);
110 112
111 let mut update_subscription = AutoUpdateSetting::get_global(cx)113 let mut update_subscription = AutoUpdateSetting::get_global(cx)
112 .0114 .0
@@ -151,10 +153,11 @@ pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) -> Option<( · 38 unmodified lines
151 ReleaseChannel::Stable | ReleaseChannel::Preview153 ReleaseChannel::Stable | ReleaseChannel::Preview
152 ) {154 ) {
153 let auto_updater = auto_updater.read(cx);155 let auto_updater = auto_updater.read(cx);
154 let server_url = &auto_updater.server_url;
155 let release_channel = release_channel.dev_name();156 let release_channel = release_channel.dev_name();
156 let current_version = auto_updater.current_version;157 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}"));
158 cx.open_url(&url);161 cx.open_url(&url);
159 }162 }
160 163
@@ -191,16 +194,11 @@ impl AutoUpdater { · 30 unmodified lines
191 cx.default_global::<Option<Model<Self>>>().clone()194 cx.default_global::<Option<Model<Self>>>().clone()
192 }195 }
193 196
194 fn new(197 fn new(current_version: SemanticVersion, http_client: Arc<ZedHttpClient>) -> Self {
195 current_version: SemanticVersion,
196 http_client: Arc<dyn HttpClient>,
197 server_url: String,
198 ) -> Self {
199 Self {198 Self {
200 status: AutoUpdateStatus::Idle,199 status: AutoUpdateStatus::Idle,
201 current_version,200 current_version,
202 http_client,201 http_client,
203 server_url,
204 pending_poll: None,202 pending_poll: None,
205 }203 }
206 }204 }
@@ -246,18 +244,14 @@ impl AutoUpdater { · 39 unmodified lines
246 }244 }
247 245
248 async fn update(this: Model<Self>, mut cx: AsyncAppContext) -> Result<()> {246 async fn update(this: Model<Self>, mut cx: AsyncAppContext) -> Result<()> {
249 let (client, server_url, current_version) = this.read_with(&cx, |this, _| {247 let (client, current_version) = this.read_with(&cx, |this, _| {
250 (248 (this.http_client.clone(), this.current_version)
251 this.http_client.clone(),
252 this.server_url.clone(),
253 this.current_version,
254 )
255 })?;249 })?;
256 250
257 let mut url_string = format!(251 let mut url_string = client.zed_url(&format!(
258 "{server_url}/api/releases/latest?asset=Zed.dmg&os={}&arch={}",252 "/api/releases/latest?asset=Zed.dmg&os={}&arch={}",
259 OS, ARCH253 OS, ARCH
260 );254 ));
261 cx.update(|cx| {255 cx.update(|cx| {
262 if let Some(param) = cx256 if let Some(param) = cx
263 .try_global::<ReleaseChannel>()257 .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
329fn init_test(cx: &mut AppContext) -> Model<ChannelStore> {329fn init_test(cx: &mut AppContext) -> Model<ChannelStore> {
330 let settings_store = SettingsStore::test(cx);330 let settings_store = SettingsStore::test(cx);
331 cx.set_global(settings_store);331 cx.set_global(settings_store);
332 client::init_settings(cx);
332 333
333 let http = FakeHttpClient::with_404_response();334 let http = FakeHttpClient::with_404_response();
334 let client = Client::new(http.clone(), cx);335 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
26use schemars::JsonSchema;26use schemars::JsonSchema;
27use serde::{Deserialize, Serialize};27use serde::{Deserialize, Serialize};
28use serde_json;28use serde_json;
29use settings::Settings;29use settings::{Settings, SettingsStore};
30use std::{30use std::{
31 any::TypeId,31 any::TypeId,
32 collections::HashMap,32 collections::HashMap,
@@ -41,8 +41,8 @@ use std::{ · 8 unmodified lines
41use telemetry::Telemetry;41use telemetry::Telemetry;
42use thiserror::Error;42use thiserror::Error;
43use url::Url;43use url::Url;
44use util::channel::ReleaseChannel;
45use util::http::HttpClient;44use util::http::HttpClient;
45use util::{channel::ReleaseChannel, http::ZedHttpClient};
46use util::{ResultExt, TryFutureExt};46use util::{ResultExt, TryFutureExt};
47 47
48pub use rpc::*;48pub use rpc::*;
@@ -50,9 +50,8 @@ pub use telemetry::Event; · 1 unmodified line
50pub use user::*;50pub use user::*;
51 51
52lazy_static! {52lazy_static! {
53 pub static ref ZED_SERVER_URL: String =53 static ref ZED_SERVER_URL: Option<String> = std::env::var("ZED_SERVER_URL").ok();
54 std::env::var("ZED_SERVER_URL").unwrap_or_else(|_| "https://zed.dev".to_string());54 static ref ZED_RPC_URL: Option<String> = std::env::var("ZED_RPC_URL").ok();
55 pub static ref ZED_RPC_URL: Option<String> = std::env::var("ZED_RPC_URL").ok();
56 pub static ref IMPERSONATE_LOGIN: Option<String> = std::env::var("ZED_IMPERSONATE")55 pub static ref IMPERSONATE_LOGIN: Option<String> = std::env::var("ZED_IMPERSONATE")
57 .ok()56 .ok()
58 .and_then(|s| if s.is_empty() { None } else { Some(s) });57 .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
73 72
74actions!(client, [SignIn, SignOut, Reconnect]);73actions!(client, [SignIn, SignOut, Reconnect]);
75 74
75#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
76pub struct ClientSettingsContent {
77 server_url: Option<String>,
78}
79
80#[derive(Deserialize)]
81pub struct ClientSettings {
82 pub server_url: String,
83}
84
85impl 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
76pub fn init_settings(cx: &mut AppContext) {106pub fn init_settings(cx: &mut AppContext) {
77 TelemetrySettings::register(cx);107 TelemetrySettings::register(cx);
108 cx.update_global(|store: &mut SettingsStore, cx| {
109 store.register_setting::<ClientSettings>(cx);
110 });
78}111}
79 112
80pub fn init(client: &Arc<Client>, cx: &mut AppContext) {113pub fn init(client: &Arc<Client>, cx: &mut AppContext) {
81 init_settings(cx);
82
83 let client = Arc::downgrade(client);114 let client = Arc::downgrade(client);
84 cx.on_action({115 cx.on_action({
85 let client = client.clone();116 let client = client.clone();
@@ -121,7 +152,7 @@ pub fn init(client: &Arc<Client>, cx: &mut AppContext) { · 35 unmodified lines
121pub struct Client {152pub struct Client {
122 id: AtomicU64,153 id: AtomicU64,
123 peer: Arc<Peer>,154 peer: Arc<Peer>,
124 http: Arc<dyn HttpClient>,155 http: Arc<ZedHttpClient>,
125 telemetry: Arc<Telemetry>,156 telemetry: Arc<Telemetry>,
126 state: RwLock<ClientState>,157 state: RwLock<ClientState>,
127 158
@@ -390,8 +421,8 @@ impl settings::Settings for TelemetrySettings { · 262 unmodified lines
390}421}
391 422
392impl Client {423impl Client {
393 pub fn new(http: Arc<dyn HttpClient>, cx: &mut AppContext) -> Arc<Self> {424 pub fn new(http: Arc<ZedHttpClient>, cx: &mut AppContext) -> Arc<Self> {
394 Arc::new(Self {425 let client = Arc::new(Self {
395 id: AtomicU64::new(0),426 id: AtomicU64::new(0),
396 peer: Peer::new(0),427 peer: Peer::new(0),
397 telemetry: Telemetry::new(http.clone(), cx),428 telemetry: Telemetry::new(http.clone(), cx),
@@ -402,14 +433,16 @@ impl Client { · 4 unmodified lines
402 authenticate: Default::default(),433 authenticate: Default::default(),
403 #[cfg(any(test, feature = "test-support"))]434 #[cfg(any(test, feature = "test-support"))]
404 establish_connection: Default::default(),435 establish_connection: Default::default(),
405 })436 });
437
438 client
406 }439 }
407 440
408 pub fn id(&self) -> u64 {441 pub fn id(&self) -> u64 {
409 self.id.load(std::sync::atomic::Ordering::SeqCst)442 self.id.load(std::sync::atomic::Ordering::SeqCst)
410 }443 }
411 444
412 pub fn http_client(&self) -> Arc<dyn HttpClient> {445 pub fn http_client(&self) -> Arc<ZedHttpClient> {
413 self.http.clone()446 self.http.clone()
414 }447 }
415 448
@@ -925,14 +958,14 @@ impl Client { · 509 unmodified lines
925 }958 }
926 959
927 async fn get_rpc_url(960 async fn get_rpc_url(
928 http: Arc<dyn HttpClient>,961 http: Arc<ZedHttpClient>,
929 release_channel: Option<ReleaseChannel>,962 release_channel: Option<ReleaseChannel>,
930 ) -> Result<Url> {963 ) -> Result<Url> {
931 if let Some(url) = &*ZED_RPC_URL {964 if let Some(url) = &*ZED_RPC_URL {
932 return Url::parse(url).context("invalid rpc url");965 return Url::parse(url).context("invalid rpc url");
933 }966 }
934 967
935 let mut url = format!("{}/rpc", *ZED_SERVER_URL);968 let mut url = http.zed_url("/rpc");
936 if let Some(preview_param) =969 if let Some(preview_param) =
937 release_channel.and_then(|channel| channel.release_query_param())970 release_channel.and_then(|channel| channel.release_query_param())
938 {971 {
@@ -1053,10 +1086,10 @@ impl Client { · 114 unmodified lines
1053 1086
1054 // Open the Zed sign-in page in the user's browser, with query parameters that indicate1087 // Open the Zed sign-in page in the user's browser, with query parameters that indicate
1055 // that the user is signing in from a Zed app running on the same device.1088 // that the user is signing in from a Zed app running on the same device.
1056 let mut url = format!(1089 let mut url = http.zed_url(&format!(
1057 "{}/native_app_signin?native_app_port={}&native_app_public_key={}",1090 "/native_app_signin?native_app_port={}&native_app_public_key={}",
1058 *ZED_SERVER_URL, port, public_key_string1091 port, public_key_string
1059 );1092 ));
1060 1093
1061 if let Some(impersonate_login) = IMPERSONATE_LOGIN.as_ref() {1094 if let Some(impersonate_login) = IMPERSONATE_LOGIN.as_ref() {
1062 log::info!("impersonating user @{}", impersonate_login);1095 log::info!("impersonating user @{}", impersonate_login);
@@ -1088,7 +1121,7 @@ impl Client { · 25 unmodified lines
1088 }1121 }
1089 1122
1090 let post_auth_url =1123 let post_auth_url =
1091 format!("{}/native_app_signin_succeeded", *ZED_SERVER_URL);1124 http.zed_url("/native_app_signin_succeeded");
1092 req.respond(1125 req.respond(
1093 tiny_http::Response::empty(302).with_header(1126 tiny_http::Response::empty(302).with_header(
1094 tiny_http::Header::from_bytes(1127 tiny_http::Header::from_bytes(
@@ -1130,7 +1163,7 @@ impl Client { · 35 unmodified lines
1130 }1163 }
1131 1164
1132 async fn authenticate_as_admin(1165 async fn authenticate_as_admin(
1133 http: Arc<dyn HttpClient>,1166 http: Arc<ZedHttpClient>,
1134 login: String,1167 login: String,
1135 mut api_token: String,1168 mut api_token: String,
1136 ) -> Result<Credentials> {1169 ) -> Result<Credentials> {
@@ -1351,7 +1384,7 @@ async fn read_credentials_from_keychain(cx: &AsyncAppContext) -> Option<Credenti · 214 unmodified lines
1351 }1384 }
1352 1385
1353 let (user_id, access_token) = cx1386 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))
1355 .log_err()?1388 .log_err()?
1356 .await1389 .await
1357 .log_err()??;1390 .log_err()??;
@@ -1368,7 +1401,7 @@ async fn write_credentials_to_keychain( · 10 unmodified lines
1368) -> Result<()> {1401) -> Result<()> {
1369 cx.update(move |cx| {1402 cx.update(move |cx| {
1370 cx.write_credentials(1403 cx.write_credentials(
1371 &ZED_SERVER_URL,1404 &ClientSettings::get_global(cx).server_url,
1372 &credentials.user_id.to_string(),1405 &credentials.user_id.to_string(),
1373 credentials.access_token.as_bytes(),1406 credentials.access_token.as_bytes(),
1374 )1407 )
@@ -1377,7 +1410,7 @@ async fn write_credentials_to_keychain( · 2 unmodified lines
1377}1410}
1378 1411
1379async fn delete_credentials_from_keychain(cx: &AsyncAppContext) -> Result<()> {1412async 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))?
1381 .await1414 .await
1382}1415}
1383 1416
@@ -1684,6 +1717,7 @@ mod tests { · 300 unmodified lines
1684 cx.update(|cx| {1717 cx.update(|cx| {
1685 let settings_store = SettingsStore::test(cx);1718 let settings_store = SettingsStore::test(cx);
1686 cx.set_global(settings_store);1719 cx.set_global(settings_store);
1720 init_settings(cx);
1687 });1721 });
1688 }1722 }
1689}1723}
crates/client/src/telemetry.rs+5−12
@@ -1,10 +1,9 @@
1mod event_coalescer;1mod event_coalescer;
2 2
3use crate::{TelemetrySettings, ZED_SERVER_URL};3use crate::TelemetrySettings;
4use chrono::{DateTime, Utc};4use chrono::{DateTime, Utc};
5use futures::Future;5use futures::Future;
6use gpui::{AppContext, AppMetadata, BackgroundExecutor, Task};6use gpui::{AppContext, AppMetadata, BackgroundExecutor, Task};
7use lazy_static::lazy_static;
8use parking_lot::Mutex;7use parking_lot::Mutex;
9use serde::Serialize;8use serde::Serialize;
10use settings::{Settings, SettingsStore};9use settings::{Settings, SettingsStore};
@@ -13,7 +12,7 @@ use sysinfo::{ · 2 unmodified lines
13 CpuRefreshKind, Pid, PidExt, ProcessExt, ProcessRefreshKind, RefreshKind, System, SystemExt,12 CpuRefreshKind, Pid, PidExt, ProcessExt, ProcessRefreshKind, RefreshKind, System, SystemExt,
14};13};
15use tempfile::NamedTempFile;14use tempfile::NamedTempFile;
16use util::http::HttpClient;15use util::http::{HttpClient, ZedHttpClient};
17#[cfg(not(debug_assertions))]16#[cfg(not(debug_assertions))]
18use util::ResultExt;17use util::ResultExt;
19use util::{channel::ReleaseChannel, TryFutureExt};18use util::{channel::ReleaseChannel, TryFutureExt};
@@ -21,7 +20,7 @@ use util::{channel::ReleaseChannel, TryFutureExt}; · 1 unmodified line
21use self::event_coalescer::EventCoalescer;20use self::event_coalescer::EventCoalescer;
22 21
23pub struct Telemetry {22pub struct Telemetry {
24 http_client: Arc<dyn HttpClient>,23 http_client: Arc<ZedHttpClient>,
25 executor: BackgroundExecutor,24 executor: BackgroundExecutor,
26 state: Arc<Mutex<TelemetryState>>,25 state: Arc<Mutex<TelemetryState>>,
27}26}
@@ -43,12 +42,6 @@ struct TelemetryState { · 15 unmodified lines
43 max_queue_size: usize,42 max_queue_size: usize,
44}43}
45 44
46const EVENTS_URL_PATH: &'static str = "/api/events";
47
48lazy_static! {
49 static ref EVENTS_URL: String = format!("{}{}", *ZED_SERVER_URL, EVENTS_URL_PATH);
50}
51
52#[derive(Serialize, Debug)]45#[derive(Serialize, Debug)]
53struct EventRequestBody {46struct EventRequestBody {
54 installation_id: Option<Arc<str>>,47 installation_id: Option<Arc<str>>,
@@ -149,7 +142,7 @@ const FLUSH_INTERVAL: Duration = Duration::from_secs(1); · 94 unmodified lines
149const FLUSH_INTERVAL: Duration = Duration::from_secs(60 * 5);142const FLUSH_INTERVAL: Duration = Duration::from_secs(60 * 5);
150 143
151impl Telemetry {144impl 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> {
153 let release_channel = cx146 let release_channel = cx
154 .try_global::<ReleaseChannel>()147 .try_global::<ReleaseChannel>()
155 .map(|release_channel| release_channel.display_name());148 .map(|release_channel| release_channel.display_name());
@@ -548,7 +541,7 @@ impl Telemetry { · 392 unmodified lines
548 }541 }
549 542
550 this.http_client543 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())
552 .await?;545 .await?;
553 anyhow::Ok(())546 anyhow::Ok(())
554 }547 }
crates/collab/src/tests/test_server.rs+1−0
@@ -153,6 +153,7 @@ impl TestServer { · 152 unmodified lines
153 }153 }
154 let settings = SettingsStore::test(cx);154 let settings = SettingsStore::test(cx);
155 cx.set_global(settings);155 cx.set_global(settings);
156 client::init_settings(cx);
156 });157 });
157 158
158 let http = FakeHttpClient::with_404_response();159 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
2 2
3use anyhow::{anyhow, bail};3use anyhow::{anyhow, bail};
4use bitflags::bitflags;4use bitflags::bitflags;
5use client::{Client, ZED_SERVER_URL};5use client::Client;
6use db::kvp::KEY_VALUE_STORE;6use db::kvp::KEY_VALUE_STORE;
7use editor::{Editor, EditorEvent};7use editor::{Editor, EditorEvent};
8use futures::AsyncReadExt;8use futures::AsyncReadExt;
@@ -16,7 +16,7 @@ use project::Project; · 7 unmodified lines
16use regex::Regex;16use regex::Regex;
17use serde_derive::Serialize;17use serde_derive::Serialize;
18use ui::{prelude::*, Button, ButtonStyle, IconPosition, Tooltip};18use ui::{prelude::*, Button, ButtonStyle, IconPosition, Tooltip};
19use util::ResultExt;19use util::{http::HttpClient, ResultExt};
20use workspace::{ModalView, Toast, Workspace};20use workspace::{ModalView, Toast, Workspace};
21 21
22use crate::{system_specs::SystemSpecs, GiveFeedback, OpenZedRepo};22use crate::{system_specs::SystemSpecs, GiveFeedback, OpenZedRepo};
@@ -293,12 +293,12 @@ impl FeedbackModal { · 270 unmodified lines
293 }293 }
294 }294 }
295 295
296 let feedback_endpoint = format!("{}/api/feedback", *ZED_SERVER_URL);
297 let telemetry = zed_client.telemetry();296 let telemetry = zed_client.telemetry();
298 let metrics_id = telemetry.metrics_id();297 let metrics_id = telemetry.metrics_id();
299 let installation_id = telemetry.installation_id();298 let installation_id = telemetry.installation_id();
300 let is_staff = telemetry.is_staff();299 let is_staff = telemetry.is_staff();
301 let http_client = zed_client.http_client();300 let http_client = zed_client.http_client();
301 let feedback_endpoint = http_client.zed_url("/api/feedback");
302 let request = FeedbackRequestBody {302 let request = FeedbackRequestBody {
303 feedback_text: &feedback_text,303 feedback_text: &feedback_text,
304 email,304 email,
crates/settings/src/settings_store.rs+40−2
@@ -1,6 +1,6 @@
1use anyhow::{anyhow, Context, Result};1use anyhow::{anyhow, Context, Result};
2use collections::{btree_map, hash_map, BTreeMap, HashMap};2use collections::{btree_map, hash_map, BTreeMap, HashMap};
3use gpui::AppContext;3use gpui::{AppContext, AsyncAppContext};
4use lazy_static::lazy_static;4use lazy_static::lazy_static;
5use schemars::{gen::SchemaGenerator, schema::RootSchema, JsonSchema};5use schemars::{gen::SchemaGenerator, schema::RootSchema, JsonSchema};
6use serde::{de::DeserializeOwned, Deserialize as _, Serialize};6use serde::{de::DeserializeOwned, Deserialize as _, Serialize};
@@ -13,7 +13,9 @@ use std::{ · 6 unmodified lines
13 str,13 str,
14 sync::Arc,14 sync::Arc,
15};15};
16use util::{merge_non_null_json_value_into, RangeExt, ResultExt as _};16use util::{
17 channel::RELEASE_CHANNEL_NAME, merge_non_null_json_value_into, RangeExt, ResultExt as _,
18};
17 19
18/// A value that can be defined as a user setting.20/// A value that can be defined as a user setting.
19///21///
@@ -102,6 +104,14 @@ pub trait Settings: 'static + Send + Sync { · 82 unmodified lines
102 cx.global::<SettingsStore>().get(None)104 cx.global::<SettingsStore>().get(None)
103 }105 }
104 106
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
105 #[track_caller]115 #[track_caller]
106 fn override_global<'a>(settings: Self, cx: &'a mut AppContext)116 fn override_global<'a>(settings: Self, cx: &'a mut AppContext)
107 where117 where
@@ -197,6 +207,15 @@ impl SettingsStore { · 89 unmodified lines
197 user_values_stack = vec![user_settings];207 user_values_stack = vec![user_settings];
198 }208 }
199 209
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
200 if let Some(setting) = setting_value219 if let Some(setting) = setting_value
201 .load_setting(&default_settings, &user_values_stack, cx)220 .load_setting(&default_settings, &user_values_stack, cx)
202 .context("A default setting must be added to the `default.json` file")221 .context("A default setting must be added to the `default.json` file")
@@ -484,6 +503,15 @@ impl SettingsStore { · 281 unmodified lines
484 }503 }
485 }504 }
486 505
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
487 serde_json::to_value(&combined_schema).unwrap()515 serde_json::to_value(&combined_schema).unwrap()
488 }516 }
489 517
@@ -509,6 +537,16 @@ impl SettingsStore { · 19 unmodified lines
509 paths_stack.push(None);537 paths_stack.push(None);
510 }538 }
511 539
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
512 // If the global settings file changed, reload the global value for the field.550 // If the global settings file changed, reload the global value for the field.
513 if changed_local_path.is_none() {551 if changed_local_path.is_none() {
514 if let Some(value) = setting_value552 if let Some(value) = setting_value
crates/util/Cargo.toml+1−0
@@ -30,6 +30,7 @@ serde_json.workspace = true · 29 unmodified lines
30git2 = { workspace = true, optional = true }30git2 = { workspace = true, optional = true }
31dirs = "3.0"31dirs = "3.0"
32take-until = "0.2.0"32take-until = "0.2.0"
33parking_lot.workspace = true
33 34
34[target.'cfg(windows)'.dependencies]35[target.'cfg(windows)'.dependencies]
35tendril = "0.4.3"36tendril = "0.4.3"
crates/util/src/http.rs+45−5
@@ -6,12 +6,49 @@ pub use isahc::{ · 5 unmodified lines
6 Error,6 Error,
7};7};
8pub use isahc::{AsyncBody, Request, Response};8pub use isahc::{AsyncBody, Request, Response};
9use parking_lot::Mutex;
9use smol::future::FutureExt;10use smol::future::FutureExt;
10#[cfg(feature = "test-support")]11#[cfg(feature = "test-support")]
11use std::fmt;12use std::fmt;
12use std::{sync::Arc, time::Duration};13use std::{sync::Arc, time::Duration};
13pub use url::Url;14pub use url::Url;
14 15
16pub struct ZedHttpClient {
17 pub zed_host: Mutex<String>,
18 client: Box<dyn HttpClient>,
19}
20
21impl ZedHttpClient {
22 pub fn zed_url(&self, path: &str) -> String {
23 format!("{}{}", self.zed_host.lock(), path)
24 }
25}
26
27impl HttpClient for Arc<ZedHttpClient> {
28 fn send(&self, req: Request<AsyncBody>) -> BoxFuture<Result<Response<AsyncBody>, Error>> {
29 self.client.send(req)
30 }
31}
32
33impl HttpClient for ZedHttpClient {
34 fn send(&self, req: Request<AsyncBody>) -> BoxFuture<Result<Response<AsyncBody>, Error>> {
35 self.client.send(req)
36 }
37}
38
39pub 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
15pub trait HttpClient: Send + Sync {52pub trait HttpClient: Send + Sync {
16 fn send(&self, req: Request<AsyncBody>) -> BoxFuture<Result<Response<AsyncBody>, Error>>;53 fn send(&self, req: Request<AsyncBody>) -> BoxFuture<Result<Response<AsyncBody>, Error>>;
17 54
@@ -81,17 +118,20 @@ pub struct FakeHttpClient { · 63 unmodified lines
81 118
82#[cfg(feature = "test-support")]119#[cfg(feature = "test-support")]
83impl FakeHttpClient {120impl FakeHttpClient {
84 pub fn create<Fut, F>(handler: F) -> Arc<dyn HttpClient>121 pub fn create<Fut, F>(handler: F) -> Arc<ZedHttpClient>
85 where122 where
86 Fut: 'static + Send + futures::Future<Output = Result<Response<AsyncBody>, Error>>,123 Fut: 'static + Send + futures::Future<Output = Result<Response<AsyncBody>, Error>>,
87 F: 'static + Send + Sync + Fn(Request<AsyncBody>) -> Fut,124 F: 'static + Send + Sync + Fn(Request<AsyncBody>) -> Fut,
88 {125 {
89 Arc::new(Self {126 Arc::new(ZedHttpClient {
90 handler: Box::new(move |req| Box::pin(handler(req))),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 }),
91 })131 })
92 }132 }
93 133
94 pub fn with_404_response() -> Arc<dyn HttpClient> {134 pub fn with_404_response() -> Arc<ZedHttpClient> {
95 Self::create(|_| async move {135 Self::create(|_| async move {
96 Ok(Response::builder()136 Ok(Response::builder()
97 .status(404)137 .status(404)
@@ -100,7 +140,7 @@ impl FakeHttpClient { · 2 unmodified lines
100 })140 })
101 }141 }
102 142
103 pub fn with_200_response() -> Arc<dyn HttpClient> {143 pub fn with_200_response() -> Arc<ZedHttpClient> {
104 Self::create(|_| async move {144 Self::create(|_| async move {
105 Ok(Response::builder()145 Ok(Response::builder()
106 .status(200)146 .status(200)
crates/zed/src/main.rs+24−9
@@ -42,7 +42,7 @@ use theme::{ActiveTheme, ThemeRegistry, ThemeSettings}; · 41 unmodified lines
42use util::{42use util::{
43 async_maybe,43 async_maybe,
44 channel::{parse_zed_link, AppCommitSha, ReleaseChannel, RELEASE_CHANNEL},44 channel::{parse_zed_link, AppCommitSha, ReleaseChannel, RELEASE_CHANNEL},
45 http::{self, HttpClient},45 http::{self, HttpClient, ZedHttpClient},
46 paths::{self, CRASHES_DIR, CRASHES_RETIRED_DIR},46 paths::{self, CRASHES_DIR, CRASHES_RETIRED_DIR},
47 ResultExt,47 ResultExt,
48};48};
@@ -59,7 +59,6 @@ fn main() { · 10 unmodified lines
59 menu::init();59 menu::init();
60 zed_actions::init();60 zed_actions::init();
61 61
62 let http = http::client();
63 init_paths();62 init_paths();
64 init_logger();63 init_logger();
65 64
@@ -132,6 +131,9 @@ fn main() { · 66 unmodified lines
132 cx.set_global(store);131 cx.set_global(store);
133 handle_settings_file_changes(user_settings_file_rx, cx);132 handle_settings_file_changes(user_settings_file_rx, cx);
134 handle_keymap_file_changes(user_keymap_file_rx, cx);133 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);
135 137
136 let client = client::Client::new(http.clone(), cx);138 let client = client::Client::new(http.clone(), cx);
137 let mut languages = LanguageRegistry::new(login_shell_env_loaded);139 let mut languages = LanguageRegistry::new(login_shell_env_loaded);
@@ -201,7 +203,20 @@ fn main() { · 63 unmodified lines
201 languages.set_theme(cx.theme().clone());203 languages.set_theme(cx.theme().clone());
202 cx.observe_global::<SettingsStore>({204 cx.observe_global::<SettingsStore>({
203 let languages = languages.clone();205 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 }
205 })220 })
206 .detach();221 .detach();
207 222
@@ -230,7 +245,7 @@ fn main() { · 22 unmodified lines
230 cx.set_global(Arc::downgrade(&app_state));245 cx.set_global(Arc::downgrade(&app_state));
231 246
232 audio::init(Assets, cx);247 audio::init(Assets, cx);
233 auto_update::init(http.clone(), client::ZED_SERVER_URL.clone(), cx);248 auto_update::init(http.clone(), cx);
234 249
235 workspace::init(app_state.clone(), cx);250 workspace::init(app_state.clone(), cx);
236 recent_projects::init(cx);251 recent_projects::init(cx);
@@ -634,7 +649,7 @@ fn init_panic_hook(app: &App, installation_id: Option<String>, session_id: Strin · 397 unmodified lines
634 }));649 }));
635}650}
636 651
637fn upload_panics_and_crashes(http: Arc<dyn HttpClient>, cx: &mut AppContext) {652fn upload_panics_and_crashes(http: Arc<ZedHttpClient>, cx: &mut AppContext) {
638 let telemetry_settings = *client::TelemetrySettings::get_global(cx);653 let telemetry_settings = *client::TelemetrySettings::get_global(cx);
639 cx.background_executor()654 cx.background_executor()
640 .spawn(async move {655 .spawn(async move {
@@ -650,10 +665,10 @@ fn upload_panics_and_crashes(http: Arc<dyn HttpClient>, cx: &mut AppContext) { · 9 unmodified lines
650 665
651/// upload panics to us (via zed.dev)666/// upload panics to us (via zed.dev)
652async fn upload_previous_panics(667async fn upload_previous_panics(
653 http: Arc<dyn HttpClient>,668 http: Arc<ZedHttpClient>,
654 telemetry_settings: client::TelemetrySettings,669 telemetry_settings: client::TelemetrySettings,
655) -> Result<()> {670) -> Result<()> {
656 let panic_report_url = format!("{}/api/panic", &*client::ZED_SERVER_URL);671 let panic_report_url = http.zed_url("/api/panic");
657 let mut children = smol::fs::read_dir(&*paths::LOGS_DIR).await?;672 let mut children = smol::fs::read_dir(&*paths::LOGS_DIR).await?;
658 while let Some(child) = children.next().await {673 while let Some(child) = children.next().await {
659 let child = child?;674 let child = child?;
@@ -717,7 +732,7 @@ static LAST_CRASH_UPLOADED: &'static str = "LAST_CRASH_UPLOADED"; · 57 unmodified lines
717/// upload crashes from apple's diagnostic reports to our server.732/// upload crashes from apple's diagnostic reports to our server.
718/// (only if telemetry is enabled)733/// (only if telemetry is enabled)
719async fn upload_previous_crashes(734async fn upload_previous_crashes(
720 http: Arc<dyn HttpClient>,735 http: Arc<ZedHttpClient>,
721 telemetry_settings: client::TelemetrySettings,736 telemetry_settings: client::TelemetrySettings,
722) -> Result<()> {737) -> Result<()> {
723 if !telemetry_settings.diagnostics {738 if !telemetry_settings.diagnostics {
@@ -728,7 +743,7 @@ async fn upload_previous_crashes( · 4 unmodified lines
728 .unwrap_or("zed-2024-01-17-221900.ips".to_string()); // don't upload old crash reports from before we had this.743 .unwrap_or("zed-2024-01-17-221900.ips".to_string()); // don't upload old crash reports from before we had this.
729 let mut uploaded = last_uploaded.clone();744 let mut uploaded = last_uploaded.clone();
730 745
731 let crash_report_url = format!("{}/api/crash", &*client::ZED_SERVER_URL);746 let crash_report_url = http.zed_url("/api/crash");
732 747
733 for dir in [&*CRASHES_DIR, &*CRASHES_RETIRED_DIR] {748 for dir in [&*CRASHES_DIR, &*CRASHES_RETIRED_DIR] {
734 let mut children = smol::fs::read_dir(&dir).await?;749 let mut children = smol::fs::read_dir(&dir).await?;