main.rs

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