Pull app / OS info out of GPUI, add Linux information, make fallible window initialization (#12869)

TODO:
- [x] Finish GPUI changes on other operating systems 

This is a largely internal change to how we report data to our
diagnostics and telemetry. This PR also includes an update to our blade
backend which allows us to report errors in a more useful way when
failing to initialize blade.


Release Notes:

- N/A

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
Mikayla Maki 2024-06-11 11:43:12 -07:00 committed by GitHub
parent ec9e700e70
commit 80c14c9198
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
50 changed files with 571 additions and 550 deletions

10
Cargo.lock generated
View file

@ -1512,7 +1512,7 @@ dependencies = [
[[package]] [[package]]
name = "blade-graphics" name = "blade-graphics"
version = "0.4.0" version = "0.4.0"
source = "git+https://github.com/kvark/blade?rev=bdaf8c534fbbc9fbca71d1cf272f45640b3a068d#bdaf8c534fbbc9fbca71d1cf272f45640b3a068d" source = "git+https://github.com/zed-industries/blade?rev=33fd51359d113c03b785e28f4a6cf75bacb0b26d#33fd51359d113c03b785e28f4a6cf75bacb0b26d"
dependencies = [ dependencies = [
"ash", "ash",
"ash-window", "ash-window",
@ -1542,7 +1542,7 @@ dependencies = [
[[package]] [[package]]
name = "blade-macros" name = "blade-macros"
version = "0.2.1" version = "0.2.1"
source = "git+https://github.com/kvark/blade?rev=bdaf8c534fbbc9fbca71d1cf272f45640b3a068d#bdaf8c534fbbc9fbca71d1cf272f45640b3a068d" source = "git+https://github.com/zed-industries/blade?rev=33fd51359d113c03b785e28f4a6cf75bacb0b26d#33fd51359d113c03b785e28f4a6cf75bacb0b26d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1552,7 +1552,7 @@ dependencies = [
[[package]] [[package]]
name = "blade-util" name = "blade-util"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/kvark/blade?rev=bdaf8c534fbbc9fbca71d1cf272f45640b3a068d#bdaf8c534fbbc9fbca71d1cf272f45640b3a068d" source = "git+https://github.com/zed-industries/blade?rev=33fd51359d113c03b785e28f4a6cf75bacb0b26d#33fd51359d113c03b785e28f4a6cf75bacb0b26d"
dependencies = [ dependencies = [
"blade-graphics", "blade-graphics",
"bytemuck", "bytemuck",
@ -2210,8 +2210,10 @@ dependencies = [
"async-tungstenite", "async-tungstenite",
"chrono", "chrono",
"clock", "clock",
"cocoa",
"collections", "collections",
"feature_flags", "feature_flags",
"fs",
"futures 0.3.28", "futures 0.3.28",
"gpui", "gpui",
"http 0.1.0", "http 0.1.0",
@ -2238,6 +2240,7 @@ dependencies = [
"tiny_http", "tiny_http",
"url", "url",
"util", "util",
"windows 0.56.0",
] ]
[[package]] [[package]]
@ -13168,6 +13171,7 @@ version = "0.140.0"
dependencies = [ dependencies = [
"activity_indicator", "activity_indicator",
"anyhow", "anyhow",
"ashpd",
"assets", "assets",
"assistant", "assistant",
"audio", "audio",

View file

@ -268,14 +268,15 @@ async-tar = "0.4.2"
async-trait = "0.1" async-trait = "0.1"
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] } async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
bitflags = "2.4.2" bitflags = "2.4.2"
blade-graphics = { git = "https://github.com/kvark/blade", rev = "bdaf8c534fbbc9fbca71d1cf272f45640b3a068d" } blade-graphics = { git = "https://github.com/zed-industries/blade", rev = "33fd51359d113c03b785e28f4a6cf75bacb0b26d" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "bdaf8c534fbbc9fbca71d1cf272f45640b3a068d" } blade-macros = { git = "https://github.com/zed-industries/blade", rev = "33fd51359d113c03b785e28f4a6cf75bacb0b26d" }
blade-util = { git = "https://github.com/kvark/blade", rev = "bdaf8c534fbbc9fbca71d1cf272f45640b3a068d" } blade-util = { git = "https://github.com/zed-industries/blade", rev = "33fd51359d113c03b785e28f4a6cf75bacb0b26d" }
cap-std = "3.0" cap-std = "3.0"
cargo_toml = "0.20" cargo_toml = "0.20"
chrono = { version = "0.4", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] }
clap = { version = "4.4", features = ["derive"] } clap = { version = "4.4", features = ["derive"] }
clickhouse = { version = "0.11.6" } clickhouse = { version = "0.11.6" }
cocoa = "0.25"
ctor = "0.2.6" ctor = "0.2.6"
signal-hook = "0.3.17" signal-hook = "0.3.17"
core-foundation = { version = "0.9.3" } core-foundation = { version = "0.9.3" }

View file

@ -97,7 +97,7 @@ pub fn open_prompt_library(
}, },
|cx| cx.new_view(|cx| PromptLibrary::new(store, language_registry, cx)), |cx| cx.new_view(|cx| PromptLibrary::new(store, language_registry, cx)),
) )
}) })?
}) })
} }
} }

View file

@ -3,7 +3,7 @@ use crate::channel_chat::ChannelChatEvent;
use super::*; use super::*;
use client::{test::FakeServer, Client, UserStore}; use client::{test::FakeServer, Client, UserStore};
use clock::FakeSystemClock; use clock::FakeSystemClock;
use gpui::{AppContext, Context, Model, TestAppContext}; use gpui::{AppContext, Context, Model, SemanticVersion, TestAppContext};
use http::FakeHttpClient; use http::FakeHttpClient;
use rpc::proto::{self}; use rpc::proto::{self};
use settings::SettingsStore; use settings::SettingsStore;
@ -340,7 +340,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
fn init_test(cx: &mut AppContext) -> Model<ChannelStore> { fn init_test(cx: &mut AppContext) -> Model<ChannelStore> {
let settings_store = SettingsStore::test(cx); let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store); cx.set_global(settings_store);
release_channel::init("0.0.0", cx); release_channel::init(SemanticVersion::default(), cx);
client::init_settings(cx); client::init_settings(cx);
let clock = Arc::new(FakeSystemClock::default()); let clock = Arc::new(FakeSystemClock::default());

View file

@ -24,6 +24,7 @@ chrono = { workspace = true, features = ["serde"] }
clock.workspace = true clock.workspace = true
collections.workspace = true collections.workspace = true
feature_flags.workspace = true feature_flags.workspace = true
fs.workspace = true
futures.workspace = true futures.workspace = true
gpui.workspace = true gpui.workspace = true
http.workspace = true http.workspace = true
@ -60,6 +61,12 @@ settings = { workspace = true, features = ["test-support"] }
util = { workspace = true, features = ["test-support"] } util = { workspace = true, features = ["test-support"] }
http = { workspace = true, features = ["test-support"] } http = { workspace = true, features = ["test-support"] }
[target.'cfg(target_os = "windows")'.dependencies]
windows.workspace = true
[target.'cfg(target_os = "macos")'.dependencies]
cocoa.workspace = true
[target.'cfg(target_os = "linux")'.dependencies] [target.'cfg(target_os = "linux")'.dependencies]
async-native-tls = {"version" = "0.5.0", features = ["vendored"]} async-native-tls = {"version" = "0.5.0", features = ["vendored"]}
# This is an indirect dependency of async-tungstenite that is included # This is an indirect dependency of async-tungstenite that is included

View file

@ -4,7 +4,7 @@ use crate::{ChannelId, TelemetrySettings};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use clock::SystemClock; use clock::SystemClock;
use futures::Future; use futures::Future;
use gpui::{AppContext, AppMetadata, BackgroundExecutor, Task}; use gpui::{AppContext, BackgroundExecutor, Task};
use http::{self, HttpClient, HttpClientWithUrl, Method}; use http::{self, HttpClient, HttpClientWithUrl, Method};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use parking_lot::Mutex; use parking_lot::Mutex;
@ -39,7 +39,6 @@ struct TelemetryState {
installation_id: Option<Arc<str>>, // Per app installation (different for dev, nightly, preview, and stable) installation_id: Option<Arc<str>>, // Per app installation (different for dev, nightly, preview, and stable)
session_id: Option<String>, // Per app launch session_id: Option<String>, // Per app launch
release_channel: Option<&'static str>, release_channel: Option<&'static str>,
app_metadata: AppMetadata,
architecture: &'static str, architecture: &'static str,
events_queue: Vec<EventWrapper>, events_queue: Vec<EventWrapper>,
flush_events_task: Option<Task<()>>, flush_events_task: Option<Task<()>>,
@ -48,6 +47,10 @@ struct TelemetryState {
first_event_date_time: Option<DateTime<Utc>>, first_event_date_time: Option<DateTime<Utc>>,
event_coalescer: EventCoalescer, event_coalescer: EventCoalescer,
max_queue_size: usize, max_queue_size: usize,
os_name: String,
app_version: String,
os_version: Option<String>,
} }
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
@ -71,6 +74,87 @@ static ZED_CLIENT_CHECKSUM_SEED: Lazy<Option<Vec<u8>>> = Lazy::new(|| {
}) })
}); });
pub fn os_name() -> String {
#[cfg(target_os = "macos")]
{
"macOS".to_string()
}
#[cfg(target_os = "linux")]
{
format!("Linux {}", gpui::guess_compositor())
}
#[cfg(target_os = "windows")]
{
"Windows".to_string()
}
}
/// Note: This might do blocking IO! Only call from background threads
pub fn os_version() -> String {
#[cfg(target_os = "macos")]
{
use cocoa::base::nil;
use cocoa::foundation::NSProcessInfo;
unsafe {
let process_info = cocoa::foundation::NSProcessInfo::processInfo(nil);
let version = process_info.operatingSystemVersion();
gpui::SemanticVersion::new(
version.majorVersion as usize,
version.minorVersion as usize,
version.patchVersion as usize,
)
.to_string()
}
}
#[cfg(target_os = "linux")]
{
use std::path::Path;
let content = if let Ok(file) = std::fs::read_to_string(&Path::new("/etc/os-release")) {
file
} else if let Ok(file) = std::fs::read_to_string(&Path::new("/usr/lib/os-release")) {
file
} else {
log::error!("Failed to load /etc/os-release, /usr/lib/os-release");
"".to_string()
};
let mut name = "unknown".to_string();
let mut version = "unknown".to_string();
for line in content.lines() {
if line.starts_with("ID=") {
name = line.trim_start_matches("ID=").trim_matches('"').to_string();
}
if line.starts_with("VERSION_ID=") {
version = line
.trim_start_matches("VERSION_ID=")
.trim_matches('"')
.to_string();
}
}
format!("{} {}", name, version)
}
#[cfg(target_os = "windows")]
{
let mut info = unsafe { std::mem::zeroed() };
let status = unsafe { windows::Wdk::System::SystemServices::RtlGetVersion(&mut info) };
if status.is_ok() {
gpui::SemanticVersion::new(
info.dwMajorVersion as _,
info.dwMinorVersion as _,
info.dwBuildNumber as _,
)
.to_string()
} else {
"unknown".to_string()
}
}
}
impl Telemetry { impl Telemetry {
pub fn new( pub fn new(
clock: Arc<dyn SystemClock>, clock: Arc<dyn SystemClock>,
@ -84,7 +168,6 @@ impl Telemetry {
let state = Arc::new(Mutex::new(TelemetryState { let state = Arc::new(Mutex::new(TelemetryState {
settings: *TelemetrySettings::get_global(cx), settings: *TelemetrySettings::get_global(cx),
app_metadata: cx.app_metadata(),
architecture: env::consts::ARCH, architecture: env::consts::ARCH,
release_channel, release_channel,
installation_id: None, installation_id: None,
@ -97,6 +180,10 @@ impl Telemetry {
first_event_date_time: None, first_event_date_time: None,
event_coalescer: EventCoalescer::new(clock.clone()), event_coalescer: EventCoalescer::new(clock.clone()),
max_queue_size: MAX_QUEUE_LEN, max_queue_size: MAX_QUEUE_LEN,
os_version: None,
os_name: os_name(),
app_version: release_channel::AppVersion::global(cx).to_string(),
})); }));
#[cfg(not(debug_assertions))] #[cfg(not(debug_assertions))]
@ -168,6 +255,9 @@ impl Telemetry {
let mut state = self.state.lock(); let mut state = self.state.lock();
state.installation_id = installation_id.map(|id| id.into()); state.installation_id = installation_id.map(|id| id.into());
state.session_id = Some(session_id); state.session_id = Some(session_id);
state.app_version = release_channel::AppVersion::global(cx).to_string();
state.os_name = os_version();
drop(state); drop(state);
let this = self.clone(); let this = self.clone();
@ -445,20 +535,14 @@ impl Telemetry {
{ {
let state = this.state.lock(); let state = this.state.lock();
let request_body = EventRequestBody { let request_body = EventRequestBody {
installation_id: state.installation_id.as_deref().map(Into::into), installation_id: state.installation_id.as_deref().map(Into::into),
session_id: state.session_id.clone(), session_id: state.session_id.clone(),
is_staff: state.is_staff, is_staff: state.is_staff,
app_version: state app_version: state.app_version.clone(),
.app_metadata os_name: state.os_name.clone(),
.app_version os_version: state.os_version.clone(),
.unwrap_or_default()
.to_string(),
os_name: state.app_metadata.os_name.to_string(),
os_version: state
.app_metadata
.os_version
.map(|version| version.to_string()),
architecture: state.architecture.to_string(), architecture: state.architecture.to_string(),
release_channel: state.release_channel.map(Into::into), release_channel: state.release_channel.map(Into::into),

View file

@ -308,6 +308,14 @@ pub async fn post_panic(
.map_err(|_| Error::Http(StatusCode::BAD_REQUEST, "invalid json".into()))?; .map_err(|_| Error::Http(StatusCode::BAD_REQUEST, "invalid json".into()))?;
let panic = report.panic; let panic = report.panic;
// better OS reporting for linux (because linux is hard):
// - Remove os_version/app_version/os_name from the gpui platform trait
// - Move platform processing data into client/telemetry
// - Duplicate some small code in macOS platform for a version check
// - Add GPUI API for reporting the selected platform integration
// - macos-blade, macos-metal, linux-X11, linux-headless
// if cfg(macos( { "Macos" } else { "Linux-{cx.compositor_name()"} ))
tracing::error!( tracing::error!(
service = "client", service = "client",
version = %panic.app_version, version = %panic.app_version,

View file

@ -161,7 +161,7 @@ impl TestServer {
} }
let settings = SettingsStore::test(cx); let settings = SettingsStore::test(cx);
cx.set_global(settings); cx.set_global(settings);
release_channel::init("0.0.0", cx); release_channel::init(SemanticVersion::default(), cx);
client::init_settings(cx); client::init_settings(cx);
}); });
@ -327,7 +327,7 @@ impl TestServer {
} }
let settings = SettingsStore::test(cx); let settings = SettingsStore::test(cx);
cx.set_global(settings); cx.set_global(settings);
release_channel::init("0.0.0", cx); release_channel::init(SemanticVersion::default(), cx);
client::init_settings(cx); client::init_settings(cx);
}); });
let (dev_server_id, _) = split_dev_server_token(&access_token).unwrap(); let (dev_server_id, _) = split_dev_server_token(&access_token).unwrap();

View file

@ -8,6 +8,7 @@ use settings::Settings;
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
use theme::ThemeSettings; use theme::ThemeSettings;
use ui::{prelude::*, Button, Label}; use ui::{prelude::*, Button, Label};
use util::ResultExt;
use workspace::AppState; use workspace::AppState;
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) { pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
@ -27,16 +28,21 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
for screen in cx.displays() { for screen in cx.displays() {
let options = notification_window_options(screen, window_size, cx); let options = notification_window_options(screen, window_size, cx);
let window = cx.open_window(options, |cx| { let Some(window) = cx
cx.new_view(|_| { .open_window(options, |cx| {
ProjectSharedNotification::new( cx.new_view(|_| {
owner.clone(), ProjectSharedNotification::new(
*project_id, owner.clone(),
worktree_root_names.clone(), *project_id,
app_state.clone(), worktree_root_names.clone(),
) app_state.clone(),
)
})
}) })
}); .log_err()
else {
continue;
};
notification_windows notification_windows
.entry(*project_id) .entry(*project_id)
.or_insert(Vec::new()) .or_insert(Vec::new())

View file

@ -10,7 +10,8 @@ use crate::{
}; };
use futures::StreamExt; use futures::StreamExt;
use gpui::{ use gpui::{
div, AssetSource, TestAppContext, UpdateGlobal, VisualTestContext, WindowBounds, WindowOptions, div, AssetSource, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext,
WindowBounds, WindowOptions,
}; };
use indoc::indoc; use indoc::indoc;
use language::{ use language::{
@ -511,6 +512,7 @@ fn test_clone(cx: &mut TestAppContext) {
.update(cx, |editor, cx| { .update(cx, |editor, cx| {
cx.open_window(Default::default(), |cx| cx.new_view(|cx| editor.clone(cx))) cx.open_window(Default::default(), |cx| cx.new_view(|cx| editor.clone(cx)))
}) })
.unwrap()
.unwrap(); .unwrap();
let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)).unwrap(); let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
@ -7659,6 +7661,7 @@ async fn test_following(cx: &mut gpui::TestAppContext) {
}, },
|cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)), |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
) )
.unwrap()
}); });
let is_still_following = Rc::new(RefCell::new(true)); let is_still_following = Rc::new(RefCell::new(true));
@ -12195,7 +12198,7 @@ pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsC
let store = SettingsStore::test(cx); let store = SettingsStore::test(cx);
cx.set_global(store); cx.set_global(store);
theme::init(theme::LoadThemes::JustBase, cx); theme::init(theme::LoadThemes::JustBase, cx);
release_channel::init("0.0.0", cx); release_channel::init(SemanticVersion::default(), cx);
client::init_settings(cx); client::init_settings(cx);
language::init(cx); language::init(cx);
Project::init_settings(cx); Project::init_settings(cx);

