main.rs

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