main.rs

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