View file

@ -1268,7 +1268,7 @@ pub mod tests {
ExcerptRange, ExcerptRange,
}; };
use futures::StreamExt; use futures::StreamExt;
use gpui::{Context, TestAppContext, WindowHandle}; use gpui::{Context, SemanticVersion, TestAppContext, WindowHandle};
use itertools::Itertools; use itertools::Itertools;
use language::{ use language::{
language_settings::AllLanguageSettingsContent, Capability, FakeLspAdapter, Language, language_settings::AllLanguageSettingsContent, Capability, FakeLspAdapter, Language,
@ -3361,7 +3361,7 @@ pub mod tests {
let settings_store = SettingsStore::test(cx); let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store); cx.set_global(settings_store);
theme::init(theme::LoadThemes::JustBase, cx); theme::init(theme::LoadThemes::JustBase, cx);
release_channel::init("0.0.0", cx); release_channel::init(SemanticVersion::default(), cx);
client::init_settings(cx); client::init_settings(cx);
language::init(cx); language::init(cx);
Project::init_settings(cx); Project::init_settings(cx);

View file

@ -10,7 +10,7 @@ use async_compression::futures::bufread::GzipEncoder;
use collections::BTreeMap; use collections::BTreeMap;
use fs::{FakeFs, Fs, RealFs}; use fs::{FakeFs, Fs, RealFs};
use futures::{io::BufReader, AsyncReadExt, StreamExt}; use futures::{io::BufReader, AsyncReadExt, StreamExt};
use gpui::{Context, TestAppContext}; use gpui::{Context, SemanticVersion, TestAppContext};
use http::{FakeHttpClient, Response}; use http::{FakeHttpClient, Response};
use language::{LanguageMatcher, LanguageRegistry, LanguageServerBinaryStatus, LanguageServerName}; use language::{LanguageMatcher, LanguageRegistry, LanguageServerBinaryStatus, LanguageServerName};
use node_runtime::FakeNodeRuntime; use node_runtime::FakeNodeRuntime;
@ -723,7 +723,7 @@ fn init_test(cx: &mut TestAppContext) {
cx.update(|cx| { cx.update(|cx| {
let store = SettingsStore::test(cx); let store = SettingsStore::test(cx);
cx.set_global(store); cx.set_global(store);
release_channel::init("0.0.0", cx); release_channel::init(SemanticVersion::default(), cx);
theme::init(theme::LoadThemes::JustBase, cx); theme::init(theme::LoadThemes::JustBase, cx);
Project::init_settings(cx); Project::init_settings(cx);
ExtensionSettings::register(cx); ExtensionSettings::register(cx);

View file

@ -1,5 +1,6 @@
use gpui::{actions, AppContext, ClipboardItem, PromptLevel}; use gpui::{actions, AppContext, ClipboardItem, PromptLevel};
use system_specs::SystemSpecs; use system_specs::SystemSpecs;
use util::ResultExt;
use workspace::Workspace; use workspace::Workspace;
pub mod feedback_modal; pub mod feedback_modal;
@ -38,25 +39,38 @@ pub fn init(cx: &mut AppContext) {
feedback_modal::FeedbackModal::register(workspace, cx); feedback_modal::FeedbackModal::register(workspace, cx);
workspace workspace
.register_action(|_, _: &CopySystemSpecsIntoClipboard, cx| { .register_action(|_, _: &CopySystemSpecsIntoClipboard, cx| {
let specs = SystemSpecs::new(&cx).to_string(); let specs = SystemSpecs::new(&cx);
let prompt = cx.prompt( cx.spawn(|_, mut cx| async move {
PromptLevel::Info, let specs = specs.await.to_string();
"Copied into clipboard",
Some(&specs), cx.update(|cx| cx.write_to_clipboard(ClipboardItem::new(specs.clone())))
&["OK"], .log_err();
);
cx.spawn(|_, _cx| async move { cx.prompt(
prompt.await.ok(); PromptLevel::Info,
"Copied into clipboard",
Some(&specs),
&["OK"],
)
.await
.ok();
}) })
.detach(); .detach();
cx.write_to_clipboard(ClipboardItem::new(specs.clone()));
}) })
.register_action(|_, _: &RequestFeature, cx| { .register_action(|_, _: &RequestFeature, cx| {
cx.open_url(request_feature_url()); cx.open_url(request_feature_url());
}) })
.register_action(move |_, _: &FileBugReport, cx| { .register_action(move |_, _: &FileBugReport, cx| {
cx.open_url(&file_bug_report_url(&SystemSpecs::new(&cx))); let specs = SystemSpecs::new(&cx);
cx.spawn(|_, mut cx| async move {
let specs = specs.await;
cx.update(|cx| {
cx.open_url(&file_bug_report_url(&specs));
})
.log_err();
})
.detach();
}) })
.register_action(move |_, _: &OpenZedRepo, cx| { .register_action(move |_, _: &OpenZedRepo, cx| {
cx.open_url(zed_repo_url()); cx.open_url(zed_repo_url());

View file

@ -141,15 +141,15 @@ impl FeedbackModal {
return; return;
} }
let system_specs = SystemSpecs::new(cx);
cx.spawn(|workspace, mut cx| async move { cx.spawn(|workspace, mut cx| async move {
let markdown = markdown.await.log_err(); let markdown = markdown.await.log_err();
let buffer = project.update(&mut cx, |project, cx| { let buffer = project.update(&mut cx, |project, cx| {
project.create_local_buffer("", markdown, cx) project.create_local_buffer("", markdown, cx)
})?; })?;
let system_specs = system_specs.await;
workspace.update(&mut cx, |workspace, cx| { workspace.update(&mut cx, |workspace, cx| {
let system_specs = SystemSpecs::new(cx);
workspace.toggle_modal(cx, move |cx| { workspace.toggle_modal(cx, move |cx| {
FeedbackModal::new(system_specs, project, buffer, cx) FeedbackModal::new(system_specs, project, buffer, cx)
}); });

View file

@ -1,4 +1,5 @@
use gpui::AppContext; use client::telemetry;
use gpui::{AppContext, Task};
use human_bytes::human_bytes; use human_bytes::human_bytes;
use release_channel::{AppCommitSha, AppVersion, ReleaseChannel}; use release_channel::{AppCommitSha, AppVersion, ReleaseChannel};
use serde::Serialize; use serde::Serialize;
@ -9,27 +10,23 @@ use sysinfo::{MemoryRefreshKind, RefreshKind, System};
pub struct SystemSpecs { pub struct SystemSpecs {
app_version: String, app_version: String,
release_channel: &'static str, release_channel: &'static str,
os_name: &'static str, os_name: String,
os_version: Option<String>, os_version: String,
memory: u64, memory: u64,
architecture: &'static str, architecture: &'static str,
commit_sha: Option<String>, commit_sha: Option<String>,
} }
impl SystemSpecs { impl SystemSpecs {
pub fn new(cx: &AppContext) -> Self { pub fn new(cx: &AppContext) -> Task<Self> {
let app_version = AppVersion::global(cx).to_string(); let app_version = AppVersion::global(cx).to_string();
let release_channel = ReleaseChannel::global(cx); let release_channel = ReleaseChannel::global(cx);
let os_name = cx.app_metadata().os_name; let os_name = telemetry::os_name();
let system = System::new_with_specifics( let system = System::new_with_specifics(
RefreshKind::new().with_memory(MemoryRefreshKind::everything()), RefreshKind::new().with_memory(MemoryRefreshKind::everything()),
); );
let memory = system.total_memory(); let memory = system.total_memory();
let architecture = env::consts::ARCH; let architecture = env::consts::ARCH;
let os_version = cx
.app_metadata()
.os_version
.map(|os_version| os_version.to_string());
let commit_sha = match release_channel { let commit_sha = match release_channel {
ReleaseChannel::Dev | ReleaseChannel::Nightly => { ReleaseChannel::Dev | ReleaseChannel::Nightly => {
AppCommitSha::try_global(cx).map(|sha| sha.0.clone()) AppCommitSha::try_global(cx).map(|sha| sha.0.clone())
@ -37,24 +34,24 @@ impl SystemSpecs {
_ => None, _ => None,
}; };
SystemSpecs { cx.background_executor().spawn(async move {
app_version, let os_version = telemetry::os_version();
release_channel: release_channel.display_name(), SystemSpecs {
os_name, app_version,
os_version, release_channel: release_channel.display_name(),
memory, os_name,
architecture, os_version,
commit_sha, memory,
} architecture,
commit_sha,
}
})
} }
} }
impl Display for SystemSpecs { impl Display for SystemSpecs {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let os_information = match &self.os_version { let os_information = format!("OS: {} {}", self.os_name, self.os_version);
Some(os_version) => format!("OS: {} {}", self.os_name, os_version),
None => format!("OS: {}", self.os_name),
};
let app_version_information = format!( let app_version_information = format!(
"Zed: v{} ({})", "Zed: v{} ({})",
self.app_version, self.app_version,

View file

@ -87,7 +87,7 @@ cbindgen = "0.26.0"
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]
block = "0.1" block = "0.1"
cocoa = "0.25" cocoa.workspace = true
core-foundation.workspace = true core-foundation.workspace = true
core-graphics = "0.23" core-graphics = "0.23"
core-text = "20.1" core-text = "20.1"

View file

@ -76,6 +76,7 @@ fn main() {
cx.open_window(options, |cx| { cx.open_window(options, |cx| {
cx.activate(false); cx.activate(false);
cx.new_view(|_cx| AnimationExample {}) cx.new_view(|_cx| AnimationExample {})
}); })
.unwrap();
}); });
} }

View file

@ -34,6 +34,7 @@ fn main() {
text: "World".into(), text: "World".into(),
}) })
}, },
); )
.unwrap();
}); });
} }

View file

@ -93,6 +93,7 @@ fn main() {
local_resource: Arc::new(PathBuf::from_str("examples/image/app-icon.png").unwrap()), local_resource: Arc::new(PathBuf::from_str("examples/image/app-icon.png").unwrap()),
remote_resource: "https://picsum.photos/512/512".into(), remote_resource: "https://picsum.photos/512/512".into(),
}) })
}); })
.unwrap();
}); });
} }

