main.rs

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