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