View file

@ -29,7 +29,8 @@ fn main() {
}]); }]);
cx.open_window(WindowOptions::default(), |cx| { cx.open_window(WindowOptions::default(), |cx| {
cx.new_view(|_cx| SetMenus {}) cx.new_view(|_cx| SetMenus {})
}); })
.unwrap();
}); });
} }

View file

@ -61,7 +61,8 @@ fn main() {
cx.new_view(|_| WindowContent { cx.new_view(|_| WindowContent {
text: format!("{:?}", screen.id()).into(), text: format!("{:?}", screen.id()).into(),
}) })
}); })
.unwrap();
} }
}); });
} }

View file

@ -27,13 +27,12 @@ use util::ResultExt;
use crate::{ use crate::{
current_platform, init_app_menus, Action, ActionRegistry, Any, AnyView, AnyWindowHandle, current_platform, init_app_menus, Action, ActionRegistry, Any, AnyView, AnyWindowHandle,
AppMetadata, AssetCache, AssetSource, BackgroundExecutor, ClipboardItem, Context, AssetCache, AssetSource, BackgroundExecutor, ClipboardItem, Context, DispatchPhase, DisplayId,
DispatchPhase, DisplayId, Entity, EventEmitter, ForegroundExecutor, Global, KeyBinding, Keymap, Entity, EventEmitter, ForegroundExecutor, Global, KeyBinding, Keymap, Keystroke, LayoutId,
Keystroke, LayoutId, Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform, Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point,
PlatformDisplay, Point, PromptBuilder, PromptHandle, PromptLevel, Render, PromptBuilder, PromptHandle, PromptLevel, Render, RenderablePromptHandle, Reservation,
RenderablePromptHandle, Reservation, SharedString, SubscriberSet, Subscription, SvgRenderer, SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, View, ViewContext,
Task, TextSystem, View, ViewContext, Window, WindowAppearance, WindowContext, WindowHandle, Window, WindowAppearance, WindowContext, WindowHandle, WindowId,
WindowId,
}; };
mod async_context; mod async_context;
@ -169,11 +168,6 @@ impl App {
self self
} }
/// Returns metadata associated with the application
pub fn metadata(&self) -> AppMetadata {
self.0.borrow().app_metadata.clone()
}
/// Returns a handle to the [`BackgroundExecutor`] associated with this app, which can be used to spawn futures in the background. /// Returns a handle to the [`BackgroundExecutor`] associated with this app, which can be used to spawn futures in the background.
pub fn background_executor(&self) -> BackgroundExecutor { pub fn background_executor(&self) -> BackgroundExecutor {
self.0.borrow().background_executor.clone() self.0.borrow().background_executor.clone()
@ -208,7 +202,6 @@ type NewViewListener = Box<dyn FnMut(AnyView, &mut WindowContext) + 'static>;
pub struct AppContext { pub struct AppContext {
pub(crate) this: Weak<AppCell>, pub(crate) this: Weak<AppCell>,
pub(crate) platform: Rc<dyn Platform>, pub(crate) platform: Rc<dyn Platform>,
app_metadata: AppMetadata,
text_system: Arc<TextSystem>, text_system: Arc<TextSystem>,
flushing_effects: bool, flushing_effects: bool,
pending_updates: usize, pending_updates: usize,
@ -261,17 +254,10 @@ impl AppContext {
let text_system = Arc::new(TextSystem::new(platform.text_system())); let text_system = Arc::new(TextSystem::new(platform.text_system()));
let entities = EntityMap::new(); let entities = EntityMap::new();
let app_metadata = AppMetadata {
os_name: platform.os_name(),
os_version: platform.os_version().ok(),
app_version: platform.app_version().ok(),
};
let app = Rc::new_cyclic(|this| AppCell { let app = Rc::new_cyclic(|this| AppCell {
app: RefCell::new(AppContext { app: RefCell::new(AppContext {
this: this.clone(), this: this.clone(),
platform: platform.clone(), platform: platform.clone(),
app_metadata,
text_system, text_system,
actions: Rc::new(ActionRegistry::default()), actions: Rc::new(ActionRegistry::default()),
flushing_effects: false, flushing_effects: false,
@ -346,11 +332,6 @@ impl AppContext {
self.platform.quit(); self.platform.quit();
} }
/// Get metadata about the app and platform.
pub fn app_metadata(&self) -> AppMetadata {
self.app_metadata.clone()
}
/// Schedules all windows in the application to be redrawn. This can be called /// Schedules all windows in the application to be redrawn. This can be called
/// multiple times in an update cycle and still result in a single redraw. /// multiple times in an update cycle and still result in a single redraw.
pub fn refresh(&mut self) { pub fn refresh(&mut self) {
@ -490,26 +471,26 @@ impl AppContext {
&mut self, &mut self,
options: crate::WindowOptions, options: crate::WindowOptions,
build_root_view: impl FnOnce(&mut WindowContext) -> View<V>, build_root_view: impl FnOnce(&mut WindowContext) -> View<V>,
) -> WindowHandle<V> { ) -> anyhow::Result<WindowHandle<V>> {
self.update(|cx| { self.update(|cx| {
let id = cx.windows.insert(None); let id = cx.windows.insert(None);
let handle = WindowHandle::new(id); let handle = WindowHandle::new(id);
let mut window = Window::new(handle.into(), options, cx); match Window::new(handle.into(), options, cx) {
let root_view = build_root_view(&mut WindowContext::new(cx, &mut window)); Ok(mut window) => {
window.root_view.replace(root_view.into()); let root_view = build_root_view(&mut WindowContext::new(cx, &mut window));
cx.window_handles.insert(id, window.handle); window.root_view.replace(root_view.into());
cx.windows.get_mut(id).unwrap().replace(window); cx.window_handles.insert(id, window.handle);
handle cx.windows.get_mut(id).unwrap().replace(window);
Ok(handle)
}
Err(e) => {
cx.windows.remove(id);
return Err(e);
}
}
}) })
} }
/// Returns Ok() if the platform supports opening windows.
/// This returns false (for example) on linux when we could
/// not establish a connection to X or Wayland.
pub fn can_open_windows(&self) -> anyhow::Result<()> {
self.platform.can_open_windows()
}
/// Instructs the platform to activate the application by bringing it to the foreground. /// Instructs the platform to activate the application by bringing it to the foreground.
pub fn activate(&self, ignoring_other_apps: bool) { pub fn activate(&self, ignoring_other_apps: bool) {
self.platform.activate(ignoring_other_apps); self.platform.activate(ignoring_other_apps);
@ -616,6 +597,12 @@ impl AppContext {
self.platform.app_path() self.platform.app_path()
} }
/// On Linux, returns the name of the compositor in use.
/// Is blank on other platforms.
pub fn compositor_name(&self) -> &'static str {
self.platform.compositor_name()
}
/// Returns the file URL of the executable with the specified name in the application bundle /// Returns the file URL of the executable with the specified name in the application bundle
pub fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> { pub fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
self.platform.path_for_auxiliary_executable(name) self.platform.path_for_auxiliary_executable(name)

View file

@ -151,7 +151,7 @@ impl AsyncAppContext {
.upgrade() .upgrade()
.ok_or_else(|| anyhow!("app was released"))?; .ok_or_else(|| anyhow!("app was released"))?;
let mut lock = app.borrow_mut(); let mut lock = app.borrow_mut();
Ok(lock.open_window(options, build_root_view)) lock.open_window(options, build_root_view)
} }
/// Schedule a future to be polled in the background. /// Schedule a future to be polled in the background.

View file

@ -193,19 +193,22 @@ impl TestAppContext {
}, },
|cx| cx.new_view(build_window), |cx| cx.new_view(build_window),
) )
.unwrap()
} }
/// Adds a new window with no content. /// Adds a new window with no content.
pub fn add_empty_window(&mut self) -> &mut VisualTestContext { pub fn add_empty_window(&mut self) -> &mut VisualTestContext {
let mut cx = self.app.borrow_mut(); let mut cx = self.app.borrow_mut();
let bounds = Bounds::maximized(None, &mut cx); let bounds = Bounds::maximized(None, &mut cx);
let window = cx.open_window( let window = cx
WindowOptions { .open_window(
window_bounds: Some(WindowBounds::Windowed(bounds)), WindowOptions {
..Default::default() window_bounds: Some(WindowBounds::Windowed(bounds)),
}, ..Default::default()
|cx| cx.new_view(|_| Empty), },
); |cx| cx.new_view(|_| Empty),
)
.unwrap();
drop(cx); drop(cx);
let cx = VisualTestContext::from_window(*window.deref(), self).as_mut(); let cx = VisualTestContext::from_window(*window.deref(), self).as_mut();
cx.run_until_parked(); cx.run_until_parked();
@ -222,13 +225,15 @@ impl TestAppContext {
{ {
let mut cx = self.app.borrow_mut(); let mut cx = self.app.borrow_mut();
let bounds = Bounds::maximized(None, &mut cx); let bounds = Bounds::maximized(None, &mut cx);
let window = cx.open_window( let window = cx
WindowOptions { .open_window(
window_bounds: Some(WindowBounds::Windowed(bounds)), WindowOptions {
..Default::default() window_bounds: Some(WindowBounds::Windowed(bounds)),
}, ..Default::default()
|cx| cx.new_view(build_root_view), },
); |cx| cx.new_view(build_root_view),
)
.unwrap();
drop(cx); drop(cx);
let view = window.root_view(self).unwrap(); let view = window.root_view(self).unwrap();
let cx = VisualTestContext::from_window(*window.deref(), self).as_mut(); let cx = VisualTestContext::from_window(*window.deref(), self).as_mut();

View file

@ -486,6 +486,7 @@ mod test {
focus_handle: cx.focus_handle(), focus_handle: cx.focus_handle(),
}) })
}) })
.unwrap()
}); });
cx.update(|cx| { cx.update(|cx| {

View file

@ -70,6 +70,19 @@ pub(crate) fn current_platform() -> Rc<dyn Platform> {
} }
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
pub(crate) fn current_platform() -> Rc<dyn Platform> { pub(crate) fn current_platform() -> Rc<dyn Platform> {
match guess_compositor() {
"Wayland" => Rc::new(WaylandClient::new()),
"X11" => Rc::new(X11Client::new()),
"Headless" => Rc::new(HeadlessClient::new()),
_ => unreachable!(),
}
}
/// Return which compositor we're guessing we'll use.
/// Does not attempt to connect to the given compositor
#[cfg(target_os = "linux")]
#[inline]
pub fn guess_compositor() -> &'static str {
let wayland_display = std::env::var_os("WAYLAND_DISPLAY"); let wayland_display = std::env::var_os("WAYLAND_DISPLAY");
let x11_display = std::env::var_os("DISPLAY"); let x11_display = std::env::var_os("DISPLAY");
@ -77,13 +90,14 @@ pub(crate) fn current_platform() -> Rc<dyn Platform> {
let use_x11 = x11_display.is_some_and(|display| !display.is_empty()); let use_x11 = x11_display.is_some_and(|display| !display.is_empty());
if use_wayland { if use_wayland {
Rc::new(WaylandClient::new()) "Wayland"
} else if use_x11 { } else if use_x11 {
Rc::new(X11Client::new()) "X11"
} else { } else {
Rc::new(HeadlessClient::new()) "Headless"
} }
} }
// todo("windows") // todo("windows")
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
pub(crate) fn current_platform() -> Rc<dyn Platform> { pub(crate) fn current_platform() -> Rc<dyn Platform> {
@ -106,14 +120,12 @@ pub(crate) trait Platform: 'static {
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>; fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>; fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
fn active_window(&self) -> Option<AnyWindowHandle>; fn active_window(&self) -> Option<AnyWindowHandle>;
fn can_open_windows(&self) -> anyhow::Result<()> {
Ok(())
}
fn open_window( fn open_window(
&self, &self,
handle: AnyWindowHandle, handle: AnyWindowHandle,
options: WindowParams, options: WindowParams,
) -> Box<dyn PlatformWindow>; ) -> anyhow::Result<Box<dyn PlatformWindow>>;
/// Returns the appearance of the application's windows. /// Returns the appearance of the application's windows.
fn window_appearance(&self) -> WindowAppearance; fn window_appearance(&self) -> WindowAppearance;
@ -143,9 +155,9 @@ pub(crate) trait Platform: 'static {
fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>); fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>); fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
fn os_name(&self) -> &'static str; fn compositor_name(&self) -> &'static str {
fn os_version(&self) -> Result<SemanticVersion>; ""
fn app_version(&self) -> Result<SemanticVersion>; }
fn app_path(&self) -> Result<PathBuf>; fn app_path(&self) -> Result<PathBuf>;
fn local_timezone(&self) -> UtcOffset; fn local_timezone(&self) -> UtcOffset;
fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>; fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
@ -288,19 +300,6 @@ pub(crate) trait PlatformTextSystem: Send + Sync {
fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout; fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
} }
/// Basic metadata about the current application and operating system.
#[derive(Clone, Debug)]
pub struct AppMetadata {
/// The name of the current operating system
pub os_name: &'static str,
/// The operating system's version
pub os_version: Option<SemanticVersion>,
/// The current version of the application
pub app_version: Option<SemanticVersion>,
}
#[derive(PartialEq, Eq, Hash, Clone)] #[derive(PartialEq, Eq, Hash, Clone)]
pub(crate) enum AtlasKey { pub(crate) enum AtlasKey {
Glyph(RenderGlyphParams), Glyph(RenderGlyphParams),

View file

@ -59,10 +59,6 @@ impl LinuxClient for HeadlessClient {
None None
} }
fn can_open_windows(&self) -> anyhow::Result<()> {
return Err(anyhow::anyhow!("neither DISPLAY, nor WAYLAND_DISPLAY found. You can still run zed for remote development with --dev-server-token."));
}
fn active_window(&self) -> Option<AnyWindowHandle> { fn active_window(&self) -> Option<AnyWindowHandle> {
None None
} }
@ -71,8 +67,14 @@ impl LinuxClient for HeadlessClient {
&self, &self,
_handle: AnyWindowHandle, _handle: AnyWindowHandle,
_params: WindowParams, _params: WindowParams,
) -> Box<dyn PlatformWindow> { ) -> anyhow::Result<Box<dyn PlatformWindow>> {
unimplemented!() Err(anyhow::anyhow!(
"neither DISPLAY nor WAYLAND_DISPLAY is set. You can run in headless mode"
))
}
fn compositor_name(&self) -> &'static str {
"headless"
} }
fn set_cursor_style(&self, _style: CursorStyle) {} fn set_cursor_style(&self, _style: CursorStyle) {}

View file

@ -39,8 +39,8 @@ use crate::{
px, Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CosmicTextSystem, CursorStyle, px, Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CosmicTextSystem, CursorStyle,
DisplayId, ForegroundExecutor, Keymap, Keystroke, LinuxDispatcher, Menu, MenuItem, Modifiers, DisplayId, ForegroundExecutor, Keymap, Keystroke, LinuxDispatcher, Menu, MenuItem, Modifiers,
OwnedMenu, PathPromptOptions, Pixels, Platform, PlatformDisplay, PlatformInputHandler, OwnedMenu, PathPromptOptions, Pixels, Platform, PlatformDisplay, PlatformInputHandler,
PlatformTextSystem, PlatformWindow, Point, PromptLevel, Result, SemanticVersion, Size, Task, PlatformTextSystem, PlatformWindow, Point, PromptLevel, Result, SemanticVersion, SharedString,
WindowAppearance, WindowOptions, WindowParams, Size, Task, WindowAppearance, WindowOptions, WindowParams,
}; };
use super::x11::X11Client; use super::x11::X11Client;
@ -54,18 +54,17 @@ pub(crate) const DOUBLE_CLICK_DISTANCE: Pixels = px(5.0);
pub(crate) const KEYRING_LABEL: &str = "zed-github-account"; pub(crate) const KEYRING_LABEL: &str = "zed-github-account";
pub trait LinuxClient { pub trait LinuxClient {
fn compositor_name(&self) -> &'static str;
fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R; fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R;
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>; fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>; fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>; fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>;
fn can_open_windows(&self) -> anyhow::Result<()> {
Ok(())
}
fn open_window( fn open_window(
&self, &self,
handle: AnyWindowHandle, handle: AnyWindowHandle,
options: WindowParams, options: WindowParams,
) -> Box<dyn PlatformWindow>; ) -> anyhow::Result<Box<dyn PlatformWindow>>;
fn set_cursor_style(&self, style: CursorStyle); fn set_cursor_style(&self, style: CursorStyle);
fn open_uri(&self, uri: &str); fn open_uri(&self, uri: &str);
fn write_to_primary(&self, item: ClipboardItem); fn write_to_primary(&self, item: ClipboardItem);
@ -152,14 +151,14 @@ impl<P: LinuxClient + 'static> Platform for P {
}); });
} }
fn can_open_windows(&self) -> anyhow::Result<()> {
self.can_open_windows()
}
fn quit(&self) { fn quit(&self) {
self.with_common(|common| common.signal.stop()); self.with_common(|common| common.signal.stop());
} }
fn compositor_name(&self) -> &'static str {
self.compositor_name()
}
fn restart(&self, binary_path: Option<PathBuf>) { fn restart(&self, binary_path: Option<PathBuf>) {
use std::os::unix::process::CommandExt as _; use std::os::unix::process::CommandExt as _;
@ -245,7 +244,7 @@ impl<P: LinuxClient + 'static> Platform for P {
&self, &self,
handle: AnyWindowHandle, handle: AnyWindowHandle,
options: WindowParams, options: WindowParams,
) -> Box<dyn PlatformWindow> { ) -> anyhow::Result<Box<dyn PlatformWindow>> {
self.open_window(handle, options) self.open_window(handle, options)
} }
@ -369,23 +368,6 @@ impl<P: LinuxClient + 'static> Platform for P {
}); });
} }
fn os_name(&self) -> &'static str {
"Linux"
}
fn os_version(&self) -> Result<SemanticVersion> {
Ok(SemanticVersion::new(1, 0, 0))
}
fn app_version(&self) -> Result<SemanticVersion> {
const VERSION: Option<&str> = option_env!("RELEASE_VERSION");
if let Some(version) = VERSION {
version.parse()
} else {
Ok(SemanticVersion::new(1, 0, 0))
}
}
fn app_path(&self) -> Result<PathBuf> { fn app_path(&self) -> Result<PathBuf> {
// get the path of the executable of the current process // get the path of the executable of the current process
let exe_path = std::env::current_exe()?; let exe_path = std::env::current_exe()?;
@ -510,6 +492,8 @@ impl<P: LinuxClient + 'static> Platform for P {
fn read_from_clipboard(&self) -> Option<ClipboardItem> { fn read_from_clipboard(&self) -> Option<ClipboardItem> {
self.read_from_clipboard() self.read_from_clipboard()
} }
fn add_recent_document(&self, _path: &Path) {}
} }
pub(super) fn open_uri_internal(uri: &str, activation_token: Option<&str>) { pub(super) fn open_uri_internal(uri: &str, activation_token: Option<&str>) {

View file

@ -559,7 +559,7 @@ impl LinuxClient for WaylandClient {
&self, &self,
handle: AnyWindowHandle, handle: AnyWindowHandle,
params: WindowParams, params: WindowParams,
) -> Box<dyn PlatformWindow> { ) -> anyhow::Result<Box<dyn PlatformWindow>> {
let mut state = self.0.borrow_mut(); let mut state = self.0.borrow_mut();
let (window, surface_id) = WaylandWindow::new( let (window, surface_id) = WaylandWindow::new(
@ -568,10 +568,10 @@ impl LinuxClient for WaylandClient {
WaylandClientStatePtr(Rc::downgrade(&self.0)), WaylandClientStatePtr(Rc::downgrade(&self.0)),
params, params,
state.common.appearance, state.common.appearance,
); )?;
state.windows.insert(surface_id, window.0.clone()); state.windows.insert(surface_id, window.0.clone());
Box::new(window) Ok(Box::new(window))
} }
fn set_cursor_style(&self, style: CursorStyle) { fn set_cursor_style(&self, style: CursorStyle) {
@ -693,6 +693,10 @@ impl LinuxClient for WaylandClient {
.as_ref() .as_ref()
.map(|window| window.handle()) .map(|window| window.handle())
} }
fn compositor_name(&self) -> &'static str {
"Wayland"
}
} }
impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for WaylandClientStatePtr { impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for WaylandClientStatePtr {

View file

@ -107,7 +107,7 @@ impl WaylandWindowState {
client: WaylandClientStatePtr, client: WaylandClientStatePtr,
globals: Globals, globals: Globals,
options: WindowParams, options: WindowParams,
) -> Self { ) -> anyhow::Result<Self> {
let bounds = options.bounds.map(|p| p.0 as u32); let bounds = options.bounds.map(|p| p.0 as u32);
let raw = RawWindow { let raw = RawWindow {
@ -130,7 +130,7 @@ impl WaylandWindowState {
}, },
) )
} }
.unwrap(), .map_err(|e| anyhow::anyhow!("{:?}", e))?,
); );
let config = BladeSurfaceConfig { let config = BladeSurfaceConfig {
size: gpu::Extent { size: gpu::Extent {
@ -141,7 +141,7 @@ impl WaylandWindowState {
transparent: options.window_background != WindowBackgroundAppearance::Opaque, transparent: options.window_background != WindowBackgroundAppearance::Opaque,
}; };
Self { Ok(Self {
xdg_surface, xdg_surface,
acknowledged_first_configure: false, acknowledged_first_configure: false,
surface, surface,
@ -164,7 +164,7 @@ impl WaylandWindowState {
appearance, appearance,
handle, handle,
active: false, active: false,
} })
} }
} }
@ -224,7 +224,7 @@ impl WaylandWindow {
client: WaylandClientStatePtr, client: WaylandClientStatePtr,
params: WindowParams, params: WindowParams,
appearance: WindowAppearance, appearance: WindowAppearance,
) -> (Self, ObjectId) { ) -> anyhow::Result<(Self, ObjectId)> {
let surface = globals.compositor.create_surface(&globals.qh, ()); let surface = globals.compositor.create_surface(&globals.qh, ());
let xdg_surface = globals let xdg_surface = globals
.wm_base .wm_base
@ -267,14 +267,14 @@ impl WaylandWindow {
client, client,
globals, globals,
params, params,
))), )?)),
callbacks: Rc::new(RefCell::new(Callbacks::default())), callbacks: Rc::new(RefCell::new(Callbacks::default())),
}); });
// Kick things off // Kick things off
surface.commit(); surface.commit();
(this, surface.id()) Ok((this, surface.id()))
} }
} }

