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