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