View file

@ -889,6 +889,10 @@ impl X11Client {
} }
impl LinuxClient for X11Client { impl LinuxClient for X11Client {
fn compositor_name(&self) -> &'static str {
"X11"
}
fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R { fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R {
f(&mut self.0.borrow_mut().common) f(&mut self.0.borrow_mut().common)
} }
@ -929,7 +933,7 @@ impl LinuxClient for X11Client {
&self, &self,
handle: AnyWindowHandle, handle: AnyWindowHandle,
params: WindowParams, params: WindowParams,
) -> Box<dyn PlatformWindow> { ) -> anyhow::Result<Box<dyn PlatformWindow>> {
let mut state = self.0.borrow_mut(); let mut state = self.0.borrow_mut();
let x_window = state.xcb_connection.generate_id().unwrap(); let x_window = state.xcb_connection.generate_id().unwrap();
@ -944,7 +948,7 @@ impl LinuxClient for X11Client {
&state.atoms, &state.atoms,
state.scale_factor, state.scale_factor,
state.common.appearance, state.common.appearance,
); )?;
let screen_resources = state let screen_resources = state
.xcb_connection .xcb_connection
@ -1012,7 +1016,7 @@ impl LinuxClient for X11Client {
}; };
state.windows.insert(x_window, window_ref); state.windows.insert(x_window, window_ref);
Box::new(window) Ok(Box::new(window))
} }
fn set_cursor_style(&self, style: CursorStyle) { fn set_cursor_style(&self, style: CursorStyle) {

View file

@ -217,7 +217,7 @@ impl X11WindowState {
atoms: &XcbAtoms, atoms: &XcbAtoms,
scale_factor: f32, scale_factor: f32,
appearance: WindowAppearance, appearance: WindowAppearance,
) -> Self { ) -> anyhow::Result<Self> {
let x_screen_index = params let x_screen_index = params
.display_id .display_id
.map_or(x_main_screen_index, |did| did.0 as usize); .map_or(x_main_screen_index, |did| did.0 as usize);
@ -249,8 +249,7 @@ impl X11WindowState {
xcb_connection xcb_connection
.create_colormap(xproto::ColormapAlloc::NONE, id, visual_set.root, visual.id) .create_colormap(xproto::ColormapAlloc::NONE, id, visual_set.root, visual.id)
.unwrap() .unwrap()
.check() .check()?;
.unwrap();
id id
}; };
@ -282,8 +281,7 @@ impl X11WindowState {
&win_aux, &win_aux,
) )
.unwrap() .unwrap()
.check() .check()?;
.unwrap();
if let Some(titlebar) = params.titlebar { if let Some(titlebar) = params.titlebar {
if let Some(title) = titlebar.title { if let Some(title) = titlebar.title {
@ -346,7 +344,7 @@ impl X11WindowState {
}, },
) )
} }
.unwrap(), .map_err(|e| anyhow::anyhow!("{:?}", e))?,
); );
let config = BladeSurfaceConfig { let config = BladeSurfaceConfig {
@ -356,7 +354,7 @@ impl X11WindowState {
transparent: params.window_background != WindowBackgroundAppearance::Opaque, transparent: params.window_background != WindowBackgroundAppearance::Opaque,
}; };
Self { Ok(Self {
client, client,
executor, executor,
display: Rc::new(X11Display::new(xcb_connection, x_screen_index).unwrap()), display: Rc::new(X11Display::new(xcb_connection, x_screen_index).unwrap()),
@ -370,7 +368,7 @@ impl X11WindowState {
appearance, appearance,
handle, handle,
destroyed: false, destroyed: false,
} })
} }
fn content_size(&self) -> Size<Pixels> { fn content_size(&self) -> Size<Pixels> {
@ -432,8 +430,8 @@ impl X11Window {
atoms: &XcbAtoms, atoms: &XcbAtoms,
scale_factor: f32, scale_factor: f32,
appearance: WindowAppearance, appearance: WindowAppearance,
) -> Self { ) -> anyhow::Result<Self> {
Self(X11WindowStatePtr { Ok(Self(X11WindowStatePtr {
state: Rc::new(RefCell::new(X11WindowState::new( state: Rc::new(RefCell::new(X11WindowState::new(
handle, handle,
client, client,
@ -445,11 +443,11 @@ impl X11Window {
atoms, atoms,
scale_factor, scale_factor,
appearance, appearance,
))), )?)),
callbacks: Rc::new(RefCell::new(Callbacks::default())), callbacks: Rc::new(RefCell::new(Callbacks::default())),
xcb_connection: xcb_connection.clone(), xcb_connection: xcb_connection.clone(),
x_window, x_window,
}) }))
} }
fn set_wm_hints(&self, wm_hint_property_state: WmHintPropertyState, prop1: u32, prop2: u32) { fn set_wm_hints(&self, wm_hint_property_state: WmHintPropertyState, prop1: u32, prop2: u32) {

View file

@ -5,7 +5,7 @@ use crate::{
Platform, PlatformDisplay, PlatformTextSystem, PlatformWindow, Result, SemanticVersion, Task, Platform, PlatformDisplay, PlatformTextSystem, PlatformWindow, Result, SemanticVersion, Task,
WindowAppearance, WindowParams, WindowAppearance, WindowParams,
}; };
use anyhow::{anyhow, bail}; use anyhow::anyhow;
use block::ConcreteBlock; use block::ConcreteBlock;
use cocoa::{ use cocoa::{
appkit::{ appkit::{
@ -367,6 +367,18 @@ impl MacPlatform {
} }
} }
} }
fn os_version(&self) -> Result<SemanticVersion> {
unsafe {
let process_info = NSProcessInfo::processInfo(nil);
let version = process_info.operatingSystemVersion();
Ok(SemanticVersion::new(
version.majorVersion as usize,
version.minorVersion as usize,
version.patchVersion as usize,
))
}
}
} }
impl Platform for MacPlatform { impl Platform for MacPlatform {
@ -504,16 +516,16 @@ impl Platform for MacPlatform {
&self, &self,
handle: AnyWindowHandle, handle: AnyWindowHandle,
options: WindowParams, options: WindowParams,
) -> Box<dyn PlatformWindow> { ) -> Result<Box<dyn PlatformWindow>> {
// Clippy thinks that this evaluates to `()`, for some reason. // Clippy thinks that this evaluates to `()`, for some reason.
#[allow(clippy::unit_arg, clippy::clone_on_copy)] #[allow(clippy::unit_arg, clippy::clone_on_copy)]
let renderer_context = self.0.lock().renderer_context.clone(); let renderer_context = self.0.lock().renderer_context.clone();
Box::new(MacWindow::open( Ok(Box::new(MacWindow::open(
handle, handle,
options, options,
self.foreground_executor(), self.foreground_executor(),
renderer_context, renderer_context,
)) )))
} }
fn window_appearance(&self) -> WindowAppearance { fn window_appearance(&self) -> WindowAppearance {
@ -705,40 +717,6 @@ impl Platform for MacPlatform {
self.0.lock().validate_menu_command = Some(callback); self.0.lock().validate_menu_command = Some(callback);
} }
fn os_name(&self) -> &'static str {
"macOS"
}
fn os_version(&self) -> Result<SemanticVersion> {
unsafe {
let process_info = NSProcessInfo::processInfo(nil);
let version = process_info.operatingSystemVersion();
Ok(SemanticVersion::new(
version.majorVersion as usize,
version.minorVersion as usize,
version.patchVersion as usize,
))
}
}
fn app_version(&self) -> Result<SemanticVersion> {
unsafe {
let bundle: id = NSBundle::mainBundle();
if bundle.is_null() {
Err(anyhow!("app is not running inside a bundle"))
} else {
let version: id = msg_send![bundle, objectForInfoDictionaryKey: ns_string("CFBundleShortVersionString")];
if version.is_null() {
bail!("bundle does not have version");
}
let len = msg_send![version, lengthOfBytesUsingEncoding: NSUTF8StringEncoding];
let bytes = version.UTF8String() as *const u8;
let version = str::from_utf8(slice::from_raw_parts(bytes, len)).unwrap();
version.parse()
}
}
}
fn app_path(&self) -> Result<PathBuf> { fn app_path(&self) -> Result<PathBuf> {
unsafe { unsafe {
let bundle: id = NSBundle::mainBundle(); let bundle: id = NSBundle::mainBundle();

View file

@ -3,7 +3,7 @@ use crate::{
Platform, PlatformDisplay, PlatformTextSystem, Task, TestDisplay, TestWindow, WindowAppearance, Platform, PlatformDisplay, PlatformTextSystem, Task, TestDisplay, TestWindow, WindowAppearance,
WindowParams, WindowParams,
}; };
use anyhow::{anyhow, Result}; use anyhow::Result;
use collections::VecDeque; use collections::VecDeque;
use futures::channel::oneshot; use futures::channel::oneshot;
use parking_lot::Mutex; use parking_lot::Mutex;
@ -187,14 +187,14 @@ impl Platform for TestPlatform {
&self, &self,
handle: AnyWindowHandle, handle: AnyWindowHandle,
params: WindowParams, params: WindowParams,
) -> Box<dyn crate::PlatformWindow> { ) -> anyhow::Result<Box<dyn crate::PlatformWindow>> {
let window = TestWindow::new( let window = TestWindow::new(
handle, handle,
params, params,
self.weak.clone(), self.weak.clone(),
self.active_display.clone(), self.active_display.clone(),
); );
Box::new(window) Ok(Box::new(window))
} }
fn window_appearance(&self) -> WindowAppearance { fn window_appearance(&self) -> WindowAppearance {
@ -249,18 +249,6 @@ impl Platform for TestPlatform {
fn on_validate_app_menu_command(&self, _callback: Box<dyn FnMut(&dyn crate::Action) -> bool>) {} fn on_validate_app_menu_command(&self, _callback: Box<dyn FnMut(&dyn crate::Action) -> bool>) {}
fn os_name(&self) -> &'static str {
"test"
}
fn os_version(&self) -> Result<crate::SemanticVersion> {
Err(anyhow!("os_version called on TestPlatform"))
}
fn app_version(&self) -> Result<crate::SemanticVersion> {
Err(anyhow!("app_version called on TestPlatform"))
}
fn app_path(&self) -> Result<std::path::PathBuf> { fn app_path(&self) -> Result<std::path::PathBuf> {
unimplemented!() unimplemented!()
} }

View file

@ -16,17 +16,14 @@ use copypasta::{ClipboardContext, ClipboardProvider};
use futures::channel::oneshot::{self, Receiver}; use futures::channel::oneshot::{self, Receiver};
use itertools::Itertools; use itertools::Itertools;
use parking_lot::RwLock; use parking_lot::RwLock;
use semantic_version::SemanticVersion;
use smallvec::SmallVec; use smallvec::SmallVec;
use time::UtcOffset; use time::UtcOffset;
use windows::{ use windows::{
core::*, core::*,
Wdk::System::SystemServices::*,
Win32::{ Win32::{
Foundation::*, Foundation::*,
Graphics::Gdi::*, Graphics::Gdi::*,
Security::Credentials::*, Security::Credentials::*,
Storage::FileSystem::*,
System::{Com::*, LibraryLoader::*, Ole::*, SystemInformation::*, Threading::*, Time::*}, System::{Com::*, LibraryLoader::*, Ole::*, SystemInformation::*, Threading::*, Time::*},
UI::{Input::KeyboardAndMouse::*, Shell::*, WindowsAndMessaging::*}, UI::{Input::KeyboardAndMouse::*, Shell::*, WindowsAndMessaging::*},
}, },
@ -287,7 +284,7 @@ impl Platform for WindowsPlatform {
&self, &self,
handle: AnyWindowHandle, handle: AnyWindowHandle,
options: WindowParams, options: WindowParams,
) -> Box<dyn PlatformWindow> { ) -> Result<Box<dyn PlatformWindow>> {
let lock = self.state.borrow(); let lock = self.state.borrow();
let window = WindowsWindow::new( let window = WindowsWindow::new(
handle, handle,
@ -300,7 +297,7 @@ impl Platform for WindowsPlatform {
let handle = window.get_raw_handle(); let handle = window.get_raw_handle();
self.raw_window_handles.write().push(handle); self.raw_window_handles.write().push(handle);
Box::new(window) Ok(Box::new(window))
} }
// todo(windows) // todo(windows)
@ -464,121 +461,6 @@ impl Platform for WindowsPlatform {
self.state.borrow_mut().callbacks.validate_app_menu_command = Some(callback); self.state.borrow_mut().callbacks.validate_app_menu_command = Some(callback);
} }
fn os_name(&self) -> &'static str {
"Windows"
}
fn os_version(&self) -> Result<SemanticVersion> {
let mut info = unsafe { std::mem::zeroed() };
let status = unsafe { RtlGetVersion(&mut info) };
if status.is_ok() {
Ok(SemanticVersion::new(
info.dwMajorVersion as _,
info.dwMinorVersion as _,
info.dwBuildNumber as _,
))
} else {
Err(anyhow::anyhow!(
"unable to get Windows version: {}",
std::io::Error::last_os_error()
))
}
}
fn app_version(&self) -> Result<SemanticVersion> {
let mut file_name_buffer = vec![0u16; MAX_PATH as usize];
let file_name = {
let mut file_name_buffer_capacity = MAX_PATH as usize;
let mut file_name_length;
loop {
file_name_length =
unsafe { GetModuleFileNameW(None, &mut file_name_buffer) } as usize;
if file_name_length < file_name_buffer_capacity {
break;
}
// buffer too small
file_name_buffer_capacity *= 2;
file_name_buffer = vec![0u16; file_name_buffer_capacity];
}
PCWSTR::from_raw(file_name_buffer[0..(file_name_length + 1)].as_ptr())
};
let version_info_block = {
let mut version_handle = 0;
let version_info_size =
unsafe { GetFileVersionInfoSizeW(file_name, Some(&mut version_handle)) } as usize;
if version_info_size == 0 {
log::error!(
"unable to get version info size: {}",
std::io::Error::last_os_error()
);
return Err(anyhow!("unable to get version info size"));
}
let mut version_data = vec![0u8; version_info_size + 2];
unsafe {
GetFileVersionInfoW(
file_name,
version_handle,
version_info_size as u32,
version_data.as_mut_ptr() as _,
)
}
.inspect_err(|_| {
log::error!(
"unable to retrieve version info: {}",
std::io::Error::last_os_error()
)
})?;
version_data
};
let version_info_raw = {
let mut buffer = unsafe { std::mem::zeroed() };
let mut size = 0;
let entry = "\\".encode_utf16().chain(Some(0)).collect_vec();
if !unsafe {
VerQueryValueW(
version_info_block.as_ptr() as _,
PCWSTR::from_raw(entry.as_ptr()),
&mut buffer,
&mut size,
)
}
.as_bool()
{
log::error!(
"unable to query version info data: {}",
std::io::Error::last_os_error()
);
return Err(anyhow!("the specified resource is not valid"));
}
if size == 0 {
log::error!(
"unable to query version info data: {}",
std::io::Error::last_os_error()
);
return Err(anyhow!("no value is available for the specified name"));
}
buffer
};
let version_info = unsafe { &*(version_info_raw as *mut VS_FIXEDFILEINFO) };
// https://learn.microsoft.com/en-us/windows/win32/api/verrsrc/ns-verrsrc-vs_fixedfileinfo
if version_info.dwSignature == 0xFEEF04BD {
return Ok(SemanticVersion::new(
((version_info.dwProductVersionMS >> 16) & 0xFFFF) as usize,
(version_info.dwProductVersionMS & 0xFFFF) as usize,
((version_info.dwProductVersionLS >> 16) & 0xFFFF) as usize,
));
} else {
log::error!(
"no version info present: {}",
std::io::Error::last_os_error()
);
return Err(anyhow!("no version info present"));
}
}
fn app_path(&self) -> Result<PathBuf> { fn app_path(&self) -> Result<PathBuf> {
Ok(std::env::current_exe()?) Ok(std::env::current_exe()?)
} }

View file

@ -605,7 +605,7 @@ impl Window {
handle: AnyWindowHandle, handle: AnyWindowHandle,
options: WindowOptions, options: WindowOptions,
cx: &mut AppContext, cx: &mut AppContext,
) -> Self { ) -> Result<Self> {
let WindowOptions { let WindowOptions {
window_bounds, window_bounds,
titlebar, titlebar,
@ -633,7 +633,7 @@ impl Window {
display_id, display_id,
window_background, window_background,
}, },
); )?;
let display_id = platform_window.display().map(|display| display.id()); let display_id = platform_window.display().map(|display| display.id());
let sprite_atlas = platform_window.sprite_atlas(); let sprite_atlas = platform_window.sprite_atlas();
let mouse_position = platform_window.mouse_position(); let mouse_position = platform_window.mouse_position();
@ -761,7 +761,7 @@ impl Window {
platform_window.set_app_id(&app_id); platform_window.set_app_id(&app_id);
} }
Window { Ok(Window {
handle, handle,
removed: false, removed: false,
platform_window, platform_window,
@ -807,7 +807,7 @@ impl Window {
focus_enabled: true, focus_enabled: true,
pending_input: None, pending_input: None,
prompt: None, prompt: None,
} })
} }
fn new_focus_listener( fn new_focus_listener(
&mut self, &mut self,

View file

@ -4,7 +4,7 @@ use crate::lsp_log::LogMenuItem;
use super::*; use super::*;
use futures::StreamExt; use futures::StreamExt;
use gpui::{Context, TestAppContext, VisualTestContext}; use gpui::{Context, SemanticVersion, TestAppContext, VisualTestContext};
use language::{ use language::{
tree_sitter_rust, FakeLspAdapter, Language, LanguageConfig, LanguageMatcher, LanguageServerName, tree_sitter_rust, FakeLspAdapter, Language, LanguageConfig, LanguageMatcher, LanguageServerName,
}; };
@ -105,7 +105,7 @@ fn init_test(cx: &mut gpui::TestAppContext) {
let settings_store = SettingsStore::test(cx); let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store); cx.set_global(settings_store);
theme::init(theme::LoadThemes::JustBase, cx); theme::init(theme::LoadThemes::JustBase, cx);
release_channel::init("0.0.0", cx); release_channel::init(SemanticVersion::default(), cx);
language::init(cx); language::init(cx);
client::init_settings(cx); client::init_settings(cx);
Project::init_settings(cx); Project::init_settings(cx);

View file

@ -1317,7 +1317,7 @@ impl FakeLanguageServer {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use gpui::TestAppContext; use gpui::{SemanticVersion, TestAppContext};
use std::str::FromStr; use std::str::FromStr;
#[ctor::ctor] #[ctor::ctor]
@ -1330,7 +1330,7 @@ mod tests {
#[gpui::test] #[gpui::test]
async fn test_fake(cx: &mut TestAppContext) { async fn test_fake(cx: &mut TestAppContext) {
cx.update(|cx| { cx.update(|cx| {
release_channel::init("0.0.0", cx); release_channel::init(SemanticVersion::default(), cx);
}); });
let (server, mut fake) = FakeLanguageServer::new( let (server, mut fake) = FakeLanguageServer::new(
LanguageServerId(0), LanguageServerId(0),

View file

@ -147,7 +147,8 @@ pub fn main() {
cx, cx,
) )
}) })
}); })
.unwrap();
}); });
} }

