main.rs

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