1// Allow binary to be called Zed for a nice application menu when running executable directly
2#![allow(non_snake_case)]
3// Disable command line from opening on release mode
4#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
5
6mod zed;
7
8use anyhow::{anyhow, Context as _, Result};
9use backtrace::Backtrace;
10use chrono::Utc;
11use clap::{command, Parser};
12use cli::FORCE_CLI_MODE_ENV_VAR_NAME;
13use client::{
14 parse_zed_link, telemetry::Telemetry, Client, ClientSettings, DevServerToken, UserStore,
15};
16use collab_ui::channel_view::ChannelView;
17use copilot::Copilot;
18use copilot_ui::CopilotCompletionProvider;
19use db::kvp::KEY_VALUE_STORE;
20use editor::{Editor, EditorMode};
21use env_logger::Builder;
22use fs::RealFs;
23use futures::{future, StreamExt};
24use gpui::{
25 App, AppContext, AsyncAppContext, Context, SemanticVersion, Task, ViewContext, VisualContext,
26};
27use image_viewer;
28use isahc::{prelude::Configurable, Request};
29use language::LanguageRegistry;
30use log::LevelFilter;
31
32use assets::Assets;
33use mimalloc::MiMalloc;
34use node_runtime::RealNodeRuntime;
35use parking_lot::Mutex;
36use release_channel::{AppCommitSha, ReleaseChannel, RELEASE_CHANNEL};
37use serde::{Deserialize, Serialize};
38use settings::{
39 default_settings, handle_settings_file_changes, watch_config_file, Settings, SettingsStore,
40};
41use simplelog::ConfigBuilder;
42use smol::process::Command;
43use std::{
44 env,
45 ffi::OsStr,
46 fs::OpenOptions,
47 io::{IsTerminal, Write},
48 panic,
49 path::Path,
50 sync::{
51 atomic::{AtomicU32, Ordering},
52 Arc,
53 },
54 thread,
55};
56use theme::{ActiveTheme, SystemAppearance, ThemeRegistry, ThemeSettings};
57use util::{
58 http::{HttpClient, HttpClientWithUrl},
59 maybe, parse_env_output,
60 paths::{self, CRASHES_DIR, CRASHES_RETIRED_DIR},
61 ResultExt, TryFutureExt,
62};
63use uuid::Uuid;
64use welcome::{show_welcome_view, BaseKeymap, FIRST_OPEN};
65use workspace::{AppState, WorkspaceStore};
66use zed::{
67 app_menus, build_window_options, ensure_only_instance, handle_cli_connection,
68 handle_keymap_file_changes, initialize_workspace, open_paths_with_positions, IsOnlyInstance,
69 OpenListener, OpenRequest,
70};
71
72#[global_allocator]
73static GLOBAL: MiMalloc = MiMalloc;
74
75fn fail_to_launch(e: anyhow::Error) {
76 App::new().run(move |cx| {
77 let window = cx.open_window(gpui::WindowOptions::default(), |cx| cx.new_view(|_| gpui::Empty));
78 window.update(cx, |_, cx| {
79 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"]);
80
81 cx.spawn(|_, mut cx| async move {
82 response.await?;
83 cx.update(|cx| {
84 cx.quit()
85 })
86 }).detach_and_log_err(cx);
87 }).log_err();
88 })
89}
90
91fn main() {
92 menu::init();
93 zed_actions::init();
94
95 if let Err(e) = init_paths() {
96 fail_to_launch(e);
97 return;
98 }
99
100 init_logger();
101
102 if ensure_only_instance() != IsOnlyInstance::Yes {
103 return;
104 }
105
106 log::info!("========== starting zed ==========");
107 let app = App::new().with_assets(Assets);
108
109 let (installation_id, existing_installation_id_found) = app
110 .background_executor()
111 .block(installation_id())
112 .ok()
113 .unzip();
114 let session_id = Uuid::new_v4().to_string();
115 init_panic_hook(&app, installation_id.clone(), session_id.clone());
116
117 let git_binary_path = if option_env!("ZED_BUNDLE").as_deref() == Some("true") {
118 app.path_for_auxiliary_executable("git")
119 .context("could not find git binary path")
120 .log_err()
121 } else {
122 None
123 };
124 log::info!("Using git binary path: {:?}", git_binary_path);
125
126 let fs = Arc::new(RealFs::new(git_binary_path));
127 let user_settings_file_rx = watch_config_file(
128 &app.background_executor(),
129 fs.clone(),
130 paths::SETTINGS.clone(),
131 );
132 let user_keymap_file_rx = watch_config_file(
133 &app.background_executor(),
134 fs.clone(),
135 paths::KEYMAP.clone(),
136 );
137
138 let login_shell_env_loaded = if stdout_is_a_pty() {
139 Task::ready(())
140 } else {
141 app.background_executor().spawn(async {
142 load_login_shell_environment().await.log_err();
143 })
144 };
145
146 let (listener, mut open_rx) = OpenListener::new();
147 let listener = Arc::new(listener);
148 let open_listener = listener.clone();
149 app.on_open_urls(move |urls| open_listener.open_urls(urls));
150 app.on_reopen(move |cx| {
151 if let Some(app_state) = AppState::try_global(cx).and_then(|app_state| app_state.upgrade())
152 {
153 workspace::open_new(app_state, cx, |workspace, cx| {
154 Editor::new_file(workspace, &Default::default(), cx)
155 })
156 .detach();
157 }
158 });
159
160 app.run(move |cx| {
161 release_channel::init(env!("CARGO_PKG_VERSION"), cx);
162 if let Some(build_sha) = option_env!("ZED_COMMIT_SHA") {
163 AppCommitSha::set_global(AppCommitSha(build_sha.into()), cx);
164 }
165
166 SystemAppearance::init(cx);
167 OpenListener::set_global(listener.clone(), cx);
168
169 load_embedded_fonts(cx);
170
171 let mut store = SettingsStore::default();
172 store
173 .set_default_settings(default_settings().as_ref(), cx)
174 .unwrap();
175 cx.set_global(store);
176 handle_settings_file_changes(user_settings_file_rx, cx);
177 handle_keymap_file_changes(user_keymap_file_rx, cx);
178 client::init_settings(cx);
179
180 let clock = Arc::new(clock::RealSystemClock);
181 let http = Arc::new(HttpClientWithUrl::new(
182 &client::ClientSettings::get_global(cx).server_url,
183 ));
184
185 let client = client::Client::new(clock, http.clone(), cx);
186 let mut languages =
187 LanguageRegistry::new(login_shell_env_loaded, cx.background_executor().clone());
188 let copilot_language_server_id = languages.next_language_server_id();
189 languages.set_language_server_download_dir(paths::LANGUAGES_DIR.clone());
190 let languages = Arc::new(languages);
191 let node_runtime = RealNodeRuntime::new(http.clone());
192
193 language::init(cx);
194 languages::init(languages.clone(), node_runtime.clone(), cx);
195 let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
196 let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
197
198 Client::set_global(client.clone(), cx);
199
200 zed::init(cx);
201 theme::init(theme::LoadThemes::All(Box::new(Assets)), cx);
202 project::Project::init(&client, cx);
203 client::init(&client, cx);
204 command_palette::init(cx);
205 language::init(cx);
206 editor::init(cx);
207 image_viewer::init(cx);
208 diagnostics::init(cx);
209 copilot::init(
210 copilot_language_server_id,
211 http.clone(),
212 node_runtime.clone(),
213 cx,
214 );
215 assistant::init(client.clone(), cx);
216 init_inline_completion_provider(client.telemetry().clone(), cx);
217
218 extension::init(
219 fs.clone(),
220 client.clone(),
221 node_runtime.clone(),
222 languages.clone(),
223 ThemeRegistry::global(cx),
224 cx,
225 );
226
227 load_user_themes_in_background(fs.clone(), cx);
228 watch_themes(fs.clone(), cx);
229
230 watch_file_types(fs.clone(), cx);
231
232 languages.set_theme(cx.theme().clone());
233
234 cx.observe_global::<SettingsStore>({
235 let languages = languages.clone();
236 let http = http.clone();
237 let client = client.clone();
238
239 move |cx| {
240 for &mut window in cx.windows().iter_mut() {
241 let background_appearance = cx.theme().window_background_appearance();
242 window
243 .update(cx, |_, cx| {
244 cx.set_background_appearance(background_appearance)
245 })
246 .ok();
247 }
248 languages.set_theme(cx.theme().clone());
249 let new_host = &client::ClientSettings::get_global(cx).server_url;
250 if &http.base_url() != new_host {
251 http.set_base_url(new_host);
252 if client.status().borrow().is_connected() {
253 client.reconnect(&cx.to_async());
254 }
255 }
256 }
257 })
258 .detach();
259
260 let telemetry = client.telemetry();
261 telemetry.start(installation_id, session_id, cx);
262 telemetry.report_setting_event("theme", cx.theme().name.to_string());
263 telemetry.report_setting_event("keymap", BaseKeymap::get_global(cx).to_string());
264 telemetry.report_app_event(
265 match existing_installation_id_found {
266 Some(false) => "first open",
267 _ => "open",
268 }
269 .to_string(),
270 );
271 telemetry.flush_events();
272
273 let app_state = Arc::new(AppState {
274 languages: languages.clone(),
275 client: client.clone(),
276 user_store: user_store.clone(),
277 fs: fs.clone(),
278 build_window_options,
279 workspace_store,
280 node_runtime,
281 });
282 AppState::set_global(Arc::downgrade(&app_state), cx);
283
284 audio::init(Assets, cx);
285 auto_update::init(http.clone(), cx);
286
287 workspace::init(app_state.clone(), cx);
288 recent_projects::init(cx);
289
290 go_to_line::init(cx);
291 file_finder::init(cx);
292 tab_switcher::init(cx);
293 outline::init(cx);
294 project_symbols::init(cx);
295 project_panel::init(Assets, cx);
296 tasks_ui::init(cx);
297 channel::init(&client, user_store.clone(), cx);
298 search::init(cx);
299 vim::init(cx);
300 terminal_view::init(cx);
301
302 journal::init(app_state.clone(), cx);
303 language_selector::init(cx);
304 theme_selector::init(cx);
305 language_tools::init(cx);
306 call::init(app_state.client.clone(), app_state.user_store.clone(), cx);
307 notifications::init(app_state.client.clone(), app_state.user_store.clone(), cx);
308 collab_ui::init(&app_state, cx);
309 feedback::init(cx);
310 markdown_preview::init(cx);
311 welcome::init(cx);
312 extensions_ui::init(cx);
313
314 cx.set_menus(app_menus());
315 initialize_workspace(app_state.clone(), cx);
316
317 // todo(linux): unblock this
318 upload_panics_and_crashes(http.clone(), cx);
319
320 cx.activate(true);
321
322 let mut args = Args::parse();
323 if let Some(dev_server_token) = args.dev_server_token.take() {
324 let dev_server_token = DevServerToken(dev_server_token);
325 let server_url = ClientSettings::get_global(&cx).server_url.clone();
326 let client = client.clone();
327 client.set_dev_server_token(dev_server_token);
328 cx.spawn(|cx| async move {
329 client.authenticate_and_connect(false, &cx).await?;
330 log::info!("Connected to {}", server_url);
331 anyhow::Ok(())
332 })
333 .detach_and_log_err(cx);
334 } else {
335 let urls: Vec<_> = args
336 .paths_or_urls
337 .iter()
338 .filter_map(|arg| parse_url_arg(arg, cx).log_err())
339 .collect();
340
341 if !urls.is_empty() {
342 listener.open_urls(urls)
343 }
344 }
345
346 let mut triggered_authentication = false;
347
348 match open_rx
349 .try_next()
350 .ok()
351 .flatten()
352 .and_then(|urls| OpenRequest::parse(urls, cx).log_err())
353 {
354 Some(request) => {
355 triggered_authentication = handle_open_request(request, app_state.clone(), cx)
356 }
357 None => cx
358 .spawn({
359 let app_state = app_state.clone();
360 |cx| async move { restore_or_create_workspace(app_state, cx).await }
361 })
362 .detach(),
363 }
364
365 let app_state = app_state.clone();
366 cx.spawn(move |cx| async move {
367 while let Some(urls) = open_rx.next().await {
368 cx.update(|cx| {
369 if let Some(request) = OpenRequest::parse(urls, cx).log_err() {
370 handle_open_request(request, app_state.clone(), cx);
371 }
372 })
373 .ok();
374 }
375 })
376 .detach();
377
378 if !triggered_authentication {
379 cx.spawn(|cx| async move { authenticate(client, &cx).await })
380 .detach_and_log_err(cx);
381 }
382 });
383}
384
385fn handle_open_request(
386 request: OpenRequest,
387 app_state: Arc<AppState>,
388 cx: &mut AppContext,
389) -> bool {
390 if let Some(connection) = request.cli_connection {
391 let app_state = app_state.clone();
392 cx.spawn(move |cx| handle_cli_connection(connection, app_state, cx))
393 .detach();
394 return false;
395 }
396
397 let mut task = None;
398 if !request.open_paths.is_empty() {
399 let app_state = app_state.clone();
400 task = Some(cx.spawn(|mut cx| async move {
401 let (_window, results) = open_paths_with_positions(
402 &request.open_paths,
403 app_state,
404 workspace::OpenOptions::default(),
405 &mut cx,
406 )
407 .await?;
408 for result in results.into_iter().flatten() {
409 if let Err(err) = result {
410 log::error!("Error opening path: {err}",);
411 }
412 }
413 anyhow::Ok(())
414 }));
415 }
416
417 if !request.open_channel_notes.is_empty() || request.join_channel.is_some() {
418 cx.spawn(|mut cx| async move {
419 if let Some(task) = task {
420 task.await?;
421 }
422 let client = app_state.client.clone();
423 // we continue even if authentication fails as join_channel/ open channel notes will
424 // show a visible error message.
425 authenticate(client, &cx).await.log_err();
426
427 if let Some(channel_id) = request.join_channel {
428 cx.update(|cx| {
429 workspace::join_channel(
430 client::ChannelId(channel_id),
431 app_state.clone(),
432 None,
433 cx,
434 )
435 })?
436 .await?;
437 }
438
439 let workspace_window =
440 workspace::get_any_active_workspace(app_state, cx.clone()).await?;
441 let workspace = workspace_window.root_view(&cx)?;
442
443 let mut promises = Vec::new();
444 for (channel_id, heading) in request.open_channel_notes {
445 promises.push(cx.update_window(workspace_window.into(), |_, cx| {
446 ChannelView::open(
447 client::ChannelId(channel_id),
448 heading,
449 workspace.clone(),
450 cx,
451 )
452 .log_err()
453 })?)
454 }
455 future::join_all(promises).await;
456 anyhow::Ok(())
457 })
458 .detach_and_log_err(cx);
459 true
460 } else {
461 if let Some(task) = task {
462 task.detach_and_log_err(cx)
463 }
464 false
465 }
466}
467
468async fn authenticate(client: Arc<Client>, cx: &AsyncAppContext) -> Result<()> {
469 if stdout_is_a_pty() {
470 if client::IMPERSONATE_LOGIN.is_some() {
471 client.authenticate_and_connect(false, &cx).await?;
472 }
473 } else if client.has_keychain_credentials(&cx).await {
474 client.authenticate_and_connect(true, &cx).await?;
475 }
476 Ok::<_, anyhow::Error>(())
477}
478
479async fn installation_id() -> Result<(String, bool)> {
480 let legacy_key_name = "device_id".to_string();
481 let key_name = "installation_id".to_string();
482
483 // Migrate legacy key to new key
484 if let Ok(Some(installation_id)) = KEY_VALUE_STORE.read_kvp(&legacy_key_name) {
485 KEY_VALUE_STORE
486 .write_kvp(key_name, installation_id.clone())
487 .await?;
488 KEY_VALUE_STORE.delete_kvp(legacy_key_name).await?;
489 return Ok((installation_id, true));
490 }
491
492 if let Ok(Some(installation_id)) = KEY_VALUE_STORE.read_kvp(&key_name) {
493 return Ok((installation_id, true));
494 }
495
496 let installation_id = Uuid::new_v4().to_string();
497
498 KEY_VALUE_STORE
499 .write_kvp(key_name, installation_id.clone())
500 .await?;
501
502 Ok((installation_id, false))
503}
504
505async fn restore_or_create_workspace(app_state: Arc<AppState>, cx: AsyncAppContext) {
506 maybe!(async {
507 if let Some(location) = workspace::last_opened_workspace_paths().await {
508 cx.update(|cx| {
509 workspace::open_paths(
510 location.paths().as_ref(),
511 app_state,
512 workspace::OpenOptions::default(),
513 cx,
514 )
515 })?
516 .await
517 .log_err();
518 } else if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) {
519 cx.update(|cx| show_welcome_view(app_state, cx)).log_err();
520 } else {
521 cx.update(|cx| {
522 workspace::open_new(app_state, cx, |workspace, cx| {
523 Editor::new_file(workspace, &Default::default(), cx)
524 })
525 .detach();
526 })?;
527 }
528 anyhow::Ok(())
529 })
530 .await
531 .log_err();
532}
533
534fn init_paths() -> anyhow::Result<()> {
535 for path in [
536 &*util::paths::CONFIG_DIR,
537 &*util::paths::EXTENSIONS_DIR,
538 &*util::paths::LANGUAGES_DIR,
539 &*util::paths::DB_DIR,
540 &*util::paths::LOGS_DIR,
541 &*util::paths::TEMP_DIR,
542 ]
543 .iter()
544 {
545 std::fs::create_dir_all(path)
546 .map_err(|e| anyhow!("Could not create directory {:?}: {}", path, e))?;
547 }
548 Ok(())
549}
550
551fn init_logger() {
552 if stdout_is_a_pty() {
553 init_stdout_logger();
554 } else {
555 let level = LevelFilter::Info;
556
557 // Prevent log file from becoming too large.
558 const KIB: u64 = 1024;
559 const MIB: u64 = 1024 * KIB;
560 const MAX_LOG_BYTES: u64 = MIB;
561 if std::fs::metadata(&*paths::LOG).map_or(false, |metadata| metadata.len() > MAX_LOG_BYTES)
562 {
563 let _ = std::fs::rename(&*paths::LOG, &*paths::OLD_LOG);
564 }
565
566 match OpenOptions::new()
567 .create(true)
568 .append(true)
569 .open(&*paths::LOG)
570 {
571 Ok(log_file) => {
572 let config = ConfigBuilder::new()
573 .set_time_format_str("%Y-%m-%dT%T%:z")
574 .set_time_to_local(true)
575 .build();
576
577 simplelog::WriteLogger::init(level, config, log_file)
578 .expect("could not initialize logger");
579 }
580 Err(err) => {
581 init_stdout_logger();
582 log::error!(
583 "could not open log file, defaulting to stdout logging: {}",
584 err
585 );
586 }
587 }
588 }
589}
590
591fn init_stdout_logger() {
592 Builder::new()
593 .parse_default_env()
594 .format(|buf, record| {
595 use env_logger::fmt::Color;
596
597 let subtle = buf
598 .style()
599 .set_color(Color::Black)
600 .set_intense(true)
601 .clone();
602 write!(buf, "{}", subtle.value("["))?;
603 write!(
604 buf,
605 "{} ",
606 chrono::Local::now().format("%Y-%m-%dT%H:%M:%S%:z")
607 )?;
608 write!(buf, "{:<5}", buf.default_styled_level(record.level()))?;
609 if let Some(path) = record.module_path() {
610 write!(buf, " {}", path)?;
611 }
612 write!(buf, "{}", subtle.value("]"))?;
613 writeln!(buf, " {}", record.args())
614 })
615 .init();
616}
617
618#[derive(Serialize, Deserialize)]
619struct LocationData {
620 file: String,
621 line: u32,
622}
623
624#[derive(Serialize, Deserialize)]
625struct Panic {
626 thread: String,
627 payload: String,
628 #[serde(skip_serializing_if = "Option::is_none")]
629 location_data: Option<LocationData>,
630 backtrace: Vec<String>,
631 app_version: String,
632 release_channel: String,
633 os_name: String,
634 os_version: Option<String>,
635 architecture: String,
636 panicked_on: i64,
637 #[serde(skip_serializing_if = "Option::is_none")]
638 installation_id: Option<String>,
639 session_id: String,
640}
641
642#[derive(Serialize)]
643struct PanicRequest {
644 panic: Panic,
645}
646
647static PANIC_COUNT: AtomicU32 = AtomicU32::new(0);
648
649fn init_panic_hook(app: &App, installation_id: Option<String>, session_id: String) {
650 let is_pty = stdout_is_a_pty();
651 let app_metadata = app.metadata();
652
653 panic::set_hook(Box::new(move |info| {
654 let prior_panic_count = PANIC_COUNT.fetch_add(1, Ordering::SeqCst);
655 if prior_panic_count > 0 {
656 // Give the panic-ing thread time to write the panic file
657 loop {
658 std::thread::yield_now();
659 }
660 }
661
662 let thread = thread::current();
663 let thread_name = thread.name().unwrap_or("<unnamed>");
664
665 let payload = info
666 .payload()
667 .downcast_ref::<&str>()
668 .map(|s| s.to_string())
669 .or_else(|| info.payload().downcast_ref::<String>().map(|s| s.clone()))
670 .unwrap_or_else(|| "Box<Any>".to_string());
671
672 if *release_channel::RELEASE_CHANNEL == ReleaseChannel::Dev {
673 let location = info.location().unwrap();
674 let backtrace = Backtrace::new();
675 eprintln!(
676 "Thread {:?} panicked with {:?} at {}:{}:{}\n{:?}",
677 thread_name,
678 payload,
679 location.file(),
680 location.line(),
681 location.column(),
682 backtrace,
683 );
684 std::process::exit(-1);
685 }
686
687 let app_version = if let Some(version) = app_metadata.app_version {
688 version.to_string()
689 } else {
690 option_env!("CARGO_PKG_VERSION")
691 .unwrap_or("dev")
692 .to_string()
693 };
694
695 let backtrace = Backtrace::new();
696 let mut backtrace = backtrace
697 .frames()
698 .iter()
699 .flat_map(|frame| {
700 frame
701 .symbols()
702 .iter()
703 .filter_map(|frame| Some(format!("{:#}", frame.name()?)))
704 })
705 .collect::<Vec<_>>();
706
707 // Strip out leading stack frames for rust panic-handling.
708 if let Some(ix) = backtrace
709 .iter()
710 .position(|name| name == "rust_begin_unwind")
711 {
712 backtrace.drain(0..=ix);
713 }
714
715 let panic_data = Panic {
716 thread: thread_name.into(),
717 payload,
718 location_data: info.location().map(|location| LocationData {
719 file: location.file().into(),
720 line: location.line(),
721 }),
722 app_version: app_version.to_string(),
723 release_channel: RELEASE_CHANNEL.display_name().into(),
724 os_name: app_metadata.os_name.into(),
725 os_version: app_metadata
726 .os_version
727 .as_ref()
728 .map(SemanticVersion::to_string),
729 architecture: env::consts::ARCH.into(),
730 panicked_on: Utc::now().timestamp_millis(),
731 backtrace,
732 installation_id: installation_id.clone(),
733 session_id: session_id.clone(),
734 };
735
736 if let Some(panic_data_json) = serde_json::to_string_pretty(&panic_data).log_err() {
737 log::error!("{}", panic_data_json);
738 }
739
740 if !is_pty {
741 if let Some(panic_data_json) = serde_json::to_string(&panic_data).log_err() {
742 let timestamp = chrono::Utc::now().format("%Y_%m_%d %H_%M_%S").to_string();
743 let panic_file_path = paths::LOGS_DIR.join(format!("zed-{}.panic", timestamp));
744 let panic_file = std::fs::OpenOptions::new()
745 .append(true)
746 .create(true)
747 .open(&panic_file_path)
748 .log_err();
749 if let Some(mut panic_file) = panic_file {
750 writeln!(&mut panic_file, "{}", panic_data_json).log_err();
751 panic_file.flush().log_err();
752 }
753 }
754 }
755
756 std::process::abort();
757 }));
758}
759
760fn upload_panics_and_crashes(http: Arc<HttpClientWithUrl>, cx: &mut AppContext) {
761 let telemetry_settings = *client::TelemetrySettings::get_global(cx);
762 cx.background_executor()
763 .spawn(async move {
764 let most_recent_panic = upload_previous_panics(http.clone(), telemetry_settings)
765 .await
766 .log_err()
767 .flatten();
768 upload_previous_crashes(http, most_recent_panic, telemetry_settings)
769 .await
770 .log_err()
771 })
772 .detach()
773}
774
775/// Uploads panics via `zed.dev`.
776async fn upload_previous_panics(
777 http: Arc<HttpClientWithUrl>,
778 telemetry_settings: client::TelemetrySettings,
779) -> Result<Option<(i64, String)>> {
780 let panic_report_url = http.build_url("/api/panic");
781 let mut children = smol::fs::read_dir(&*paths::LOGS_DIR).await?;
782
783 let mut most_recent_panic = None;
784
785 while let Some(child) = children.next().await {
786 let child = child?;
787 let child_path = child.path();
788
789 if child_path.extension() != Some(OsStr::new("panic")) {
790 continue;
791 }
792 let filename = if let Some(filename) = child_path.file_name() {
793 filename.to_string_lossy()
794 } else {
795 continue;
796 };
797
798 if !filename.starts_with("zed") {
799 continue;
800 }
801
802 if telemetry_settings.diagnostics {
803 let panic_file_content = smol::fs::read_to_string(&child_path)
804 .await
805 .context("error reading panic file")?;
806
807 let panic: Option<Panic> = serde_json::from_str(&panic_file_content)
808 .ok()
809 .or_else(|| {
810 panic_file_content
811 .lines()
812 .next()
813 .and_then(|line| serde_json::from_str(line).ok())
814 })
815 .unwrap_or_else(|| {
816 log::error!("failed to deserialize panic file {:?}", panic_file_content);
817 None
818 });
819
820 if let Some(panic) = panic {
821 most_recent_panic = Some((panic.panicked_on, panic.payload.clone()));
822
823 let body = serde_json::to_string(&PanicRequest { panic }).unwrap();
824
825 let request = Request::post(&panic_report_url)
826 .redirect_policy(isahc::config::RedirectPolicy::Follow)
827 .header("Content-Type", "application/json")
828 .body(body.into())?;
829 let response = http.send(request).await.context("error sending panic")?;
830 if !response.status().is_success() {
831 log::error!("Error uploading panic to server: {}", response.status());
832 }
833 }
834 }
835
836 // We've done what we can, delete the file
837 std::fs::remove_file(child_path)
838 .context("error removing panic")
839 .log_err();
840 }
841 Ok::<_, anyhow::Error>(most_recent_panic)
842}
843
844static LAST_CRASH_UPLOADED: &'static str = "LAST_CRASH_UPLOADED";
845
846/// upload crashes from apple's diagnostic reports to our server.
847/// (only if telemetry is enabled)
848async fn upload_previous_crashes(
849 http: Arc<HttpClientWithUrl>,
850 most_recent_panic: Option<(i64, String)>,
851 telemetry_settings: client::TelemetrySettings,
852) -> Result<()> {
853 if !telemetry_settings.diagnostics {
854 return Ok(());
855 }
856 let last_uploaded = KEY_VALUE_STORE
857 .read_kvp(LAST_CRASH_UPLOADED)?
858 .unwrap_or("zed-2024-01-17-221900.ips".to_string()); // don't upload old crash reports from before we had this.
859 let mut uploaded = last_uploaded.clone();
860
861 let crash_report_url = http.build_zed_api_url("/telemetry/crashes", &[])?;
862
863 for dir in [&*CRASHES_DIR, &*CRASHES_RETIRED_DIR] {
864 let mut children = smol::fs::read_dir(&dir).await?;
865 while let Some(child) = children.next().await {
866 let child = child?;
867 let Some(filename) = child
868 .path()
869 .file_name()
870 .map(|f| f.to_string_lossy().to_lowercase())
871 else {
872 continue;
873 };
874
875 if !filename.starts_with("zed-") || !filename.ends_with(".ips") {
876 continue;
877 }
878
879 if filename <= last_uploaded {
880 continue;
881 }
882
883 let body = smol::fs::read_to_string(&child.path())
884 .await
885 .context("error reading crash file")?;
886
887 let mut request = Request::post(&crash_report_url.to_string())
888 .redirect_policy(isahc::config::RedirectPolicy::Follow)
889 .header("Content-Type", "text/plain");
890
891 if let Some((panicked_on, payload)) = most_recent_panic.as_ref() {
892 request = request
893 .header("x-zed-panicked-on", format!("{}", panicked_on))
894 .header("x-zed-panic", payload)
895 }
896
897 let request = request.body(body.into())?;
898
899 let response = http.send(request).await.context("error sending crash")?;
900 if !response.status().is_success() {
901 log::error!("Error uploading crash to server: {}", response.status());
902 }
903
904 if uploaded < filename {
905 uploaded = filename.clone();
906 KEY_VALUE_STORE
907 .write_kvp(LAST_CRASH_UPLOADED.to_string(), filename)
908 .await?;
909 }
910 }
911 }
912
913 Ok(())
914}
915
916async fn load_login_shell_environment() -> Result<()> {
917 let marker = "ZED_LOGIN_SHELL_START";
918 let shell = env::var("SHELL").context(
919 "SHELL environment variable is not assigned so we can't source login environment variables",
920 )?;
921
922 // If possible, we want to `cd` in the user's `$HOME` to trigger programs
923 // such as direnv, asdf, mise, ... to adjust the PATH. These tools often hook
924 // into shell's `cd` command (and hooks) to manipulate env.
925 // We do this so that we get the env a user would have when spawning a shell
926 // in home directory.
927 let shell_cmd_prefix = std::env::var_os("HOME")
928 .and_then(|home| home.into_string().ok())
929 .map(|home| format!("cd '{home}';"));
930
931 // The `exit 0` is the result of hours of debugging, trying to find out
932 // why running this command here, without `exit 0`, would mess
933 // up signal process for our process so that `ctrl-c` doesn't work
934 // anymore.
935 // We still don't know why `$SHELL -l -i -c '/usr/bin/env -0'` would
936 // do that, but it does, and `exit 0` helps.
937 let shell_cmd = format!(
938 "{}printf '%s' {marker}; /usr/bin/env; exit 0;",
939 shell_cmd_prefix.as_deref().unwrap_or("")
940 );
941
942 let output = Command::new(&shell)
943 .args(["-l", "-i", "-c", &shell_cmd])
944 .output()
945 .await
946 .context("failed to spawn login shell to source login environment variables")?;
947 if !output.status.success() {
948 Err(anyhow!("login shell exited with error"))?;
949 }
950
951 let stdout = String::from_utf8_lossy(&output.stdout);
952
953 if let Some(env_output_start) = stdout.find(marker) {
954 let env_output = &stdout[env_output_start + marker.len()..];
955
956 parse_env_output(env_output, |key, value| env::set_var(key, value));
957
958 log::info!(
959 "set environment variables from shell:{}, path:{}",
960 shell,
961 env::var("PATH").unwrap_or_default(),
962 );
963 }
964
965 Ok(())
966}
967
968fn stdout_is_a_pty() -> bool {
969 std::env::var(FORCE_CLI_MODE_ENV_VAR_NAME).ok().is_none() && std::io::stdout().is_terminal()
970}
971
972#[derive(Parser, Debug)]
973#[command(name = "zed", disable_version_flag = true)]
974struct Args {
975 /// A sequence of space-separated paths or urls that you want to open.
976 ///
977 /// Use `path:line:row` syntax to open a file at a specific location.
978 /// Non-existing paths and directories will ignore `:line:row` suffix.
979 ///
980 /// URLs can either be file:// or zed:// scheme, or relative to https://zed.dev.
981 paths_or_urls: Vec<String>,
982
983 /// Instructs zed to run as a dev server on this machine. (not implemented)
984 #[arg(long)]
985 dev_server_token: Option<String>,
986}
987
988fn parse_url_arg(arg: &str, cx: &AppContext) -> Result<String> {
989 match std::fs::canonicalize(Path::new(&arg)) {
990 Ok(path) => Ok(format!("file://{}", path.to_string_lossy())),
991 Err(error) => {
992 if arg.starts_with("file://") || arg.starts_with("zed-cli://") {
993 Ok(arg.into())
994 } else if let Some(_) = parse_zed_link(&arg, cx) {
995 Ok(arg.into())
996 } else {
997 Err(anyhow!("error parsing path argument: {}", error))
998 }
999 }
1000 }
1001}
1002
1003fn load_embedded_fonts(cx: &AppContext) {
1004 let asset_source = cx.asset_source();
1005 let font_paths = asset_source.list("fonts").unwrap();
1006 let embedded_fonts = Mutex::new(Vec::new());
1007 let executor = cx.background_executor();
1008
1009 executor.block(executor.scoped(|scope| {
1010 for font_path in &font_paths {
1011 if !font_path.ends_with(".ttf") {
1012 continue;
1013 }
1014
1015 scope.spawn(async {
1016 let font_bytes = asset_source.load(font_path).unwrap();
1017 embedded_fonts.lock().push(font_bytes);
1018 });
1019 }
1020 }));
1021
1022 cx.text_system()
1023 .add_fonts(embedded_fonts.into_inner())
1024 .unwrap();
1025}
1026
1027/// Spawns a background task to load the user themes from the themes directory.
1028fn load_user_themes_in_background(fs: Arc<dyn fs::Fs>, cx: &mut AppContext) {
1029 cx.spawn({
1030 let fs = fs.clone();
1031 |cx| async move {
1032 if let Some(theme_registry) =
1033 cx.update(|cx| ThemeRegistry::global(cx).clone()).log_err()
1034 {
1035 let themes_dir = paths::THEMES_DIR.as_ref();
1036 match fs
1037 .metadata(themes_dir)
1038 .await
1039 .ok()
1040 .flatten()
1041 .map(|m| m.is_dir)
1042 {
1043 Some(is_dir) => {
1044 anyhow::ensure!(is_dir, "Themes dir path {themes_dir:?} is not a directory")
1045 }
1046 None => {
1047 fs.create_dir(themes_dir).await.with_context(|| {
1048 format!("Failed to create themes dir at path {themes_dir:?}")
1049 })?;
1050 }
1051 }
1052 theme_registry.load_user_themes(themes_dir, fs).await?;
1053 cx.update(|cx| ThemeSettings::reload_current_theme(cx))?;
1054 }
1055 anyhow::Ok(())
1056 }
1057 })
1058 .detach_and_log_err(cx);
1059}
1060
1061/// Spawns a background task to watch the themes directory for changes.
1062fn watch_themes(fs: Arc<dyn fs::Fs>, cx: &mut AppContext) {
1063 use std::time::Duration;
1064 cx.spawn(|cx| async move {
1065 let mut events = fs
1066 .watch(&paths::THEMES_DIR.clone(), Duration::from_millis(100))
1067 .await;
1068
1069 while let Some(paths) = events.next().await {
1070 for path in paths {
1071 if fs.metadata(&path).await.ok().flatten().is_some() {
1072 if let Some(theme_registry) =
1073 cx.update(|cx| ThemeRegistry::global(cx).clone()).log_err()
1074 {
1075 if let Some(()) = theme_registry
1076 .load_user_theme(&path, fs.clone())
1077 .await
1078 .log_err()
1079 {
1080 cx.update(|cx| ThemeSettings::reload_current_theme(cx))
1081 .log_err();
1082 }
1083 }
1084 }
1085 }
1086 }
1087 })
1088 .detach()
1089}
1090
1091#[cfg(debug_assertions)]
1092fn watch_file_types(fs: Arc<dyn fs::Fs>, cx: &mut AppContext) {
1093 use std::time::Duration;
1094
1095 use gpui::BorrowAppContext;
1096
1097 let path = {
1098 let p = Path::new("assets/icons/file_icons/file_types.json");
1099 let Ok(full_path) = p.canonicalize() else {
1100 return;
1101 };
1102 full_path
1103 };
1104
1105 cx.spawn(|cx| async move {
1106 let mut events = fs.watch(path.as_path(), Duration::from_millis(100)).await;
1107 while (events.next().await).is_some() {
1108 cx.update(|cx| {
1109 cx.update_global(|file_types, _| {
1110 *file_types = file_icons::FileIcons::new(Assets);
1111 });
1112 })
1113 .ok();
1114 }
1115 })
1116 .detach()
1117}
1118
1119#[cfg(not(debug_assertions))]
1120fn watch_file_types(_fs: Arc<dyn fs::Fs>, _cx: &mut AppContext) {}
1121
1122fn init_inline_completion_provider(telemetry: Arc<Telemetry>, cx: &mut AppContext) {
1123 if let Some(copilot) = Copilot::global(cx) {
1124 cx.observe_new_views(move |editor: &mut Editor, cx: &mut ViewContext<Editor>| {
1125 if editor.mode() == EditorMode::Full {
1126 // We renamed some of these actions to not be copilot-specific, but that
1127 // would have not been backwards-compatible. So here we are re-registering
1128 // the actions with the old names to not break people's keymaps.
1129 editor
1130 .register_action(cx.listener(
1131 |editor, _: &copilot::Suggest, cx: &mut ViewContext<Editor>| {
1132 editor.show_inline_completion(&Default::default(), cx);
1133 },
1134 ))
1135 .register_action(cx.listener(
1136 |editor, _: &copilot::NextSuggestion, cx: &mut ViewContext<Editor>| {
1137 editor.next_inline_completion(&Default::default(), cx);
1138 },
1139 ))
1140 .register_action(cx.listener(
1141 |editor, _: &copilot::PreviousSuggestion, cx: &mut ViewContext<Editor>| {
1142 editor.previous_inline_completion(&Default::default(), cx);
1143 },
1144 ))
1145 .register_action(cx.listener(
1146 |editor,
1147 _: &editor::actions::AcceptPartialCopilotSuggestion,
1148 cx: &mut ViewContext<Editor>| {
1149 editor.accept_partial_inline_completion(&Default::default(), cx);
1150 },
1151 ));
1152
1153 let provider = cx.new_model(|_| {
1154 CopilotCompletionProvider::new(copilot.clone())
1155 .with_telemetry(telemetry.clone())
1156 });
1157 editor.set_inline_completion_provider(provider, cx)
1158 }
1159 })
1160 .detach();
1161 }
1162}