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