1// Disable command line from opening on release mode
2#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
3
4mod reliability;
5mod zed;
6
7use anyhow::{Context as _, Result};
8use clap::{Parser, command};
9use cli::FORCE_CLI_MODE_ENV_VAR_NAME;
10use client::{Client, ProxySettings, UserStore, parse_zed_link};
11use collab_ui::channel_view::ChannelView;
12use collections::HashMap;
13use db::kvp::{GLOBAL_KEY_VALUE_STORE, KEY_VALUE_STORE};
14use editor::Editor;
15use extension::ExtensionHostProxy;
16use extension_host::ExtensionStore;
17use fs::{Fs, RealFs};
18use futures::{StreamExt, channel::oneshot, future};
19use git::GitHostingProviderRegistry;
20use gpui::{App, AppContext as _, Application, AsyncApp, UpdateGlobal as _};
21
22use gpui_tokio::Tokio;
23use http_client::{Url, read_proxy_from_env};
24use language::LanguageRegistry;
25use prompt_store::PromptBuilder;
26use reqwest_client::ReqwestClient;
27
28use assets::Assets;
29use node_runtime::{NodeBinaryOptions, NodeRuntime};
30use parking_lot::Mutex;
31use project::project_settings::ProjectSettings;
32use recent_projects::{SshSettings, open_ssh_project};
33use release_channel::{AppCommitSha, AppVersion, ReleaseChannel};
34use session::{AppSession, Session};
35use settings::{Settings, SettingsStore, watch_config_file};
36use std::{
37 env,
38 io::{self, IsTerminal},
39 path::{Path, PathBuf},
40 process,
41 sync::Arc,
42};
43use theme::{
44 ActiveTheme, IconThemeNotFoundError, SystemAppearance, ThemeNotFoundError, ThemeRegistry,
45 ThemeSettings,
46};
47use util::{ConnectionResult, ResultExt, TryFutureExt, maybe};
48use uuid::Uuid;
49use welcome::{BaseKeymap, FIRST_OPEN, show_welcome_view};
50use workspace::{AppState, SerializedWorkspaceLocation, WorkspaceSettings, WorkspaceStore};
51use zed::{
52 OpenListener, OpenRequest, app_menus, build_window_options, derive_paths_with_position,
53 handle_cli_connection, handle_keymap_file_changes, handle_settings_changed,
54 handle_settings_file_changes, initialize_workspace, inline_completion_registry,
55 open_paths_with_positions,
56};
57
58#[cfg(feature = "mimalloc")]
59#[global_allocator]
60static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
61
62fn files_not_created_on_launch(errors: HashMap<io::ErrorKind, Vec<&Path>>) {
63 let message = "Zed failed to launch";
64 let error_details = errors
65 .into_iter()
66 .flat_map(|(kind, paths)| {
67 #[allow(unused_mut)] // for non-unix platforms
68 let mut error_kind_details = match paths.len() {
69 0 => return None,
70 1 => format!(
71 "{kind} when creating directory {:?}",
72 paths.first().expect("match arm checks for a single entry")
73 ),
74 _many => format!("{kind} when creating directories {paths:?}"),
75 };
76
77 #[cfg(unix)]
78 {
79 match kind {
80 io::ErrorKind::PermissionDenied => {
81 error_kind_details.push_str("\n\nConsider using chown and chmod tools for altering the directories permissions if your user has corresponding rights.\
82 \nFor example, `sudo chown $(whoami):staff ~/.config` and `chmod +uwrx ~/.config`");
83 }
84 _ => {}
85 }
86 }
87
88 Some(error_kind_details)
89 })
90 .collect::<Vec<_>>().join("\n\n");
91
92 eprintln!("{message}: {error_details}");
93 Application::new().run(move |cx| {
94 if let Ok(window) = cx.open_window(gpui::WindowOptions::default(), |_, cx| {
95 cx.new(|_| gpui::Empty)
96 }) {
97 window
98 .update(cx, |_, window, cx| {
99 let response = window.prompt(
100 gpui::PromptLevel::Critical,
101 message,
102 Some(&error_details),
103 &["Exit"],
104 cx,
105 );
106
107 cx.spawn_in(window, async move |_, cx| {
108 response.await?;
109 cx.update(|_, cx| cx.quit())
110 })
111 .detach_and_log_err(cx);
112 })
113 .log_err();
114 } else {
115 fail_to_open_window(anyhow::anyhow!("{message}: {error_details}"), cx)
116 }
117 })
118}
119
120fn fail_to_open_window_async(e: anyhow::Error, cx: &mut AsyncApp) {
121 cx.update(|cx| fail_to_open_window(e, cx)).log_err();
122}
123
124fn fail_to_open_window(e: anyhow::Error, _cx: &mut App) {
125 eprintln!(
126 "Zed failed to open a window: {e:?}. See https://zed.dev/docs/linux for troubleshooting steps."
127 );
128 #[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
129 {
130 process::exit(1);
131 }
132
133 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
134 {
135 use ashpd::desktop::notification::{Notification, NotificationProxy, Priority};
136 _cx.spawn(async move |_cx| {
137 let Ok(proxy) = NotificationProxy::new().await else {
138 process::exit(1);
139 };
140
141 let notification_id = "dev.zed.Oops";
142 proxy
143 .add_notification(
144 notification_id,
145 Notification::new("Zed failed to launch")
146 .body(Some(
147 format!(
148 "{e:?}. See https://zed.dev/docs/linux for troubleshooting steps."
149 )
150 .as_str(),
151 ))
152 .priority(Priority::High)
153 .icon(ashpd::desktop::Icon::with_names(&[
154 "dialog-question-symbolic",
155 ])),
156 )
157 .await
158 .ok();
159
160 process::exit(1);
161 })
162 .detach();
163 }
164}
165
166fn main() {
167 // Check if there is a pending installer
168 // If there is, run the installer and exit
169 // And we don't want to run the installer if we are not the first instance
170 #[cfg(target_os = "windows")]
171 let is_first_instance = crate::zed::windows_only_instance::is_first_instance();
172 #[cfg(target_os = "windows")]
173 if is_first_instance && auto_update::check_pending_installation() {
174 return;
175 }
176
177 let args = Args::parse();
178
179 if let Some(socket) = &args.askpass {
180 askpass::main(socket);
181 return;
182 }
183
184 // Set custom data directory.
185 if let Some(dir) = &args.user_data_dir {
186 paths::set_custom_data_dir(dir);
187 }
188
189 #[cfg(all(not(debug_assertions), target_os = "windows"))]
190 unsafe {
191 use windows::Win32::System::Console::{ATTACH_PARENT_PROCESS, AttachConsole};
192
193 if args.foreground {
194 let _ = AttachConsole(ATTACH_PARENT_PROCESS);
195 }
196 }
197
198 menu::init();
199 zed_actions::init();
200
201 let file_errors = init_paths();
202 if !file_errors.is_empty() {
203 files_not_created_on_launch(file_errors);
204 return;
205 }
206
207 zlog::init();
208 if stdout_is_a_pty() {
209 zlog::init_output_stdout();
210 } else {
211 let result = zlog::init_output_file(paths::log_file(), Some(paths::old_log_file()));
212 if let Err(err) = result {
213 eprintln!("Could not open log file: {}... Defaulting to stdout", err);
214 zlog::init_output_stdout();
215 };
216 }
217
218 let app_version = AppVersion::load(env!("CARGO_PKG_VERSION"));
219 let app_commit_sha =
220 option_env!("ZED_COMMIT_SHA").map(|commit_sha| AppCommitSha::new(commit_sha.to_string()));
221
222 if args.system_specs {
223 let system_specs = feedback::system_specs::SystemSpecs::new_stateless(
224 app_version,
225 app_commit_sha.clone(),
226 *release_channel::RELEASE_CHANNEL,
227 );
228 println!("Zed System Specs (from CLI):\n{}", system_specs);
229 return;
230 }
231
232 log::info!("========== starting zed ==========");
233
234 let app = Application::new().with_assets(Assets);
235
236 let system_id = app.background_executor().block(system_id()).ok();
237 let installation_id = app.background_executor().block(installation_id()).ok();
238 let session_id = Uuid::new_v4().to_string();
239 let session = app.background_executor().block(Session::new());
240
241 reliability::init_panic_hook(
242 app_version,
243 app_commit_sha.clone(),
244 system_id.as_ref().map(|id| id.to_string()),
245 installation_id.as_ref().map(|id| id.to_string()),
246 session_id.clone(),
247 );
248
249 let (open_listener, mut open_rx) = OpenListener::new();
250
251 let failed_single_instance_check =
252 if *db::ZED_STATELESS || *release_channel::RELEASE_CHANNEL == ReleaseChannel::Dev {
253 false
254 } else {
255 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
256 {
257 crate::zed::listen_for_cli_connections(open_listener.clone()).is_err()
258 }
259
260 #[cfg(target_os = "windows")]
261 {
262 !crate::zed::windows_only_instance::handle_single_instance(
263 open_listener.clone(),
264 &args,
265 is_first_instance,
266 )
267 }
268
269 #[cfg(target_os = "macos")]
270 {
271 use zed::mac_only_instance::*;
272 ensure_only_instance() != IsOnlyInstance::Yes
273 }
274 };
275 if failed_single_instance_check {
276 println!("zed is already running");
277 return;
278 }
279
280 let git_hosting_provider_registry = Arc::new(GitHostingProviderRegistry::new());
281 let git_binary_path =
282 if cfg!(target_os = "macos") && option_env!("ZED_BUNDLE").as_deref() == Some("true") {
283 app.path_for_auxiliary_executable("git")
284 .context("could not find git binary path")
285 .log_err()
286 } else {
287 None
288 };
289 log::info!("Using git binary path: {:?}", git_binary_path);
290
291 let fs = Arc::new(RealFs::new(git_binary_path, app.background_executor()));
292 let user_settings_file_rx = watch_config_file(
293 &app.background_executor(),
294 fs.clone(),
295 paths::settings_file().clone(),
296 );
297 let global_settings_file_rx = watch_config_file(
298 &app.background_executor(),
299 fs.clone(),
300 paths::global_settings_file().clone(),
301 );
302 let user_keymap_file_rx = watch_config_file(
303 &app.background_executor(),
304 fs.clone(),
305 paths::keymap_file().clone(),
306 );
307
308 let (shell_env_loaded_tx, shell_env_loaded_rx) = oneshot::channel();
309 if !stdout_is_a_pty() {
310 app.background_executor()
311 .spawn(async {
312 #[cfg(unix)]
313 util::load_login_shell_environment().log_err();
314 shell_env_loaded_tx.send(()).ok();
315 })
316 .detach()
317 } else {
318 drop(shell_env_loaded_tx)
319 }
320
321 app.on_open_urls({
322 let open_listener = open_listener.clone();
323 move |urls| open_listener.open_urls(urls)
324 });
325 app.on_reopen(move |cx| {
326 if let Some(app_state) = AppState::try_global(cx).and_then(|app_state| app_state.upgrade())
327 {
328 cx.spawn({
329 let app_state = app_state.clone();
330 async move |mut cx| {
331 if let Err(e) = restore_or_create_workspace(app_state, &mut cx).await {
332 fail_to_open_window_async(e, &mut cx)
333 }
334 }
335 })
336 .detach();
337 }
338 });
339
340 app.run(move |cx| {
341 release_channel::init(app_version, cx);
342 gpui_tokio::init(cx);
343 if let Some(app_commit_sha) = app_commit_sha {
344 AppCommitSha::set_global(app_commit_sha, cx);
345 }
346 settings::init(cx);
347 zlog_settings::init(cx);
348 handle_settings_file_changes(
349 user_settings_file_rx,
350 global_settings_file_rx,
351 cx,
352 handle_settings_changed,
353 );
354 handle_keymap_file_changes(user_keymap_file_rx, cx);
355 client::init_settings(cx);
356 let user_agent = format!(
357 "Zed/{} ({}; {})",
358 AppVersion::global(cx),
359 std::env::consts::OS,
360 std::env::consts::ARCH
361 );
362 let proxy_str = ProxySettings::get_global(cx).proxy.to_owned();
363 let proxy_url = proxy_str
364 .as_ref()
365 .and_then(|input| {
366 input
367 .parse::<Url>()
368 .inspect_err(|e| log::error!("Error parsing proxy settings: {}", e))
369 .ok()
370 })
371 .or_else(read_proxy_from_env);
372 let http = {
373 let _guard = Tokio::handle(cx).enter();
374
375 ReqwestClient::proxy_and_user_agent(proxy_url, &user_agent)
376 .expect("could not start HTTP client")
377 };
378 cx.set_http_client(Arc::new(http));
379
380 <dyn Fs>::set_global(fs.clone(), cx);
381
382 GitHostingProviderRegistry::set_global(git_hosting_provider_registry, cx);
383 git_hosting_providers::init(cx);
384
385 OpenListener::set_global(cx, open_listener.clone());
386
387 extension::init(cx);
388 let extension_host_proxy = ExtensionHostProxy::global(cx);
389
390 let client = Client::production(cx);
391 cx.set_http_client(client.http_client());
392 let mut languages = LanguageRegistry::new(cx.background_executor().clone());
393 languages.set_language_server_download_dir(paths::languages_dir().clone());
394 let languages = Arc::new(languages);
395 let (tx, rx) = async_watch::channel(None);
396 cx.observe_global::<SettingsStore>(move |cx| {
397 let settings = &ProjectSettings::get_global(cx).node;
398 let options = NodeBinaryOptions {
399 allow_path_lookup: !settings.ignore_system_version,
400 // TODO: Expose this setting
401 allow_binary_download: true,
402 use_paths: settings.path.as_ref().map(|node_path| {
403 let node_path = PathBuf::from(shellexpand::tilde(node_path).as_ref());
404 let npm_path = settings
405 .npm_path
406 .as_ref()
407 .map(|path| PathBuf::from(shellexpand::tilde(&path).as_ref()));
408 (
409 node_path.clone(),
410 npm_path.unwrap_or_else(|| {
411 let base_path = PathBuf::new();
412 node_path.parent().unwrap_or(&base_path).join("npm")
413 }),
414 )
415 }),
416 };
417 tx.send(Some(options)).log_err();
418 })
419 .detach();
420 let node_runtime = NodeRuntime::new(client.http_client(), Some(shell_env_loaded_rx), rx);
421
422 debug_adapter_extension::init(extension_host_proxy.clone(), cx);
423 language::init(cx);
424 language_extension::init(extension_host_proxy.clone(), languages.clone());
425 languages::init(languages.clone(), node_runtime.clone(), cx);
426 let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
427 let workspace_store = cx.new(|cx| WorkspaceStore::new(client.clone(), cx));
428
429 Client::set_global(client.clone(), cx);
430
431 zed::init(cx);
432 project::Project::init(&client, cx);
433 debugger_ui::init(cx);
434 debugger_tools::init(cx);
435 client::init(&client, cx);
436 let telemetry = client.telemetry();
437 telemetry.start(
438 system_id.as_ref().map(|id| id.to_string()),
439 installation_id.as_ref().map(|id| id.to_string()),
440 session_id.clone(),
441 cx,
442 );
443
444 // We should rename these in the future to `first app open`, `first app open for release channel`, and `app open`
445 if let (Some(system_id), Some(installation_id)) = (&system_id, &installation_id) {
446 match (&system_id, &installation_id) {
447 (IdType::New(_), IdType::New(_)) => {
448 telemetry::event!("App First Opened");
449 telemetry::event!("App First Opened For Release Channel");
450 }
451 (IdType::Existing(_), IdType::New(_)) => {
452 telemetry::event!("App First Opened For Release Channel");
453 }
454 (_, IdType::Existing(_)) => {
455 telemetry::event!("App Opened");
456 }
457 }
458 }
459 let app_session = cx.new(|cx| AppSession::new(session, cx));
460
461 let app_state = Arc::new(AppState {
462 languages: languages.clone(),
463 client: client.clone(),
464 user_store: user_store.clone(),
465 fs: fs.clone(),
466 build_window_options,
467 workspace_store,
468 node_runtime: node_runtime.clone(),
469 session: app_session,
470 });
471 AppState::set_global(Arc::downgrade(&app_state), cx);
472
473 auto_update::init(client.http_client(), cx);
474 dap_adapters::init(cx);
475 auto_update_ui::init(cx);
476 reliability::init(
477 client.http_client(),
478 system_id.as_ref().map(|id| id.to_string()),
479 installation_id.clone().map(|id| id.to_string()),
480 session_id.clone(),
481 cx,
482 );
483
484 SystemAppearance::init(cx);
485 theme::init(theme::LoadThemes::All(Box::new(Assets)), cx);
486 theme_extension::init(
487 extension_host_proxy.clone(),
488 ThemeRegistry::global(cx),
489 cx.background_executor().clone(),
490 );
491 command_palette::init(cx);
492 let copilot_language_server_id = app_state.languages.next_language_server_id();
493 copilot::init(
494 copilot_language_server_id,
495 app_state.fs.clone(),
496 app_state.client.http_client(),
497 app_state.node_runtime.clone(),
498 cx,
499 );
500 supermaven::init(app_state.client.clone(), cx);
501 language_model::init(app_state.client.clone(), cx);
502 language_models::init(
503 app_state.user_store.clone(),
504 app_state.client.clone(),
505 app_state.fs.clone(),
506 cx,
507 );
508 web_search::init(cx);
509 web_search_providers::init(app_state.client.clone(), cx);
510 snippet_provider::init(cx);
511 inline_completion_registry::init(
512 app_state.client.clone(),
513 app_state.user_store.clone(),
514 cx,
515 );
516 let prompt_builder = PromptBuilder::load(app_state.fs.clone(), stdout_is_a_pty(), cx);
517 agent::init(
518 app_state.fs.clone(),
519 app_state.client.clone(),
520 prompt_builder.clone(),
521 app_state.languages.clone(),
522 false,
523 cx,
524 );
525 assistant_tools::init(app_state.client.http_client(), cx);
526 repl::init(app_state.fs.clone(), cx);
527 extension_host::init(
528 extension_host_proxy,
529 app_state.fs.clone(),
530 app_state.client.clone(),
531 app_state.node_runtime.clone(),
532 cx,
533 );
534 recent_projects::init(cx);
535
536 load_embedded_fonts(cx);
537
538 app_state.languages.set_theme(cx.theme().clone());
539 editor::init(cx);
540 image_viewer::init(cx);
541 repl::notebook::init(cx);
542 diagnostics::init(cx);
543
544 audio::init(Assets, cx);
545 workspace::init(app_state.clone(), cx);
546 ui_prompt::init(cx);
547
548 go_to_line::init(cx);
549 file_finder::init(cx);
550 tab_switcher::init(cx);
551 outline::init(cx);
552 project_symbols::init(cx);
553 project_panel::init(cx);
554 outline_panel::init(cx);
555 tasks_ui::init(cx);
556 snippets_ui::init(cx);
557 channel::init(&app_state.client.clone(), app_state.user_store.clone(), cx);
558 search::init(cx);
559 vim::init(cx);
560 terminal_view::init(cx);
561 journal::init(app_state.clone(), cx);
562 language_selector::init(cx);
563 toolchain_selector::init(cx);
564 theme_selector::init(cx);
565 language_tools::init(cx);
566 call::init(app_state.client.clone(), app_state.user_store.clone(), cx);
567 notifications::init(app_state.client.clone(), app_state.user_store.clone(), cx);
568 collab_ui::init(&app_state, cx);
569 git_ui::init(cx);
570 jj_ui::init(cx);
571 feedback::init(cx);
572 markdown_preview::init(cx);
573 welcome::init(cx);
574 settings_ui::init(cx);
575 extensions_ui::init(cx);
576 zeta::init(cx);
577 inspector_ui::init(app_state.clone(), cx);
578
579 cx.observe_global::<SettingsStore>({
580 let fs = fs.clone();
581 let languages = app_state.languages.clone();
582 let http = app_state.client.http_client();
583 let client = app_state.client.clone();
584 move |cx| {
585 for &mut window in cx.windows().iter_mut() {
586 let background_appearance = cx.theme().window_background_appearance();
587 window
588 .update(cx, |_, window, _| {
589 window.set_background_appearance(background_appearance)
590 })
591 .ok();
592 }
593
594 eager_load_active_theme_and_icon_theme(fs.clone(), cx);
595
596 languages.set_theme(cx.theme().clone());
597 let new_host = &client::ClientSettings::get_global(cx).server_url;
598 if &http.base_url() != new_host {
599 http.set_base_url(new_host);
600 if client.status().borrow().is_connected() {
601 client.reconnect(&cx.to_async());
602 }
603 }
604 }
605 })
606 .detach();
607 telemetry::event!(
608 "Settings Changed",
609 setting = "theme",
610 value = cx.theme().name.to_string()
611 );
612 telemetry::event!(
613 "Settings Changed",
614 setting = "keymap",
615 value = BaseKeymap::get_global(cx).to_string()
616 );
617 telemetry.flush_events().detach();
618
619 let fs = app_state.fs.clone();
620 load_user_themes_in_background(fs.clone(), cx);
621 watch_themes(fs.clone(), cx);
622 watch_languages(fs.clone(), app_state.languages.clone(), cx);
623
624 cx.set_menus(app_menus());
625 initialize_workspace(app_state.clone(), prompt_builder, cx);
626
627 cx.activate(true);
628
629 cx.spawn({
630 let client = app_state.client.clone();
631 async move |cx| match authenticate(client, &cx).await {
632 ConnectionResult::Timeout => log::error!("Timeout during initial auth"),
633 ConnectionResult::ConnectionReset => {
634 log::error!("Connection reset during initial auth")
635 }
636 ConnectionResult::Result(r) => {
637 r.log_err();
638 }
639 }
640 })
641 .detach();
642
643 let urls: Vec<_> = args
644 .paths_or_urls
645 .iter()
646 .filter_map(|arg| parse_url_arg(arg, cx).log_err())
647 .collect();
648
649 if !urls.is_empty() {
650 open_listener.open_urls(urls)
651 }
652
653 match open_rx
654 .try_next()
655 .ok()
656 .flatten()
657 .and_then(|urls| OpenRequest::parse(urls, cx).log_err())
658 {
659 Some(request) => {
660 handle_open_request(request, app_state.clone(), cx);
661 }
662 None => {
663 cx.spawn({
664 let app_state = app_state.clone();
665 async move |mut cx| {
666 if let Err(e) = restore_or_create_workspace(app_state, &mut cx).await {
667 fail_to_open_window_async(e, &mut cx)
668 }
669 }
670 })
671 .detach();
672 }
673 }
674
675 let app_state = app_state.clone();
676
677 crate::zed::component_preview::init(app_state.clone(), cx);
678
679 cx.spawn(async move |cx| {
680 while let Some(urls) = open_rx.next().await {
681 cx.update(|cx| {
682 if let Some(request) = OpenRequest::parse(urls, cx).log_err() {
683 handle_open_request(request, app_state.clone(), cx);
684 }
685 })
686 .ok();
687 }
688 })
689 .detach();
690 });
691}
692
693fn handle_open_request(request: OpenRequest, app_state: Arc<AppState>, cx: &mut App) {
694 if let Some(connection) = request.cli_connection {
695 let app_state = app_state.clone();
696 cx.spawn(async move |cx| handle_cli_connection(connection, app_state, cx).await)
697 .detach();
698 return;
699 }
700
701 if let Some(action_index) = request.dock_menu_action {
702 cx.perform_dock_menu_action(action_index);
703 return;
704 }
705
706 if let Some(connection_options) = request.ssh_connection {
707 cx.spawn(async move |mut cx| {
708 let paths_with_position =
709 derive_paths_with_position(app_state.fs.as_ref(), request.open_paths).await;
710 open_ssh_project(
711 connection_options,
712 paths_with_position.into_iter().map(|p| p.path).collect(),
713 app_state,
714 workspace::OpenOptions::default(),
715 &mut cx,
716 )
717 .await
718 })
719 .detach_and_log_err(cx);
720 return;
721 }
722
723 let mut task = None;
724 if !request.open_paths.is_empty() {
725 let app_state = app_state.clone();
726 task = Some(cx.spawn(async move |mut cx| {
727 let paths_with_position =
728 derive_paths_with_position(app_state.fs.as_ref(), request.open_paths).await;
729 let (_window, results) = open_paths_with_positions(
730 &paths_with_position,
731 app_state,
732 workspace::OpenOptions::default(),
733 &mut cx,
734 )
735 .await?;
736 for result in results.into_iter().flatten() {
737 if let Err(err) = result {
738 log::error!("Error opening path: {err}",);
739 }
740 }
741 anyhow::Ok(())
742 }));
743 }
744
745 if !request.open_channel_notes.is_empty() || request.join_channel.is_some() {
746 cx.spawn(async move |mut cx| {
747 let result = maybe!(async {
748 if let Some(task) = task {
749 task.await?;
750 }
751 let client = app_state.client.clone();
752 // we continue even if authentication fails as join_channel/ open channel notes will
753 // show a visible error message.
754 match authenticate(client, &cx).await {
755 ConnectionResult::Timeout => {
756 log::error!("Timeout during open request handling")
757 }
758 ConnectionResult::ConnectionReset => {
759 log::error!("Connection reset during open request handling")
760 }
761 ConnectionResult::Result(r) => r?,
762 };
763
764 if let Some(channel_id) = request.join_channel {
765 cx.update(|cx| {
766 workspace::join_channel(
767 client::ChannelId(channel_id),
768 app_state.clone(),
769 None,
770 cx,
771 )
772 })?
773 .await?;
774 }
775
776 let workspace_window =
777 workspace::get_any_active_workspace(app_state, cx.clone()).await?;
778 let workspace = workspace_window.entity(cx)?;
779
780 let mut promises = Vec::new();
781 for (channel_id, heading) in request.open_channel_notes {
782 promises.push(cx.update_window(workspace_window.into(), |_, window, cx| {
783 ChannelView::open(
784 client::ChannelId(channel_id),
785 heading,
786 workspace.clone(),
787 window,
788 cx,
789 )
790 .log_err()
791 })?)
792 }
793 future::join_all(promises).await;
794 anyhow::Ok(())
795 })
796 .await;
797 if let Err(err) = result {
798 fail_to_open_window_async(err, &mut cx);
799 }
800 })
801 .detach()
802 } else if let Some(task) = task {
803 cx.spawn(async move |mut cx| {
804 if let Err(err) = task.await {
805 fail_to_open_window_async(err, &mut cx);
806 }
807 })
808 .detach();
809 }
810}
811
812async fn authenticate(client: Arc<Client>, cx: &AsyncApp) -> ConnectionResult<()> {
813 if stdout_is_a_pty() {
814 if client::IMPERSONATE_LOGIN.is_some() {
815 return client.authenticate_and_connect(false, cx).await;
816 } else if client.has_credentials(cx).await {
817 return client.authenticate_and_connect(true, cx).await;
818 }
819 } else if client.has_credentials(cx).await {
820 return client.authenticate_and_connect(true, cx).await;
821 }
822
823 ConnectionResult::Result(Ok(()))
824}
825
826async fn system_id() -> Result<IdType> {
827 let key_name = "system_id".to_string();
828
829 if let Ok(Some(system_id)) = GLOBAL_KEY_VALUE_STORE.read_kvp(&key_name) {
830 return Ok(IdType::Existing(system_id));
831 }
832
833 let system_id = Uuid::new_v4().to_string();
834
835 GLOBAL_KEY_VALUE_STORE
836 .write_kvp(key_name, system_id.clone())
837 .await?;
838
839 Ok(IdType::New(system_id))
840}
841
842async fn installation_id() -> Result<IdType> {
843 let legacy_key_name = "device_id".to_string();
844 let key_name = "installation_id".to_string();
845
846 // Migrate legacy key to new key
847 if let Ok(Some(installation_id)) = KEY_VALUE_STORE.read_kvp(&legacy_key_name) {
848 KEY_VALUE_STORE
849 .write_kvp(key_name, installation_id.clone())
850 .await?;
851 KEY_VALUE_STORE.delete_kvp(legacy_key_name).await?;
852 return Ok(IdType::Existing(installation_id));
853 }
854
855 if let Ok(Some(installation_id)) = KEY_VALUE_STORE.read_kvp(&key_name) {
856 return Ok(IdType::Existing(installation_id));
857 }
858
859 let installation_id = Uuid::new_v4().to_string();
860
861 KEY_VALUE_STORE
862 .write_kvp(key_name, installation_id.clone())
863 .await?;
864
865 Ok(IdType::New(installation_id))
866}
867
868async fn restore_or_create_workspace(app_state: Arc<AppState>, cx: &mut AsyncApp) -> Result<()> {
869 if let Some(locations) = restorable_workspace_locations(cx, &app_state).await {
870 for location in locations {
871 match location {
872 SerializedWorkspaceLocation::Local(location, _) => {
873 let task = cx.update(|cx| {
874 workspace::open_paths(
875 location.paths().as_ref(),
876 app_state.clone(),
877 workspace::OpenOptions::default(),
878 cx,
879 )
880 })?;
881 task.await?;
882 }
883 SerializedWorkspaceLocation::Ssh(ssh) => {
884 let connection_options = cx.update(|cx| {
885 SshSettings::get_global(cx)
886 .connection_options_for(ssh.host, ssh.port, ssh.user)
887 })?;
888 let app_state = app_state.clone();
889 cx.spawn(async move |cx| {
890 recent_projects::open_ssh_project(
891 connection_options,
892 ssh.paths.into_iter().map(PathBuf::from).collect(),
893 app_state,
894 workspace::OpenOptions::default(),
895 cx,
896 )
897 .await
898 .log_err();
899 })
900 .detach();
901 }
902 }
903 }
904 } else if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) {
905 cx.update(|cx| show_welcome_view(app_state, cx))?.await?;
906 } else {
907 cx.update(|cx| {
908 workspace::open_new(
909 Default::default(),
910 app_state,
911 cx,
912 |workspace, window, cx| {
913 Editor::new_file(workspace, &Default::default(), window, cx)
914 },
915 )
916 })?
917 .await?;
918 }
919
920 Ok(())
921}
922
923pub(crate) async fn restorable_workspace_locations(
924 cx: &mut AsyncApp,
925 app_state: &Arc<AppState>,
926) -> Option<Vec<SerializedWorkspaceLocation>> {
927 let mut restore_behavior = cx
928 .update(|cx| WorkspaceSettings::get(None, cx).restore_on_startup)
929 .ok()?;
930
931 let session_handle = app_state.session.clone();
932 let (last_session_id, last_session_window_stack) = cx
933 .update(|cx| {
934 let session = session_handle.read(cx);
935
936 (
937 session.last_session_id().map(|id| id.to_string()),
938 session.last_session_window_stack(),
939 )
940 })
941 .ok()?;
942
943 if last_session_id.is_none()
944 && matches!(
945 restore_behavior,
946 workspace::RestoreOnStartupBehavior::LastSession
947 )
948 {
949 restore_behavior = workspace::RestoreOnStartupBehavior::LastWorkspace;
950 }
951
952 match restore_behavior {
953 workspace::RestoreOnStartupBehavior::LastWorkspace => {
954 workspace::last_opened_workspace_location()
955 .await
956 .map(|location| vec![location])
957 }
958 workspace::RestoreOnStartupBehavior::LastSession => {
959 if let Some(last_session_id) = last_session_id {
960 let ordered = last_session_window_stack.is_some();
961
962 let mut locations = workspace::last_session_workspace_locations(
963 &last_session_id,
964 last_session_window_stack,
965 )
966 .filter(|locations| !locations.is_empty());
967
968 // Since last_session_window_order returns the windows ordered front-to-back
969 // we need to open the window that was frontmost last.
970 if ordered {
971 if let Some(locations) = locations.as_mut() {
972 locations.reverse();
973 }
974 }
975
976 locations
977 } else {
978 None
979 }
980 }
981 _ => None,
982 }
983}
984
985fn init_paths() -> HashMap<io::ErrorKind, Vec<&'static Path>> {
986 [
987 paths::config_dir(),
988 paths::extensions_dir(),
989 paths::languages_dir(),
990 paths::database_dir(),
991 paths::logs_dir(),
992 paths::temp_dir(),
993 ]
994 .into_iter()
995 .fold(HashMap::default(), |mut errors, path| {
996 if let Err(e) = std::fs::create_dir_all(path) {
997 errors.entry(e.kind()).or_insert_with(Vec::new).push(path);
998 }
999 errors
1000 })
1001}
1002
1003fn stdout_is_a_pty() -> bool {
1004 std::env::var(FORCE_CLI_MODE_ENV_VAR_NAME).ok().is_none() && io::stdout().is_terminal()
1005}
1006
1007#[derive(Parser, Debug)]
1008#[command(name = "zed", disable_version_flag = true)]
1009struct Args {
1010 /// A sequence of space-separated paths or urls that you want to open.
1011 ///
1012 /// Use `path:line:row` syntax to open a file at a specific location.
1013 /// Non-existing paths and directories will ignore `:line:row` suffix.
1014 ///
1015 /// URLs can either be `file://` or `zed://` scheme, or relative to <https://zed.dev>.
1016 paths_or_urls: Vec<String>,
1017
1018 /// Sets a custom directory for all user data (e.g., database, extensions, logs).
1019 /// This overrides the default platform-specific data directory location.
1020 /// On macOS, the default is `~/Library/Application Support/Zed`.
1021 /// On Linux/FreeBSD, the default is `$XDG_DATA_HOME/zed`.
1022 /// On Windows, the default is `%LOCALAPPDATA%\Zed`.
1023 #[arg(long, value_name = "DIR")]
1024 user_data_dir: Option<String>,
1025
1026 /// Instructs zed to run as a dev server on this machine. (not implemented)
1027 #[arg(long)]
1028 dev_server_token: Option<String>,
1029
1030 /// Prints system specs. Useful for submitting issues on GitHub when encountering a bug
1031 /// that prevents Zed from starting, so you can't run `zed: copy system specs to clipboard`
1032 #[arg(long)]
1033 system_specs: bool,
1034
1035 /// Used for SSH/Git password authentication, to remove the need for netcat as a dependency,
1036 /// by having Zed act like netcat communicating over a Unix socket.
1037 #[arg(long, hide = true)]
1038 askpass: Option<String>,
1039
1040 /// Run zed in the foreground, only used on Windows, to match the behavior of the behavior on macOS.
1041 #[arg(long)]
1042 #[cfg(target_os = "windows")]
1043 #[arg(hide = true)]
1044 foreground: bool,
1045
1046 /// The dock action to perform. This is used on Windows only.
1047 #[arg(long)]
1048 #[cfg(target_os = "windows")]
1049 #[arg(hide = true)]
1050 dock_action: Option<usize>,
1051}
1052
1053#[derive(Clone, Debug)]
1054enum IdType {
1055 New(String),
1056 Existing(String),
1057}
1058
1059impl ToString for IdType {
1060 fn to_string(&self) -> String {
1061 match self {
1062 IdType::New(id) | IdType::Existing(id) => id.clone(),
1063 }
1064 }
1065}
1066
1067fn parse_url_arg(arg: &str, cx: &App) -> Result<String> {
1068 match std::fs::canonicalize(Path::new(&arg)) {
1069 Ok(path) => Ok(format!("file://{}", path.display())),
1070 Err(error) => {
1071 if arg.starts_with("file://")
1072 || arg.starts_with("zed-cli://")
1073 || arg.starts_with("ssh://")
1074 || parse_zed_link(arg, cx).is_some()
1075 {
1076 Ok(arg.into())
1077 } else {
1078 anyhow::bail!("error parsing path argument: {error}")
1079 }
1080 }
1081 }
1082}
1083
1084fn load_embedded_fonts(cx: &App) {
1085 let asset_source = cx.asset_source();
1086 let font_paths = asset_source.list("fonts").unwrap();
1087 let embedded_fonts = Mutex::new(Vec::new());
1088 let executor = cx.background_executor();
1089
1090 executor.block(executor.scoped(|scope| {
1091 for font_path in &font_paths {
1092 if !font_path.ends_with(".ttf") {
1093 continue;
1094 }
1095
1096 scope.spawn(async {
1097 let font_bytes = asset_source.load(font_path).unwrap().unwrap();
1098 embedded_fonts.lock().push(font_bytes);
1099 });
1100 }
1101 }));
1102
1103 cx.text_system()
1104 .add_fonts(embedded_fonts.into_inner())
1105 .unwrap();
1106}
1107
1108/// Eagerly loads the active theme and icon theme based on the selections in the
1109/// theme settings.
1110///
1111/// This fast path exists to load these themes as soon as possible so the user
1112/// doesn't see the default themes while waiting on extensions to load.
1113fn eager_load_active_theme_and_icon_theme(fs: Arc<dyn Fs>, cx: &App) {
1114 let extension_store = ExtensionStore::global(cx);
1115 let theme_registry = ThemeRegistry::global(cx);
1116 let theme_settings = ThemeSettings::get_global(cx);
1117 let appearance = SystemAppearance::global(cx).0;
1118
1119 if let Some(theme_selection) = theme_settings.theme_selection.as_ref() {
1120 let theme_name = theme_selection.theme(appearance);
1121 if matches!(theme_registry.get(theme_name), Err(ThemeNotFoundError(_))) {
1122 if let Some(theme_path) = extension_store.read(cx).path_to_extension_theme(theme_name) {
1123 cx.spawn({
1124 let theme_registry = theme_registry.clone();
1125 let fs = fs.clone();
1126 async move |cx| {
1127 theme_registry.load_user_theme(&theme_path, fs).await?;
1128
1129 cx.update(|cx| {
1130 ThemeSettings::reload_current_theme(cx);
1131 })
1132 }
1133 })
1134 .detach_and_log_err(cx);
1135 }
1136 }
1137 }
1138
1139 if let Some(icon_theme_selection) = theme_settings.icon_theme_selection.as_ref() {
1140 let icon_theme_name = icon_theme_selection.icon_theme(appearance);
1141 if matches!(
1142 theme_registry.get_icon_theme(icon_theme_name),
1143 Err(IconThemeNotFoundError(_))
1144 ) {
1145 if let Some((icon_theme_path, icons_root_path)) = extension_store
1146 .read(cx)
1147 .path_to_extension_icon_theme(icon_theme_name)
1148 {
1149 cx.spawn({
1150 let theme_registry = theme_registry.clone();
1151 let fs = fs.clone();
1152 async move |cx| {
1153 theme_registry
1154 .load_icon_theme(&icon_theme_path, &icons_root_path, fs)
1155 .await?;
1156
1157 cx.update(|cx| {
1158 ThemeSettings::reload_current_icon_theme(cx);
1159 })
1160 }
1161 })
1162 .detach_and_log_err(cx);
1163 }
1164 }
1165 }
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 App) {
1170 cx.spawn({
1171 let fs = fs.clone();
1172 async move |cx| {
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 App) {
1204 use std::time::Duration;
1205 cx.spawn(async move |cx| {
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 App) {
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 App) {}