View file

@ -1,7 +1,7 @@
use crate::{Event, *}; use crate::{Event, *};
use fs::FakeFs; use fs::FakeFs;
use futures::{future, StreamExt}; use futures::{future, StreamExt};
use gpui::{AppContext, UpdateGlobal}; use gpui::{AppContext, SemanticVersion, UpdateGlobal};
use language::{ use language::{
language_settings::{AllLanguageSettings, LanguageSettingsContent}, language_settings::{AllLanguageSettings, LanguageSettingsContent},
tree_sitter_rust, tree_sitter_typescript, Diagnostic, FakeLspAdapter, LanguageConfig, tree_sitter_rust, tree_sitter_typescript, Diagnostic, FakeLspAdapter, LanguageConfig,
@ -5135,7 +5135,7 @@ fn init_test(cx: &mut gpui::TestAppContext) {
cx.update(|cx| { cx.update(|cx| {
let settings_store = SettingsStore::test(cx); let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store); cx.set_global(settings_store);
release_channel::init("0.0.0", cx); release_channel::init(SemanticVersion::default(), cx);
language::init(cx); language::init(cx);
Project::init_settings(cx); Project::init_settings(cx);
}); });

View file

@ -260,7 +260,7 @@ impl PickerDelegate for ProjectSymbolsDelegate {
mod tests { mod tests {
use super::*; use super::*;
use futures::StreamExt; use futures::StreamExt;
use gpui::{TestAppContext, VisualContext}; use gpui::{SemanticVersion, TestAppContext, VisualContext};
use language::{FakeLspAdapter, Language, LanguageConfig, LanguageMatcher}; use language::{FakeLspAdapter, Language, LanguageConfig, LanguageMatcher};
use project::FakeFs; use project::FakeFs;
use serde_json::json; use serde_json::json;
@ -395,7 +395,7 @@ mod tests {
let store = SettingsStore::test(cx); let store = SettingsStore::test(cx);
cx.set_global(store); cx.set_global(store);
theme::init(theme::LoadThemes::JustBase, cx); theme::init(theme::LoadThemes::JustBase, cx);
release_channel::init("0.0.0", cx); release_channel::init(SemanticVersion::default(), cx);
language::init(cx); language::init(cx);
Project::init_settings(cx); Project::init_settings(cx);
workspace::init_settings(cx); workspace::init_settings(cx);

View file

@ -59,20 +59,21 @@ impl AppVersion {
/// 1. the `ZED_APP_VERSION` environment variable, /// 1. the `ZED_APP_VERSION` environment variable,
/// 2. the [`AppContext::app_metadata`], /// 2. the [`AppContext::app_metadata`],
/// 3. the passed in `pkg_version`. /// 3. the passed in `pkg_version`.
pub fn init(pkg_version: &str, cx: &mut AppContext) { pub fn init(pkg_version: &str) -> SemanticVersion {
let version = if let Ok(from_env) = env::var("ZED_APP_VERSION") { if let Ok(from_env) = env::var("ZED_APP_VERSION") {
from_env.parse().expect("invalid ZED_APP_VERSION") from_env.parse().expect("invalid ZED_APP_VERSION")
} else { } else {
cx.app_metadata() pkg_version.parse().expect("invalid version in Cargo.toml")
.app_version }
.unwrap_or_else(|| pkg_version.parse().expect("invalid version in Cargo.toml"))
};
cx.set_global(GlobalAppVersion(version))
} }
/// Returns the global version number. /// Returns the global version number.
pub fn global(cx: &AppContext) -> SemanticVersion { pub fn global(cx: &AppContext) -> SemanticVersion {
cx.global::<GlobalAppVersion>().0 if cx.has_global::<GlobalAppVersion>() {
cx.global::<GlobalAppVersion>().0
} else {
SemanticVersion::default()
}
} }
} }
@ -100,8 +101,8 @@ struct GlobalReleaseChannel(ReleaseChannel);
impl Global for GlobalReleaseChannel {} impl Global for GlobalReleaseChannel {}
/// Initializes the release channel. /// Initializes the release channel.
pub fn init(pkg_version: &str, cx: &mut AppContext) { pub fn init(app_version: SemanticVersion, cx: &mut AppContext) {
AppVersion::init(pkg_version, cx); cx.set_global(GlobalAppVersion(app_version));
cx.set_global(GlobalReleaseChannel(*RELEASE_CHANNEL)) cx.set_global(GlobalReleaseChannel(*RELEASE_CHANNEL))
} }

View file

@ -160,7 +160,7 @@ pub struct HangReport {
pub backtrace: Vec<BacktraceFrame>, pub backtrace: Vec<BacktraceFrame>,
pub app_version: Option<SemanticVersion>, pub app_version: Option<SemanticVersion>,
pub os_name: String, pub os_name: String,
pub os_version: Option<SemanticVersion>, pub os_version: Option<String>,
pub architecture: String, pub architecture: String,
pub installation_id: Option<String>, pub installation_id: Option<String>,
} }

View file

@ -1,7 +1,7 @@
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use editor::test::editor_lsp_test_context::EditorLspTestContext; use editor::test::editor_lsp_test_context::EditorLspTestContext;
use gpui::{Context, View, VisualContext}; use gpui::{Context, SemanticVersion, View, VisualContext};
use search::{project_search::ProjectSearchBar, BufferSearchBar}; use search::{project_search::ProjectSearchBar, BufferSearchBar};
use crate::{state::Operator, *}; use crate::{state::Operator, *};
@ -19,7 +19,7 @@ impl VimTestContext {
search::init(cx); search::init(cx);
let settings = SettingsStore::test(cx); let settings = SettingsStore::test(cx);
cx.set_global(settings); cx.set_global(settings);
release_channel::init("0.0.0", cx); release_channel::init(SemanticVersion::default(), cx);
command_palette::init(cx); command_palette::init(cx);
crate::init(cx); crate::init(cx);
}); });

View file

@ -5,7 +5,7 @@ use client::{telemetry::Telemetry, TelemetrySettings};
use db::kvp::KEY_VALUE_STORE; use db::kvp::KEY_VALUE_STORE;
use gpui::{ use gpui::{
svg, AnyElement, AppContext, EventEmitter, FocusHandle, FocusableView, InteractiveElement, svg, AnyElement, AppContext, EventEmitter, FocusHandle, FocusableView, InteractiveElement,
ParentElement, Render, Styled, Subscription, View, ViewContext, VisualContext, WeakView, ParentElement, Render, Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView,
WindowContext, WindowContext,
}; };
use settings::{Settings, SettingsStore}; use settings::{Settings, SettingsStore};
@ -36,19 +36,21 @@ pub fn init(cx: &mut AppContext) {
base_keymap_picker::init(cx); base_keymap_picker::init(cx);
} }
pub fn show_welcome_view(app_state: Arc<AppState>, cx: &mut AppContext) { pub fn show_welcome_view(
app_state: Arc<AppState>,
cx: &mut AppContext,
) -> Task<anyhow::Result<()>> {
open_new(app_state, cx, |workspace, cx| { open_new(app_state, cx, |workspace, cx| {
workspace.toggle_dock(DockPosition::Left, cx); workspace.toggle_dock(DockPosition::Left, cx);
let welcome_page = WelcomePage::new(workspace, cx); let welcome_page = WelcomePage::new(workspace, cx);
workspace.add_item_to_center(Box::new(welcome_page.clone()), cx); workspace.add_item_to_center(Box::new(welcome_page.clone()), cx);
cx.focus_view(&welcome_page); cx.focus_view(&welcome_page);
cx.notify(); cx.notify();
})
.detach();
db::write_and_log(cx, || { db::write_and_log(cx, || {
KEY_VALUE_STORE.write_kvp(FIRST_OPEN.to_string(), "false".to_string()) KEY_VALUE_STORE.write_kvp(FIRST_OPEN.to_string(), "false".to_string())
}); });
})
} }
pub struct WelcomePage { pub struct WelcomePage {

View file

@ -4853,18 +4853,16 @@ pub fn open_new(
app_state: Arc<AppState>, app_state: Arc<AppState>,
cx: &mut AppContext, cx: &mut AppContext,
init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send, init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
) -> Task<()> { ) -> Task<anyhow::Result<()>> {
let task = Workspace::new_local(Vec::new(), app_state, None, cx); let task = Workspace::new_local(Vec::new(), app_state, None, cx);
cx.spawn(|mut cx| async move { cx.spawn(|mut cx| async move {
if let Some((workspace, opened_paths)) = task.await.log_err() { let (workspace, opened_paths) = task.await?;
workspace workspace.update(&mut cx, |workspace, cx| {
.update(&mut cx, |workspace, cx| { if opened_paths.is_empty() {
if opened_paths.is_empty() { init(workspace, cx)
init(workspace, cx) }
} })?;
}) Ok(())
.log_err();
}
}) })
} }
@ -4936,7 +4934,7 @@ pub fn join_hosted_project(
Workspace::new(Default::default(), project, app_state.clone(), cx) Workspace::new(Default::default(), project, app_state.clone(), cx)
}) })
}) })
})? })??
}; };
workspace.update(&mut cx, |_, cx| { workspace.update(&mut cx, |_, cx| {
@ -5011,7 +5009,7 @@ pub fn join_dev_server_project(
Workspace::new(Some(workspace_id), project, app_state.clone(), cx) Workspace::new(Some(workspace_id), project, app_state.clone(), cx)
}) })
}) })
})? })??
} }
}; };
@ -5074,7 +5072,7 @@ pub fn join_in_room_project(
Workspace::new(Default::default(), project, app_state.clone(), cx) Workspace::new(Default::default(), project, app_state.clone(), cx)
}) })
}) })
})? })??
}; };
workspace.update(&mut cx, |workspace, cx| { workspace.update(&mut cx, |workspace, cx| {

View file

@ -103,6 +103,9 @@ zed_actions.workspace = true
[target.'cfg(target_os = "windows")'.build-dependencies] [target.'cfg(target_os = "windows")'.build-dependencies]
winresource = "0.1" winresource = "0.1"
[target.'cfg(target_os = "linux")'.dependencies]
ashpd.workspace = true
[dev-dependencies] [dev-dependencies]
call = { workspace = true, features = ["test-support"] } call = { workspace = true, features = ["test-support"] }
editor = { workspace = true, features = ["test-support"] } editor = { workspace = true, features = ["test-support"] }

View file

@ -27,7 +27,7 @@ use log::LevelFilter;
use assets::Assets; use assets::Assets;
use node_runtime::RealNodeRuntime; use node_runtime::RealNodeRuntime;
use parking_lot::Mutex; use parking_lot::Mutex;
use release_channel::AppCommitSha; use release_channel::{AppCommitSha, AppVersion};
use settings::{handle_settings_file_changes, watch_config_file, Settings, SettingsStore}; use settings::{handle_settings_file_changes, watch_config_file, Settings, SettingsStore};
use simplelog::ConfigBuilder; use simplelog::ConfigBuilder;
use smol::process::Command; use smol::process::Command;
@ -56,21 +56,64 @@ use crate::zed::inline_completion_registry;
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
fn fail_to_launch(e: anyhow::Error) { fn fail_to_launch(e: anyhow::Error) {
eprintln!("Zed failed to launch: {:?}", e);
App::new().run(move |cx| { App::new().run(move |cx| {
let window = cx.open_window(gpui::WindowOptions::default(), |cx| cx.new_view(|_| gpui::Empty)); if let Ok(window) = cx.open_window(gpui::WindowOptions::default(), |cx| cx.new_view(|_| gpui::Empty)) {
window.update(cx, |_, cx| { window.update(cx, |_, cx| {
let response = cx.prompt(gpui::PromptLevel::Critical, "Zed failed to launch", Some(&format!("{}\n\nFor help resolving this, please open an issue on https://github.com/zed-industries/zed", e)), &["Exit"]); let response = cx.prompt(gpui::PromptLevel::Critical, "Zed failed to launch", Some(&format!("{}\n\nFor help resolving this, please open an issue on https://github.com/zed-industries/zed", e)), &["Exit"]);
cx.spawn(|_, mut cx| async move { cx.spawn(|_, mut cx| async move {
response.await?; response.await?;
cx.update(|cx| { cx.update(|cx| {
cx.quit() cx.quit()
}) })
}).detach_and_log_err(cx); }).detach_and_log_err(cx);
}).log_err(); }).log_err();
} else {
fail_to_open_window(e, cx)
}
}) })
} }
fn fail_to_open_window_async(e: anyhow::Error, cx: &mut AsyncAppContext) {
cx.update(|cx| fail_to_open_window(e, cx)).log_err();
}
fn fail_to_open_window(e: anyhow::Error, _cx: &mut AppContext) {
eprintln!("Zed failed to open a window: {:?}", e);
#[cfg(not(target_os = "linux"))]
{
process::exit(1);
}
#[cfg(target_os = "linux")]
{
use ashpd::desktop::notification::{Notification, NotificationProxy, Priority};
_cx.spawn(|_cx| async move {
let Ok(proxy) = NotificationProxy::new().await else {
process::exit(1);
};
let notification_id = "dev.zed.Oops";
proxy
.add_notification(
notification_id,
Notification::new("Zed failed to launch")
.body(Some(format!("{:?}", e).as_str()))
.priority(Priority::High)
.icon(ashpd::desktop::Icon::with_names(&[
"dialog-question-symbolic",
])),
)
.await
.ok();
process::exit(1);
})
.detach();
}
}
enum AppMode { enum AppMode {
Headless(DevServerToken), Headless(DevServerToken),
Ui, Ui,
@ -122,10 +165,6 @@ fn init_ui(app_state: Arc<AppState>, cx: &mut AppContext) -> Result<()> {
} }
}; };
if let Err(err) = cx.can_open_windows() {
return Err(err);
}
SystemAppearance::init(cx); SystemAppearance::init(cx);
load_embedded_fonts(cx); load_embedded_fonts(cx);
@ -256,7 +295,9 @@ fn main() {
.ok() .ok()
.unzip(); .unzip();
let session_id = Uuid::new_v4().to_string(); let session_id = Uuid::new_v4().to_string();
reliability::init_panic_hook(&app, installation_id.clone(), session_id.clone());
let app_version = AppVersion::init(env!("CARGO_PKG_VERSION"));
reliability::init_panic_hook(installation_id.clone(), app_version, session_id.clone());
let (open_listener, mut open_rx) = OpenListener::new(); let (open_listener, mut open_rx) = OpenListener::new();
@ -319,14 +360,18 @@ fn main() {
{ {
cx.spawn({ cx.spawn({
let app_state = app_state.clone(); let app_state = app_state.clone();
|cx| async move { restore_or_create_workspace(app_state, cx).await } |mut cx| async move {
if let Err(e) = restore_or_create_workspace(app_state, &mut cx).await {
fail_to_open_window_async(e, &mut cx)
}
}
}) })
.detach(); .detach();
} }
}); });
app.run(move |cx| { app.run(move |cx| {
release_channel::init(env!("CARGO_PKG_VERSION"), cx); release_channel::init(app_version, cx);
if let Some(build_sha) = option_env!("ZED_COMMIT_SHA") { if let Some(build_sha) = option_env!("ZED_COMMIT_SHA") {
AppCommitSha::set_global(AppCommitSha(build_sha.into()), cx); AppCommitSha::set_global(AppCommitSha(build_sha.into()), cx);
} }
@ -421,7 +466,11 @@ fn main() {
init_ui(app_state.clone(), cx).unwrap(); init_ui(app_state.clone(), cx).unwrap();
cx.spawn({ cx.spawn({
let app_state = app_state.clone(); let app_state = app_state.clone();
|cx| async move { restore_or_create_workspace(app_state, cx).await } |mut cx| async move {
if let Err(e) = restore_or_create_workspace(app_state, &mut cx).await {
fail_to_open_window_async(e, &mut cx)
}
}
}) })
.detach(); .detach();
} }
@ -448,13 +497,12 @@ fn handle_open_request(request: OpenRequest, app_state: Arc<AppState>, cx: &mut
let app_state = app_state.clone(); let app_state = app_state.clone();
cx.spawn(move |cx| handle_cli_connection(connection, app_state, cx)) cx.spawn(move |cx| handle_cli_connection(connection, app_state, cx))
.detach(); .detach();
return;
} }
if let Err(e) = init_ui(app_state.clone(), cx) { if let Err(e) = init_ui(app_state.clone(), cx) {
log::error!("{}", e); fail_to_open_window(e, cx);
return; return;
} };
let mut task = None; let mut task = None;
if !request.open_paths.is_empty() { if !request.open_paths.is_empty() {
@ -478,48 +526,59 @@ fn handle_open_request(request: OpenRequest, app_state: Arc<AppState>, cx: &mut
if !request.open_channel_notes.is_empty() || request.join_channel.is_some() { if !request.open_channel_notes.is_empty() || request.join_channel.is_some() {
cx.spawn(|mut cx| async move { cx.spawn(|mut cx| async move {
if let Some(task) = task { let result = maybe!(async {
task.await?; if let Some(task) = task {
} task.await?;
let client = app_state.client.clone(); }
// we continue even if authentication fails as join_channel/ open channel notes will let client = app_state.client.clone();
// show a visible error message. // we continue even if authentication fails as join_channel/ open channel notes will
authenticate(client, &cx).await.log_err(); // show a visible error message.
authenticate(client, &cx).await.log_err();
if let Some(channel_id) = request.join_channel { if let Some(channel_id) = request.join_channel {
cx.update(|cx| { cx.update(|cx| {
workspace::join_channel( workspace::join_channel(
client::ChannelId(channel_id), client::ChannelId(channel_id),
app_state.clone(), app_state.clone(),
None, None,
cx, cx,
) )
})? })?
.await?; .await?;
} }
let workspace_window = let workspace_window =
workspace::get_any_active_workspace(app_state, cx.clone()).await?; workspace::get_any_active_workspace(app_state, cx.clone()).await?;
let workspace = workspace_window.root_view(&cx)?; let workspace = workspace_window.root_view(&cx)?;
let mut promises = Vec::new(); let mut promises = Vec::new();
for (channel_id, heading) in request.open_channel_notes { for (channel_id, heading) in request.open_channel_notes {
promises.push(cx.update_window(workspace_window.into(), |_, cx| { promises.push(cx.update_window(workspace_window.into(), |_, cx| {
ChannelView::open( ChannelView::open(
client::ChannelId(channel_id), client::ChannelId(channel_id),
heading, heading,
workspace.clone(), workspace.clone(),
cx, cx,
) )
.log_err() .log_err()
})?) })?)
}
future::join_all(promises).await;
anyhow::Ok(())
})
.await;
if let Err(err) = result {
fail_to_open_window_async(err, &mut cx);
} }
future::join_all(promises).await;
anyhow::Ok(())
}) })
.detach_and_log_err(cx); .detach()
} else if let Some(task) = task { } else if let Some(task) = task {
task.detach_and_log_err(cx) cx.spawn(|mut cx| async move {
if let Err(err) = task.await {
fail_to_open_window_async(err, &mut cx);
}
})
.detach();
} }
} }
@ -562,41 +621,39 @@ async fn installation_id() -> Result<(String, bool)> {
Ok((installation_id, false)) Ok((installation_id, false))
} }
async fn restore_or_create_workspace(app_state: Arc<AppState>, cx: AsyncAppContext) { async fn restore_or_create_workspace(
maybe!(async { app_state: Arc<AppState>,
let restore_behaviour = cx: &mut AsyncAppContext,
cx.update(|cx| WorkspaceSettings::get(None, cx).restore_on_startup)?; ) -> Result<()> {
let location = match restore_behaviour { let restore_behaviour = cx.update(|cx| WorkspaceSettings::get(None, cx).restore_on_startup)?;
workspace::RestoreOnStartupBehaviour::LastWorkspace => { let location = match restore_behaviour {
workspace::last_opened_workspace_paths().await workspace::RestoreOnStartupBehaviour::LastWorkspace => {
} workspace::last_opened_workspace_paths().await
_ => None,
};
if let Some(location) = location {
cx.update(|cx| {
workspace::open_paths(
location.paths().as_ref(),
app_state,
workspace::OpenOptions::default(),
cx,
)
})?
.await
.log_err();
} else if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) {
cx.update(|cx| show_welcome_view(app_state, cx)).log_err();
} else {
cx.update(|cx| {
workspace::open_new(app_state, cx, |workspace, cx| {
Editor::new_file(workspace, &Default::default(), cx)
})
.detach();
})?;
} }
anyhow::Ok(()) _ => None,
}) };
.await if let Some(location) = location {
.log_err(); cx.update(|cx| {
workspace::open_paths(
location.paths().as_ref(),
app_state,
workspace::OpenOptions::default(),
cx,
)
})?
.await?;
} else if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) {
cx.update(|cx| show_welcome_view(app_state, cx))?.await?;
} else {
cx.update(|cx| {
workspace::open_new(app_state, cx, |workspace, cx| {
Editor::new_file(workspace, &Default::default(), cx)
})
})?
.await?;
}
Ok(())
} }
fn init_paths() -> anyhow::Result<()> { fn init_paths() -> anyhow::Result<()> {

View file

@ -1,8 +1,9 @@
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use backtrace::{self, Backtrace}; use backtrace::{self, Backtrace};
use chrono::Utc; use chrono::Utc;
use client::telemetry;
use db::kvp::KEY_VALUE_STORE; use db::kvp::KEY_VALUE_STORE;
use gpui::{App, AppContext, SemanticVersion}; use gpui::{AppContext, SemanticVersion};
use http::Method; use http::Method;
use isahc::config::Configurable; use isahc::config::Configurable;
@ -26,9 +27,12 @@ use util::{paths, ResultExt};
use crate::stdout_is_a_pty; use crate::stdout_is_a_pty;
static PANIC_COUNT: AtomicU32 = AtomicU32::new(0); static PANIC_COUNT: AtomicU32 = AtomicU32::new(0);
pub fn init_panic_hook(app: &App, installation_id: Option<String>, session_id: String) { pub fn init_panic_hook(
installation_id: Option<String>,
app_version: SemanticVersion,
session_id: String,
) {
let is_pty = stdout_is_a_pty(); let is_pty = stdout_is_a_pty();
let app_metadata = app.metadata();
panic::set_hook(Box::new(move |info| { panic::set_hook(Box::new(move |info| {
let prior_panic_count = PANIC_COUNT.fetch_add(1, Ordering::SeqCst); let prior_panic_count = PANIC_COUNT.fetch_add(1, Ordering::SeqCst);
@ -64,14 +68,6 @@ pub fn init_panic_hook(app: &App, installation_id: Option<String>, session_id: S
std::process::exit(-1); std::process::exit(-1);
} }
let app_version = if let Some(version) = app_metadata.app_version {
version.to_string()
} else {
option_env!("CARGO_PKG_VERSION")
.unwrap_or("dev")
.to_string()
};
let backtrace = Backtrace::new(); let backtrace = Backtrace::new();
let mut backtrace = backtrace let mut backtrace = backtrace
.frames() .frames()
@ -101,11 +97,8 @@ pub fn init_panic_hook(app: &App, installation_id: Option<String>, session_id: S
}), }),
app_version: app_version.to_string(), app_version: app_version.to_string(),
release_channel: RELEASE_CHANNEL.display_name().into(), release_channel: RELEASE_CHANNEL.display_name().into(),
os_name: app_metadata.os_name.into(), os_name: telemetry::os_name(),
os_version: app_metadata os_version: Some(telemetry::os_version()),
.os_version
.as_ref()
.map(SemanticVersion::to_string),
architecture: env::consts::ARCH.into(), architecture: env::consts::ARCH.into(),
panicked_on: Utc::now().timestamp_millis(), panicked_on: Utc::now().timestamp_millis(),
backtrace, backtrace,
@ -182,7 +175,6 @@ pub fn monitor_main_thread_hangs(
let foreground_executor = cx.foreground_executor(); let foreground_executor = cx.foreground_executor();
let background_executor = cx.background_executor(); let background_executor = cx.background_executor();
let telemetry_settings = *client::TelemetrySettings::get_global(cx); let telemetry_settings = *client::TelemetrySettings::get_global(cx);
let metadata = cx.app_metadata();
// Initialize SIGUSR2 handler to send a backrace to a channel. // Initialize SIGUSR2 handler to send a backrace to a channel.
let (backtrace_tx, backtrace_rx) = mpsc::channel(); let (backtrace_tx, backtrace_rx) = mpsc::channel();
@ -258,9 +250,14 @@ pub fn monitor_main_thread_hangs(
}) })
.detach(); .detach();
let app_version = release_channel::AppVersion::global(cx);
let os_name = client::telemetry::os_name();
background_executor background_executor
.clone() .clone()
.spawn(async move { .spawn(async move {
let os_version = client::telemetry::os_version();
loop { loop {
while let Some(_) = backtrace_rx.recv().ok() { while let Some(_) = backtrace_rx.recv().ok() {
if !telemetry_settings.diagnostics { if !telemetry_settings.diagnostics {
@ -306,9 +303,9 @@ pub fn monitor_main_thread_hangs(
let report = HangReport { let report = HangReport {
backtrace, backtrace,
app_version: metadata.app_version, app_version: Some(app_version),
os_name: metadata.os_name.to_owned(), os_name: os_name.clone(),
os_version: metadata.os_version, os_version: Some(os_version.clone()),
architecture: env::consts::ARCH.into(), architecture: env::consts::ARCH.into(),
installation_id: installation_id.clone(), installation_id: installation_id.clone(),
}; };

View file

@ -894,7 +894,7 @@ mod tests {
use editor::{display_map::DisplayRow, scroll::Autoscroll, DisplayPoint, Editor}; use editor::{display_map::DisplayRow, scroll::Autoscroll, DisplayPoint, Editor};
use gpui::{ use gpui::{
actions, Action, AnyWindowHandle, AppContext, AssetSource, BorrowAppContext, Entity, actions, Action, AnyWindowHandle, AppContext, AssetSource, BorrowAppContext, Entity,
TestAppContext, VisualTestContext, WindowHandle, SemanticVersion, TestAppContext, VisualTestContext, WindowHandle,
}; };
use language::{LanguageMatcher, LanguageRegistry}; use language::{LanguageMatcher, LanguageRegistry};
use project::{Project, ProjectPath, WorktreeSettings}; use project::{Project, ProjectPath, WorktreeSettings};
@ -1314,7 +1314,8 @@ mod tests {
Editor::new_file(workspace, &Default::default(), cx) Editor::new_file(workspace, &Default::default(), cx)
}) })
}) })
.await; .await
.unwrap();
cx.run_until_parked(); cx.run_until_parked();
let workspace = cx let workspace = cx
@ -3088,7 +3089,7 @@ mod tests {
notifications::init(app_state.client.clone(), app_state.user_store.clone(), cx); notifications::init(app_state.client.clone(), app_state.user_store.clone(), cx);
workspace::init(app_state.clone(), cx); workspace::init(app_state.clone(), cx);
Project::init_settings(cx); Project::init_settings(cx);
release_channel::init("0.0.0", cx); release_channel::init(SemanticVersion::default(), cx);
command_palette::init(cx); command_palette::init(cx);
language::init(cx); language::init(cx);
editor::init(cx); editor::init(cx);