main.rs

   1// Allow binary to be called Zed for a nice application menu when running executable directly
   2#![allow(non_snake_case)]
   3// Disable command line from opening on release mode
   4#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
   5
   6mod zed;
   7
   8use anyhow::{anyhow, Context as _, Result};
   9use backtrace::Backtrace;
  10use chrono::Utc;
  11use clap::{command, Parser};
  12use cli::FORCE_CLI_MODE_ENV_VAR_NAME;
  13use client::{
  14    parse_zed_link, telemetry::Telemetry, Client, ClientSettings, DevServerToken, UserStore,
  15};
  16use collab_ui::channel_view::ChannelView;
  17use copilot::Copilot;
  18use copilot_ui::CopilotCompletionProvider;
  19use db::kvp::KEY_VALUE_STORE;
  20use editor::{Editor, EditorMode};
  21use env_logger::Builder;
  22use fs::RealFs;
  23use futures::{future, StreamExt};
  24use gpui::{
  25    App, AppContext, AsyncAppContext, Context, SemanticVersion, Task, ViewContext, VisualContext,
  26};
  27use image_viewer;
  28use isahc::{prelude::Configurable, Request};
  29use language::LanguageRegistry;
  30use log::LevelFilter;
  31
  32use assets::Assets;
  33use mimalloc::MiMalloc;
  34use node_runtime::RealNodeRuntime;
  35use parking_lot::Mutex;
  36use release_channel::{AppCommitSha, ReleaseChannel, RELEASE_CHANNEL};
  37use serde::{Deserialize, Serialize};
  38use settings::{
  39    default_settings, handle_settings_file_changes, watch_config_file, Settings, SettingsStore,
  40};
  41use simplelog::ConfigBuilder;
  42use smol::process::Command;
  43use std::{
  44    env,
  45    ffi::OsStr,
  46    fs::OpenOptions,
  47    io::{IsTerminal, Write},
  48    panic,
  49    path::Path,
  50    sync::{
  51        atomic::{AtomicU32, Ordering},
  52        Arc,
  53    },
  54    thread,
  55};
  56use theme::{ActiveTheme, SystemAppearance, ThemeRegistry, ThemeSettings};
  57use util::{
  58    http::{HttpClient, HttpClientWithUrl},
  59    maybe, parse_env_output,
  60    paths::{self, CRASHES_DIR, CRASHES_RETIRED_DIR},
  61    ResultExt, TryFutureExt,
  62};
  63use uuid::Uuid;
  64use welcome::{show_welcome_view, BaseKeymap, FIRST_OPEN};
  65use workspace::{AppState, WorkspaceStore};
  66use zed::{
  67    app_menus, build_window_options, ensure_only_instance, handle_cli_connection,
  68    handle_keymap_file_changes, initialize_workspace, open_paths_with_positions, IsOnlyInstance,
  69    OpenListener, OpenRequest,
  70};
  71
  72#[global_allocator]
  73static GLOBAL: MiMalloc = MiMalloc;
  74
  75fn fail_to_launch(e: anyhow::Error) {
  76    App::new().run(move |cx| {
  77        let window = cx.open_window(gpui::WindowOptions::default(), |cx| cx.new_view(|_| gpui::Empty));
  78        window.update(cx, |_, cx| {
  79            let response = cx.prompt(gpui::PromptLevel::Critical, "Zed failed to launch", Some(&format!("{}\n\nFor help resolving this, please open an issue on https://github.com/zed-industries/zed", e)), &["Exit"]);
  80
  81            cx.spawn(|_, mut cx| async move {
  82                response.await?;
  83                cx.update(|cx| {
  84                    cx.quit()
  85                })
  86            }).detach_and_log_err(cx);
  87        }).log_err();
  88    })
  89}
  90
  91fn main() {
  92    menu::init();
  93    zed_actions::init();
  94
  95    if let Err(e) = init_paths() {
  96        fail_to_launch(e);
  97        return;
  98    }
  99
 100    init_logger();
 101
 102    if ensure_only_instance() != IsOnlyInstance::Yes {
 103        return;
 104    }
 105
 106    log::info!("========== starting zed ==========");
 107    let app = App::new().with_assets(Assets);
 108
 109    let (installation_id, existing_installation_id_found) = app
 110        .background_executor()
 111        .block(installation_id())
 112        .ok()
 113        .unzip();
 114    let session_id = Uuid::new_v4().to_string();
 115    init_panic_hook(&app, installation_id.clone(), session_id.clone());
 116
 117    let git_binary_path = if option_env!("ZED_BUNDLE").as_deref() == Some("true") {
 118        app.path_for_auxiliary_executable("git")
 119            .context("could not find git binary path")
 120            .log_err()
 121    } else {
 122        None
 123    };
 124    log::info!("Using git binary path: {:?}", git_binary_path);
 125
 126    let fs = Arc::new(RealFs::new(git_binary_path));
 127    let user_settings_file_rx = watch_config_file(
 128        &app.background_executor(),
 129        fs.clone(),
 130        paths::SETTINGS.clone(),
 131    );
 132    let user_keymap_file_rx = watch_config_file(
 133        &app.background_executor(),
 134        fs.clone(),
 135        paths::KEYMAP.clone(),
 136    );
 137
 138    let login_shell_env_loaded = if stdout_is_a_pty() {
 139        Task::ready(())
 140    } else {
 141        app.background_executor().spawn(async {
 142            load_login_shell_environment().await.log_err();
 143        })
 144    };
 145
 146    let (listener, mut open_rx) = OpenListener::new();
 147    let listener = Arc::new(listener);
 148    let open_listener = listener.clone();
 149    app.on_open_urls(move |urls| open_listener.open_urls(urls));
 150    app.on_reopen(move |cx| {
 151        if let Some(app_state) = AppState::try_global(cx).and_then(|app_state| app_state.upgrade())
 152        {
 153            workspace::open_new(app_state, cx, |workspace, cx| {
 154                Editor::new_file(workspace, &Default::default(), cx)
 155            })
 156            .detach();
 157        }
 158    });
 159
 160    app.run(move |cx| {
 161        release_channel::init(env!("CARGO_PKG_VERSION"), cx);
 162        if let Some(build_sha) = option_env!("ZED_COMMIT_SHA") {
 163            AppCommitSha::set_global(AppCommitSha(build_sha.into()), cx);
 164        }
 165
 166        SystemAppearance::init(cx);
 167        OpenListener::set_global(listener.clone(), cx);
 168
 169        load_embedded_fonts(cx);
 170
 171        let mut store = SettingsStore::default();
 172        store
 173            .set_default_settings(default_settings().as_ref(), cx)
 174            .unwrap();
 175        cx.set_global(store);
 176        handle_settings_file_changes(user_settings_file_rx, cx);
 177        handle_keymap_file_changes(user_keymap_file_rx, cx);
 178        client::init_settings(cx);
 179
 180        let clock = Arc::new(clock::RealSystemClock);
 181        let http = Arc::new(HttpClientWithUrl::new(
 182            &client::ClientSettings::get_global(cx).server_url,
 183        ));
 184
 185        let client = client::Client::new(clock, http.clone(), cx);
 186        let mut languages =
 187            LanguageRegistry::new(login_shell_env_loaded, cx.background_executor().clone());
 188        let copilot_language_server_id = languages.next_language_server_id();
 189        languages.set_language_server_download_dir(paths::LANGUAGES_DIR.clone());
 190        let languages = Arc::new(languages);
 191        let node_runtime = RealNodeRuntime::new(http.clone());
 192
 193        language::init(cx);
 194        languages::init(languages.clone(), node_runtime.clone(), cx);
 195        let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
 196        let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
 197
 198        Client::set_global(client.clone(), cx);
 199
 200        zed::init(cx);
 201        theme::init(theme::LoadThemes::All(Box::new(Assets)), cx);
 202        project::Project::init(&client, cx);
 203        client::init(&client, cx);
 204        command_palette::init(cx);
 205        language::init(cx);
 206        editor::init(cx);
 207        image_viewer::init(cx);
 208        diagnostics::init(cx);
 209        copilot::init(
 210            copilot_language_server_id,
 211            http.clone(),
 212            node_runtime.clone(),
 213            cx,
 214        );
 215        assistant::init(client.clone(), cx);
 216        init_inline_completion_provider(client.telemetry().clone(), cx);
 217
 218        extension::init(
 219            fs.clone(),
 220            client.clone(),
 221            node_runtime.clone(),
 222            languages.clone(),
 223            ThemeRegistry::global(cx),
 224            cx,
 225        );
 226
 227        load_user_themes_in_background(fs.clone(), cx);
 228        watch_themes(fs.clone(), cx);
 229
 230        watch_file_types(fs.clone(), cx);
 231
 232        languages.set_theme(cx.theme().clone());
 233
 234        cx.observe_global::<SettingsStore>({
 235            let languages = languages.clone();
 236            let http = http.clone();
 237            let client = client.clone();
 238
 239            move |cx| {
 240                for &mut window in cx.windows().iter_mut() {
 241                    let background_appearance = cx.theme().window_background_appearance();
 242                    window
 243                        .update(cx, |_, cx| {
 244                            cx.set_background_appearance(background_appearance)
 245                        })
 246                        .ok();
 247                }
 248                languages.set_theme(cx.theme().clone());
 249                let new_host = &client::ClientSettings::get_global(cx).server_url;
 250                if &http.base_url() != new_host {
 251                    http.set_base_url(new_host);
 252                    if client.status().borrow().is_connected() {
 253                        client.reconnect(&cx.to_async());
 254                    }
 255                }
 256            }
 257        })
 258        .detach();
 259
 260        let telemetry = client.telemetry();
 261        telemetry.start(installation_id, session_id, cx);
 262        telemetry.report_setting_event("theme", cx.theme().name.to_string());
 263        telemetry.report_setting_event("keymap", BaseKeymap::get_global(cx).to_string());
 264        telemetry.report_app_event(
 265            match existing_installation_id_found {
 266                Some(false) => "first open",
 267                _ => "open",
 268            }
 269            .to_string(),
 270        );
 271        telemetry.flush_events();
 272
 273        let app_state = Arc::new(AppState {
 274            languages: languages.clone(),
 275            client: client.clone(),
 276            user_store: user_store.clone(),
 277            fs: fs.clone(),
 278            build_window_options,
 279            workspace_store,
 280            node_runtime,
 281        });
 282        AppState::set_global(Arc::downgrade(&app_state), cx);
 283
 284        audio::init(Assets, cx);
 285        auto_update::init(http.clone(), cx);
 286
 287        workspace::init(app_state.clone(), cx);
 288        recent_projects::init(cx);
 289
 290        go_to_line::init(cx);
 291        file_finder::init(cx);
 292        tab_switcher::init(cx);
 293        outline::init(cx);
 294        project_symbols::init(cx);
 295        project_panel::init(Assets, cx);
 296        tasks_ui::init(cx);
 297        channel::init(&client, user_store.clone(), cx);
 298        search::init(cx);
 299        vim::init(cx);
 300        terminal_view::init(cx);
 301
 302        journal::init(app_state.clone(), cx);
 303        language_selector::init(cx);
 304        theme_selector::init(cx);
 305        language_tools::init(cx);
 306        call::init(app_state.client.clone(), app_state.user_store.clone(), cx);
 307        notifications::init(app_state.client.clone(), app_state.user_store.clone(), cx);
 308        collab_ui::init(&app_state, cx);
 309        feedback::init(cx);
 310        markdown_preview::init(cx);
 311        welcome::init(cx);
 312        extensions_ui::init(cx);
 313
 314        cx.set_menus(app_menus());
 315        initialize_workspace(app_state.clone(), cx);
 316
 317        // todo(linux): unblock this
 318        upload_panics_and_crashes(http.clone(), cx);
 319
 320        cx.activate(true);
 321
 322        let mut args = Args::parse();
 323        if let Some(dev_server_token) = args.dev_server_token.take() {
 324            let dev_server_token = DevServerToken(dev_server_token);
 325            let server_url = ClientSettings::get_global(&cx).server_url.clone();
 326            let client = client.clone();
 327            client.set_dev_server_token(dev_server_token);
 328            cx.spawn(|cx| async move {
 329                client.authenticate_and_connect(false, &cx).await?;
 330                log::info!("Connected to {}", server_url);
 331                anyhow::Ok(())
 332            })
 333            .detach_and_log_err(cx);
 334        } else {
 335            let urls: Vec<_> = args
 336                .paths_or_urls
 337                .iter()
 338                .filter_map(|arg| parse_url_arg(arg, cx).log_err())
 339                .collect();
 340
 341            if !urls.is_empty() {
 342                listener.open_urls(urls)
 343            }
 344        }
 345
 346        let mut triggered_authentication = false;
 347
 348        match open_rx
 349            .try_next()
 350            .ok()
 351            .flatten()
 352            .and_then(|urls| OpenRequest::parse(urls, cx).log_err())
 353        {
 354            Some(request) => {
 355                triggered_authentication = handle_open_request(request, app_state.clone(), cx)
 356            }
 357            None => cx
 358                .spawn({
 359                    let app_state = app_state.clone();
 360                    |cx| async move { restore_or_create_workspace(app_state, cx).await }
 361                })
 362                .detach(),
 363        }
 364
 365        let app_state = app_state.clone();
 366        cx.spawn(move |cx| async move {
 367            while let Some(urls) = open_rx.next().await {
 368                cx.update(|cx| {
 369                    if let Some(request) = OpenRequest::parse(urls, cx).log_err() {
 370                        handle_open_request(request, app_state.clone(), cx);
 371                    }
 372                })
 373                .ok();
 374            }
 375        })
 376        .detach();
 377
 378        if !triggered_authentication {
 379            cx.spawn(|cx| async move { authenticate(client, &cx).await })
 380                .detach_and_log_err(cx);
 381        }
 382    });
 383}
 384
 385fn handle_open_request(
 386    request: OpenRequest,
 387    app_state: Arc<AppState>,
 388    cx: &mut AppContext,
 389) -> bool {
 390    if let Some(connection) = request.cli_connection {
 391        let app_state = app_state.clone();
 392        cx.spawn(move |cx| handle_cli_connection(connection, app_state, cx))
 393            .detach();
 394        return false;
 395    }
 396
 397    let mut task = None;
 398    if !request.open_paths.is_empty() {
 399        let app_state = app_state.clone();
 400        task = Some(cx.spawn(|mut cx| async move {
 401            let (_window, results) = open_paths_with_positions(
 402                &request.open_paths,
 403                app_state,
 404                workspace::OpenOptions::default(),
 405                &mut cx,
 406            )
 407            .await?;
 408            for result in results.into_iter().flatten() {
 409                if let Err(err) = result {
 410                    log::error!("Error opening path: {err}",);
 411                }
 412            }
 413            anyhow::Ok(())
 414        }));
 415    }
 416
 417    if !request.open_channel_notes.is_empty() || request.join_channel.is_some() {
 418        cx.spawn(|mut cx| async move {
 419            if let Some(task) = task {
 420                task.await?;
 421            }
 422            let client = app_state.client.clone();
 423            // we continue even if authentication fails as join_channel/ open channel notes will
 424            // show a visible error message.
 425            authenticate(client, &cx).await.log_err();
 426
 427            if let Some(channel_id) = request.join_channel {
 428                cx.update(|cx| {
 429                    workspace::join_channel(
 430                        client::ChannelId(channel_id),
 431                        app_state.clone(),
 432                        None,
 433                        cx,
 434                    )
 435                })?
 436                .await?;
 437            }
 438
 439            let workspace_window =
 440                workspace::get_any_active_workspace(app_state, cx.clone()).await?;
 441            let workspace = workspace_window.root_view(&cx)?;
 442
 443            let mut promises = Vec::new();
 444            for (channel_id, heading) in request.open_channel_notes {
 445                promises.push(cx.update_window(workspace_window.into(), |_, cx| {
 446                    ChannelView::open(
 447                        client::ChannelId(channel_id),
 448                        heading,
 449                        workspace.clone(),
 450                        cx,
 451                    )
 452                    .log_err()
 453                })?)
 454            }
 455            future::join_all(promises).await;
 456            anyhow::Ok(())
 457        })
 458        .detach_and_log_err(cx);
 459        true
 460    } else {
 461        if let Some(task) = task {
 462            task.detach_and_log_err(cx)
 463        }
 464        false
 465    }
 466}
 467
 468async fn authenticate(client: Arc<Client>, cx: &AsyncAppContext) -> Result<()> {
 469    if stdout_is_a_pty() {
 470        if client::IMPERSONATE_LOGIN.is_some() {
 471            client.authenticate_and_connect(false, &cx).await?;
 472        }
 473    } else if client.has_keychain_credentials(&cx).await {
 474        client.authenticate_and_connect(true, &cx).await?;
 475    }
 476    Ok::<_, anyhow::Error>(())
 477}
 478
 479async fn installation_id() -> Result<(String, bool)> {
 480    let legacy_key_name = "device_id".to_string();
 481    let key_name = "installation_id".to_string();
 482
 483    // Migrate legacy key to new key
 484    if let Ok(Some(installation_id)) = KEY_VALUE_STORE.read_kvp(&legacy_key_name) {
 485        KEY_VALUE_STORE
 486            .write_kvp(key_name, installation_id.clone())
 487            .await?;
 488        KEY_VALUE_STORE.delete_kvp(legacy_key_name).await?;
 489        return Ok((installation_id, true));
 490    }
 491
 492    if let Ok(Some(installation_id)) = KEY_VALUE_STORE.read_kvp(&key_name) {
 493        return Ok((installation_id, true));
 494    }
 495
 496    let installation_id = Uuid::new_v4().to_string();
 497
 498    KEY_VALUE_STORE
 499        .write_kvp(key_name, installation_id.clone())
 500        .await?;
 501
 502    Ok((installation_id, false))
 503}
 504
 505async fn restore_or_create_workspace(app_state: Arc<AppState>, cx: AsyncAppContext) {
 506    maybe!(async {
 507        if let Some(location) = workspace::last_opened_workspace_paths().await {
 508            cx.update(|cx| {
 509                workspace::open_paths(
 510                    location.paths().as_ref(),
 511                    app_state,
 512                    workspace::OpenOptions::default(),
 513                    cx,
 514                )
 515            })?
 516            .await
 517            .log_err();
 518        } else if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) {
 519            cx.update(|cx| show_welcome_view(app_state, cx)).log_err();
 520        } else {
 521            cx.update(|cx| {
 522                workspace::open_new(app_state, cx, |workspace, cx| {
 523                    Editor::new_file(workspace, &Default::default(), cx)
 524                })
 525                .detach();
 526            })?;
 527        }
 528        anyhow::Ok(())
 529    })
 530    .await
 531    .log_err();
 532}
 533
 534fn init_paths() -> anyhow::Result<()> {
 535    for path in [
 536        &*util::paths::CONFIG_DIR,
 537        &*util::paths::EXTENSIONS_DIR,
 538        &*util::paths::LANGUAGES_DIR,
 539        &*util::paths::DB_DIR,
 540        &*util::paths::LOGS_DIR,
 541        &*util::paths::TEMP_DIR,
 542    ]
 543    .iter()
 544    {
 545        std::fs::create_dir_all(path)
 546            .map_err(|e| anyhow!("Could not create directory {:?}: {}", path, e))?;
 547    }
 548    Ok(())
 549}
 550
 551fn init_logger() {
 552    if stdout_is_a_pty() {
 553        init_stdout_logger();
 554    } else {
 555        let level = LevelFilter::Info;
 556
 557        // Prevent log file from becoming too large.
 558        const KIB: u64 = 1024;
 559        const MIB: u64 = 1024 * KIB;
 560        const MAX_LOG_BYTES: u64 = MIB;
 561        if std::fs::metadata(&*paths::LOG).map_or(false, |metadata| metadata.len() > MAX_LOG_BYTES)
 562        {
 563            let _ = std::fs::rename(&*paths::LOG, &*paths::OLD_LOG);
 564        }
 565
 566        match OpenOptions::new()
 567            .create(true)
 568            .append(true)
 569            .open(&*paths::LOG)
 570        {
 571            Ok(log_file) => {
 572                let config = ConfigBuilder::new()
 573                    .set_time_format_str("%Y-%m-%dT%T%:z")
 574                    .set_time_to_local(true)
 575                    .build();
 576
 577                simplelog::WriteLogger::init(level, config, log_file)
 578                    .expect("could not initialize logger");
 579            }
 580            Err(err) => {
 581                init_stdout_logger();
 582                log::error!(
 583                    "could not open log file, defaulting to stdout logging: {}",
 584                    err
 585                );
 586            }
 587        }
 588    }
 589}
 590
 591fn init_stdout_logger() {
 592    Builder::new()
 593        .parse_default_env()
 594        .format(|buf, record| {
 595            use env_logger::fmt::Color;
 596
 597            let subtle = buf
 598                .style()
 599                .set_color(Color::Black)
 600                .set_intense(true)
 601                .clone();
 602            write!(buf, "{}", subtle.value("["))?;
 603            write!(
 604                buf,
 605                "{} ",
 606                chrono::Local::now().format("%Y-%m-%dT%H:%M:%S%:z")
 607            )?;
 608            write!(buf, "{:<5}", buf.default_styled_level(record.level()))?;
 609            if let Some(path) = record.module_path() {
 610                write!(buf, " {}", path)?;
 611            }
 612            write!(buf, "{}", subtle.value("]"))?;
 613            writeln!(buf, " {}", record.args())
 614        })
 615        .init();
 616}
 617
 618#[derive(Serialize, Deserialize)]
 619struct LocationData {
 620    file: String,
 621    line: u32,
 622}
 623
 624#[derive(Serialize, Deserialize)]
 625struct Panic {
 626    thread: String,
 627    payload: String,
 628    #[serde(skip_serializing_if = "Option::is_none")]
 629    location_data: Option<LocationData>,
 630    backtrace: Vec<String>,
 631    app_version: String,
 632    release_channel: String,
 633    os_name: String,
 634    os_version: Option<String>,
 635    architecture: String,
 636    panicked_on: i64,
 637    #[serde(skip_serializing_if = "Option::is_none")]
 638    installation_id: Option<String>,
 639    session_id: String,
 640}
 641
 642#[derive(Serialize)]
 643struct PanicRequest {
 644    panic: Panic,
 645}
 646
 647static PANIC_COUNT: AtomicU32 = AtomicU32::new(0);
 648
 649fn init_panic_hook(app: &App, installation_id: Option<String>, session_id: String) {
 650    let is_pty = stdout_is_a_pty();
 651    let app_metadata = app.metadata();
 652
 653    panic::set_hook(Box::new(move |info| {
 654        let prior_panic_count = PANIC_COUNT.fetch_add(1, Ordering::SeqCst);
 655        if prior_panic_count > 0 {
 656            // Give the panic-ing thread time to write the panic file
 657            loop {
 658                std::thread::yield_now();
 659            }
 660        }
 661
 662        let thread = thread::current();
 663        let thread_name = thread.name().unwrap_or("<unnamed>");
 664
 665        let payload = info
 666            .payload()
 667            .downcast_ref::<&str>()
 668            .map(|s| s.to_string())
 669            .or_else(|| info.payload().downcast_ref::<String>().map(|s| s.clone()))
 670            .unwrap_or_else(|| "Box<Any>".to_string());
 671
 672        if *release_channel::RELEASE_CHANNEL == ReleaseChannel::Dev {
 673            let location = info.location().unwrap();
 674            let backtrace = Backtrace::new();
 675            eprintln!(
 676                "Thread {:?} panicked with {:?} at {}:{}:{}\n{:?}",
 677                thread_name,
 678                payload,
 679                location.file(),
 680                location.line(),
 681                location.column(),
 682                backtrace,
 683            );
 684            std::process::exit(-1);
 685        }
 686
 687        let app_version = if let Some(version) = app_metadata.app_version {
 688            version.to_string()
 689        } else {
 690            option_env!("CARGO_PKG_VERSION")
 691                .unwrap_or("dev")
 692                .to_string()
 693        };
 694
 695        let backtrace = Backtrace::new();
 696        let mut backtrace = backtrace
 697            .frames()
 698            .iter()
 699            .flat_map(|frame| {
 700                frame
 701                    .symbols()
 702                    .iter()
 703                    .filter_map(|frame| Some(format!("{:#}", frame.name()?)))
 704            })
 705            .collect::<Vec<_>>();
 706
 707        // Strip out leading stack frames for rust panic-handling.
 708        if let Some(ix) = backtrace
 709            .iter()
 710            .position(|name| name == "rust_begin_unwind")
 711        {
 712            backtrace.drain(0..=ix);
 713        }
 714
 715        let panic_data = Panic {
 716            thread: thread_name.into(),
 717            payload,
 718            location_data: info.location().map(|location| LocationData {
 719                file: location.file().into(),
 720                line: location.line(),
 721            }),
 722            app_version: app_version.to_string(),
 723            release_channel: RELEASE_CHANNEL.display_name().into(),
 724            os_name: app_metadata.os_name.into(),
 725            os_version: app_metadata
 726                .os_version
 727                .as_ref()
 728                .map(SemanticVersion::to_string),
 729            architecture: env::consts::ARCH.into(),
 730            panicked_on: Utc::now().timestamp_millis(),
 731            backtrace,
 732            installation_id: installation_id.clone(),
 733            session_id: session_id.clone(),
 734        };
 735
 736        if let Some(panic_data_json) = serde_json::to_string_pretty(&panic_data).log_err() {
 737            log::error!("{}", panic_data_json);
 738        }
 739
 740        if !is_pty {
 741            if let Some(panic_data_json) = serde_json::to_string(&panic_data).log_err() {
 742                let timestamp = chrono::Utc::now().format("%Y_%m_%d %H_%M_%S").to_string();
 743                let panic_file_path = paths::LOGS_DIR.join(format!("zed-{}.panic", timestamp));
 744                let panic_file = std::fs::OpenOptions::new()
 745                    .append(true)
 746                    .create(true)
 747                    .open(&panic_file_path)
 748                    .log_err();
 749                if let Some(mut panic_file) = panic_file {
 750                    writeln!(&mut panic_file, "{}", panic_data_json).log_err();
 751                    panic_file.flush().log_err();
 752                }
 753            }
 754        }
 755
 756        std::process::abort();
 757    }));
 758}
 759
 760fn upload_panics_and_crashes(http: Arc<HttpClientWithUrl>, cx: &mut AppContext) {
 761    let telemetry_settings = *client::TelemetrySettings::get_global(cx);
 762    cx.background_executor()
 763        .spawn(async move {
 764            let most_recent_panic = upload_previous_panics(http.clone(), telemetry_settings)
 765                .await
 766                .log_err()
 767                .flatten();
 768            upload_previous_crashes(http, most_recent_panic, telemetry_settings)
 769                .await
 770                .log_err()
 771        })
 772        .detach()
 773}
 774
 775/// Uploads panics via `zed.dev`.
 776async fn upload_previous_panics(
 777    http: Arc<HttpClientWithUrl>,
 778    telemetry_settings: client::TelemetrySettings,
 779) -> Result<Option<(i64, String)>> {
 780    let panic_report_url = http.build_url("/api/panic");
 781    let mut children = smol::fs::read_dir(&*paths::LOGS_DIR).await?;
 782
 783    let mut most_recent_panic = None;
 784
 785    while let Some(child) = children.next().await {
 786        let child = child?;
 787        let child_path = child.path();
 788
 789        if child_path.extension() != Some(OsStr::new("panic")) {
 790            continue;
 791        }
 792        let filename = if let Some(filename) = child_path.file_name() {
 793            filename.to_string_lossy()
 794        } else {
 795            continue;
 796        };
 797
 798        if !filename.starts_with("zed") {
 799            continue;
 800        }
 801
 802        if telemetry_settings.diagnostics {
 803            let panic_file_content = smol::fs::read_to_string(&child_path)
 804                .await
 805                .context("error reading panic file")?;
 806
 807            let panic: Option<Panic> = serde_json::from_str(&panic_file_content)
 808                .ok()
 809                .or_else(|| {
 810                    panic_file_content
 811                        .lines()
 812                        .next()
 813                        .and_then(|line| serde_json::from_str(line).ok())
 814                })
 815                .unwrap_or_else(|| {
 816                    log::error!("failed to deserialize panic file {:?}", panic_file_content);
 817                    None
 818                });
 819
 820            if let Some(panic) = panic {
 821                most_recent_panic = Some((panic.panicked_on, panic.payload.clone()));
 822
 823                let body = serde_json::to_string(&PanicRequest { panic }).unwrap();
 824
 825                let request = Request::post(&panic_report_url)
 826                    .redirect_policy(isahc::config::RedirectPolicy::Follow)
 827                    .header("Content-Type", "application/json")
 828                    .body(body.into())?;
 829                let response = http.send(request).await.context("error sending panic")?;
 830                if !response.status().is_success() {
 831                    log::error!("Error uploading panic to server: {}", response.status());
 832                }
 833            }
 834        }
 835
 836        // We've done what we can, delete the file
 837        std::fs::remove_file(child_path)
 838            .context("error removing panic")
 839            .log_err();
 840    }
 841    Ok::<_, anyhow::Error>(most_recent_panic)
 842}
 843
 844static LAST_CRASH_UPLOADED: &'static str = "LAST_CRASH_UPLOADED";
 845
 846/// upload crashes from apple's diagnostic reports to our server.
 847/// (only if telemetry is enabled)
 848async fn upload_previous_crashes(
 849    http: Arc<HttpClientWithUrl>,
 850    most_recent_panic: Option<(i64, String)>,
 851    telemetry_settings: client::TelemetrySettings,
 852) -> Result<()> {
 853    if !telemetry_settings.diagnostics {
 854        return Ok(());
 855    }
 856    let last_uploaded = KEY_VALUE_STORE
 857        .read_kvp(LAST_CRASH_UPLOADED)?
 858        .unwrap_or("zed-2024-01-17-221900.ips".to_string()); // don't upload old crash reports from before we had this.
 859    let mut uploaded = last_uploaded.clone();
 860
 861    let crash_report_url = http.build_zed_api_url("/telemetry/crashes", &[])?;
 862
 863    for dir in [&*CRASHES_DIR, &*CRASHES_RETIRED_DIR] {
 864        let mut children = smol::fs::read_dir(&dir).await?;
 865        while let Some(child) = children.next().await {
 866            let child = child?;
 867            let Some(filename) = child
 868                .path()
 869                .file_name()
 870                .map(|f| f.to_string_lossy().to_lowercase())
 871            else {
 872                continue;
 873            };
 874
 875            if !filename.starts_with("zed-") || !filename.ends_with(".ips") {
 876                continue;
 877            }
 878
 879            if filename <= last_uploaded {
 880                continue;
 881            }
 882
 883            let body = smol::fs::read_to_string(&child.path())
 884                .await
 885                .context("error reading crash file")?;
 886
 887            let mut request = Request::post(&crash_report_url.to_string())
 888                .redirect_policy(isahc::config::RedirectPolicy::Follow)
 889                .header("Content-Type", "text/plain");
 890
 891            if let Some((panicked_on, payload)) = most_recent_panic.as_ref() {
 892                request = request
 893                    .header("x-zed-panicked-on", format!("{}", panicked_on))
 894                    .header("x-zed-panic", payload)
 895            }
 896
 897            let request = request.body(body.into())?;
 898
 899            let response = http.send(request).await.context("error sending crash")?;
 900            if !response.status().is_success() {
 901                log::error!("Error uploading crash to server: {}", response.status());
 902            }
 903
 904            if uploaded < filename {
 905                uploaded = filename.clone();
 906                KEY_VALUE_STORE
 907                    .write_kvp(LAST_CRASH_UPLOADED.to_string(), filename)
 908                    .await?;
 909            }
 910        }
 911    }
 912
 913    Ok(())
 914}
 915
 916async fn load_login_shell_environment() -> Result<()> {
 917    let marker = "ZED_LOGIN_SHELL_START";
 918    let shell = env::var("SHELL").context(
 919        "SHELL environment variable is not assigned so we can't source login environment variables",
 920    )?;
 921
 922    // If possible, we want to `cd` in the user's `$HOME` to trigger programs
 923    // such as direnv, asdf, mise, ... to adjust the PATH. These tools often hook
 924    // into shell's `cd` command (and hooks) to manipulate env.
 925    // We do this so that we get the env a user would have when spawning a shell
 926    // in home directory.
 927    let shell_cmd_prefix = std::env::var_os("HOME")
 928        .and_then(|home| home.into_string().ok())
 929        .map(|home| format!("cd '{home}';"));
 930
 931    // The `exit 0` is the result of hours of debugging, trying to find out
 932    // why running this command here, without `exit 0`, would mess
 933    // up signal process for our process so that `ctrl-c` doesn't work
 934    // anymore.
 935    // We still don't know why `$SHELL -l -i -c '/usr/bin/env -0'`  would
 936    // do that, but it does, and `exit 0` helps.
 937    let shell_cmd = format!(
 938        "{}printf '%s' {marker}; /usr/bin/env; exit 0;",
 939        shell_cmd_prefix.as_deref().unwrap_or("")
 940    );
 941
 942    let output = Command::new(&shell)
 943        .args(["-l", "-i", "-c", &shell_cmd])
 944        .output()
 945        .await
 946        .context("failed to spawn login shell to source login environment variables")?;
 947    if !output.status.success() {
 948        Err(anyhow!("login shell exited with error"))?;
 949    }
 950
 951    let stdout = String::from_utf8_lossy(&output.stdout);
 952
 953    if let Some(env_output_start) = stdout.find(marker) {
 954        let env_output = &stdout[env_output_start + marker.len()..];
 955
 956        parse_env_output(env_output, |key, value| env::set_var(key, value));
 957
 958        log::info!(
 959            "set environment variables from shell:{}, path:{}",
 960            shell,
 961            env::var("PATH").unwrap_or_default(),
 962        );
 963    }
 964
 965    Ok(())
 966}
 967
 968fn stdout_is_a_pty() -> bool {
 969    std::env::var(FORCE_CLI_MODE_ENV_VAR_NAME).ok().is_none() && std::io::stdout().is_terminal()
 970}
 971
 972#[derive(Parser, Debug)]
 973#[command(name = "zed", disable_version_flag = true)]
 974struct Args {
 975    /// A sequence of space-separated paths or urls that you want to open.
 976    ///
 977    /// Use `path:line:row` syntax to open a file at a specific location.
 978    /// Non-existing paths and directories will ignore `:line:row` suffix.
 979    ///
 980    /// URLs can either be file:// or zed:// scheme, or relative to https://zed.dev.
 981    paths_or_urls: Vec<String>,
 982
 983    /// Instructs zed to run as a dev server on this machine. (not implemented)
 984    #[arg(long)]
 985    dev_server_token: Option<String>,
 986}
 987
 988fn parse_url_arg(arg: &str, cx: &AppContext) -> Result<String> {
 989    match std::fs::canonicalize(Path::new(&arg)) {
 990        Ok(path) => Ok(format!("file://{}", path.to_string_lossy())),
 991        Err(error) => {
 992            if arg.starts_with("file://") || arg.starts_with("zed-cli://") {
 993                Ok(arg.into())
 994            } else if let Some(_) = parse_zed_link(&arg, cx) {
 995                Ok(arg.into())
 996            } else {
 997                Err(anyhow!("error parsing path argument: {}", error))
 998            }
 999        }
1000    }
1001}
1002
1003fn load_embedded_fonts(cx: &AppContext) {
1004    let asset_source = cx.asset_source();
1005    let font_paths = asset_source.list("fonts").unwrap();
1006    let embedded_fonts = Mutex::new(Vec::new());
1007    let executor = cx.background_executor();
1008
1009    executor.block(executor.scoped(|scope| {
1010        for font_path in &font_paths {
1011            if !font_path.ends_with(".ttf") {
1012                continue;
1013            }
1014
1015            scope.spawn(async {
1016                let font_bytes = asset_source.load(font_path).unwrap();
1017                embedded_fonts.lock().push(font_bytes);
1018            });
1019        }
1020    }));
1021
1022    cx.text_system()
1023        .add_fonts(embedded_fonts.into_inner())
1024        .unwrap();
1025}
1026
1027/// Spawns a background task to load the user themes from the themes directory.
1028fn load_user_themes_in_background(fs: Arc<dyn fs::Fs>, cx: &mut AppContext) {
1029    cx.spawn({
1030        let fs = fs.clone();
1031        |cx| async move {
1032            if let Some(theme_registry) =
1033                cx.update(|cx| ThemeRegistry::global(cx).clone()).log_err()
1034            {
1035                let themes_dir = paths::THEMES_DIR.as_ref();
1036                match fs
1037                    .metadata(themes_dir)
1038                    .await
1039                    .ok()
1040                    .flatten()
1041                    .map(|m| m.is_dir)
1042                {
1043                    Some(is_dir) => {
1044                        anyhow::ensure!(is_dir, "Themes dir path {themes_dir:?} is not a directory")
1045                    }
1046                    None => {
1047                        fs.create_dir(themes_dir).await.with_context(|| {
1048                            format!("Failed to create themes dir at path {themes_dir:?}")
1049                        })?;
1050                    }
1051                }
1052                theme_registry.load_user_themes(themes_dir, fs).await?;
1053                cx.update(|cx| ThemeSettings::reload_current_theme(cx))?;
1054            }
1055            anyhow::Ok(())
1056        }
1057    })
1058    .detach_and_log_err(cx);
1059}
1060
1061/// Spawns a background task to watch the themes directory for changes.
1062fn watch_themes(fs: Arc<dyn fs::Fs>, cx: &mut AppContext) {
1063    use std::time::Duration;
1064    cx.spawn(|cx| async move {
1065        let mut events = fs
1066            .watch(&paths::THEMES_DIR.clone(), Duration::from_millis(100))
1067            .await;
1068
1069        while let Some(paths) = events.next().await {
1070            for path in paths {
1071                if fs.metadata(&path).await.ok().flatten().is_some() {
1072                    if let Some(theme_registry) =
1073                        cx.update(|cx| ThemeRegistry::global(cx).clone()).log_err()
1074                    {
1075                        if let Some(()) = theme_registry
1076                            .load_user_theme(&path, fs.clone())
1077                            .await
1078                            .log_err()
1079                        {
1080                            cx.update(|cx| ThemeSettings::reload_current_theme(cx))
1081                                .log_err();
1082                        }
1083                    }
1084                }
1085            }
1086        }
1087    })
1088    .detach()
1089}
1090
1091#[cfg(debug_assertions)]
1092fn watch_file_types(fs: Arc<dyn fs::Fs>, cx: &mut AppContext) {
1093    use std::time::Duration;
1094
1095    use gpui::BorrowAppContext;
1096
1097    let path = {
1098        let p = Path::new("assets/icons/file_icons/file_types.json");
1099        let Ok(full_path) = p.canonicalize() else {
1100            return;
1101        };
1102        full_path
1103    };
1104
1105    cx.spawn(|cx| async move {
1106        let mut events = fs.watch(path.as_path(), Duration::from_millis(100)).await;
1107        while (events.next().await).is_some() {
1108            cx.update(|cx| {
1109                cx.update_global(|file_types, _| {
1110                    *file_types = file_icons::FileIcons::new(Assets);
1111                });
1112            })
1113            .ok();
1114        }
1115    })
1116    .detach()
1117}
1118
1119#[cfg(not(debug_assertions))]
1120fn watch_file_types(_fs: Arc<dyn fs::Fs>, _cx: &mut AppContext) {}
1121
1122fn init_inline_completion_provider(telemetry: Arc<Telemetry>, cx: &mut AppContext) {
1123    if let Some(copilot) = Copilot::global(cx) {
1124        cx.observe_new_views(move |editor: &mut Editor, cx: &mut ViewContext<Editor>| {
1125            if editor.mode() == EditorMode::Full {
1126                // We renamed some of these actions to not be copilot-specific, but that
1127                // would have not been backwards-compatible. So here we are re-registering
1128                // the actions with the old names to not break people's keymaps.
1129                editor
1130                    .register_action(cx.listener(
1131                        |editor, _: &copilot::Suggest, cx: &mut ViewContext<Editor>| {
1132                            editor.show_inline_completion(&Default::default(), cx);
1133                        },
1134                    ))
1135                    .register_action(cx.listener(
1136                        |editor, _: &copilot::NextSuggestion, cx: &mut ViewContext<Editor>| {
1137                            editor.next_inline_completion(&Default::default(), cx);
1138                        },
1139                    ))
1140                    .register_action(cx.listener(
1141                        |editor, _: &copilot::PreviousSuggestion, cx: &mut ViewContext<Editor>| {
1142                            editor.previous_inline_completion(&Default::default(), cx);
1143                        },
1144                    ))
1145                    .register_action(cx.listener(
1146                        |editor,
1147                         _: &editor::actions::AcceptPartialCopilotSuggestion,
1148                         cx: &mut ViewContext<Editor>| {
1149                            editor.accept_partial_inline_completion(&Default::default(), cx);
1150                        },
1151                    ));
1152
1153                let provider = cx.new_model(|_| {
1154                    CopilotCompletionProvider::new(copilot.clone())
1155                        .with_telemetry(telemetry.clone())
1156                });
1157                editor.set_inline_completion_provider(provider, cx)
1158            }
1159        })
1160        .detach();
1161    }
1162}