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 onboarding::{FIRST_OPEN, show_onboarding_view};
  24use prompt_store::PromptBuilder;
  25use reqwest_client::ReqwestClient;
  26
  27use assets::Assets;
  28use node_runtime::{NodeBinaryOptions, NodeRuntime};
  29use parking_lot::Mutex;
  30use project::project_settings::ProjectSettings;
  31use recent_projects::{SshSettings, open_ssh_project};
  32use release_channel::{AppCommitSha, AppVersion, ReleaseChannel};
  33use session::{AppSession, Session};
  34use settings::{BaseKeymap, Settings, SettingsStore, watch_config_file};
  35use std::{
  36    env,
  37    io::{self, IsTerminal},
  38    path::{Path, PathBuf},
  39    process,
  40    sync::Arc,
  41};
  42use theme::{
  43    ActiveTheme, IconThemeNotFoundError, SystemAppearance, ThemeNotFoundError, ThemeRegistry,
  44    ThemeSettings,
  45};
  46use util::{ResultExt, TryFutureExt, maybe};
  47use uuid::Uuid;
  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, edit_prediction_registry, handle_cli_connection,
  55    handle_keymap_file_changes, handle_settings_changed, handle_settings_file_changes,
  56    initialize_workspace, 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 --crash-handler` Makes zed operate in minidump crash handler mode
 176    if let Some(socket) = &args.crash_handler {
 177        crashes::crash_server(socket.as_path());
 178        return;
 179    }
 180
 181    // `zed --askpass` Makes zed operate in nc/netcat mode for use with askpass
 182    if let Some(socket) = &args.askpass {
 183        askpass::main(socket);
 184        return;
 185    }
 186
 187    // `zed --nc` Makes zed operate in nc/netcat mode for use with MCP
 188    if let Some(socket) = &args.nc {
 189        match nc::main(socket) {
 190            Ok(()) => return,
 191            Err(err) => {
 192                eprintln!("Error: {}", err);
 193                process::exit(1);
 194            }
 195        }
 196    }
 197
 198    // `zed --printenv` Outputs environment variables as JSON to stdout
 199    if args.printenv {
 200        util::shell_env::print_env();
 201        return;
 202    }
 203
 204    if args.dump_all_actions {
 205        dump_all_gpui_actions();
 206        return;
 207    }
 208
 209    // Set custom data directory.
 210    if let Some(dir) = &args.user_data_dir {
 211        paths::set_custom_data_dir(dir);
 212    }
 213
 214    #[cfg(all(not(debug_assertions), target_os = "windows"))]
 215    unsafe {
 216        use windows::Win32::System::Console::{ATTACH_PARENT_PROCESS, AttachConsole};
 217
 218        if args.foreground {
 219            let _ = AttachConsole(ATTACH_PARENT_PROCESS);
 220        }
 221    }
 222
 223    let file_errors = init_paths();
 224    if !file_errors.is_empty() {
 225        files_not_created_on_launch(file_errors);
 226        return;
 227    }
 228
 229    zlog::init();
 230    if stdout_is_a_pty() {
 231        zlog::init_output_stdout();
 232    } else {
 233        let result = zlog::init_output_file(paths::log_file(), Some(paths::old_log_file()));
 234        if let Err(err) = result {
 235            eprintln!("Could not open log file: {}... Defaulting to stdout", err);
 236            zlog::init_output_stdout();
 237        };
 238    }
 239
 240    let app_version = AppVersion::load(env!("CARGO_PKG_VERSION"));
 241    let app_commit_sha =
 242        option_env!("ZED_COMMIT_SHA").map(|commit_sha| AppCommitSha::new(commit_sha.to_string()));
 243
 244    if args.system_specs {
 245        let system_specs = feedback::system_specs::SystemSpecs::new_stateless(
 246            app_version,
 247            app_commit_sha.clone(),
 248            *release_channel::RELEASE_CHANNEL,
 249        );
 250        println!("Zed System Specs (from CLI):\n{}", system_specs);
 251        return;
 252    }
 253
 254    log::info!(
 255        "========== starting zed version {}, sha {} ==========",
 256        app_version,
 257        app_commit_sha
 258            .as_ref()
 259            .map(|sha| sha.short())
 260            .as_deref()
 261            .unwrap_or("unknown"),
 262    );
 263
 264    let app = Application::new().with_assets(Assets);
 265
 266    let system_id = app.background_executor().block(system_id()).ok();
 267    let installation_id = app.background_executor().block(installation_id()).ok();
 268    let session_id = Uuid::new_v4().to_string();
 269    let session = app.background_executor().block(Session::new());
 270
 271    app.background_executor()
 272        .spawn(crashes::init(session_id.clone()))
 273        .detach();
 274    reliability::init_panic_hook(
 275        app_version,
 276        app_commit_sha.clone(),
 277        system_id.as_ref().map(|id| id.to_string()),
 278        installation_id.as_ref().map(|id| id.to_string()),
 279        session_id.clone(),
 280    );
 281
 282    let (open_listener, mut open_rx) = OpenListener::new();
 283
 284    let failed_single_instance_check = if *db::ZED_STATELESS
 285        || *release_channel::RELEASE_CHANNEL == ReleaseChannel::Dev
 286    {
 287        false
 288    } else {
 289        #[cfg(any(target_os = "linux", target_os = "freebsd"))]
 290        {
 291            crate::zed::listen_for_cli_connections(open_listener.clone()).is_err()
 292        }
 293
 294        #[cfg(target_os = "windows")]
 295        {
 296            !crate::zed::windows_only_instance::handle_single_instance(open_listener.clone(), &args)
 297        }
 298
 299        #[cfg(target_os = "macos")]
 300        {
 301            use zed::mac_only_instance::*;
 302            ensure_only_instance() != IsOnlyInstance::Yes
 303        }
 304    };
 305    if failed_single_instance_check {
 306        println!("zed is already running");
 307        return;
 308    }
 309
 310    let git_hosting_provider_registry = Arc::new(GitHostingProviderRegistry::new());
 311    let git_binary_path =
 312        if cfg!(target_os = "macos") && option_env!("ZED_BUNDLE").as_deref() == Some("true") {
 313            app.path_for_auxiliary_executable("git")
 314                .context("could not find git binary path")
 315                .log_err()
 316        } else {
 317            None
 318        };
 319    log::info!("Using git binary path: {:?}", git_binary_path);
 320
 321    let fs = Arc::new(RealFs::new(git_binary_path, app.background_executor()));
 322    let user_settings_file_rx = watch_config_file(
 323        &app.background_executor(),
 324        fs.clone(),
 325        paths::settings_file().clone(),
 326    );
 327    let global_settings_file_rx = watch_config_file(
 328        &app.background_executor(),
 329        fs.clone(),
 330        paths::global_settings_file().clone(),
 331    );
 332    let user_keymap_file_rx = watch_config_file(
 333        &app.background_executor(),
 334        fs.clone(),
 335        paths::keymap_file().clone(),
 336    );
 337
 338    let (shell_env_loaded_tx, shell_env_loaded_rx) = oneshot::channel();
 339    if !stdout_is_a_pty() {
 340        app.background_executor()
 341            .spawn(async {
 342                #[cfg(unix)]
 343                util::load_login_shell_environment().log_err();
 344                shell_env_loaded_tx.send(()).ok();
 345            })
 346            .detach()
 347    } else {
 348        drop(shell_env_loaded_tx)
 349    }
 350
 351    app.on_open_urls({
 352        let open_listener = open_listener.clone();
 353        move |urls| {
 354            open_listener.open(RawOpenRequest {
 355                urls,
 356                diff_paths: Vec::new(),
 357            })
 358        }
 359    });
 360    app.on_reopen(move |cx| {
 361        if let Some(app_state) = AppState::try_global(cx).and_then(|app_state| app_state.upgrade())
 362        {
 363            cx.spawn({
 364                let app_state = app_state.clone();
 365                async move |mut cx| {
 366                    if let Err(e) = restore_or_create_workspace(app_state, &mut cx).await {
 367                        fail_to_open_window_async(e, &mut cx)
 368                    }
 369                }
 370            })
 371            .detach();
 372        }
 373    });
 374
 375    app.run(move |cx| {
 376        menu::init();
 377        zed_actions::init();
 378
 379        release_channel::init(app_version, cx);
 380        gpui_tokio::init(cx);
 381        if let Some(app_commit_sha) = app_commit_sha {
 382            AppCommitSha::set_global(app_commit_sha, cx);
 383        }
 384        settings::init(cx);
 385        zlog_settings::init(cx);
 386        handle_settings_file_changes(
 387            user_settings_file_rx,
 388            global_settings_file_rx,
 389            cx,
 390            handle_settings_changed,
 391        );
 392        handle_keymap_file_changes(user_keymap_file_rx, cx);
 393        client::init_settings(cx);
 394        let user_agent = format!(
 395            "Zed/{} ({}; {})",
 396            AppVersion::global(cx),
 397            std::env::consts::OS,
 398            std::env::consts::ARCH
 399        );
 400        let proxy_str = ProxySettings::get_global(cx).proxy.to_owned();
 401        let proxy_url = proxy_str
 402            .as_ref()
 403            .and_then(|input| {
 404                input
 405                    .parse::<Url>()
 406                    .inspect_err(|e| log::error!("Error parsing proxy settings: {}", e))
 407                    .ok()
 408            })
 409            .or_else(read_proxy_from_env);
 410        let http = {
 411            let _guard = Tokio::handle(cx).enter();
 412
 413            ReqwestClient::proxy_and_user_agent(proxy_url, &user_agent)
 414                .expect("could not start HTTP client")
 415        };
 416        cx.set_http_client(Arc::new(http));
 417
 418        <dyn Fs>::set_global(fs.clone(), cx);
 419
 420        GitHostingProviderRegistry::set_global(git_hosting_provider_registry, cx);
 421        git_hosting_providers::init(cx);
 422
 423        OpenListener::set_global(cx, open_listener.clone());
 424
 425        extension::init(cx);
 426        let extension_host_proxy = ExtensionHostProxy::global(cx);
 427
 428        let client = Client::production(cx);
 429        cx.set_http_client(client.http_client());
 430        let mut languages = LanguageRegistry::new(cx.background_executor().clone());
 431        languages.set_language_server_download_dir(paths::languages_dir().clone());
 432        let languages = Arc::new(languages);
 433        let (mut tx, rx) = watch::channel(None);
 434        cx.observe_global::<SettingsStore>(move |cx| {
 435            let settings = &ProjectSettings::get_global(cx).node;
 436            let options = NodeBinaryOptions {
 437                allow_path_lookup: !settings.ignore_system_version,
 438                // TODO: Expose this setting
 439                allow_binary_download: true,
 440                use_paths: settings.path.as_ref().map(|node_path| {
 441                    let node_path = PathBuf::from(shellexpand::tilde(node_path).as_ref());
 442                    let npm_path = settings
 443                        .npm_path
 444                        .as_ref()
 445                        .map(|path| PathBuf::from(shellexpand::tilde(&path).as_ref()));
 446                    (
 447                        node_path.clone(),
 448                        npm_path.unwrap_or_else(|| {
 449                            let base_path = PathBuf::new();
 450                            node_path.parent().unwrap_or(&base_path).join("npm")
 451                        }),
 452                    )
 453                }),
 454            };
 455            tx.send(Some(options)).log_err();
 456        })
 457        .detach();
 458        let node_runtime = NodeRuntime::new(client.http_client(), Some(shell_env_loaded_rx), rx);
 459
 460        debug_adapter_extension::init(extension_host_proxy.clone(), cx);
 461        language::init(cx);
 462        languages::init(languages.clone(), node_runtime.clone(), cx);
 463        let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
 464        let workspace_store = cx.new(|cx| WorkspaceStore::new(client.clone(), cx));
 465
 466        language_extension::init(
 467            language_extension::LspAccess::ViaWorkspaces({
 468                let workspace_store = workspace_store.clone();
 469                Arc::new(move |cx: &mut App| {
 470                    workspace_store.update(cx, |workspace_store, cx| {
 471                        workspace_store
 472                            .workspaces()
 473                            .iter()
 474                            .map(|workspace| {
 475                                workspace.update(cx, |workspace, _, cx| {
 476                                    workspace.project().read(cx).lsp_store()
 477                                })
 478                            })
 479                            .collect()
 480                    })
 481                })
 482            }),
 483            extension_host_proxy.clone(),
 484            languages.clone(),
 485        );
 486
 487        Client::set_global(client.clone(), cx);
 488
 489        zed::init(cx);
 490        project::Project::init(&client, cx);
 491        debugger_ui::init(cx);
 492        debugger_tools::init(cx);
 493        client::init(&client, cx);
 494        let telemetry = client.telemetry();
 495        telemetry.start(
 496            system_id.as_ref().map(|id| id.to_string()),
 497            installation_id.as_ref().map(|id| id.to_string()),
 498            session_id.clone(),
 499            cx,
 500        );
 501
 502        // We should rename these in the future to `first app open`, `first app open for release channel`, and `app open`
 503        if let (Some(system_id), Some(installation_id)) = (&system_id, &installation_id) {
 504            match (&system_id, &installation_id) {
 505                (IdType::New(_), IdType::New(_)) => {
 506                    telemetry::event!("App First Opened");
 507                    telemetry::event!("App First Opened For Release Channel");
 508                }
 509                (IdType::Existing(_), IdType::New(_)) => {
 510                    telemetry::event!("App First Opened For Release Channel");
 511                }
 512                (_, IdType::Existing(_)) => {
 513                    telemetry::event!("App Opened");
 514                }
 515            }
 516        }
 517        let app_session = cx.new(|cx| AppSession::new(session, cx));
 518
 519        let app_state = Arc::new(AppState {
 520            languages: languages.clone(),
 521            client: client.clone(),
 522            user_store: user_store.clone(),
 523            fs: fs.clone(),
 524            build_window_options,
 525            workspace_store,
 526            node_runtime: node_runtime.clone(),
 527            session: app_session,
 528        });
 529        AppState::set_global(Arc::downgrade(&app_state), cx);
 530
 531        auto_update::init(client.http_client(), cx);
 532        dap_adapters::init(cx);
 533        auto_update_ui::init(cx);
 534        reliability::init(
 535            client.http_client(),
 536            system_id.as_ref().map(|id| id.to_string()),
 537            installation_id.clone().map(|id| id.to_string()),
 538            session_id.clone(),
 539            cx,
 540        );
 541
 542        SystemAppearance::init(cx);
 543        theme::init(theme::LoadThemes::All(Box::new(Assets)), cx);
 544        theme_extension::init(
 545            extension_host_proxy.clone(),
 546            ThemeRegistry::global(cx),
 547            cx.background_executor().clone(),
 548        );
 549        command_palette::init(cx);
 550        let copilot_language_server_id = app_state.languages.next_language_server_id();
 551        copilot::init(
 552            copilot_language_server_id,
 553            app_state.fs.clone(),
 554            app_state.client.http_client(),
 555            app_state.node_runtime.clone(),
 556            cx,
 557        );
 558        supermaven::init(app_state.client.clone(), cx);
 559        language_model::init(app_state.client.clone(), cx);
 560        language_models::init(app_state.user_store.clone(), app_state.client.clone(), cx);
 561        agent_settings::init(cx);
 562        agent_servers::init(cx);
 563        web_search::init(cx);
 564        web_search_providers::init(app_state.client.clone(), cx);
 565        snippet_provider::init(cx);
 566        edit_prediction_registry::init(app_state.client.clone(), app_state.user_store.clone(), cx);
 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        onboarding::init(cx);
 627        settings_ui::init(cx);
 628        extensions_ui::init(cx);
 629        zeta::init(cx);
 630        inspector_ui::init(app_state.clone(), cx);
 631
 632        cx.observe_global::<SettingsStore>({
 633            let fs = fs.clone();
 634            let languages = app_state.languages.clone();
 635            let http = app_state.client.http_client();
 636            let client = app_state.client.clone();
 637            move |cx| {
 638                for &mut window in cx.windows().iter_mut() {
 639                    let background_appearance = cx.theme().window_background_appearance();
 640                    window
 641                        .update(cx, |_, window, _| {
 642                            window.set_background_appearance(background_appearance)
 643                        })
 644                        .ok();
 645                }
 646
 647                eager_load_active_theme_and_icon_theme(fs.clone(), cx);
 648
 649                languages.set_theme(cx.theme().clone());
 650                let new_host = &client::ClientSettings::get_global(cx).server_url;
 651                if &http.base_url() != new_host {
 652                    http.set_base_url(new_host);
 653                    if client.status().borrow().is_connected() {
 654                        client.reconnect(&cx.to_async());
 655                    }
 656                }
 657            }
 658        })
 659        .detach();
 660        telemetry::event!(
 661            "Settings Changed",
 662            setting = "theme",
 663            value = cx.theme().name.to_string()
 664        );
 665        telemetry::event!(
 666            "Settings Changed",
 667            setting = "keymap",
 668            value = BaseKeymap::get_global(cx).to_string()
 669        );
 670        telemetry.flush_events().detach();
 671
 672        let fs = app_state.fs.clone();
 673        load_user_themes_in_background(fs.clone(), cx);
 674        watch_themes(fs.clone(), cx);
 675        watch_languages(fs.clone(), app_state.languages.clone(), cx);
 676
 677        cx.set_menus(app_menus());
 678        initialize_workspace(app_state.clone(), prompt_builder, cx);
 679
 680        cx.activate(true);
 681
 682        cx.spawn({
 683            let client = app_state.client.clone();
 684            async move |cx| authenticate(client, &cx).await
 685        })
 686        .detach_and_log_err(cx);
 687
 688        let urls: Vec<_> = args
 689            .paths_or_urls
 690            .iter()
 691            .filter_map(|arg| parse_url_arg(arg, cx).log_err())
 692            .collect();
 693
 694        let diff_paths: Vec<[String; 2]> = args
 695            .diff
 696            .chunks(2)
 697            .map(|chunk| [chunk[0].clone(), chunk[1].clone()])
 698            .collect();
 699
 700        if !urls.is_empty() || !diff_paths.is_empty() {
 701            open_listener.open(RawOpenRequest { urls, diff_paths })
 702        }
 703
 704        match open_rx
 705            .try_next()
 706            .ok()
 707            .flatten()
 708            .and_then(|request| OpenRequest::parse(request, cx).log_err())
 709        {
 710            Some(request) => {
 711                handle_open_request(request, app_state.clone(), cx);
 712            }
 713            None => {
 714                cx.spawn({
 715                    let app_state = app_state.clone();
 716                    async move |mut cx| {
 717                        if let Err(e) = restore_or_create_workspace(app_state, &mut cx).await {
 718                            fail_to_open_window_async(e, &mut cx)
 719                        }
 720                    }
 721                })
 722                .detach();
 723            }
 724        }
 725
 726        let app_state = app_state.clone();
 727
 728        crate::zed::component_preview::init(app_state.clone(), cx);
 729
 730        cx.spawn(async move |cx| {
 731            while let Some(urls) = open_rx.next().await {
 732                cx.update(|cx| {
 733                    if let Some(request) = OpenRequest::parse(urls, cx).log_err() {
 734                        handle_open_request(request, app_state.clone(), cx);
 735                    }
 736                })
 737                .ok();
 738            }
 739        })
 740        .detach();
 741    });
 742}
 743
 744fn handle_open_request(request: OpenRequest, app_state: Arc<AppState>, cx: &mut App) {
 745    if let Some(kind) = request.kind {
 746        match kind {
 747            OpenRequestKind::CliConnection(connection) => {
 748                let app_state = app_state.clone();
 749                cx.spawn(async move |cx| handle_cli_connection(connection, app_state, cx).await)
 750                    .detach();
 751            }
 752            OpenRequestKind::Extension { extension_id } => {
 753                cx.spawn(async move |cx| {
 754                    let workspace =
 755                        workspace::get_any_active_workspace(app_state, cx.clone()).await?;
 756                    workspace.update(cx, |_, window, cx| {
 757                        window.dispatch_action(
 758                            Box::new(zed_actions::Extensions {
 759                                category_filter: None,
 760                                id: Some(extension_id),
 761                            }),
 762                            cx,
 763                        );
 764                    })
 765                })
 766                .detach_and_log_err(cx);
 767            }
 768            OpenRequestKind::AgentPanel => {
 769                cx.spawn(async move |cx| {
 770                    let workspace =
 771                        workspace::get_any_active_workspace(app_state, cx.clone()).await?;
 772                    workspace.update(cx, |workspace, window, cx| {
 773                        if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
 774                            panel.focus_handle(cx).focus(window);
 775                        }
 776                    })
 777                })
 778                .detach_and_log_err(cx);
 779            }
 780            OpenRequestKind::DockMenuAction { index } => {
 781                cx.perform_dock_menu_action(index);
 782            }
 783        }
 784
 785        return;
 786    }
 787
 788    if let Some(connection_options) = request.ssh_connection {
 789        cx.spawn(async move |mut cx| {
 790            let paths: Vec<PathBuf> = request.open_paths.into_iter().map(PathBuf::from).collect();
 791            open_ssh_project(
 792                connection_options,
 793                paths,
 794                app_state,
 795                workspace::OpenOptions::default(),
 796                &mut cx,
 797            )
 798            .await
 799        })
 800        .detach_and_log_err(cx);
 801        return;
 802    }
 803
 804    let mut task = None;
 805    if !request.open_paths.is_empty() || !request.diff_paths.is_empty() {
 806        let app_state = app_state.clone();
 807        task = Some(cx.spawn(async move |mut cx| {
 808            let paths_with_position =
 809                derive_paths_with_position(app_state.fs.as_ref(), request.open_paths).await;
 810            let (_window, results) = open_paths_with_positions(
 811                &paths_with_position,
 812                &request.diff_paths,
 813                app_state,
 814                workspace::OpenOptions::default(),
 815                &mut cx,
 816            )
 817            .await?;
 818            for result in results.into_iter().flatten() {
 819                if let Err(err) = result {
 820                    log::error!("Error opening path: {err}",);
 821                }
 822            }
 823            anyhow::Ok(())
 824        }));
 825    }
 826
 827    if !request.open_channel_notes.is_empty() || request.join_channel.is_some() {
 828        cx.spawn(async move |mut cx| {
 829            let result = maybe!(async {
 830                if let Some(task) = task {
 831                    task.await?;
 832                }
 833                let client = app_state.client.clone();
 834                // we continue even if authentication fails as join_channel/ open channel notes will
 835                // show a visible error message.
 836                authenticate(client, &cx).await.log_err();
 837
 838                if let Some(channel_id) = request.join_channel {
 839                    cx.update(|cx| {
 840                        workspace::join_channel(
 841                            client::ChannelId(channel_id),
 842                            app_state.clone(),
 843                            None,
 844                            cx,
 845                        )
 846                    })?
 847                    .await?;
 848                }
 849
 850                let workspace_window =
 851                    workspace::get_any_active_workspace(app_state, cx.clone()).await?;
 852                let workspace = workspace_window.entity(cx)?;
 853
 854                let mut promises = Vec::new();
 855                for (channel_id, heading) in request.open_channel_notes {
 856                    promises.push(cx.update_window(workspace_window.into(), |_, window, cx| {
 857                        ChannelView::open(
 858                            client::ChannelId(channel_id),
 859                            heading,
 860                            workspace.clone(),
 861                            window,
 862                            cx,
 863                        )
 864                        .log_err()
 865                    })?)
 866                }
 867                future::join_all(promises).await;
 868                anyhow::Ok(())
 869            })
 870            .await;
 871            if let Err(err) = result {
 872                fail_to_open_window_async(err, &mut cx);
 873            }
 874        })
 875        .detach()
 876    } else if let Some(task) = task {
 877        cx.spawn(async move |mut cx| {
 878            if let Err(err) = task.await {
 879                fail_to_open_window_async(err, &mut cx);
 880            }
 881        })
 882        .detach();
 883    }
 884}
 885
 886async fn authenticate(client: Arc<Client>, cx: &AsyncApp) -> Result<()> {
 887    if stdout_is_a_pty() {
 888        if client::IMPERSONATE_LOGIN.is_some() {
 889            client.sign_in_with_optional_connect(false, cx).await?;
 890        } else if client.has_credentials(cx).await {
 891            client.sign_in_with_optional_connect(true, cx).await?;
 892        }
 893    } else if client.has_credentials(cx).await {
 894        client.sign_in_with_optional_connect(true, cx).await?;
 895    }
 896
 897    Ok(())
 898}
 899
 900async fn system_id() -> Result<IdType> {
 901    let key_name = "system_id".to_string();
 902
 903    if let Ok(Some(system_id)) = GLOBAL_KEY_VALUE_STORE.read_kvp(&key_name) {
 904        return Ok(IdType::Existing(system_id));
 905    }
 906
 907    let system_id = Uuid::new_v4().to_string();
 908
 909    GLOBAL_KEY_VALUE_STORE
 910        .write_kvp(key_name, system_id.clone())
 911        .await?;
 912
 913    Ok(IdType::New(system_id))
 914}
 915
 916async fn installation_id() -> Result<IdType> {
 917    let legacy_key_name = "device_id".to_string();
 918    let key_name = "installation_id".to_string();
 919
 920    // Migrate legacy key to new key
 921    if let Ok(Some(installation_id)) = KEY_VALUE_STORE.read_kvp(&legacy_key_name) {
 922        KEY_VALUE_STORE
 923            .write_kvp(key_name, installation_id.clone())
 924            .await?;
 925        KEY_VALUE_STORE.delete_kvp(legacy_key_name).await?;
 926        return Ok(IdType::Existing(installation_id));
 927    }
 928
 929    if let Ok(Some(installation_id)) = KEY_VALUE_STORE.read_kvp(&key_name) {
 930        return Ok(IdType::Existing(installation_id));
 931    }
 932
 933    let installation_id = Uuid::new_v4().to_string();
 934
 935    KEY_VALUE_STORE
 936        .write_kvp(key_name, installation_id.clone())
 937        .await?;
 938
 939    Ok(IdType::New(installation_id))
 940}
 941
 942async fn restore_or_create_workspace(app_state: Arc<AppState>, cx: &mut AsyncApp) -> Result<()> {
 943    if let Some(locations) = restorable_workspace_locations(cx, &app_state).await {
 944        let mut tasks = Vec::new();
 945
 946        for location in locations {
 947            match location {
 948                SerializedWorkspaceLocation::Local(location, _) => {
 949                    let app_state = app_state.clone();
 950                    let paths = location.paths().to_vec();
 951                    let task = cx.spawn(async move |cx| {
 952                        let open_task = cx.update(|cx| {
 953                            workspace::open_paths(
 954                                &paths,
 955                                app_state,
 956                                workspace::OpenOptions::default(),
 957                                cx,
 958                            )
 959                        })?;
 960                        open_task.await.map(|_| ())
 961                    });
 962                    tasks.push(task);
 963                }
 964                SerializedWorkspaceLocation::Ssh(ssh) => {
 965                    let app_state = app_state.clone();
 966                    let ssh_host = ssh.host.clone();
 967                    let task = cx.spawn(async move |cx| {
 968                        let connection_options = cx.update(|cx| {
 969                            SshSettings::get_global(cx)
 970                                .connection_options_for(ssh.host, ssh.port, ssh.user)
 971                        });
 972
 973                        match connection_options {
 974                            Ok(connection_options) => recent_projects::open_ssh_project(
 975                                connection_options,
 976                                ssh.paths.into_iter().map(PathBuf::from).collect(),
 977                                app_state,
 978                                workspace::OpenOptions::default(),
 979                                cx,
 980                            )
 981                            .await
 982                            .map_err(|e| anyhow::anyhow!(e)),
 983                            Err(e) => Err(anyhow::anyhow!(
 984                                "Failed to get SSH connection options for {}: {}",
 985                                ssh_host,
 986                                e
 987                            )),
 988                        }
 989                    });
 990                    tasks.push(task);
 991                }
 992            }
 993        }
 994
 995        // Wait for all workspaces to open concurrently
 996        let results = future::join_all(tasks).await;
 997
 998        // Show notifications for any errors that occurred
 999        let mut error_count = 0;
1000        for result in results {
1001            if let Err(e) = result {
1002                log::error!("Failed to restore workspace: {}", e);
1003                error_count += 1;
1004            }
1005        }
1006
1007        if error_count > 0 {
1008            let message = if error_count == 1 {
1009                "Failed to restore 1 workspace. Check logs for details.".to_string()
1010            } else {
1011                format!(
1012                    "Failed to restore {} workspaces. Check logs for details.",
1013                    error_count
1014                )
1015            };
1016
1017            // Try to find an active workspace to show the toast
1018            let toast_shown = cx
1019                .update(|cx| {
1020                    if let Some(window) = cx.active_window() {
1021                        if let Some(workspace) = window.downcast::<Workspace>() {
1022                            workspace
1023                                .update(cx, |workspace, _, cx| {
1024                                    workspace.show_toast(
1025                                        Toast::new(NotificationId::unique::<()>(), message),
1026                                        cx,
1027                                    )
1028                                })
1029                                .ok();
1030                            return true;
1031                        }
1032                    }
1033                    false
1034                })
1035                .unwrap_or(false);
1036
1037            // If we couldn't show a toast (no windows opened successfully),
1038            // we've already logged the errors above, so the user can check logs
1039            if !toast_shown {
1040                log::error!(
1041                    "Failed to show notification for window restoration errors, because no workspace windows were available."
1042                );
1043            }
1044        }
1045    } else if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) {
1046        cx.update(|cx| show_onboarding_view(app_state, cx))?.await?;
1047    } else {
1048        cx.update(|cx| {
1049            workspace::open_new(
1050                Default::default(),
1051                app_state,
1052                cx,
1053                |workspace, window, cx| {
1054                    Editor::new_file(workspace, &Default::default(), window, cx)
1055                },
1056            )
1057        })?
1058        .await?;
1059    }
1060
1061    Ok(())
1062}
1063
1064pub(crate) async fn restorable_workspace_locations(
1065    cx: &mut AsyncApp,
1066    app_state: &Arc<AppState>,
1067) -> Option<Vec<SerializedWorkspaceLocation>> {
1068    let mut restore_behavior = cx
1069        .update(|cx| WorkspaceSettings::get(None, cx).restore_on_startup)
1070        .ok()?;
1071
1072    let session_handle = app_state.session.clone();
1073    let (last_session_id, last_session_window_stack) = cx
1074        .update(|cx| {
1075            let session = session_handle.read(cx);
1076
1077            (
1078                session.last_session_id().map(|id| id.to_string()),
1079                session.last_session_window_stack(),
1080            )
1081        })
1082        .ok()?;
1083
1084    if last_session_id.is_none()
1085        && matches!(
1086            restore_behavior,
1087            workspace::RestoreOnStartupBehavior::LastSession
1088        )
1089    {
1090        restore_behavior = workspace::RestoreOnStartupBehavior::LastWorkspace;
1091    }
1092
1093    match restore_behavior {
1094        workspace::RestoreOnStartupBehavior::LastWorkspace => {
1095            workspace::last_opened_workspace_location()
1096                .await
1097                .map(|location| vec![location])
1098        }
1099        workspace::RestoreOnStartupBehavior::LastSession => {
1100            if let Some(last_session_id) = last_session_id {
1101                let ordered = last_session_window_stack.is_some();
1102
1103                let mut locations = workspace::last_session_workspace_locations(
1104                    &last_session_id,
1105                    last_session_window_stack,
1106                )
1107                .filter(|locations| !locations.is_empty());
1108
1109                // Since last_session_window_order returns the windows ordered front-to-back
1110                // we need to open the window that was frontmost last.
1111                if ordered {
1112                    if let Some(locations) = locations.as_mut() {
1113                        locations.reverse();
1114                    }
1115                }
1116
1117                locations
1118            } else {
1119                None
1120            }
1121        }
1122        _ => None,
1123    }
1124}
1125
1126fn init_paths() -> HashMap<io::ErrorKind, Vec<&'static Path>> {
1127    [
1128        paths::config_dir(),
1129        paths::extensions_dir(),
1130        paths::languages_dir(),
1131        paths::debug_adapters_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    /// Used for recording minidumps on crashes by having Zed run a separate
1192    /// process communicating over a socket.
1193    #[arg(long, hide = true)]
1194    crash_handler: Option<PathBuf>,
1195
1196    /// Run zed in the foreground, only used on Windows, to match the behavior on macOS.
1197    #[arg(long)]
1198    #[cfg(target_os = "windows")]
1199    #[arg(hide = true)]
1200    foreground: bool,
1201
1202    /// The dock action to perform. This is used on Windows only.
1203    #[arg(long)]
1204    #[cfg(target_os = "windows")]
1205    #[arg(hide = true)]
1206    dock_action: Option<usize>,
1207
1208    #[arg(long, hide = true)]
1209    dump_all_actions: bool,
1210
1211    /// Output current environment variables as JSON to stdout
1212    #[arg(long, hide = true)]
1213    printenv: bool,
1214}
1215
1216#[derive(Clone, Debug)]
1217enum IdType {
1218    New(String),
1219    Existing(String),
1220}
1221
1222impl ToString for IdType {
1223    fn to_string(&self) -> String {
1224        match self {
1225            IdType::New(id) | IdType::Existing(id) => id.clone(),
1226        }
1227    }
1228}
1229
1230fn parse_url_arg(arg: &str, cx: &App) -> Result<String> {
1231    match std::fs::canonicalize(Path::new(&arg)) {
1232        Ok(path) => Ok(format!("file://{}", path.display())),
1233        Err(error) => {
1234            if arg.starts_with("file://")
1235                || arg.starts_with("zed-cli://")
1236                || arg.starts_with("ssh://")
1237                || parse_zed_link(arg, cx).is_some()
1238            {
1239                Ok(arg.into())
1240            } else {
1241                anyhow::bail!("error parsing path argument: {error}")
1242            }
1243        }
1244    }
1245}
1246
1247fn load_embedded_fonts(cx: &App) {
1248    let asset_source = cx.asset_source();
1249    let font_paths = asset_source.list("fonts").unwrap();
1250    let embedded_fonts = Mutex::new(Vec::new());
1251    let executor = cx.background_executor();
1252
1253    executor.block(executor.scoped(|scope| {
1254        for font_path in &font_paths {
1255            if !font_path.ends_with(".ttf") {
1256                continue;
1257            }
1258
1259            scope.spawn(async {
1260                let font_bytes = asset_source.load(font_path).unwrap().unwrap();
1261                embedded_fonts.lock().push(font_bytes);
1262            });
1263        }
1264    }));
1265
1266    cx.text_system()
1267        .add_fonts(embedded_fonts.into_inner())
1268        .unwrap();
1269}
1270
1271/// Eagerly loads the active theme and icon theme based on the selections in the
1272/// theme settings.
1273///
1274/// This fast path exists to load these themes as soon as possible so the user
1275/// doesn't see the default themes while waiting on extensions to load.
1276fn eager_load_active_theme_and_icon_theme(fs: Arc<dyn Fs>, cx: &App) {
1277    let extension_store = ExtensionStore::global(cx);
1278    let theme_registry = ThemeRegistry::global(cx);
1279    let theme_settings = ThemeSettings::get_global(cx);
1280    let appearance = SystemAppearance::global(cx).0;
1281
1282    if let Some(theme_selection) = theme_settings.theme_selection.as_ref() {
1283        let theme_name = theme_selection.theme(appearance);
1284        if matches!(theme_registry.get(theme_name), Err(ThemeNotFoundError(_))) {
1285            if let Some(theme_path) = extension_store.read(cx).path_to_extension_theme(theme_name) {
1286                cx.spawn({
1287                    let theme_registry = theme_registry.clone();
1288                    let fs = fs.clone();
1289                    async move |cx| {
1290                        theme_registry.load_user_theme(&theme_path, fs).await?;
1291
1292                        cx.update(|cx| {
1293                            ThemeSettings::reload_current_theme(cx);
1294                        })
1295                    }
1296                })
1297                .detach_and_log_err(cx);
1298            }
1299        }
1300    }
1301
1302    if let Some(icon_theme_selection) = theme_settings.icon_theme_selection.as_ref() {
1303        let icon_theme_name = icon_theme_selection.icon_theme(appearance);
1304        if matches!(
1305            theme_registry.get_icon_theme(icon_theme_name),
1306            Err(IconThemeNotFoundError(_))
1307        ) {
1308            if let Some((icon_theme_path, icons_root_path)) = extension_store
1309                .read(cx)
1310                .path_to_extension_icon_theme(icon_theme_name)
1311            {
1312                cx.spawn({
1313                    let theme_registry = theme_registry.clone();
1314                    let fs = fs.clone();
1315                    async move |cx| {
1316                        theme_registry
1317                            .load_icon_theme(&icon_theme_path, &icons_root_path, fs)
1318                            .await?;
1319
1320                        cx.update(|cx| {
1321                            ThemeSettings::reload_current_icon_theme(cx);
1322                        })
1323                    }
1324                })
1325                .detach_and_log_err(cx);
1326            }
1327        }
1328    }
1329}
1330
1331/// Spawns a background task to load the user themes from the themes directory.
1332fn load_user_themes_in_background(fs: Arc<dyn fs::Fs>, cx: &mut App) {
1333    cx.spawn({
1334        let fs = fs.clone();
1335        async move |cx| {
1336            if let Some(theme_registry) =
1337                cx.update(|cx| ThemeRegistry::global(cx).clone()).log_err()
1338            {
1339                let themes_dir = paths::themes_dir().as_ref();
1340                match fs
1341                    .metadata(themes_dir)
1342                    .await
1343                    .ok()
1344                    .flatten()
1345                    .map(|m| m.is_dir)
1346                {
1347                    Some(is_dir) => {
1348                        anyhow::ensure!(is_dir, "Themes dir path {themes_dir:?} is not a directory")
1349                    }
1350                    None => {
1351                        fs.create_dir(themes_dir).await.with_context(|| {
1352                            format!("Failed to create themes dir at path {themes_dir:?}")
1353                        })?;
1354                    }
1355                }
1356                theme_registry.load_user_themes(themes_dir, fs).await?;
1357                cx.update(ThemeSettings::reload_current_theme)?;
1358            }
1359            anyhow::Ok(())
1360        }
1361    })
1362    .detach_and_log_err(cx);
1363}
1364
1365/// Spawns a background task to watch the themes directory for changes.
1366fn watch_themes(fs: Arc<dyn fs::Fs>, cx: &mut App) {
1367    use std::time::Duration;
1368    cx.spawn(async move |cx| {
1369        let (mut events, _) = fs
1370            .watch(paths::themes_dir(), Duration::from_millis(100))
1371            .await;
1372
1373        while let Some(paths) = events.next().await {
1374            for event in paths {
1375                if fs.metadata(&event.path).await.ok().flatten().is_some() {
1376                    if let Some(theme_registry) =
1377                        cx.update(|cx| ThemeRegistry::global(cx).clone()).log_err()
1378                    {
1379                        if let Some(()) = theme_registry
1380                            .load_user_theme(&event.path, fs.clone())
1381                            .await
1382                            .log_err()
1383                        {
1384                            cx.update(ThemeSettings::reload_current_theme).log_err();
1385                        }
1386                    }
1387                }
1388            }
1389        }
1390    })
1391    .detach()
1392}
1393
1394#[cfg(debug_assertions)]
1395fn watch_languages(fs: Arc<dyn fs::Fs>, languages: Arc<LanguageRegistry>, cx: &mut App) {
1396    use std::time::Duration;
1397
1398    let path = {
1399        let p = Path::new("crates/languages/src");
1400        let Ok(full_path) = p.canonicalize() else {
1401            return;
1402        };
1403        full_path
1404    };
1405
1406    cx.spawn(async move |_| {
1407        let (mut events, _) = fs.watch(path.as_path(), Duration::from_millis(100)).await;
1408        while let Some(event) = events.next().await {
1409            let has_language_file = event.iter().any(|event| {
1410                event
1411                    .path
1412                    .extension()
1413                    .map(|ext| ext.to_string_lossy().as_ref() == "scm")
1414                    .unwrap_or(false)
1415            });
1416            if has_language_file {
1417                languages.reload();
1418            }
1419        }
1420    })
1421    .detach()
1422}
1423
1424#[cfg(not(debug_assertions))]
1425fn watch_languages(_fs: Arc<dyn fs::Fs>, _languages: Arc<LanguageRegistry>, _cx: &mut App) {}
1426
1427fn dump_all_gpui_actions() {
1428    #[derive(Debug, serde::Serialize)]
1429    struct ActionDef {
1430        name: &'static str,
1431        human_name: String,
1432        aliases: &'static [&'static str],
1433        documentation: Option<&'static str>,
1434    }
1435    let mut actions = gpui::generate_list_of_all_registered_actions()
1436        .map(|action| ActionDef {
1437            name: action.name,
1438            human_name: command_palette::humanize_action_name(action.name),
1439            aliases: action.deprecated_aliases,
1440            documentation: action.documentation,
1441        })
1442        .collect::<Vec<ActionDef>>();
1443
1444    actions.sort_by_key(|a| a.name);
1445
1446    io::Write::write(
1447        &mut std::io::stdout(),
1448        serde_json::to_string_pretty(&actions).unwrap().as_bytes(),
1449    )
1450    .unwrap();
1451}