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};
   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::new(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        debug_adapter_extension::init(extension_host_proxy.clone(), cx);
 423        language::init(cx);
 424        language_extension::init(extension_host_proxy.clone(), languages.clone());
 425        languages::init(languages.clone(), node_runtime.clone(), cx);
 426        let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
 427        let workspace_store = cx.new(|cx| WorkspaceStore::new(client.clone(), cx));
 428
 429        Client::set_global(client.clone(), cx);
 430
 431        zed::init(cx);
 432        project::Project::init(&client, cx);
 433        debugger_ui::init(cx);
 434        debugger_tools::init(cx);
 435        client::init(&client, cx);
 436        let telemetry = client.telemetry();
 437        telemetry.start(
 438            system_id.as_ref().map(|id| id.to_string()),
 439            installation_id.as_ref().map(|id| id.to_string()),
 440            session_id.clone(),
 441            cx,
 442        );
 443
 444        // We should rename these in the future to `first app open`, `first app open for release channel`, and `app open`
 445        if let (Some(system_id), Some(installation_id)) = (&system_id, &installation_id) {
 446            match (&system_id, &installation_id) {
 447                (IdType::New(_), IdType::New(_)) => {
 448                    telemetry::event!("App First Opened");
 449                    telemetry::event!("App First Opened For Release Channel");
 450                }
 451                (IdType::Existing(_), IdType::New(_)) => {
 452                    telemetry::event!("App First Opened For Release Channel");
 453                }
 454                (_, IdType::Existing(_)) => {
 455                    telemetry::event!("App Opened");
 456                }
 457            }
 458        }
 459        let app_session = cx.new(|cx| AppSession::new(session, cx));
 460
 461        let app_state = Arc::new(AppState {
 462            languages: languages.clone(),
 463            client: client.clone(),
 464            user_store: user_store.clone(),
 465            fs: fs.clone(),
 466            build_window_options,
 467            workspace_store,
 468            node_runtime: node_runtime.clone(),
 469            session: app_session,
 470        });
 471        AppState::set_global(Arc::downgrade(&app_state), cx);
 472
 473        auto_update::init(client.http_client(), cx);
 474        dap_adapters::init(cx);
 475        auto_update_ui::init(cx);
 476        reliability::init(
 477            client.http_client(),
 478            system_id.as_ref().map(|id| id.to_string()),
 479            installation_id.clone().map(|id| id.to_string()),
 480            session_id.clone(),
 481            cx,
 482        );
 483
 484        SystemAppearance::init(cx);
 485        theme::init(theme::LoadThemes::All(Box::new(Assets)), cx);
 486        theme_extension::init(
 487            extension_host_proxy.clone(),
 488            ThemeRegistry::global(cx),
 489            cx.background_executor().clone(),
 490        );
 491        command_palette::init(cx);
 492        let copilot_language_server_id = app_state.languages.next_language_server_id();
 493        copilot::init(
 494            copilot_language_server_id,
 495            app_state.fs.clone(),
 496            app_state.client.http_client(),
 497            app_state.node_runtime.clone(),
 498            cx,
 499        );
 500        supermaven::init(app_state.client.clone(), cx);
 501        language_model::init(app_state.client.clone(), cx);
 502        language_models::init(
 503            app_state.user_store.clone(),
 504            app_state.client.clone(),
 505            app_state.fs.clone(),
 506            cx,
 507        );
 508        web_search::init(cx);
 509        web_search_providers::init(app_state.client.clone(), cx);
 510        snippet_provider::init(cx);
 511        inline_completion_registry::init(
 512            app_state.client.clone(),
 513            app_state.user_store.clone(),
 514            cx,
 515        );
 516        let prompt_builder = PromptBuilder::load(app_state.fs.clone(), stdout_is_a_pty(), cx);
 517        agent::init(
 518            app_state.fs.clone(),
 519            app_state.client.clone(),
 520            prompt_builder.clone(),
 521            app_state.languages.clone(),
 522            false,
 523            cx,
 524        );
 525        assistant_tools::init(app_state.client.http_client(), cx);
 526        repl::init(app_state.fs.clone(), cx);
 527        extension_host::init(
 528            extension_host_proxy,
 529            app_state.fs.clone(),
 530            app_state.client.clone(),
 531            app_state.node_runtime.clone(),
 532            cx,
 533        );
 534        recent_projects::init(cx);
 535
 536        load_embedded_fonts(cx);
 537
 538        app_state.languages.set_theme(cx.theme().clone());
 539        editor::init(cx);
 540        image_viewer::init(cx);
 541        repl::notebook::init(cx);
 542        diagnostics::init(cx);
 543
 544        audio::init(Assets, cx);
 545        workspace::init(app_state.clone(), cx);
 546        ui_prompt::init(cx);
 547
 548        go_to_line::init(cx);
 549        file_finder::init(cx);
 550        tab_switcher::init(cx);
 551        outline::init(cx);
 552        project_symbols::init(cx);
 553        project_panel::init(cx);
 554        outline_panel::init(cx);
 555        tasks_ui::init(cx);
 556        snippets_ui::init(cx);
 557        channel::init(&app_state.client.clone(), app_state.user_store.clone(), cx);
 558        search::init(cx);
 559        vim::init(cx);
 560        terminal_view::init(cx);
 561        journal::init(app_state.clone(), cx);
 562        language_selector::init(cx);
 563        toolchain_selector::init(cx);
 564        theme_selector::init(cx);
 565        language_tools::init(cx);
 566        call::init(app_state.client.clone(), app_state.user_store.clone(), cx);
 567        notifications::init(app_state.client.clone(), app_state.user_store.clone(), cx);
 568        collab_ui::init(&app_state, cx);
 569        git_ui::init(cx);
 570        jj_ui::init(cx);
 571        feedback::init(cx);
 572        markdown_preview::init(cx);
 573        welcome::init(cx);
 574        settings_ui::init(cx);
 575        extensions_ui::init(cx);
 576        zeta::init(cx);
 577        inspector_ui::init(app_state.clone(), cx);
 578
 579        cx.observe_global::<SettingsStore>({
 580            let fs = fs.clone();
 581            let languages = app_state.languages.clone();
 582            let http = app_state.client.http_client();
 583            let client = app_state.client.clone();
 584            move |cx| {
 585                for &mut window in cx.windows().iter_mut() {
 586                    let background_appearance = cx.theme().window_background_appearance();
 587                    window
 588                        .update(cx, |_, window, _| {
 589                            window.set_background_appearance(background_appearance)
 590                        })
 591                        .ok();
 592                }
 593
 594                eager_load_active_theme_and_icon_theme(fs.clone(), cx);
 595
 596                languages.set_theme(cx.theme().clone());
 597                let new_host = &client::ClientSettings::get_global(cx).server_url;
 598                if &http.base_url() != new_host {
 599                    http.set_base_url(new_host);
 600                    if client.status().borrow().is_connected() {
 601                        client.reconnect(&cx.to_async());
 602                    }
 603                }
 604            }
 605        })
 606        .detach();
 607        telemetry::event!(
 608            "Settings Changed",
 609            setting = "theme",
 610            value = cx.theme().name.to_string()
 611        );
 612        telemetry::event!(
 613            "Settings Changed",
 614            setting = "keymap",
 615            value = BaseKeymap::get_global(cx).to_string()
 616        );
 617        telemetry.flush_events().detach();
 618
 619        let fs = app_state.fs.clone();
 620        load_user_themes_in_background(fs.clone(), cx);
 621        watch_themes(fs.clone(), cx);
 622        watch_languages(fs.clone(), app_state.languages.clone(), cx);
 623
 624        cx.set_menus(app_menus());
 625        initialize_workspace(app_state.clone(), prompt_builder, cx);
 626
 627        cx.activate(true);
 628
 629        cx.spawn({
 630            let client = app_state.client.clone();
 631            async move |cx| match authenticate(client, &cx).await {
 632                ConnectionResult::Timeout => log::error!("Timeout during initial auth"),
 633                ConnectionResult::ConnectionReset => {
 634                    log::error!("Connection reset during initial auth")
 635                }
 636                ConnectionResult::Result(r) => {
 637                    r.log_err();
 638                }
 639            }
 640        })
 641        .detach();
 642
 643        let urls: Vec<_> = args
 644            .paths_or_urls
 645            .iter()
 646            .filter_map(|arg| parse_url_arg(arg, cx).log_err())
 647            .collect();
 648
 649        if !urls.is_empty() {
 650            open_listener.open_urls(urls)
 651        }
 652
 653        match open_rx
 654            .try_next()
 655            .ok()
 656            .flatten()
 657            .and_then(|urls| OpenRequest::parse(urls, cx).log_err())
 658        {
 659            Some(request) => {
 660                handle_open_request(request, app_state.clone(), cx);
 661            }
 662            None => {
 663                cx.spawn({
 664                    let app_state = app_state.clone();
 665                    async move |mut cx| {
 666                        if let Err(e) = restore_or_create_workspace(app_state, &mut cx).await {
 667                            fail_to_open_window_async(e, &mut cx)
 668                        }
 669                    }
 670                })
 671                .detach();
 672            }
 673        }
 674
 675        let app_state = app_state.clone();
 676
 677        crate::zed::component_preview::init(app_state.clone(), cx);
 678
 679        cx.spawn(async move |cx| {
 680            while let Some(urls) = open_rx.next().await {
 681                cx.update(|cx| {
 682                    if let Some(request) = OpenRequest::parse(urls, cx).log_err() {
 683                        handle_open_request(request, app_state.clone(), cx);
 684                    }
 685                })
 686                .ok();
 687            }
 688        })
 689        .detach();
 690    });
 691}
 692
 693fn handle_open_request(request: OpenRequest, app_state: Arc<AppState>, cx: &mut App) {
 694    if let Some(connection) = request.cli_connection {
 695        let app_state = app_state.clone();
 696        cx.spawn(async move |cx| handle_cli_connection(connection, app_state, cx).await)
 697            .detach();
 698        return;
 699    }
 700
 701    if let Some(action_index) = request.dock_menu_action {
 702        cx.perform_dock_menu_action(action_index);
 703        return;
 704    }
 705
 706    if let Some(connection_options) = request.ssh_connection {
 707        cx.spawn(async move |mut cx| {
 708            let paths_with_position =
 709                derive_paths_with_position(app_state.fs.as_ref(), request.open_paths).await;
 710            open_ssh_project(
 711                connection_options,
 712                paths_with_position.into_iter().map(|p| p.path).collect(),
 713                app_state,
 714                workspace::OpenOptions::default(),
 715                &mut cx,
 716            )
 717            .await
 718        })
 719        .detach_and_log_err(cx);
 720        return;
 721    }
 722
 723    let mut task = None;
 724    if !request.open_paths.is_empty() {
 725        let app_state = app_state.clone();
 726        task = Some(cx.spawn(async move |mut cx| {
 727            let paths_with_position =
 728                derive_paths_with_position(app_state.fs.as_ref(), request.open_paths).await;
 729            let (_window, results) = open_paths_with_positions(
 730                &paths_with_position,
 731                app_state,
 732                workspace::OpenOptions::default(),
 733                &mut cx,
 734            )
 735            .await?;
 736            for result in results.into_iter().flatten() {
 737                if let Err(err) = result {
 738                    log::error!("Error opening path: {err}",);
 739                }
 740            }
 741            anyhow::Ok(())
 742        }));
 743    }
 744
 745    if !request.open_channel_notes.is_empty() || request.join_channel.is_some() {
 746        cx.spawn(async move |mut cx| {
 747            let result = maybe!(async {
 748                if let Some(task) = task {
 749                    task.await?;
 750                }
 751                let client = app_state.client.clone();
 752                // we continue even if authentication fails as join_channel/ open channel notes will
 753                // show a visible error message.
 754                match authenticate(client, &cx).await {
 755                    ConnectionResult::Timeout => {
 756                        log::error!("Timeout during open request handling")
 757                    }
 758                    ConnectionResult::ConnectionReset => {
 759                        log::error!("Connection reset during open request handling")
 760                    }
 761                    ConnectionResult::Result(r) => r?,
 762                };
 763
 764                if let Some(channel_id) = request.join_channel {
 765                    cx.update(|cx| {
 766                        workspace::join_channel(
 767                            client::ChannelId(channel_id),
 768                            app_state.clone(),
 769                            None,
 770                            cx,
 771                        )
 772                    })?
 773                    .await?;
 774                }
 775
 776                let workspace_window =
 777                    workspace::get_any_active_workspace(app_state, cx.clone()).await?;
 778                let workspace = workspace_window.entity(cx)?;
 779
 780                let mut promises = Vec::new();
 781                for (channel_id, heading) in request.open_channel_notes {
 782                    promises.push(cx.update_window(workspace_window.into(), |_, window, cx| {
 783                        ChannelView::open(
 784                            client::ChannelId(channel_id),
 785                            heading,
 786                            workspace.clone(),
 787                            window,
 788                            cx,
 789                        )
 790                        .log_err()
 791                    })?)
 792                }
 793                future::join_all(promises).await;
 794                anyhow::Ok(())
 795            })
 796            .await;
 797            if let Err(err) = result {
 798                fail_to_open_window_async(err, &mut cx);
 799            }
 800        })
 801        .detach()
 802    } else if let Some(task) = task {
 803        cx.spawn(async move |mut cx| {
 804            if let Err(err) = task.await {
 805                fail_to_open_window_async(err, &mut cx);
 806            }
 807        })
 808        .detach();
 809    }
 810}
 811
 812async fn authenticate(client: Arc<Client>, cx: &AsyncApp) -> ConnectionResult<()> {
 813    if stdout_is_a_pty() {
 814        if client::IMPERSONATE_LOGIN.is_some() {
 815            return client.authenticate_and_connect(false, cx).await;
 816        } else if client.has_credentials(cx).await {
 817            return client.authenticate_and_connect(true, cx).await;
 818        }
 819    } else if client.has_credentials(cx).await {
 820        return client.authenticate_and_connect(true, cx).await;
 821    }
 822
 823    ConnectionResult::Result(Ok(()))
 824}
 825
 826async fn system_id() -> Result<IdType> {
 827    let key_name = "system_id".to_string();
 828
 829    if let Ok(Some(system_id)) = GLOBAL_KEY_VALUE_STORE.read_kvp(&key_name) {
 830        return Ok(IdType::Existing(system_id));
 831    }
 832
 833    let system_id = Uuid::new_v4().to_string();
 834
 835    GLOBAL_KEY_VALUE_STORE
 836        .write_kvp(key_name, system_id.clone())
 837        .await?;
 838
 839    Ok(IdType::New(system_id))
 840}
 841
 842async fn installation_id() -> Result<IdType> {
 843    let legacy_key_name = "device_id".to_string();
 844    let key_name = "installation_id".to_string();
 845
 846    // Migrate legacy key to new key
 847    if let Ok(Some(installation_id)) = KEY_VALUE_STORE.read_kvp(&legacy_key_name) {
 848        KEY_VALUE_STORE
 849            .write_kvp(key_name, installation_id.clone())
 850            .await?;
 851        KEY_VALUE_STORE.delete_kvp(legacy_key_name).await?;
 852        return Ok(IdType::Existing(installation_id));
 853    }
 854
 855    if let Ok(Some(installation_id)) = KEY_VALUE_STORE.read_kvp(&key_name) {
 856        return Ok(IdType::Existing(installation_id));
 857    }
 858
 859    let installation_id = Uuid::new_v4().to_string();
 860
 861    KEY_VALUE_STORE
 862        .write_kvp(key_name, installation_id.clone())
 863        .await?;
 864
 865    Ok(IdType::New(installation_id))
 866}
 867
 868async fn restore_or_create_workspace(app_state: Arc<AppState>, cx: &mut AsyncApp) -> Result<()> {
 869    if let Some(locations) = restorable_workspace_locations(cx, &app_state).await {
 870        for location in locations {
 871            match location {
 872                SerializedWorkspaceLocation::Local(location, _) => {
 873                    let task = cx.update(|cx| {
 874                        workspace::open_paths(
 875                            location.paths().as_ref(),
 876                            app_state.clone(),
 877                            workspace::OpenOptions::default(),
 878                            cx,
 879                        )
 880                    })?;
 881                    task.await?;
 882                }
 883                SerializedWorkspaceLocation::Ssh(ssh) => {
 884                    let connection_options = cx.update(|cx| {
 885                        SshSettings::get_global(cx)
 886                            .connection_options_for(ssh.host, ssh.port, ssh.user)
 887                    })?;
 888                    let app_state = app_state.clone();
 889                    cx.spawn(async move |cx| {
 890                        recent_projects::open_ssh_project(
 891                            connection_options,
 892                            ssh.paths.into_iter().map(PathBuf::from).collect(),
 893                            app_state,
 894                            workspace::OpenOptions::default(),
 895                            cx,
 896                        )
 897                        .await
 898                        .log_err();
 899                    })
 900                    .detach();
 901                }
 902            }
 903        }
 904    } else if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) {
 905        cx.update(|cx| show_welcome_view(app_state, cx))?.await?;
 906    } else {
 907        cx.update(|cx| {
 908            workspace::open_new(
 909                Default::default(),
 910                app_state,
 911                cx,
 912                |workspace, window, cx| {
 913                    Editor::new_file(workspace, &Default::default(), window, cx)
 914                },
 915            )
 916        })?
 917        .await?;
 918    }
 919
 920    Ok(())
 921}
 922
 923pub(crate) async fn restorable_workspace_locations(
 924    cx: &mut AsyncApp,
 925    app_state: &Arc<AppState>,
 926) -> Option<Vec<SerializedWorkspaceLocation>> {
 927    let mut restore_behavior = cx
 928        .update(|cx| WorkspaceSettings::get(None, cx).restore_on_startup)
 929        .ok()?;
 930
 931    let session_handle = app_state.session.clone();
 932    let (last_session_id, last_session_window_stack) = cx
 933        .update(|cx| {
 934            let session = session_handle.read(cx);
 935
 936            (
 937                session.last_session_id().map(|id| id.to_string()),
 938                session.last_session_window_stack(),
 939            )
 940        })
 941        .ok()?;
 942
 943    if last_session_id.is_none()
 944        && matches!(
 945            restore_behavior,
 946            workspace::RestoreOnStartupBehavior::LastSession
 947        )
 948    {
 949        restore_behavior = workspace::RestoreOnStartupBehavior::LastWorkspace;
 950    }
 951
 952    match restore_behavior {
 953        workspace::RestoreOnStartupBehavior::LastWorkspace => {
 954            workspace::last_opened_workspace_location()
 955                .await
 956                .map(|location| vec![location])
 957        }
 958        workspace::RestoreOnStartupBehavior::LastSession => {
 959            if let Some(last_session_id) = last_session_id {
 960                let ordered = last_session_window_stack.is_some();
 961
 962                let mut locations = workspace::last_session_workspace_locations(
 963                    &last_session_id,
 964                    last_session_window_stack,
 965                )
 966                .filter(|locations| !locations.is_empty());
 967
 968                // Since last_session_window_order returns the windows ordered front-to-back
 969                // we need to open the window that was frontmost last.
 970                if ordered {
 971                    if let Some(locations) = locations.as_mut() {
 972                        locations.reverse();
 973                    }
 974                }
 975
 976                locations
 977            } else {
 978                None
 979            }
 980        }
 981        _ => None,
 982    }
 983}
 984
 985fn init_paths() -> HashMap<io::ErrorKind, Vec<&'static Path>> {
 986    [
 987        paths::config_dir(),
 988        paths::extensions_dir(),
 989        paths::languages_dir(),
 990        paths::database_dir(),
 991        paths::logs_dir(),
 992        paths::temp_dir(),
 993    ]
 994    .into_iter()
 995    .fold(HashMap::default(), |mut errors, path| {
 996        if let Err(e) = std::fs::create_dir_all(path) {
 997            errors.entry(e.kind()).or_insert_with(Vec::new).push(path);
 998        }
 999        errors
1000    })
1001}
1002
1003fn stdout_is_a_pty() -> bool {
1004    std::env::var(FORCE_CLI_MODE_ENV_VAR_NAME).ok().is_none() && io::stdout().is_terminal()
1005}
1006
1007#[derive(Parser, Debug)]
1008#[command(name = "zed", disable_version_flag = true)]
1009struct Args {
1010    /// A sequence of space-separated paths or urls that you want to open.
1011    ///
1012    /// Use `path:line:row` syntax to open a file at a specific location.
1013    /// Non-existing paths and directories will ignore `:line:row` suffix.
1014    ///
1015    /// URLs can either be `file://` or `zed://` scheme, or relative to <https://zed.dev>.
1016    paths_or_urls: Vec<String>,
1017
1018    /// Sets a custom directory for all user data (e.g., database, extensions, logs).
1019    /// This overrides the default platform-specific data directory location.
1020    /// On macOS, the default is `~/Library/Application Support/Zed`.
1021    /// On Linux/FreeBSD, the default is `$XDG_DATA_HOME/zed`.
1022    /// On Windows, the default is `%LOCALAPPDATA%\Zed`.
1023    #[arg(long, value_name = "DIR")]
1024    user_data_dir: Option<String>,
1025
1026    /// Instructs zed to run as a dev server on this machine. (not implemented)
1027    #[arg(long)]
1028    dev_server_token: Option<String>,
1029
1030    /// Prints system specs. Useful for submitting issues on GitHub when encountering a bug
1031    /// that prevents Zed from starting, so you can't run `zed: copy system specs to clipboard`
1032    #[arg(long)]
1033    system_specs: bool,
1034
1035    /// Used for SSH/Git password authentication, to remove the need for netcat as a dependency,
1036    /// by having Zed act like netcat communicating over a Unix socket.
1037    #[arg(long, hide = true)]
1038    askpass: Option<String>,
1039
1040    /// Run zed in the foreground, only used on Windows, to match the behavior of the behavior on macOS.
1041    #[arg(long)]
1042    #[cfg(target_os = "windows")]
1043    #[arg(hide = true)]
1044    foreground: bool,
1045
1046    /// The dock action to perform. This is used on Windows only.
1047    #[arg(long)]
1048    #[cfg(target_os = "windows")]
1049    #[arg(hide = true)]
1050    dock_action: Option<usize>,
1051}
1052
1053#[derive(Clone, Debug)]
1054enum IdType {
1055    New(String),
1056    Existing(String),
1057}
1058
1059impl ToString for IdType {
1060    fn to_string(&self) -> String {
1061        match self {
1062            IdType::New(id) | IdType::Existing(id) => id.clone(),
1063        }
1064    }
1065}
1066
1067fn parse_url_arg(arg: &str, cx: &App) -> Result<String> {
1068    match std::fs::canonicalize(Path::new(&arg)) {
1069        Ok(path) => Ok(format!("file://{}", path.display())),
1070        Err(error) => {
1071            if arg.starts_with("file://")
1072                || arg.starts_with("zed-cli://")
1073                || arg.starts_with("ssh://")
1074                || parse_zed_link(arg, cx).is_some()
1075            {
1076                Ok(arg.into())
1077            } else {
1078                anyhow::bail!("error parsing path argument: {error}")
1079            }
1080        }
1081    }
1082}
1083
1084fn load_embedded_fonts(cx: &App) {
1085    let asset_source = cx.asset_source();
1086    let font_paths = asset_source.list("fonts").unwrap();
1087    let embedded_fonts = Mutex::new(Vec::new());
1088    let executor = cx.background_executor();
1089
1090    executor.block(executor.scoped(|scope| {
1091        for font_path in &font_paths {
1092            if !font_path.ends_with(".ttf") {
1093                continue;
1094            }
1095
1096            scope.spawn(async {
1097                let font_bytes = asset_source.load(font_path).unwrap().unwrap();
1098                embedded_fonts.lock().push(font_bytes);
1099            });
1100        }
1101    }));
1102
1103    cx.text_system()
1104        .add_fonts(embedded_fonts.into_inner())
1105        .unwrap();
1106}
1107
1108/// Eagerly loads the active theme and icon theme based on the selections in the
1109/// theme settings.
1110///
1111/// This fast path exists to load these themes as soon as possible so the user
1112/// doesn't see the default themes while waiting on extensions to load.
1113fn eager_load_active_theme_and_icon_theme(fs: Arc<dyn Fs>, cx: &App) {
1114    let extension_store = ExtensionStore::global(cx);
1115    let theme_registry = ThemeRegistry::global(cx);
1116    let theme_settings = ThemeSettings::get_global(cx);
1117    let appearance = SystemAppearance::global(cx).0;
1118
1119    if let Some(theme_selection) = theme_settings.theme_selection.as_ref() {
1120        let theme_name = theme_selection.theme(appearance);
1121        if matches!(theme_registry.get(theme_name), Err(ThemeNotFoundError(_))) {
1122            if let Some(theme_path) = extension_store.read(cx).path_to_extension_theme(theme_name) {
1123                cx.spawn({
1124                    let theme_registry = theme_registry.clone();
1125                    let fs = fs.clone();
1126                    async move |cx| {
1127                        theme_registry.load_user_theme(&theme_path, fs).await?;
1128
1129                        cx.update(|cx| {
1130                            ThemeSettings::reload_current_theme(cx);
1131                        })
1132                    }
1133                })
1134                .detach_and_log_err(cx);
1135            }
1136        }
1137    }
1138
1139    if let Some(icon_theme_selection) = theme_settings.icon_theme_selection.as_ref() {
1140        let icon_theme_name = icon_theme_selection.icon_theme(appearance);
1141        if matches!(
1142            theme_registry.get_icon_theme(icon_theme_name),
1143            Err(IconThemeNotFoundError(_))
1144        ) {
1145            if let Some((icon_theme_path, icons_root_path)) = extension_store
1146                .read(cx)
1147                .path_to_extension_icon_theme(icon_theme_name)
1148            {
1149                cx.spawn({
1150                    let theme_registry = theme_registry.clone();
1151                    let fs = fs.clone();
1152                    async move |cx| {
1153                        theme_registry
1154                            .load_icon_theme(&icon_theme_path, &icons_root_path, fs)
1155                            .await?;
1156
1157                        cx.update(|cx| {
1158                            ThemeSettings::reload_current_icon_theme(cx);
1159                        })
1160                    }
1161                })
1162                .detach_and_log_err(cx);
1163            }
1164        }
1165    }
1166}
1167
1168/// Spawns a background task to load the user themes from the themes directory.
1169fn load_user_themes_in_background(fs: Arc<dyn fs::Fs>, cx: &mut App) {
1170    cx.spawn({
1171        let fs = fs.clone();
1172        async move |cx| {
1173            if let Some(theme_registry) =
1174                cx.update(|cx| ThemeRegistry::global(cx).clone()).log_err()
1175            {
1176                let themes_dir = paths::themes_dir().as_ref();
1177                match fs
1178                    .metadata(themes_dir)
1179                    .await
1180                    .ok()
1181                    .flatten()
1182                    .map(|m| m.is_dir)
1183                {
1184                    Some(is_dir) => {
1185                        anyhow::ensure!(is_dir, "Themes dir path {themes_dir:?} is not a directory")
1186                    }
1187                    None => {
1188                        fs.create_dir(themes_dir).await.with_context(|| {
1189                            format!("Failed to create themes dir at path {themes_dir:?}")
1190                        })?;
1191                    }
1192                }
1193                theme_registry.load_user_themes(themes_dir, fs).await?;
1194                cx.update(ThemeSettings::reload_current_theme)?;
1195            }
1196            anyhow::Ok(())
1197        }
1198    })
1199    .detach_and_log_err(cx);
1200}
1201
1202/// Spawns a background task to watch the themes directory for changes.
1203fn watch_themes(fs: Arc<dyn fs::Fs>, cx: &mut App) {
1204    use std::time::Duration;
1205    cx.spawn(async move |cx| {
1206        let (mut events, _) = fs
1207            .watch(paths::themes_dir(), Duration::from_millis(100))
1208            .await;
1209
1210        while let Some(paths) = events.next().await {
1211            for event in paths {
1212                if fs.metadata(&event.path).await.ok().flatten().is_some() {
1213                    if let Some(theme_registry) =
1214                        cx.update(|cx| ThemeRegistry::global(cx).clone()).log_err()
1215                    {
1216                        if let Some(()) = theme_registry
1217                            .load_user_theme(&event.path, fs.clone())
1218                            .await
1219                            .log_err()
1220                        {
1221                            cx.update(ThemeSettings::reload_current_theme).log_err();
1222                        }
1223                    }
1224                }
1225            }
1226        }
1227    })
1228    .detach()
1229}
1230
1231#[cfg(debug_assertions)]
1232fn watch_languages(fs: Arc<dyn fs::Fs>, languages: Arc<LanguageRegistry>, cx: &mut App) {
1233    use std::time::Duration;
1234
1235    let path = {
1236        let p = Path::new("crates/languages/src");
1237        let Ok(full_path) = p.canonicalize() else {
1238            return;
1239        };
1240        full_path
1241    };
1242
1243    cx.spawn(async move |_| {
1244        let (mut events, _) = fs.watch(path.as_path(), Duration::from_millis(100)).await;
1245        while let Some(event) = events.next().await {
1246            let has_language_file = event.iter().any(|event| {
1247                event
1248                    .path
1249                    .extension()
1250                    .map(|ext| ext.to_string_lossy().as_ref() == "scm")
1251                    .unwrap_or(false)
1252            });
1253            if has_language_file {
1254                languages.reload();
1255            }
1256        }
1257    })
1258    .detach()
1259}
1260
1261#[cfg(not(debug_assertions))]
1262fn watch_languages(_fs: Arc<dyn fs::Fs>, _languages: Arc<LanguageRegistry>, _cx: &mut App) {}