main.rs

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