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 reliability;
7mod zed;
8
9use anyhow::{anyhow, Context as _, Result};
10use assistant_slash_command::SlashCommandRegistry;
11use chrono::Offset;
12use clap::{command, Parser};
13use cli::FORCE_CLI_MODE_ENV_VAR_NAME;
14use client::{parse_zed_link, Client, ProxySettings, UserStore};
15use collab_ui::channel_view::ChannelView;
16use db::kvp::{GLOBAL_KEY_VALUE_STORE, KEY_VALUE_STORE};
17use editor::Editor;
18use env_logger::Builder;
19use fs::{Fs, RealFs};
20use futures::{future, StreamExt};
21use git::GitHostingProviderRegistry;
22use gpui::{
23 Action, App, AppContext, AsyncAppContext, Context, DismissEvent, UpdateGlobal as _,
24 VisualContext,
25};
26use http_client::{read_proxy_from_env, Uri};
27use indexed_docs::IndexedDocsRegistry;
28use language::LanguageRegistry;
29use log::LevelFilter;
30use reqwest_client::ReqwestClient;
31
32use assets::Assets;
33use node_runtime::{NodeBinaryOptions, NodeRuntime};
34use parking_lot::Mutex;
35use project::project_settings::ProjectSettings;
36use recent_projects::{open_ssh_project, SshSettings};
37use release_channel::{AppCommitSha, AppVersion, ReleaseChannel};
38use session::{AppSession, Session};
39use settings::{
40 handle_settings_file_changes, watch_config_file, InvalidSettingsError, Settings, SettingsStore,
41};
42use simplelog::ConfigBuilder;
43use smol::process::Command;
44use snippet_provider::SnippetRegistry;
45use std::{
46 env,
47 fs::OpenOptions,
48 io::{IsTerminal, Write},
49 path::{Path, PathBuf},
50 process,
51 sync::Arc,
52};
53use theme::{ActiveTheme, SystemAppearance, ThemeRegistry, ThemeSettings};
54use time::UtcOffset;
55use util::{maybe, parse_env_output, ResultExt, TryFutureExt};
56use uuid::Uuid;
57use welcome::{show_welcome_view, BaseKeymap, FIRST_OPEN};
58use workspace::{
59 notifications::{simple_message_notification::MessageNotification, NotificationId},
60 AppState, SerializedWorkspaceLocation, WorkspaceSettings, WorkspaceStore,
61};
62use zed::{
63 app_menus, build_window_options, derive_paths_with_position, handle_cli_connection,
64 handle_keymap_file_changes, initialize_workspace, open_paths_with_positions, OpenListener,
65 OpenRequest,
66};
67
68use crate::zed::inline_completion_registry;
69
70#[cfg(feature = "mimalloc")]
71#[global_allocator]
72static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
73
74fn fail_to_launch(e: anyhow::Error) {
75 eprintln!("Zed failed to launch: {e:?}");
76 App::new().run(move |cx| {
77 if let Ok(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!("{e}\n\nFor help resolving this, please open an issue on https://github.com/zed-industries/zed")), &["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 } else {
89 fail_to_open_window(e, cx)
90 }
91 })
92}
93
94fn fail_to_open_window_async(e: anyhow::Error, cx: &mut AsyncAppContext) {
95 cx.update(|cx| fail_to_open_window(e, cx)).log_err();
96}
97
98fn fail_to_open_window(e: anyhow::Error, _cx: &mut AppContext) {
99 eprintln!(
100 "Zed failed to open a window: {e:?}. See https://zed.dev/docs/linux for troubleshooting steps."
101 );
102 #[cfg(not(target_os = "linux"))]
103 {
104 process::exit(1);
105 }
106
107 #[cfg(target_os = "linux")]
108 {
109 use ashpd::desktop::notification::{Notification, NotificationProxy, Priority};
110 _cx.spawn(|_cx| async move {
111 let Ok(proxy) = NotificationProxy::new().await else {
112 process::exit(1);
113 };
114
115 let notification_id = "dev.zed.Oops";
116 proxy
117 .add_notification(
118 notification_id,
119 Notification::new("Zed failed to launch")
120 .body(Some(
121 format!(
122 "{e:?}. See https://zed.dev/docs/linux for troubleshooting steps."
123 )
124 .as_str(),
125 ))
126 .priority(Priority::High)
127 .icon(ashpd::desktop::Icon::with_names(&[
128 "dialog-question-symbolic",
129 ])),
130 )
131 .await
132 .ok();
133
134 process::exit(1);
135 })
136 .detach();
137 }
138}
139
140fn main() {
141 menu::init();
142 zed_actions::init();
143
144 if let Err(e) = init_paths() {
145 fail_to_launch(e);
146 return;
147 }
148
149 init_logger();
150
151 log::info!("========== starting zed ==========");
152
153 let app = App::new().with_assets(Assets);
154
155 let system_id = app.background_executor().block(system_id()).ok();
156 let installation_id = app.background_executor().block(installation_id()).ok();
157 let session_id = Uuid::new_v4().to_string();
158 let session = app.background_executor().block(Session::new());
159 let app_version = AppVersion::init(env!("CARGO_PKG_VERSION"));
160
161 reliability::init_panic_hook(
162 app_version,
163 system_id.as_ref().map(|id| id.to_string()),
164 installation_id.as_ref().map(|id| id.to_string()),
165 session_id.clone(),
166 );
167
168 let (open_listener, mut open_rx) = OpenListener::new();
169
170 let failed_single_instance_check =
171 if *db::ZED_STATELESS || *release_channel::RELEASE_CHANNEL == ReleaseChannel::Dev {
172 false
173 } else {
174 #[cfg(target_os = "linux")]
175 {
176 crate::zed::listen_for_cli_connections(open_listener.clone()).is_err()
177 }
178
179 #[cfg(target_os = "windows")]
180 {
181 !crate::zed::windows_only_instance::check_single_instance()
182 }
183
184 #[cfg(target_os = "macos")]
185 {
186 use zed::mac_only_instance::*;
187 ensure_only_instance() != IsOnlyInstance::Yes
188 }
189 };
190 if failed_single_instance_check {
191 println!("zed is already running");
192 return;
193 }
194
195 let git_hosting_provider_registry = Arc::new(GitHostingProviderRegistry::new());
196 let git_binary_path =
197 if cfg!(target_os = "macos") && option_env!("ZED_BUNDLE").as_deref() == Some("true") {
198 app.path_for_auxiliary_executable("git")
199 .context("could not find git binary path")
200 .log_err()
201 } else {
202 None
203 };
204 log::info!("Using git binary path: {:?}", git_binary_path);
205
206 let fs = Arc::new(RealFs::new(
207 git_hosting_provider_registry.clone(),
208 git_binary_path,
209 ));
210 let user_settings_file_rx = watch_config_file(
211 &app.background_executor(),
212 fs.clone(),
213 paths::settings_file().clone(),
214 );
215 let user_keymap_file_rx = watch_config_file(
216 &app.background_executor(),
217 fs.clone(),
218 paths::keymap_file().clone(),
219 );
220
221 if !stdout_is_a_pty() {
222 app.background_executor()
223 .spawn(async {
224 #[cfg(unix)]
225 {
226 load_shell_from_passwd().await.log_err();
227 }
228 load_login_shell_environment().await.log_err();
229 })
230 .detach()
231 };
232
233 app.on_open_urls({
234 let open_listener = open_listener.clone();
235 move |urls| open_listener.open_urls(urls)
236 });
237 app.on_reopen(move |cx| {
238 if let Some(app_state) = AppState::try_global(cx).and_then(|app_state| app_state.upgrade())
239 {
240 cx.spawn({
241 let app_state = app_state.clone();
242 |mut cx| async move {
243 if let Err(e) = restore_or_create_workspace(app_state, &mut cx).await {
244 fail_to_open_window_async(e, &mut cx)
245 }
246 }
247 })
248 .detach();
249 }
250 });
251
252 app.run(move |cx| {
253 release_channel::init(app_version, cx);
254 if let Some(build_sha) = option_env!("ZED_COMMIT_SHA") {
255 AppCommitSha::set_global(AppCommitSha(build_sha.into()), cx);
256 }
257 settings::init(cx);
258 handle_settings_file_changes(user_settings_file_rx, cx, handle_settings_changed);
259 handle_keymap_file_changes(user_keymap_file_rx, cx, handle_keymap_changed);
260 client::init_settings(cx);
261 let user_agent = format!(
262 "Zed/{} ({}; {})",
263 AppVersion::global(cx),
264 std::env::consts::OS,
265 std::env::consts::ARCH
266 );
267 let proxy_str = ProxySettings::get_global(cx).proxy.to_owned();
268 let proxy_url = proxy_str
269 .as_ref()
270 .and_then(|input| {
271 input
272 .parse::<Uri>()
273 .inspect_err(|e| log::error!("Error parsing proxy settings: {}", e))
274 .ok()
275 })
276 .or_else(read_proxy_from_env);
277 let http = ReqwestClient::proxy_and_user_agent(proxy_url, &user_agent)
278 .expect("could not start HTTP client");
279 cx.set_http_client(Arc::new(http));
280
281 <dyn Fs>::set_global(fs.clone(), cx);
282
283 GitHostingProviderRegistry::set_global(git_hosting_provider_registry, cx);
284 git_hosting_providers::init(cx);
285
286 OpenListener::set_global(cx, open_listener.clone());
287
288 let client = Client::production(cx);
289 cx.set_http_client(client.http_client().clone());
290 let mut languages = LanguageRegistry::new(cx.background_executor().clone());
291 languages.set_language_server_download_dir(paths::languages_dir().clone());
292 let languages = Arc::new(languages);
293 let (tx, rx) = async_watch::channel(None);
294 cx.observe_global::<SettingsStore>(move |cx| {
295 let settings = &ProjectSettings::get_global(cx).node;
296 let options = NodeBinaryOptions {
297 allow_path_lookup: !settings.ignore_system_version.unwrap_or_default(),
298 // TODO: Expose this setting
299 allow_binary_download: true,
300 use_paths: settings.path.as_ref().map(|node_path| {
301 let node_path = PathBuf::from(shellexpand::tilde(node_path).as_ref());
302 let npm_path = settings
303 .npm_path
304 .as_ref()
305 .map(|path| PathBuf::from(shellexpand::tilde(&path).as_ref()));
306 (
307 node_path.clone(),
308 npm_path.unwrap_or_else(|| {
309 let base_path = PathBuf::new();
310 node_path.parent().unwrap_or(&base_path).join("npm")
311 }),
312 )
313 }),
314 };
315 tx.send(Some(options)).log_err();
316 })
317 .detach();
318 let node_runtime = NodeRuntime::new(client.http_client(), rx);
319
320 language::init(cx);
321 languages::init(languages.clone(), node_runtime.clone(), cx);
322 let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
323 let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
324
325 Client::set_global(client.clone(), cx);
326
327 zed::init(cx);
328 project::Project::init(&client, cx);
329 client::init(&client, cx);
330 language::init(cx);
331 let telemetry = client.telemetry();
332 telemetry.start(
333 system_id.as_ref().map(|id| id.to_string()),
334 installation_id.as_ref().map(|id| id.to_string()),
335 session_id.clone(),
336 cx,
337 );
338
339 // We should rename these in the future to `first app open`, `first app open for release channel`, and `app open`
340 if let (Some(system_id), Some(installation_id)) = (&system_id, &installation_id) {
341 match (&system_id, &installation_id) {
342 (IdType::New(_), IdType::New(_)) => {
343 telemetry.report_app_event("first open".to_string());
344 telemetry.report_app_event("first open for release channel".to_string());
345 }
346 (IdType::Existing(_), IdType::New(_)) => {
347 telemetry.report_app_event("first open for release channel".to_string());
348 }
349 (_, IdType::Existing(_)) => {
350 telemetry.report_app_event("open".to_string());
351 }
352 }
353 }
354 let app_session = cx.new_model(|cx| AppSession::new(session, cx));
355
356 let app_state = Arc::new(AppState {
357 languages: languages.clone(),
358 client: client.clone(),
359 user_store: user_store.clone(),
360 fs: fs.clone(),
361 build_window_options,
362 workspace_store,
363 node_runtime: node_runtime.clone(),
364 session: app_session,
365 });
366 AppState::set_global(Arc::downgrade(&app_state), cx);
367
368 auto_update::init(client.http_client(), cx);
369 reliability::init(
370 client.http_client(),
371 system_id.as_ref().map(|id| id.to_string()),
372 installation_id.clone().map(|id| id.to_string()),
373 session_id.clone(),
374 cx,
375 );
376
377 SystemAppearance::init(cx);
378 theme::init(theme::LoadThemes::All(Box::new(Assets)), cx);
379 command_palette::init(cx);
380 let copilot_language_server_id = app_state.languages.next_language_server_id();
381 copilot::init(
382 copilot_language_server_id,
383 app_state.fs.clone(),
384 app_state.client.http_client(),
385 app_state.node_runtime.clone(),
386 cx,
387 );
388 supermaven::init(app_state.client.clone(), cx);
389 language_model::init(
390 app_state.user_store.clone(),
391 app_state.client.clone(),
392 app_state.fs.clone(),
393 cx,
394 );
395 snippet_provider::init(cx);
396 inline_completion_registry::init(app_state.client.telemetry().clone(), cx);
397 let prompt_builder = assistant::init(
398 app_state.fs.clone(),
399 app_state.client.clone(),
400 stdout_is_a_pty(),
401 cx,
402 );
403 repl::init(
404 app_state.fs.clone(),
405 app_state.client.telemetry().clone(),
406 cx,
407 );
408 let api = extensions_ui::ConcreteExtensionRegistrationHooks::new(
409 ThemeRegistry::global(cx),
410 SlashCommandRegistry::global(cx),
411 IndexedDocsRegistry::global(cx),
412 SnippetRegistry::global(cx),
413 app_state.languages.clone(),
414 cx,
415 );
416 extension_host::init(
417 api,
418 app_state.fs.clone(),
419 app_state.client.clone(),
420 app_state.node_runtime.clone(),
421 cx,
422 );
423 recent_projects::init(cx);
424
425 load_embedded_fonts(cx);
426
427 #[cfg(target_os = "linux")]
428 crate::zed::linux_prompts::init(cx);
429
430 app_state.languages.set_theme(cx.theme().clone());
431 editor::init(cx);
432 image_viewer::init(cx);
433 repl::notebook::init(cx);
434 diagnostics::init(cx);
435
436 audio::init(Assets, cx);
437 workspace::init(app_state.clone(), cx);
438
439 go_to_line::init(cx);
440 file_finder::init(cx);
441 tab_switcher::init(cx);
442 outline::init(cx);
443 project_symbols::init(cx);
444 project_panel::init(Assets, cx);
445 outline_panel::init(Assets, cx);
446 tasks_ui::init(cx);
447 snippets_ui::init(cx);
448 channel::init(&app_state.client.clone(), app_state.user_store.clone(), cx);
449 search::init(cx);
450 vim::init(cx);
451 terminal_view::init(cx);
452 journal::init(app_state.clone(), cx);
453 language_selector::init(cx);
454 toolchain_selector::init(cx);
455 theme_selector::init(cx);
456 language_tools::init(cx);
457 call::init(app_state.client.clone(), app_state.user_store.clone(), cx);
458 notifications::init(app_state.client.clone(), app_state.user_store.clone(), cx);
459 collab_ui::init(&app_state, cx);
460 feedback::init(cx);
461 markdown_preview::init(cx);
462 welcome::init(cx);
463 settings_ui::init(cx);
464 extensions_ui::init(cx);
465
466 cx.observe_global::<SettingsStore>({
467 let languages = app_state.languages.clone();
468 let http = app_state.client.http_client();
469 let client = app_state.client.clone();
470
471 move |cx| {
472 for &mut window in cx.windows().iter_mut() {
473 let background_appearance = cx.theme().window_background_appearance();
474 window
475 .update(cx, |_, cx| {
476 cx.set_background_appearance(background_appearance)
477 })
478 .ok();
479 }
480 languages.set_theme(cx.theme().clone());
481 let new_host = &client::ClientSettings::get_global(cx).server_url;
482 if &http.base_url() != new_host {
483 http.set_base_url(new_host);
484 if client.status().borrow().is_connected() {
485 client.reconnect(&cx.to_async());
486 }
487 }
488 }
489 })
490 .detach();
491 let telemetry = app_state.client.telemetry();
492 telemetry.report_setting_event("theme", cx.theme().name.to_string());
493 telemetry.report_setting_event("keymap", BaseKeymap::get_global(cx).to_string());
494 telemetry.flush_events();
495
496 let fs = app_state.fs.clone();
497 load_user_themes_in_background(fs.clone(), cx);
498 watch_themes(fs.clone(), cx);
499 watch_languages(fs.clone(), app_state.languages.clone(), cx);
500 watch_file_types(fs.clone(), cx);
501
502 cx.set_menus(app_menus());
503 initialize_workspace(app_state.clone(), prompt_builder, cx);
504
505 cx.activate(true);
506
507 cx.spawn({
508 let client = app_state.client.clone();
509 |cx| async move { authenticate(client, &cx).await }
510 })
511 .detach_and_log_err(cx);
512
513 let args = Args::parse();
514 let urls: Vec<_> = args
515 .paths_or_urls
516 .iter()
517 .filter_map(|arg| parse_url_arg(arg, cx).log_err())
518 .collect();
519
520 if !urls.is_empty() {
521 open_listener.open_urls(urls)
522 }
523
524 match open_rx
525 .try_next()
526 .ok()
527 .flatten()
528 .and_then(|urls| OpenRequest::parse(urls, cx).log_err())
529 {
530 Some(request) => {
531 handle_open_request(request, app_state.clone(), cx);
532 }
533 None => {
534 cx.spawn({
535 let app_state = app_state.clone();
536 |mut cx| async move {
537 if let Err(e) = restore_or_create_workspace(app_state, &mut cx).await {
538 fail_to_open_window_async(e, &mut cx)
539 }
540 }
541 })
542 .detach();
543 }
544 }
545
546 let app_state = app_state.clone();
547 cx.spawn(move |cx| async move {
548 while let Some(urls) = open_rx.next().await {
549 cx.update(|cx| {
550 if let Some(request) = OpenRequest::parse(urls, cx).log_err() {
551 handle_open_request(request, app_state.clone(), cx);
552 }
553 })
554 .ok();
555 }
556 })
557 .detach();
558 });
559}
560
561fn handle_keymap_changed(error: Option<anyhow::Error>, cx: &mut AppContext) {
562 struct KeymapParseErrorNotification;
563 let id = NotificationId::unique::<KeymapParseErrorNotification>();
564
565 for workspace in workspace::local_workspace_windows(cx) {
566 workspace
567 .update(cx, |workspace, cx| match &error {
568 Some(error) => {
569 workspace.show_notification(id.clone(), cx, |cx| {
570 cx.new_view(|_| {
571 MessageNotification::new(format!("Invalid keymap file\n{error}"))
572 .with_click_message("Open keymap file")
573 .on_click(|cx| {
574 cx.dispatch_action(zed_actions::OpenKeymap.boxed_clone());
575 cx.emit(DismissEvent);
576 })
577 })
578 });
579 }
580 None => workspace.dismiss_notification(&id, cx),
581 })
582 .log_err();
583 }
584}
585
586fn handle_settings_changed(error: Option<anyhow::Error>, cx: &mut AppContext) {
587 struct SettingsParseErrorNotification;
588 let id = NotificationId::unique::<SettingsParseErrorNotification>();
589
590 for workspace in workspace::local_workspace_windows(cx) {
591 workspace
592 .update(cx, |workspace, cx| {
593 match error.as_ref() {
594 Some(error) => {
595 if let Some(InvalidSettingsError::LocalSettings { .. }) =
596 error.downcast_ref::<InvalidSettingsError>()
597 {
598 // Local settings will be displayed by the projects
599 } else {
600 workspace.show_notification(id.clone(), cx, |cx| {
601 cx.new_view(|_| {
602 MessageNotification::new(format!(
603 "Invalid user settings file\n{error}"
604 ))
605 .with_click_message("Open settings file")
606 .on_click(|cx| {
607 cx.dispatch_action(zed_actions::OpenSettings.boxed_clone());
608 cx.emit(DismissEvent);
609 })
610 })
611 });
612 }
613 }
614 None => workspace.dismiss_notification(&id, cx),
615 }
616 })
617 .log_err();
618 }
619}
620
621fn handle_open_request(request: OpenRequest, app_state: Arc<AppState>, cx: &mut AppContext) {
622 if let Some(connection) = request.cli_connection {
623 let app_state = app_state.clone();
624 cx.spawn(move |cx| handle_cli_connection(connection, app_state, cx))
625 .detach();
626 return;
627 }
628
629 if let Some(connection_options) = request.ssh_connection {
630 cx.spawn(|mut cx| async move {
631 let paths_with_position =
632 derive_paths_with_position(app_state.fs.as_ref(), request.open_paths).await;
633 open_ssh_project(
634 connection_options,
635 paths_with_position.into_iter().map(|p| p.path).collect(),
636 app_state,
637 workspace::OpenOptions::default(),
638 &mut cx,
639 )
640 .await
641 })
642 .detach_and_log_err(cx);
643 return;
644 }
645
646 let mut task = None;
647 if !request.open_paths.is_empty() {
648 let app_state = app_state.clone();
649 task = Some(cx.spawn(|mut cx| async move {
650 let paths_with_position =
651 derive_paths_with_position(app_state.fs.as_ref(), request.open_paths).await;
652 let (_window, results) = open_paths_with_positions(
653 &paths_with_position,
654 app_state,
655 workspace::OpenOptions::default(),
656 &mut cx,
657 )
658 .await?;
659 for result in results.into_iter().flatten() {
660 if let Err(err) = result {
661 log::error!("Error opening path: {err}",);
662 }
663 }
664 anyhow::Ok(())
665 }));
666 }
667
668 if !request.open_channel_notes.is_empty() || request.join_channel.is_some() {
669 cx.spawn(|mut cx| async move {
670 let result = maybe!(async {
671 if let Some(task) = task {
672 task.await?;
673 }
674 let client = app_state.client.clone();
675 // we continue even if authentication fails as join_channel/ open channel notes will
676 // show a visible error message.
677 authenticate(client, &cx).await.log_err();
678
679 if let Some(channel_id) = request.join_channel {
680 cx.update(|cx| {
681 workspace::join_channel(
682 client::ChannelId(channel_id),
683 app_state.clone(),
684 None,
685 cx,
686 )
687 })?
688 .await?;
689 }
690
691 let workspace_window =
692 workspace::get_any_active_workspace(app_state, cx.clone()).await?;
693 let workspace = workspace_window.root_view(&cx)?;
694
695 let mut promises = Vec::new();
696 for (channel_id, heading) in request.open_channel_notes {
697 promises.push(cx.update_window(workspace_window.into(), |_, cx| {
698 ChannelView::open(
699 client::ChannelId(channel_id),
700 heading,
701 workspace.clone(),
702 cx,
703 )
704 .log_err()
705 })?)
706 }
707 future::join_all(promises).await;
708 anyhow::Ok(())
709 })
710 .await;
711 if let Err(err) = result {
712 fail_to_open_window_async(err, &mut cx);
713 }
714 })
715 .detach()
716 } else if let Some(task) = task {
717 cx.spawn(|mut cx| async move {
718 if let Err(err) = task.await {
719 fail_to_open_window_async(err, &mut cx);
720 }
721 })
722 .detach();
723 }
724}
725
726async fn authenticate(client: Arc<Client>, cx: &AsyncAppContext) -> Result<()> {
727 if stdout_is_a_pty() {
728 if *client::ZED_DEVELOPMENT_AUTH {
729 client.authenticate_and_connect(true, cx).await?;
730 } else if client::IMPERSONATE_LOGIN.is_some() {
731 client.authenticate_and_connect(false, cx).await?;
732 }
733 } else if client.has_credentials(cx).await {
734 client.authenticate_and_connect(true, cx).await?;
735 }
736 Ok::<_, anyhow::Error>(())
737}
738
739async fn system_id() -> Result<IdType> {
740 let key_name = "system_id".to_string();
741
742 if let Ok(Some(system_id)) = GLOBAL_KEY_VALUE_STORE.read_kvp(&key_name) {
743 return Ok(IdType::Existing(system_id));
744 }
745
746 let system_id = Uuid::new_v4().to_string();
747
748 GLOBAL_KEY_VALUE_STORE
749 .write_kvp(key_name, system_id.clone())
750 .await?;
751
752 Ok(IdType::New(system_id))
753}
754
755async fn installation_id() -> Result<IdType> {
756 let legacy_key_name = "device_id".to_string();
757 let key_name = "installation_id".to_string();
758
759 // Migrate legacy key to new key
760 if let Ok(Some(installation_id)) = KEY_VALUE_STORE.read_kvp(&legacy_key_name) {
761 KEY_VALUE_STORE
762 .write_kvp(key_name, installation_id.clone())
763 .await?;
764 KEY_VALUE_STORE.delete_kvp(legacy_key_name).await?;
765 return Ok(IdType::Existing(installation_id));
766 }
767
768 if let Ok(Some(installation_id)) = KEY_VALUE_STORE.read_kvp(&key_name) {
769 return Ok(IdType::Existing(installation_id));
770 }
771
772 let installation_id = Uuid::new_v4().to_string();
773
774 KEY_VALUE_STORE
775 .write_kvp(key_name, installation_id.clone())
776 .await?;
777
778 Ok(IdType::New(installation_id))
779}
780
781async fn restore_or_create_workspace(
782 app_state: Arc<AppState>,
783 cx: &mut AsyncAppContext,
784) -> Result<()> {
785 if let Some(locations) = restorable_workspace_locations(cx, &app_state).await {
786 for location in locations {
787 match location {
788 SerializedWorkspaceLocation::Local(location, _) => {
789 let task = cx.update(|cx| {
790 workspace::open_paths(
791 location.paths().as_ref(),
792 app_state.clone(),
793 workspace::OpenOptions::default(),
794 cx,
795 )
796 })?;
797 task.await?;
798 }
799 SerializedWorkspaceLocation::Ssh(ssh) => {
800 let connection_options = cx.update(|cx| {
801 SshSettings::get_global(cx)
802 .connection_options_for(ssh.host, ssh.port, ssh.user)
803 })?;
804 let app_state = app_state.clone();
805 cx.spawn(move |mut cx| async move {
806 recent_projects::open_ssh_project(
807 connection_options,
808 ssh.paths.into_iter().map(PathBuf::from).collect(),
809 app_state,
810 workspace::OpenOptions::default(),
811 &mut cx,
812 )
813 .await
814 .log_err();
815 })
816 .detach();
817 }
818 }
819 }
820 } else if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) {
821 cx.update(|cx| show_welcome_view(app_state, cx))?.await?;
822 } else {
823 cx.update(|cx| {
824 workspace::open_new(Default::default(), app_state, cx, |workspace, cx| {
825 Editor::new_file(workspace, &Default::default(), cx)
826 })
827 })?
828 .await?;
829 }
830
831 Ok(())
832}
833
834pub(crate) async fn restorable_workspace_locations(
835 cx: &mut AsyncAppContext,
836 app_state: &Arc<AppState>,
837) -> Option<Vec<SerializedWorkspaceLocation>> {
838 let mut restore_behavior = cx
839 .update(|cx| WorkspaceSettings::get(None, cx).restore_on_startup)
840 .ok()?;
841
842 let session_handle = app_state.session.clone();
843 let (last_session_id, last_session_window_stack) = cx
844 .update(|cx| {
845 let session = session_handle.read(cx);
846
847 (
848 session.last_session_id().map(|id| id.to_string()),
849 session.last_session_window_stack(),
850 )
851 })
852 .ok()?;
853
854 if last_session_id.is_none()
855 && matches!(
856 restore_behavior,
857 workspace::RestoreOnStartupBehavior::LastSession
858 )
859 {
860 restore_behavior = workspace::RestoreOnStartupBehavior::LastWorkspace;
861 }
862
863 match restore_behavior {
864 workspace::RestoreOnStartupBehavior::LastWorkspace => {
865 workspace::last_opened_workspace_location()
866 .await
867 .map(|location| vec![location])
868 }
869 workspace::RestoreOnStartupBehavior::LastSession => {
870 if let Some(last_session_id) = last_session_id {
871 let ordered = last_session_window_stack.is_some();
872
873 let mut locations = workspace::last_session_workspace_locations(
874 &last_session_id,
875 last_session_window_stack,
876 )
877 .filter(|locations| !locations.is_empty());
878
879 // Since last_session_window_order returns the windows ordered front-to-back
880 // we need to open the window that was frontmost last.
881 if ordered {
882 if let Some(locations) = locations.as_mut() {
883 locations.reverse();
884 }
885 }
886
887 locations
888 } else {
889 None
890 }
891 }
892 _ => None,
893 }
894}
895
896fn init_paths() -> anyhow::Result<()> {
897 for path in [
898 paths::config_dir(),
899 paths::extensions_dir(),
900 paths::languages_dir(),
901 paths::database_dir(),
902 paths::logs_dir(),
903 paths::temp_dir(),
904 ]
905 .iter()
906 {
907 std::fs::create_dir_all(path)
908 .map_err(|e| anyhow!("Could not create directory {:?}: {}", path, e))?;
909 }
910 Ok(())
911}
912
913fn init_logger() {
914 if stdout_is_a_pty() {
915 init_stdout_logger();
916 } else {
917 let level = LevelFilter::Info;
918
919 // Prevent log file from becoming too large.
920 const KIB: u64 = 1024;
921 const MIB: u64 = 1024 * KIB;
922 const MAX_LOG_BYTES: u64 = MIB;
923 if std::fs::metadata(paths::log_file())
924 .map_or(false, |metadata| metadata.len() > MAX_LOG_BYTES)
925 {
926 let _ = std::fs::rename(paths::log_file(), paths::old_log_file());
927 }
928
929 match OpenOptions::new()
930 .create(true)
931 .append(true)
932 .open(paths::log_file())
933 {
934 Ok(log_file) => {
935 let mut config_builder = ConfigBuilder::new();
936
937 config_builder.set_time_format_rfc3339();
938 let local_offset = chrono::Local::now().offset().fix().local_minus_utc();
939 if let Ok(offset) = UtcOffset::from_whole_seconds(local_offset) {
940 config_builder.set_time_offset(offset);
941 }
942
943 #[cfg(target_os = "linux")]
944 {
945 config_builder.add_filter_ignore_str("zbus");
946 config_builder.add_filter_ignore_str("blade_graphics::hal::resource");
947 config_builder.add_filter_ignore_str("naga::back::spv::writer");
948 }
949
950 let config = config_builder.build();
951 simplelog::WriteLogger::init(level, config, log_file)
952 .expect("could not initialize logger");
953 }
954 Err(err) => {
955 init_stdout_logger();
956 log::error!(
957 "could not open log file, defaulting to stdout logging: {}",
958 err
959 );
960 }
961 }
962 }
963}
964
965fn init_stdout_logger() {
966 Builder::new()
967 .parse_default_env()
968 .format(|buf, record| {
969 use env_logger::fmt::style::{AnsiColor, Style};
970
971 let subtle = Style::new().fg_color(Some(AnsiColor::BrightBlack.into()));
972 write!(buf, "{subtle}[{subtle:#}")?;
973 write!(
974 buf,
975 "{} ",
976 chrono::Local::now().format("%Y-%m-%dT%H:%M:%S%:z")
977 )?;
978 let level_style = buf.default_level_style(record.level());
979 write!(buf, "{level_style}{:<5}{level_style:#}", record.level())?;
980 if let Some(path) = record.module_path() {
981 write!(buf, " {path}")?;
982 }
983 write!(buf, "{subtle}]{subtle:#}")?;
984 writeln!(buf, " {}", record.args())
985 })
986 .init();
987}
988
989#[cfg(unix)]
990async fn load_shell_from_passwd() -> Result<()> {
991 let buflen = match unsafe { libc::sysconf(libc::_SC_GETPW_R_SIZE_MAX) } {
992 n if n < 0 => 1024,
993 n => n as usize,
994 };
995 let mut buffer = Vec::with_capacity(buflen);
996
997 let mut pwd: std::mem::MaybeUninit<libc::passwd> = std::mem::MaybeUninit::uninit();
998 let mut result: *mut libc::passwd = std::ptr::null_mut();
999
1000 let uid = unsafe { libc::getuid() };
1001 let status = unsafe {
1002 libc::getpwuid_r(
1003 uid,
1004 pwd.as_mut_ptr(),
1005 buffer.as_mut_ptr() as *mut libc::c_char,
1006 buflen,
1007 &mut result,
1008 )
1009 };
1010 let entry = unsafe { pwd.assume_init() };
1011
1012 anyhow::ensure!(
1013 status == 0,
1014 "call to getpwuid_r failed. uid: {}, status: {}",
1015 uid,
1016 status
1017 );
1018 anyhow::ensure!(!result.is_null(), "passwd entry for uid {} not found", uid);
1019 anyhow::ensure!(
1020 entry.pw_uid == uid,
1021 "passwd entry has different uid ({}) than getuid ({}) returned",
1022 entry.pw_uid,
1023 uid,
1024 );
1025
1026 let shell = unsafe { std::ffi::CStr::from_ptr(entry.pw_shell).to_str().unwrap() };
1027 if env::var("SHELL").map_or(true, |shell_env| shell_env != shell) {
1028 log::info!(
1029 "updating SHELL environment variable to value from passwd entry: {:?}",
1030 shell,
1031 );
1032 env::set_var("SHELL", shell);
1033 }
1034
1035 Ok(())
1036}
1037
1038async fn load_login_shell_environment() -> Result<()> {
1039 let marker = "ZED_LOGIN_SHELL_START";
1040 let shell = env::var("SHELL").context(
1041 "SHELL environment variable is not assigned so we can't source login environment variables",
1042 )?;
1043
1044 // If possible, we want to `cd` in the user's `$HOME` to trigger programs
1045 // such as direnv, asdf, mise, ... to adjust the PATH. These tools often hook
1046 // into shell's `cd` command (and hooks) to manipulate env.
1047 // We do this so that we get the env a user would have when spawning a shell
1048 // in home directory.
1049 let shell_cmd_prefix = std::env::var_os("HOME")
1050 .and_then(|home| home.into_string().ok())
1051 .map(|home| format!("cd '{home}';"));
1052
1053 // The `exit 0` is the result of hours of debugging, trying to find out
1054 // why running this command here, without `exit 0`, would mess
1055 // up signal process for our process so that `ctrl-c` doesn't work
1056 // anymore.
1057 // We still don't know why `$SHELL -l -i -c '/usr/bin/env -0'` would
1058 // do that, but it does, and `exit 0` helps.
1059 let shell_cmd = format!(
1060 "{}printf '%s' {marker}; /usr/bin/env; exit 0;",
1061 shell_cmd_prefix.as_deref().unwrap_or("")
1062 );
1063
1064 let output = Command::new(&shell)
1065 .args(["-l", "-i", "-c", &shell_cmd])
1066 .output()
1067 .await
1068 .context("failed to spawn login shell to source login environment variables")?;
1069 if !output.status.success() {
1070 Err(anyhow!("login shell exited with error"))?;
1071 }
1072
1073 let stdout = String::from_utf8_lossy(&output.stdout);
1074
1075 if let Some(env_output_start) = stdout.find(marker) {
1076 let env_output = &stdout[env_output_start + marker.len()..];
1077
1078 parse_env_output(env_output, |key, value| env::set_var(key, value));
1079
1080 log::info!(
1081 "set environment variables from shell:{}, path:{}",
1082 shell,
1083 env::var("PATH").unwrap_or_default(),
1084 );
1085 }
1086
1087 Ok(())
1088}
1089
1090fn stdout_is_a_pty() -> bool {
1091 std::env::var(FORCE_CLI_MODE_ENV_VAR_NAME).ok().is_none() && std::io::stdout().is_terminal()
1092}
1093
1094#[derive(Parser, Debug)]
1095#[command(name = "zed", disable_version_flag = true)]
1096struct Args {
1097 /// A sequence of space-separated paths or urls that you want to open.
1098 ///
1099 /// Use `path:line:row` syntax to open a file at a specific location.
1100 /// Non-existing paths and directories will ignore `:line:row` suffix.
1101 ///
1102 /// URLs can either be `file://` or `zed://` scheme, or relative to <https://zed.dev>.
1103 paths_or_urls: Vec<String>,
1104
1105 /// Instructs zed to run as a dev server on this machine. (not implemented)
1106 #[arg(long)]
1107 dev_server_token: Option<String>,
1108}
1109
1110#[derive(Clone, Debug)]
1111enum IdType {
1112 New(String),
1113 Existing(String),
1114}
1115
1116impl ToString for IdType {
1117 fn to_string(&self) -> String {
1118 match self {
1119 IdType::New(id) | IdType::Existing(id) => id.clone(),
1120 }
1121 }
1122}
1123
1124fn parse_url_arg(arg: &str, cx: &AppContext) -> Result<String> {
1125 match std::fs::canonicalize(Path::new(&arg)) {
1126 Ok(path) => Ok(format!(
1127 "file://{}",
1128 path.to_string_lossy().trim_start_matches(r#"\\?\"#)
1129 )),
1130 Err(error) => {
1131 if arg.starts_with("file://")
1132 || arg.starts_with("zed-cli://")
1133 || arg.starts_with("ssh://")
1134 || parse_zed_link(arg, cx).is_some()
1135 {
1136 Ok(arg.into())
1137 } else {
1138 Err(anyhow!("error parsing path argument: {}", error))
1139 }
1140 }
1141 }
1142}
1143
1144fn load_embedded_fonts(cx: &AppContext) {
1145 let asset_source = cx.asset_source();
1146 let font_paths = asset_source.list("fonts").unwrap();
1147 let embedded_fonts = Mutex::new(Vec::new());
1148 let executor = cx.background_executor();
1149
1150 executor.block(executor.scoped(|scope| {
1151 for font_path in &font_paths {
1152 if !font_path.ends_with(".ttf") {
1153 continue;
1154 }
1155
1156 scope.spawn(async {
1157 let font_bytes = asset_source.load(font_path).unwrap().unwrap();
1158 embedded_fonts.lock().push(font_bytes);
1159 });
1160 }
1161 }));
1162
1163 cx.text_system()
1164 .add_fonts(embedded_fonts.into_inner())
1165 .unwrap();
1166}
1167
1168/// Spawns a background task to load the user themes from the themes directory.
1169fn load_user_themes_in_background(fs: Arc<dyn fs::Fs>, cx: &mut AppContext) {
1170 cx.spawn({
1171 let fs = fs.clone();
1172 |cx| async move {
1173 if let Some(theme_registry) =
1174 cx.update(|cx| ThemeRegistry::global(cx).clone()).log_err()
1175 {
1176 let themes_dir = paths::themes_dir().as_ref();
1177 match fs
1178 .metadata(themes_dir)
1179 .await
1180 .ok()
1181 .flatten()
1182 .map(|m| m.is_dir)
1183 {
1184 Some(is_dir) => {
1185 anyhow::ensure!(is_dir, "Themes dir path {themes_dir:?} is not a directory")
1186 }
1187 None => {
1188 fs.create_dir(themes_dir).await.with_context(|| {
1189 format!("Failed to create themes dir at path {themes_dir:?}")
1190 })?;
1191 }
1192 }
1193 theme_registry.load_user_themes(themes_dir, fs).await?;
1194 cx.update(ThemeSettings::reload_current_theme)?;
1195 }
1196 anyhow::Ok(())
1197 }
1198 })
1199 .detach_and_log_err(cx);
1200}
1201
1202/// Spawns a background task to watch the themes directory for changes.
1203fn watch_themes(fs: Arc<dyn fs::Fs>, cx: &mut AppContext) {
1204 use std::time::Duration;
1205 cx.spawn(|cx| async move {
1206 let (mut events, _) = fs
1207 .watch(paths::themes_dir(), Duration::from_millis(100))
1208 .await;
1209
1210 while let Some(paths) = events.next().await {
1211 for event in paths {
1212 if fs.metadata(&event.path).await.ok().flatten().is_some() {
1213 if let Some(theme_registry) =
1214 cx.update(|cx| ThemeRegistry::global(cx).clone()).log_err()
1215 {
1216 if let Some(()) = theme_registry
1217 .load_user_theme(&event.path, fs.clone())
1218 .await
1219 .log_err()
1220 {
1221 cx.update(ThemeSettings::reload_current_theme).log_err();
1222 }
1223 }
1224 }
1225 }
1226 }
1227 })
1228 .detach()
1229}
1230
1231#[cfg(debug_assertions)]
1232fn watch_languages(fs: Arc<dyn fs::Fs>, languages: Arc<LanguageRegistry>, cx: &mut AppContext) {
1233 use std::time::Duration;
1234
1235 let path = {
1236 let p = Path::new("crates/languages/src");
1237 let Ok(full_path) = p.canonicalize() else {
1238 return;
1239 };
1240 full_path
1241 };
1242
1243 cx.spawn(|_| async move {
1244 let (mut events, _) = fs.watch(path.as_path(), Duration::from_millis(100)).await;
1245 while let Some(event) = events.next().await {
1246 let has_language_file = event.iter().any(|event| {
1247 event
1248 .path
1249 .extension()
1250 .map(|ext| ext.to_string_lossy().as_ref() == "scm")
1251 .unwrap_or(false)
1252 });
1253 if has_language_file {
1254 languages.reload();
1255 }
1256 }
1257 })
1258 .detach()
1259}
1260
1261#[cfg(not(debug_assertions))]
1262fn watch_languages(_fs: Arc<dyn fs::Fs>, _languages: Arc<LanguageRegistry>, _cx: &mut AppContext) {}
1263
1264#[cfg(debug_assertions)]
1265fn watch_file_types(fs: Arc<dyn fs::Fs>, cx: &mut AppContext) {
1266 use std::time::Duration;
1267
1268 use file_icons::FileIcons;
1269 use gpui::UpdateGlobal;
1270
1271 let path = {
1272 let p = Path::new("assets/icons/file_icons/file_types.json");
1273 let Ok(full_path) = p.canonicalize() else {
1274 return;
1275 };
1276 full_path
1277 };
1278
1279 cx.spawn(|cx| async move {
1280 let (mut events, _) = fs.watch(path.as_path(), Duration::from_millis(100)).await;
1281 while (events.next().await).is_some() {
1282 cx.update(|cx| {
1283 FileIcons::update_global(cx, |file_types, _cx| {
1284 *file_types = file_icons::FileIcons::new(Assets);
1285 });
1286 })
1287 .ok();
1288 }
1289 })
1290 .detach()
1291}
1292
1293#[cfg(not(debug_assertions))]
1294fn watch_file_types(_fs: Arc<dyn fs::Fs>, _cx: &mut AppContext) {}