main.rs

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