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