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