zed2.rs

   1#![allow(unused_variables, unused_mut)]
   2//todo!()
   3
   4mod app_menus;
   5mod assets;
   6pub mod languages;
   7mod only_instance;
   8mod open_listener;
   9
  10pub use app_menus::*;
  11pub use assets::*;
  12use assistant::AssistantPanel;
  13use breadcrumbs::Breadcrumbs;
  14use collections::VecDeque;
  15use editor::{Editor, MultiBuffer};
  16use gpui::{
  17    actions, point, px, AppContext, Context, FocusableView, PromptLevel, TitlebarOptions, View,
  18    ViewContext, VisualContext, WindowBounds, WindowKind, WindowOptions,
  19};
  20pub use only_instance::*;
  21pub use open_listener::*;
  22
  23use anyhow::{anyhow, Context as _};
  24use futures::{channel::mpsc, StreamExt};
  25use project_panel::ProjectPanel;
  26use quick_action_bar::QuickActionBar;
  27use search::project_search::ProjectSearchBar;
  28use settings::{initial_local_settings_content, load_default_keymap, KeymapFile, Settings};
  29use std::{borrow::Cow, ops::Deref, sync::Arc};
  30use terminal_view::terminal_panel::TerminalPanel;
  31use util::{
  32    asset_str,
  33    channel::{AppCommitSha, ReleaseChannel},
  34    paths::{self, LOCAL_SETTINGS_RELATIVE_PATH},
  35    ResultExt,
  36};
  37use uuid::Uuid;
  38use workspace::Pane;
  39use workspace::{
  40    create_and_open_local_file, dock::PanelHandle,
  41    notifications::simple_message_notification::MessageNotification, open_new, AppState, NewFile,
  42    NewWindow, Workspace, WorkspaceSettings,
  43};
  44use zed_actions::{OpenBrowser, OpenSettings, OpenZedURL, Quit};
  45
  46actions!(
  47    zed,
  48    [
  49        About,
  50        DebugElements,
  51        DecreaseBufferFontSize,
  52        Hide,
  53        HideOthers,
  54        IncreaseBufferFontSize,
  55        Minimize,
  56        OpenDefaultKeymap,
  57        OpenDefaultSettings,
  58        OpenKeymap,
  59        OpenLicenses,
  60        OpenLocalSettings,
  61        OpenLog,
  62        OpenTelemetryLog,
  63        ResetBufferFontSize,
  64        ResetDatabase,
  65        ShowAll,
  66        ToggleFullScreen,
  67        Zoom,
  68    ]
  69);
  70
  71pub fn build_window_options(
  72    bounds: Option<WindowBounds>,
  73    display_uuid: Option<Uuid>,
  74    cx: &mut AppContext,
  75) -> WindowOptions {
  76    let bounds = bounds.unwrap_or(WindowBounds::Maximized);
  77    let display = display_uuid.and_then(|uuid| {
  78        cx.displays()
  79            .into_iter()
  80            .find(|display| display.uuid().ok() == Some(uuid))
  81    });
  82
  83    WindowOptions {
  84        bounds,
  85        titlebar: Some(TitlebarOptions {
  86            title: None,
  87            appears_transparent: true,
  88            traffic_light_position: Some(point(px(8.), px(8.))),
  89        }),
  90        center: false,
  91        focus: false,
  92        show: false,
  93        kind: WindowKind::Normal,
  94        is_movable: true,
  95        display_id: display.map(|display| display.id()),
  96    }
  97}
  98
  99pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
 100    cx.observe_new_views(move |workspace: &mut Workspace, cx| {
 101        let workspace_handle = cx.view().clone();
 102        let center_pane = workspace.active_pane().clone();
 103        initialize_pane(workspace, &center_pane, cx);
 104        cx.subscribe(&workspace_handle, {
 105            move |workspace, _, event, cx| {
 106                if let workspace::Event::PaneAdded(pane) = event {
 107                    initialize_pane(workspace, pane, cx);
 108                }
 109            }
 110        })
 111        .detach();
 112
 113        //     cx.emit(workspace2::Event::PaneAdded(
 114        //         workspace.active_pane().clone(),
 115        //     ));
 116
 117        //     let collab_titlebar_item =
 118        //         cx.add_view(|cx| CollabTitlebarItem::new(workspace, &workspace_handle, cx));
 119        //     workspace.set_titlebar_item(collab_titlebar_item.into_any(), cx);
 120
 121        let copilot =
 122            cx.build_view(|cx| copilot_button::CopilotButton::new(app_state.fs.clone(), cx));
 123        let diagnostic_summary =
 124            cx.build_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace, cx));
 125        let activity_indicator =
 126            activity_indicator::ActivityIndicator::new(workspace, app_state.languages.clone(), cx);
 127        let active_buffer_language =
 128            cx.build_view(|_| language_selector::ActiveBufferLanguage::new(workspace));
 129        let vim_mode_indicator = cx.build_view(|cx| vim::ModeIndicator::new(cx));
 130        let feedback_button = cx
 131            .build_view(|_| feedback::deploy_feedback_button::DeployFeedbackButton::new(workspace));
 132        let cursor_position = cx.build_view(|_| editor::items::CursorPosition::new());
 133        workspace.status_bar().update(cx, |status_bar, cx| {
 134            status_bar.add_left_item(diagnostic_summary, cx);
 135            status_bar.add_left_item(activity_indicator, cx);
 136            status_bar.add_right_item(feedback_button, cx);
 137            // status_bar.add_right_item(copilot, cx);
 138            status_bar.add_right_item(copilot, cx);
 139            status_bar.add_right_item(active_buffer_language, cx);
 140            status_bar.add_right_item(vim_mode_indicator, cx);
 141            status_bar.add_right_item(cursor_position, cx);
 142        });
 143
 144        auto_update::notify_of_any_new_update(cx);
 145
 146        vim::observe_keystrokes(cx);
 147
 148        let handle = cx.view().downgrade();
 149        cx.on_window_should_close(move |cx| {
 150            handle
 151                .update(cx, |workspace, cx| {
 152                    workspace.close_window(&Default::default(), cx);
 153                    false
 154                })
 155                .unwrap_or(true)
 156        });
 157
 158        cx.spawn(|workspace_handle, mut cx| async move {
 159            let project_panel = ProjectPanel::load(workspace_handle.clone(), cx.clone());
 160            let terminal_panel = TerminalPanel::load(workspace_handle.clone(), cx.clone());
 161            let assistant_panel = AssistantPanel::load(workspace_handle.clone(), cx.clone());
 162            let channels_panel =
 163                collab_ui::collab_panel::CollabPanel::load(workspace_handle.clone(), cx.clone());
 164            let chat_panel =
 165                collab_ui::chat_panel::ChatPanel::load(workspace_handle.clone(), cx.clone());
 166            let notification_panel = collab_ui::notification_panel::NotificationPanel::load(
 167                workspace_handle.clone(),
 168                cx.clone(),
 169            );
 170            let (
 171                project_panel,
 172                terminal_panel,
 173                assistant_panel,
 174                channels_panel,
 175                chat_panel,
 176                notification_panel,
 177            ) = futures::try_join!(
 178                project_panel,
 179                terminal_panel,
 180                assistant_panel,
 181                channels_panel,
 182                chat_panel,
 183                notification_panel,
 184            )?;
 185
 186            workspace_handle.update(&mut cx, |workspace, cx| {
 187                let project_panel_position = project_panel.position(cx);
 188                workspace.add_panel(project_panel, cx);
 189                workspace.add_panel(terminal_panel, cx);
 190                workspace.add_panel(assistant_panel, cx);
 191                workspace.add_panel(channels_panel, cx);
 192                workspace.add_panel(chat_panel, cx);
 193                workspace.add_panel(notification_panel, cx);
 194
 195                // if !was_deserialized
 196                //     && workspace
 197                //         .project()
 198                //         .read(cx)
 199                //         .visible_worktrees(cx)
 200                //         .any(|tree| {
 201                //             tree.read(cx)
 202                //                 .root_entry()
 203                //                 .map_or(false, |entry| entry.is_dir())
 204                //         })
 205                // {
 206                //     workspace.toggle_dock(project_panel_position, cx);
 207                // }
 208                cx.focus_self();
 209            })
 210        })
 211        .detach();
 212
 213        workspace
 214            .register_action(about)
 215            .register_action(|_, _: &Hide, cx| {
 216                cx.hide();
 217            })
 218            .register_action(|_, _: &HideOthers, cx| {
 219                cx.hide_other_apps();
 220            })
 221            .register_action(|_, _: &ShowAll, cx| {
 222                cx.unhide_other_apps();
 223            })
 224            .register_action(|_, _: &Minimize, cx| {
 225                cx.minimize_window();
 226            })
 227            .register_action(|_, _: &Zoom, cx| {
 228                cx.zoom_window();
 229            })
 230            .register_action(|_, _: &ToggleFullScreen, cx| {
 231                cx.toggle_full_screen();
 232            })
 233            .register_action(quit)
 234            .register_action(|_, action: &OpenZedURL, cx| {
 235                cx.global::<Arc<OpenListener>>()
 236                    .open_urls(&[action.url.clone()])
 237            })
 238            .register_action(|_, action: &OpenBrowser, cx| cx.open_url(&action.url))
 239            .register_action(move |_, _: &IncreaseBufferFontSize, cx| {
 240                theme::adjust_font_size(cx, |size| *size += px(1.0))
 241            })
 242            .register_action(move |_, _: &DecreaseBufferFontSize, cx| {
 243                theme::adjust_font_size(cx, |size| *size -= px(1.0))
 244            })
 245            .register_action(move |_, _: &ResetBufferFontSize, cx| theme::reset_font_size(cx))
 246            .register_action(|_, _: &install_cli::Install, cx| {
 247                cx.spawn(|_, cx| async move {
 248                    install_cli::install_cli(cx.deref())
 249                        .await
 250                        .context("error creating CLI symlink")
 251                })
 252                .detach_and_log_err(cx);
 253            })
 254            .register_action(|workspace, _: &OpenLog, cx| {
 255                open_log_file(workspace, cx);
 256            })
 257            .register_action(|workspace, _: &OpenLicenses, cx| {
 258                open_bundled_file(
 259                    workspace,
 260                    asset_str::<Assets>("licenses.md"),
 261                    "Open Source License Attribution",
 262                    "Markdown",
 263                    cx,
 264                );
 265            })
 266            .register_action(
 267                move |workspace: &mut Workspace,
 268                      _: &OpenTelemetryLog,
 269                      cx: &mut ViewContext<Workspace>| {
 270                    open_telemetry_log_file(workspace, cx);
 271                },
 272            )
 273            .register_action(
 274                move |_: &mut Workspace, _: &OpenKeymap, cx: &mut ViewContext<Workspace>| {
 275                    create_and_open_local_file(&paths::KEYMAP, cx, Default::default)
 276                        .detach_and_log_err(cx);
 277                },
 278            )
 279            .register_action(
 280                move |_: &mut Workspace, _: &OpenSettings, cx: &mut ViewContext<Workspace>| {
 281                    create_and_open_local_file(&paths::SETTINGS, cx, || {
 282                        settings::initial_user_settings_content().as_ref().into()
 283                    })
 284                    .detach_and_log_err(cx);
 285                },
 286            )
 287            .register_action(open_local_settings_file)
 288            .register_action(
 289                move |workspace: &mut Workspace,
 290                      _: &OpenDefaultKeymap,
 291                      cx: &mut ViewContext<Workspace>| {
 292                    open_bundled_file(
 293                        workspace,
 294                        settings::default_keymap(),
 295                        "Default Key Bindings",
 296                        "JSON",
 297                        cx,
 298                    );
 299                },
 300            )
 301            .register_action(
 302                move |workspace: &mut Workspace,
 303                      _: &OpenDefaultSettings,
 304                      cx: &mut ViewContext<Workspace>| {
 305                    open_bundled_file(
 306                        workspace,
 307                        settings::default_settings(),
 308                        "Default Settings",
 309                        "JSON",
 310                        cx,
 311                    );
 312                },
 313            )
 314            //todo!()
 315            // cx.add_action({
 316            //     move |workspace: &mut Workspace, _: &DebugElements, cx: &mut ViewContext<Workspace>| {
 317            //         let app_state = workspace.app_state().clone();
 318            //         let markdown = app_state.languages.language_for_name("JSON");
 319            //         let window = cx.window();
 320            //         cx.spawn(|workspace, mut cx| async move {
 321            //             let markdown = markdown.await.log_err();
 322            //             let content = to_string_pretty(&window.debug_elements(&cx).ok_or_else(|| {
 323            //                 anyhow!("could not debug elements for window {}", window.id())
 324            //             })?)
 325            //             .unwrap();
 326            //             workspace
 327            //                 .update(&mut cx, |workspace, cx| {
 328            //                     workspace.with_local_workspace(cx, move |workspace, cx| {
 329            //                         let project = workspace.project().clone();
 330            //                         let buffer = project
 331            //                             .update(cx, |project, cx| {
 332            //                                 project.create_buffer(&content, markdown, cx)
 333            //                             })
 334            //                             .expect("creating buffers on a local workspace always succeeds");
 335            //                         let buffer = cx.add_model(|cx| {
 336            //                             MultiBuffer::singleton(buffer, cx)
 337            //                                 .with_title("Debug Elements".into())
 338            //                         });
 339            //                         workspace.add_item(
 340            //                             Box::new(cx.add_view(|cx| {
 341            //                                 Editor::for_multibuffer(buffer, Some(project.clone()), cx)
 342            //                             })),
 343            //                             cx,
 344            //                         );
 345            //                     })
 346            //                 })?
 347            //                 .await
 348            //         })
 349            //         .detach_and_log_err(cx);
 350            //     }
 351            // });
 352            // .register_action(
 353            //     |workspace: &mut Workspace,
 354            //      _: &project_panel::ToggleFocus,
 355            //      cx: &mut ViewContext<Workspace>| {
 356            //         workspace.toggle_panel_focus::<ProjectPanel>(cx);
 357            //     },
 358            // );
 359            // cx.add_action(
 360            //     |workspace: &mut Workspace,
 361            //      _: &collab_ui::collab_panel::ToggleFocus,
 362            //      cx: &mut ViewContext<Workspace>| {
 363            //         workspace.toggle_panel_focus::<collab_ui::collab_panel::CollabPanel>(cx);
 364            //     },
 365            // );
 366            // cx.add_action(
 367            //     |workspace: &mut Workspace,
 368            //      _: &collab_ui::chat_panel::ToggleFocus,
 369            //      cx: &mut ViewContext<Workspace>| {
 370            //         workspace.toggle_panel_focus::<collab_ui::chat_panel::ChatPanel>(cx);
 371            //     },
 372            // );
 373            // cx.add_action(
 374            //     |workspace: &mut Workspace,
 375            //      _: &collab_ui::notification_panel::ToggleFocus,
 376            //      cx: &mut ViewContext<Workspace>| {
 377            //         workspace.toggle_panel_focus::<collab_ui::notification_panel::NotificationPanel>(cx);
 378            //     },
 379            // );
 380            // cx.add_action(
 381            //     |workspace: &mut Workspace,
 382            //      _: &terminal_panel::ToggleFocus,
 383            //      cx: &mut ViewContext<Workspace>| {
 384            //         workspace.toggle_panel_focus::<TerminalPanel>(cx);
 385            //     },
 386            // );
 387            .register_action({
 388                let app_state = Arc::downgrade(&app_state);
 389                move |_, _: &NewWindow, cx| {
 390                    if let Some(app_state) = app_state.upgrade() {
 391                        open_new(&app_state, cx, |workspace, cx| {
 392                            Editor::new_file(workspace, &Default::default(), cx)
 393                        })
 394                        .detach();
 395                    }
 396                }
 397            })
 398            .register_action({
 399                let app_state = Arc::downgrade(&app_state);
 400                move |_, _: &NewFile, cx| {
 401                    if let Some(app_state) = app_state.upgrade() {
 402                        open_new(&app_state, cx, |workspace, cx| {
 403                            Editor::new_file(workspace, &Default::default(), cx)
 404                        })
 405                        .detach();
 406                    }
 407                }
 408            });
 409
 410        workspace.focus_handle(cx).focus(cx);
 411        //todo!()
 412        // load_default_keymap(cx);
 413    })
 414    .detach();
 415}
 416
 417fn initialize_pane(workspace: &mut Workspace, pane: &View<Pane>, cx: &mut ViewContext<Workspace>) {
 418    pane.update(cx, |pane, cx| {
 419        pane.toolbar().update(cx, |toolbar, cx| {
 420            let breadcrumbs = cx.build_view(|_| Breadcrumbs::new());
 421            toolbar.add_item(breadcrumbs, cx);
 422            let buffer_search_bar = cx.build_view(search::BufferSearchBar::new);
 423            toolbar.add_item(buffer_search_bar.clone(), cx);
 424
 425            let quick_action_bar =
 426                cx.build_view(|_| QuickActionBar::new(buffer_search_bar, workspace));
 427            toolbar.add_item(quick_action_bar, cx);
 428            let diagnostic_editor_controls = cx.build_view(|_| diagnostics::ToolbarControls::new());
 429            toolbar.add_item(diagnostic_editor_controls, cx);
 430            let project_search_bar = cx.build_view(|_| ProjectSearchBar::new());
 431            toolbar.add_item(project_search_bar, cx);
 432            //     let lsp_log_item =
 433            //         cx.add_view(|_| language_tools::LspLogToolbarItemView::new());
 434            //     toolbar.add_item(lsp_log_item, cx);
 435            //     let syntax_tree_item = cx
 436            //         .add_view(|_| language_tools::SyntaxTreeToolbarItemView::new());
 437            //     toolbar.add_item(syntax_tree_item, cx);
 438        })
 439    });
 440}
 441
 442fn about(_: &mut Workspace, _: &About, cx: &mut gpui::ViewContext<Workspace>) {
 443    use std::fmt::Write as _;
 444
 445    let app_name = cx.global::<ReleaseChannel>().display_name();
 446    let version = env!("CARGO_PKG_VERSION");
 447    let mut message = format!("{app_name} {version}");
 448    if let Some(sha) = cx.try_global::<AppCommitSha>() {
 449        write!(&mut message, "\n\n{}", sha.0).unwrap();
 450    }
 451
 452    let prompt = cx.prompt(PromptLevel::Info, &message, &["OK"]);
 453    cx.foreground_executor()
 454        .spawn(async {
 455            prompt.await.ok();
 456        })
 457        .detach();
 458}
 459
 460fn quit(_: &mut Workspace, _: &Quit, cx: &mut gpui::ViewContext<Workspace>) {
 461    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
 462    cx.spawn(|_, mut cx| async move {
 463        let mut workspace_windows = cx.update(|_, cx| {
 464            cx.windows()
 465                .into_iter()
 466                .filter_map(|window| window.downcast::<Workspace>())
 467                .collect::<Vec<_>>()
 468        })?;
 469
 470        // If multiple windows have unsaved changes, and need a save prompt,
 471        // prompt in the active window before switching to a different window.
 472        cx.update(|_, cx| {
 473            workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
 474        })
 475        .log_err();
 476
 477        if let (true, Some(window)) = (should_confirm, workspace_windows.first().copied()) {
 478            let answer = cx
 479                .update(|_, cx| {
 480                    cx.prompt(
 481                        PromptLevel::Info,
 482                        "Are you sure you want to quit?",
 483                        &["Quit", "Cancel"],
 484                    )
 485                })
 486                .log_err();
 487
 488            if let Some(mut answer) = answer {
 489                let answer = answer.await.ok();
 490                if answer != Some(0) {
 491                    return Ok(());
 492                }
 493            }
 494        }
 495
 496        // If the user cancels any save prompt, then keep the app open.
 497        for window in workspace_windows {
 498            if let Some(should_close) = window
 499                .update(&mut cx, |workspace, cx| {
 500                    workspace.prepare_to_close(true, cx)
 501                })
 502                .log_err()
 503            {
 504                if !should_close.await? {
 505                    return Ok(());
 506                }
 507            }
 508        }
 509        cx.update(|_, cx| {
 510            cx.quit();
 511        })?;
 512        anyhow::Ok(())
 513    })
 514    .detach_and_log_err(cx);
 515}
 516
 517fn open_log_file(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
 518    const MAX_LINES: usize = 1000;
 519    workspace
 520        .with_local_workspace(cx, move |workspace, cx| {
 521            let fs = workspace.app_state().fs.clone();
 522            cx.spawn(|workspace, mut cx| async move {
 523                let (old_log, new_log) =
 524                    futures::join!(fs.load(&paths::OLD_LOG), fs.load(&paths::LOG));
 525
 526                let mut lines = VecDeque::with_capacity(MAX_LINES);
 527                for line in old_log
 528                    .iter()
 529                    .flat_map(|log| log.lines())
 530                    .chain(new_log.iter().flat_map(|log| log.lines()))
 531                {
 532                    if lines.len() == MAX_LINES {
 533                        lines.pop_front();
 534                    }
 535                    lines.push_back(line);
 536                }
 537                let log = lines
 538                    .into_iter()
 539                    .flat_map(|line| [line, "\n"])
 540                    .collect::<String>();
 541
 542                workspace
 543                    .update(&mut cx, |workspace, cx| {
 544                        let project = workspace.project().clone();
 545                        let buffer = project
 546                            .update(cx, |project, cx| project.create_buffer("", None, cx))
 547                            .expect("creating buffers on a local workspace always succeeds");
 548                        buffer.update(cx, |buffer, cx| buffer.edit([(0..0, log)], None, cx));
 549
 550                        let buffer = cx.build_model(|cx| {
 551                            MultiBuffer::singleton(buffer, cx).with_title("Log".into())
 552                        });
 553                        workspace.add_item(
 554                            Box::new(cx.build_view(|cx| {
 555                                Editor::for_multibuffer(buffer, Some(project), cx)
 556                            })),
 557                            cx,
 558                        );
 559                    })
 560                    .log_err();
 561            })
 562            .detach();
 563        })
 564        .detach();
 565}
 566
 567pub fn handle_keymap_file_changes(
 568    mut user_keymap_file_rx: mpsc::UnboundedReceiver<String>,
 569    cx: &mut AppContext,
 570) {
 571    cx.spawn(move |cx| async move {
 572        //  let mut settings_subscription = None;
 573        while let Some(user_keymap_content) = user_keymap_file_rx.next().await {
 574            if let Some(keymap_content) = KeymapFile::parse(&user_keymap_content).log_err() {
 575                cx.update(|cx| reload_keymaps(cx, &keymap_content)).ok();
 576
 577                // todo!()
 578                // let mut old_base_keymap = cx.read(|cx| *settings::get::<BaseKeymap>(cx));
 579                // drop(settings_subscription);
 580                // settings_subscription = Some(cx.update(|cx| {
 581                //     cx.observe_global::<SettingsStore, _>(move |cx| {
 582                //         let new_base_keymap = *settings::get::<BaseKeymap>(cx);
 583                //         if new_base_keymap != old_base_keymap {
 584                //             old_base_keymap = new_base_keymap.clone();
 585                //             reload_keymaps(cx, &keymap_content);
 586                //         }
 587                //     })
 588                // }));
 589            }
 590        }
 591    })
 592    .detach();
 593}
 594
 595fn reload_keymaps(cx: &mut AppContext, keymap_content: &KeymapFile) {
 596    // todo!()
 597    // cx.clear_bindings();
 598    load_default_keymap(cx);
 599    keymap_content.clone().add_to_cx(cx).log_err();
 600    cx.set_menus(app_menus());
 601}
 602
 603fn open_local_settings_file(
 604    workspace: &mut Workspace,
 605    _: &OpenLocalSettings,
 606    cx: &mut ViewContext<Workspace>,
 607) {
 608    let project = workspace.project().clone();
 609    let worktree = project
 610        .read(cx)
 611        .visible_worktrees(cx)
 612        .find_map(|tree| tree.read(cx).root_entry()?.is_dir().then_some(tree));
 613    if let Some(worktree) = worktree {
 614        let tree_id = worktree.read(cx).id();
 615        cx.spawn(|workspace, mut cx| async move {
 616            let file_path = &*LOCAL_SETTINGS_RELATIVE_PATH;
 617
 618            if let Some(dir_path) = file_path.parent() {
 619                if worktree.update(&mut cx, |tree, _| tree.entry_for_path(dir_path).is_none())? {
 620                    project
 621                        .update(&mut cx, |project, cx| {
 622                            project.create_entry((tree_id, dir_path), true, cx)
 623                        })?
 624                        .await
 625                        .context("worktree was removed")?;
 626                }
 627            }
 628
 629            if worktree.update(&mut cx, |tree, _| tree.entry_for_path(file_path).is_none())? {
 630                project
 631                    .update(&mut cx, |project, cx| {
 632                        project.create_entry((tree_id, file_path), false, cx)
 633                    })?
 634                    .await
 635                    .context("worktree was removed")?;
 636            }
 637
 638            let editor = workspace
 639                .update(&mut cx, |workspace, cx| {
 640                    workspace.open_path((tree_id, file_path), None, true, cx)
 641                })?
 642                .await?
 643                .downcast::<Editor>()
 644                .ok_or_else(|| anyhow!("unexpected item type"))?;
 645
 646            editor
 647                .downgrade()
 648                .update(&mut cx, |editor, cx| {
 649                    if let Some(buffer) = editor.buffer().read(cx).as_singleton() {
 650                        if buffer.read(cx).is_empty() {
 651                            buffer.update(cx, |buffer, cx| {
 652                                buffer.edit([(0..0, initial_local_settings_content())], None, cx)
 653                            });
 654                        }
 655                    }
 656                })
 657                .ok();
 658
 659            anyhow::Ok(())
 660        })
 661        .detach();
 662    } else {
 663        workspace.show_notification(0, cx, |cx| {
 664            cx.build_view(|_| MessageNotification::new("This project has no folders open."))
 665        })
 666    }
 667}
 668
 669fn open_telemetry_log_file(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
 670    workspace.with_local_workspace(cx, move |workspace, cx| {
 671        let app_state = workspace.app_state().clone();
 672        cx.spawn(|workspace, mut cx| async move {
 673            async fn fetch_log_string(app_state: &Arc<AppState>) -> Option<String> {
 674                let path = app_state.client.telemetry().log_file_path()?;
 675                app_state.fs.load(&path).await.log_err()
 676            }
 677
 678            let log = fetch_log_string(&app_state).await.unwrap_or_else(|| "// No data has been collected yet".to_string());
 679
 680            const MAX_TELEMETRY_LOG_LEN: usize = 5 * 1024 * 1024;
 681            let mut start_offset = log.len().saturating_sub(MAX_TELEMETRY_LOG_LEN);
 682            if let Some(newline_offset) = log[start_offset..].find('\n') {
 683                start_offset += newline_offset + 1;
 684            }
 685            let log_suffix = &log[start_offset..];
 686            let json = app_state.languages.language_for_name("JSON").await.log_err();
 687
 688            workspace.update(&mut cx, |workspace, cx| {
 689                let project = workspace.project().clone();
 690                let buffer = project
 691                    .update(cx, |project, cx| project.create_buffer("", None, cx))
 692                    .expect("creating buffers on a local workspace always succeeds");
 693                buffer.update(cx, |buffer, cx| {
 694                    buffer.set_language(json, cx);
 695                    buffer.edit(
 696                        [(
 697                            0..0,
 698                            concat!(
 699                                "// Zed collects anonymous usage data to help us understand how people are using the app.\n",
 700                                "// Telemetry can be disabled via the `settings.json` file.\n",
 701                                "// Here is the data that has been reported for the current session:\n",
 702                                "\n"
 703                            ),
 704                        )],
 705                        None,
 706                        cx,
 707                    );
 708                    buffer.edit([(buffer.len()..buffer.len(), log_suffix)], None, cx);
 709                });
 710
 711                let buffer = cx.build_model(|cx| {
 712                    MultiBuffer::singleton(buffer, cx).with_title("Telemetry Log".into())
 713                });
 714                workspace.add_item(
 715                    Box::new(cx.build_view(|cx| Editor::for_multibuffer(buffer, Some(project), cx))),
 716                    cx,
 717                );
 718            }).log_err()?;
 719
 720            Some(())
 721        })
 722        .detach();
 723    }).detach();
 724}
 725
 726fn open_bundled_file(
 727    workspace: &mut Workspace,
 728    text: Cow<'static, str>,
 729    title: &'static str,
 730    language: &'static str,
 731    cx: &mut ViewContext<Workspace>,
 732) {
 733    let language = workspace.app_state().languages.language_for_name(language);
 734    cx.spawn(|workspace, mut cx| async move {
 735        let language = language.await.log_err();
 736        workspace
 737            .update(&mut cx, |workspace, cx| {
 738                workspace.with_local_workspace(cx, |workspace, cx| {
 739                    let project = workspace.project();
 740                    let buffer = project.update(cx, move |project, cx| {
 741                        project
 742                            .create_buffer(text.as_ref(), language, cx)
 743                            .expect("creating buffers on a local workspace always succeeds")
 744                    });
 745                    let buffer = cx.build_model(|cx| {
 746                        MultiBuffer::singleton(buffer, cx).with_title(title.into())
 747                    });
 748                    workspace.add_item(
 749                        Box::new(cx.build_view(|cx| {
 750                            Editor::for_multibuffer(buffer, Some(project.clone()), cx)
 751                        })),
 752                        cx,
 753                    );
 754                })
 755            })?
 756            .await
 757    })
 758    .detach_and_log_err(cx);
 759}
 760
 761// todo!()
 762// #[cfg(test)]
 763// mod tests {
 764//     use super::*;
 765//     use assets::Assets;
 766//     use editor::{scroll::autoscroll::Autoscroll, DisplayPoint, Editor};
 767//     use fs::{FakeFs, Fs};
 768//     use gpui::{
 769//         actions, elements::Empty, executor::Deterministic, Action, AnyElement, AnyWindowHandle,
 770//         AppContext, AssetSource, Element, Entity, TestAppContext, View, ViewHandle,
 771//     };
 772//     use language::LanguageRegistry;
 773//     use project::{project_settings::ProjectSettings, Project, ProjectPath};
 774//     use serde_json::json;
 775//     use settings::{handle_settings_file_changes, watch_config_file, SettingsStore};
 776//     use std::{
 777//         collections::HashSet,
 778//         path::{Path, PathBuf},
 779//     };
 780//     use theme::{ThemeRegistry, ThemeSettings};
 781//     use workspace::{
 782//         item::{Item, ItemHandle},
 783//         open_new, open_paths, pane, NewFile, SaveIntent, SplitDirection, WorkspaceHandle,
 784//     };
 785
 786//     #[gpui::test]
 787//     async fn test_open_paths_action(cx: &mut TestAppContext) {
 788//         let app_state = init_test(cx);
 789//         app_state
 790//             .fs
 791//             .as_fake()
 792//             .insert_tree(
 793//                 "/root",
 794//                 json!({
 795//                     "a": {
 796//                         "aa": null,
 797//                         "ab": null,
 798//                     },
 799//                     "b": {
 800//                         "ba": null,
 801//                         "bb": null,
 802//                     },
 803//                     "c": {
 804//                         "ca": null,
 805//                         "cb": null,
 806//                     },
 807//                     "d": {
 808//                         "da": null,
 809//                         "db": null,
 810//                     },
 811//                 }),
 812//             )
 813//             .await;
 814
 815//         cx.update(|cx| {
 816//             open_paths(
 817//                 &[PathBuf::from("/root/a"), PathBuf::from("/root/b")],
 818//                 &app_state,
 819//                 None,
 820//                 cx,
 821//             )
 822//         })
 823//         .await
 824//         .unwrap();
 825//         assert_eq!(cx.windows().len(), 1);
 826
 827//         cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, None, cx))
 828//             .await
 829//             .unwrap();
 830//         assert_eq!(cx.windows().len(), 1);
 831//         let workspace_1 = cx.windows()[0].downcast::<Workspace>().unwrap().root(cx);
 832//         workspace_1.update(cx, |workspace, cx| {
 833//             assert_eq!(workspace.worktrees(cx).count(), 2);
 834//             assert!(workspace.left_dock().read(cx).is_open());
 835//             assert!(workspace.active_pane().is_focused(cx));
 836//         });
 837
 838//         cx.update(|cx| {
 839//             open_paths(
 840//                 &[PathBuf::from("/root/b"), PathBuf::from("/root/c")],
 841//                 &app_state,
 842//                 None,
 843//                 cx,
 844//             )
 845//         })
 846//         .await
 847//         .unwrap();
 848//         assert_eq!(cx.windows().len(), 2);
 849
 850//         // Replace existing windows
 851//         let window = cx.windows()[0].downcast::<Workspace>().unwrap();
 852//         cx.update(|cx| {
 853//             open_paths(
 854//                 &[PathBuf::from("/root/c"), PathBuf::from("/root/d")],
 855//                 &app_state,
 856//                 Some(window),
 857//                 cx,
 858//             )
 859//         })
 860//         .await
 861//         .unwrap();
 862//         assert_eq!(cx.windows().len(), 2);
 863//         let workspace_1 = cx.windows()[0].downcast::<Workspace>().unwrap().root(cx);
 864//         workspace_1.update(cx, |workspace, cx| {
 865//             assert_eq!(
 866//                 workspace
 867//                     .worktrees(cx)
 868//                     .map(|w| w.read(cx).abs_path())
 869//                     .collect::<Vec<_>>(),
 870//                 &[Path::new("/root/c").into(), Path::new("/root/d").into()]
 871//             );
 872//             assert!(workspace.left_dock().read(cx).is_open());
 873//             assert!(workspace.active_pane().is_focused(cx));
 874//         });
 875//     }
 876
 877//     #[gpui::test]
 878//     async fn test_window_edit_state(executor: Arc<Deterministic>, cx: &mut TestAppContext) {
 879//         let app_state = init_test(cx);
 880//         app_state
 881//             .fs
 882//             .as_fake()
 883//             .insert_tree("/root", json!({"a": "hey"}))
 884//             .await;
 885
 886//         cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, None, cx))
 887//             .await
 888//             .unwrap();
 889//         assert_eq!(cx.windows().len(), 1);
 890
 891//         // When opening the workspace, the window is not in a edited state.
 892//         let window = cx.windows()[0].downcast::<Workspace>().unwrap();
 893//         let workspace = window.root(cx);
 894//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
 895//         let editor = workspace.read_with(cx, |workspace, cx| {
 896//             workspace
 897//                 .active_item(cx)
 898//                 .unwrap()
 899//                 .downcast::<Editor>()
 900//                 .unwrap()
 901//         });
 902//         assert!(!window.is_edited(cx));
 903
 904//         // Editing a buffer marks the window as edited.
 905//         editor.update(cx, |editor, cx| editor.insert("EDIT", cx));
 906//         assert!(window.is_edited(cx));
 907
 908//         // Undoing the edit restores the window's edited state.
 909//         editor.update(cx, |editor, cx| editor.undo(&Default::default(), cx));
 910//         assert!(!window.is_edited(cx));
 911
 912//         // Redoing the edit marks the window as edited again.
 913//         editor.update(cx, |editor, cx| editor.redo(&Default::default(), cx));
 914//         assert!(window.is_edited(cx));
 915
 916//         // Closing the item restores the window's edited state.
 917//         let close = pane.update(cx, |pane, cx| {
 918//             drop(editor);
 919//             pane.close_active_item(&Default::default(), cx).unwrap()
 920//         });
 921//         executor.run_until_parked();
 922
 923//         window.simulate_prompt_answer(1, cx);
 924//         close.await.unwrap();
 925//         assert!(!window.is_edited(cx));
 926
 927//         // Opening the buffer again doesn't impact the window's edited state.
 928//         cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, None, cx))
 929//             .await
 930//             .unwrap();
 931//         let editor = workspace.read_with(cx, |workspace, cx| {
 932//             workspace
 933//                 .active_item(cx)
 934//                 .unwrap()
 935//                 .downcast::<Editor>()
 936//                 .unwrap()
 937//         });
 938//         assert!(!window.is_edited(cx));
 939
 940//         // Editing the buffer marks the window as edited.
 941//         editor.update(cx, |editor, cx| editor.insert("EDIT", cx));
 942//         assert!(window.is_edited(cx));
 943
 944//         // Ensure closing the window via the mouse gets preempted due to the
 945//         // buffer having unsaved changes.
 946//         assert!(!window.simulate_close(cx));
 947//         executor.run_until_parked();
 948//         assert_eq!(cx.windows().len(), 1);
 949
 950//         // The window is successfully closed after the user dismisses the prompt.
 951//         window.simulate_prompt_answer(1, cx);
 952//         executor.run_until_parked();
 953//         assert_eq!(cx.windows().len(), 0);
 954//     }
 955
 956//     #[gpui::test]
 957//     async fn test_new_empty_workspace(cx: &mut TestAppContext) {
 958//         let app_state = init_test(cx);
 959//         cx.update(|cx| {
 960//             open_new(&app_state, cx, |workspace, cx| {
 961//                 Editor::new_file(workspace, &Default::default(), cx)
 962//             })
 963//         })
 964//         .await;
 965
 966//         let window = cx
 967//             .windows()
 968//             .first()
 969//             .unwrap()
 970//             .downcast::<Workspace>()
 971//             .unwrap();
 972//         let workspace = window.root(cx);
 973
 974//         let editor = workspace.update(cx, |workspace, cx| {
 975//             workspace
 976//                 .active_item(cx)
 977//                 .unwrap()
 978//                 .downcast::<editor::Editor>()
 979//                 .unwrap()
 980//         });
 981
 982//         editor.update(cx, |editor, cx| {
 983//             assert!(editor.text(cx).is_empty());
 984//             assert!(!editor.is_dirty(cx));
 985//         });
 986
 987//         let save_task = workspace.update(cx, |workspace, cx| {
 988//             workspace.save_active_item(SaveIntent::Save, cx)
 989//         });
 990//         app_state.fs.create_dir(Path::new("/root")).await.unwrap();
 991//         cx.foreground().run_until_parked();
 992//         cx.simulate_new_path_selection(|_| Some(PathBuf::from("/root/the-new-name")));
 993//         save_task.await.unwrap();
 994//         editor.read_with(cx, |editor, cx| {
 995//             assert!(!editor.is_dirty(cx));
 996//             assert_eq!(editor.title(cx), "the-new-name");
 997//         });
 998//     }
 999
1000//     #[gpui::test]
1001//     async fn test_open_entry(cx: &mut TestAppContext) {
1002//         let app_state = init_test(cx);
1003//         app_state
1004//             .fs
1005//             .as_fake()
1006//             .insert_tree(
1007//                 "/root",
1008//                 json!({
1009//                     "a": {
1010//                         "file1": "contents 1",
1011//                         "file2": "contents 2",
1012//                         "file3": "contents 3",
1013//                     },
1014//                 }),
1015//             )
1016//             .await;
1017
1018//         let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
1019//         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
1020//         let workspace = window.root(cx);
1021
1022//         let entries = cx.read(|cx| workspace.file_project_paths(cx));
1023//         let file1 = entries[0].clone();
1024//         let file2 = entries[1].clone();
1025//         let file3 = entries[2].clone();
1026
1027//         // Open the first entry
1028//         let entry_1 = workspace
1029//             .update(cx, |w, cx| w.open_path(file1.clone(), None, true, cx))
1030//             .await
1031//             .unwrap();
1032//         cx.read(|cx| {
1033//             let pane = workspace.read(cx).active_pane().read(cx);
1034//             assert_eq!(
1035//                 pane.active_item().unwrap().project_path(cx),
1036//                 Some(file1.clone())
1037//             );
1038//             assert_eq!(pane.items_len(), 1);
1039//         });
1040
1041//         // Open the second entry
1042//         workspace
1043//             .update(cx, |w, cx| w.open_path(file2.clone(), None, true, cx))
1044//             .await
1045//             .unwrap();
1046//         cx.read(|cx| {
1047//             let pane = workspace.read(cx).active_pane().read(cx);
1048//             assert_eq!(
1049//                 pane.active_item().unwrap().project_path(cx),
1050//                 Some(file2.clone())
1051//             );
1052//             assert_eq!(pane.items_len(), 2);
1053//         });
1054
1055//         // Open the first entry again. The existing pane item is activated.
1056//         let entry_1b = workspace
1057//             .update(cx, |w, cx| w.open_path(file1.clone(), None, true, cx))
1058//             .await
1059//             .unwrap();
1060//         assert_eq!(entry_1.id(), entry_1b.id());
1061
1062//         cx.read(|cx| {
1063//             let pane = workspace.read(cx).active_pane().read(cx);
1064//             assert_eq!(
1065//                 pane.active_item().unwrap().project_path(cx),
1066//                 Some(file1.clone())
1067//             );
1068//             assert_eq!(pane.items_len(), 2);
1069//         });
1070
1071//         // Split the pane with the first entry, then open the second entry again.
1072//         workspace
1073//             .update(cx, |w, cx| {
1074//                 w.split_and_clone(w.active_pane().clone(), SplitDirection::Right, cx);
1075//                 w.open_path(file2.clone(), None, true, cx)
1076//             })
1077//             .await
1078//             .unwrap();
1079
1080//         workspace.read_with(cx, |w, cx| {
1081//             assert_eq!(
1082//                 w.active_pane()
1083//                     .read(cx)
1084//                     .active_item()
1085//                     .unwrap()
1086//                     .project_path(cx),
1087//                 Some(file2.clone())
1088//             );
1089//         });
1090
1091//         // Open the third entry twice concurrently. Only one pane item is added.
1092//         let (t1, t2) = workspace.update(cx, |w, cx| {
1093//             (
1094//                 w.open_path(file3.clone(), None, true, cx),
1095//                 w.open_path(file3.clone(), None, true, cx),
1096//             )
1097//         });
1098//         t1.await.unwrap();
1099//         t2.await.unwrap();
1100//         cx.read(|cx| {
1101//             let pane = workspace.read(cx).active_pane().read(cx);
1102//             assert_eq!(
1103//                 pane.active_item().unwrap().project_path(cx),
1104//                 Some(file3.clone())
1105//             );
1106//             let pane_entries = pane
1107//                 .items()
1108//                 .map(|i| i.project_path(cx).unwrap())
1109//                 .collect::<Vec<_>>();
1110//             assert_eq!(pane_entries, &[file1, file2, file3]);
1111//         });
1112//     }
1113
1114//     #[gpui::test]
1115//     async fn test_open_paths(cx: &mut TestAppContext) {
1116//         let app_state = init_test(cx);
1117
1118//         app_state
1119//             .fs
1120//             .as_fake()
1121//             .insert_tree(
1122//                 "/",
1123//                 json!({
1124//                     "dir1": {
1125//                         "a.txt": ""
1126//                     },
1127//                     "dir2": {
1128//                         "b.txt": ""
1129//                     },
1130//                     "dir3": {
1131//                         "c.txt": ""
1132//                     },
1133//                     "d.txt": ""
1134//                 }),
1135//             )
1136//             .await;
1137
1138//         cx.update(|cx| open_paths(&[PathBuf::from("/dir1/")], &app_state, None, cx))
1139//             .await
1140//             .unwrap();
1141//         assert_eq!(cx.windows().len(), 1);
1142//         let workspace = cx.windows()[0].downcast::<Workspace>().unwrap().root(cx);
1143
1144//         #[track_caller]
1145//         fn assert_project_panel_selection(
1146//             workspace: &Workspace,
1147//             expected_worktree_path: &Path,
1148//             expected_entry_path: &Path,
1149//             cx: &AppContext,
1150//         ) {
1151//             let project_panel = [
1152//                 workspace.left_dock().read(cx).panel::<ProjectPanel>(),
1153//                 workspace.right_dock().read(cx).panel::<ProjectPanel>(),
1154//                 workspace.bottom_dock().read(cx).panel::<ProjectPanel>(),
1155//             ]
1156//             .into_iter()
1157//             .find_map(std::convert::identity)
1158//             .expect("found no project panels")
1159//             .read(cx);
1160//             let (selected_worktree, selected_entry) = project_panel
1161//                 .selected_entry(cx)
1162//                 .expect("project panel should have a selected entry");
1163//             assert_eq!(
1164//                 selected_worktree.abs_path().as_ref(),
1165//                 expected_worktree_path,
1166//                 "Unexpected project panel selected worktree path"
1167//             );
1168//             assert_eq!(
1169//                 selected_entry.path.as_ref(),
1170//                 expected_entry_path,
1171//                 "Unexpected project panel selected entry path"
1172//             );
1173//         }
1174
1175//         // Open a file within an existing worktree.
1176//         workspace
1177//             .update(cx, |view, cx| {
1178//                 view.open_paths(vec!["/dir1/a.txt".into()], true, cx)
1179//             })
1180//             .await;
1181//         cx.read(|cx| {
1182//             let workspace = workspace.read(cx);
1183//             assert_project_panel_selection(workspace, Path::new("/dir1"), Path::new("a.txt"), cx);
1184//             assert_eq!(
1185//                 workspace
1186//                     .active_pane()
1187//                     .read(cx)
1188//                     .active_item()
1189//                     .unwrap()
1190//                     .as_any()
1191//                     .downcast_ref::<Editor>()
1192//                     .unwrap()
1193//                     .read(cx)
1194//                     .title(cx),
1195//                 "a.txt"
1196//             );
1197//         });
1198
1199//         // Open a file outside of any existing worktree.
1200//         workspace
1201//             .update(cx, |view, cx| {
1202//                 view.open_paths(vec!["/dir2/b.txt".into()], true, cx)
1203//             })
1204//             .await;
1205//         cx.read(|cx| {
1206//             let workspace = workspace.read(cx);
1207//             assert_project_panel_selection(workspace, Path::new("/dir2/b.txt"), Path::new(""), cx);
1208//             let worktree_roots = workspace
1209//                 .worktrees(cx)
1210//                 .map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref())
1211//                 .collect::<HashSet<_>>();
1212//             assert_eq!(
1213//                 worktree_roots,
1214//                 vec!["/dir1", "/dir2/b.txt"]
1215//                     .into_iter()
1216//                     .map(Path::new)
1217//                     .collect(),
1218//             );
1219//             assert_eq!(
1220//                 workspace
1221//                     .active_pane()
1222//                     .read(cx)
1223//                     .active_item()
1224//                     .unwrap()
1225//                     .as_any()
1226//                     .downcast_ref::<Editor>()
1227//                     .unwrap()
1228//                     .read(cx)
1229//                     .title(cx),
1230//                 "b.txt"
1231//             );
1232//         });
1233
1234//         // Ensure opening a directory and one of its children only adds one worktree.
1235//         workspace
1236//             .update(cx, |view, cx| {
1237//                 view.open_paths(vec!["/dir3".into(), "/dir3/c.txt".into()], true, cx)
1238//             })
1239//             .await;
1240//         cx.read(|cx| {
1241//             let workspace = workspace.read(cx);
1242//             assert_project_panel_selection(workspace, Path::new("/dir3"), Path::new("c.txt"), cx);
1243//             let worktree_roots = workspace
1244//                 .worktrees(cx)
1245//                 .map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref())
1246//                 .collect::<HashSet<_>>();
1247//             assert_eq!(
1248//                 worktree_roots,
1249//                 vec!["/dir1", "/dir2/b.txt", "/dir3"]
1250//                     .into_iter()
1251//                     .map(Path::new)
1252//                     .collect(),
1253//             );
1254//             assert_eq!(
1255//                 workspace
1256//                     .active_pane()
1257//                     .read(cx)
1258//                     .active_item()
1259//                     .unwrap()
1260//                     .as_any()
1261//                     .downcast_ref::<Editor>()
1262//                     .unwrap()
1263//                     .read(cx)
1264//                     .title(cx),
1265//                 "c.txt"
1266//             );
1267//         });
1268
1269//         // Ensure opening invisibly a file outside an existing worktree adds a new, invisible worktree.
1270//         workspace
1271//             .update(cx, |view, cx| {
1272//                 view.open_paths(vec!["/d.txt".into()], false, cx)
1273//             })
1274//             .await;
1275//         cx.read(|cx| {
1276//             let workspace = workspace.read(cx);
1277//             assert_project_panel_selection(workspace, Path::new("/d.txt"), Path::new(""), cx);
1278//             let worktree_roots = workspace
1279//                 .worktrees(cx)
1280//                 .map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref())
1281//                 .collect::<HashSet<_>>();
1282//             assert_eq!(
1283//                 worktree_roots,
1284//                 vec!["/dir1", "/dir2/b.txt", "/dir3", "/d.txt"]
1285//                     .into_iter()
1286//                     .map(Path::new)
1287//                     .collect(),
1288//             );
1289
1290//             let visible_worktree_roots = workspace
1291//                 .visible_worktrees(cx)
1292//                 .map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref())
1293//                 .collect::<HashSet<_>>();
1294//             assert_eq!(
1295//                 visible_worktree_roots,
1296//                 vec!["/dir1", "/dir2/b.txt", "/dir3"]
1297//                     .into_iter()
1298//                     .map(Path::new)
1299//                     .collect(),
1300//             );
1301
1302//             assert_eq!(
1303//                 workspace
1304//                     .active_pane()
1305//                     .read(cx)
1306//                     .active_item()
1307//                     .unwrap()
1308//                     .as_any()
1309//                     .downcast_ref::<Editor>()
1310//                     .unwrap()
1311//                     .read(cx)
1312//                     .title(cx),
1313//                 "d.txt"
1314//             );
1315//         });
1316//     }
1317
1318//     #[gpui::test]
1319//     async fn test_opening_excluded_paths(cx: &mut TestAppContext) {
1320//         let app_state = init_test(cx);
1321//         cx.update(|cx| {
1322//             cx.update_global::<SettingsStore, _, _>(|store, cx| {
1323//                 store.update_user_settings::<ProjectSettings>(cx, |project_settings| {
1324//                     project_settings.file_scan_exclusions =
1325//                         Some(vec!["excluded_dir".to_string(), "**/.git".to_string()]);
1326//                 });
1327//             });
1328//         });
1329//         app_state
1330//             .fs
1331//             .as_fake()
1332//             .insert_tree(
1333//                 "/root",
1334//                 json!({
1335//                     ".gitignore": "ignored_dir\n",
1336//                     ".git": {
1337//                         "HEAD": "ref: refs/heads/main",
1338//                     },
1339//                     "regular_dir": {
1340//                         "file": "regular file contents",
1341//                     },
1342//                     "ignored_dir": {
1343//                         "ignored_subdir": {
1344//                             "file": "ignored subfile contents",
1345//                         },
1346//                         "file": "ignored file contents",
1347//                     },
1348//                     "excluded_dir": {
1349//                         "file": "excluded file contents",
1350//                     },
1351//                 }),
1352//             )
1353//             .await;
1354
1355//         let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
1356//         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
1357//         let workspace = window.root(cx);
1358
1359//         let initial_entries = cx.read(|cx| workspace.file_project_paths(cx));
1360//         let paths_to_open = [
1361//             Path::new("/root/excluded_dir/file").to_path_buf(),
1362//             Path::new("/root/.git/HEAD").to_path_buf(),
1363//             Path::new("/root/excluded_dir/ignored_subdir").to_path_buf(),
1364//         ];
1365//         let (opened_workspace, new_items) = cx
1366//             .update(|cx| workspace::open_paths(&paths_to_open, &app_state, None, cx))
1367//             .await
1368//             .unwrap();
1369
1370//         assert_eq!(
1371//             opened_workspace.id(),
1372//             workspace.id(),
1373//             "Excluded files in subfolders of a workspace root should be opened in the workspace"
1374//         );
1375//         let mut opened_paths = cx.read(|cx| {
1376//             assert_eq!(
1377//                 new_items.len(),
1378//                 paths_to_open.len(),
1379//                 "Expect to get the same number of opened items as submitted paths to open"
1380//             );
1381//             new_items
1382//                 .iter()
1383//                 .zip(paths_to_open.iter())
1384//                 .map(|(i, path)| {
1385//                     match i {
1386//                         Some(Ok(i)) => {
1387//                             Some(i.project_path(cx).map(|p| p.path.display().to_string()))
1388//                         }
1389//                         Some(Err(e)) => panic!("Excluded file {path:?} failed to open: {e:?}"),
1390//                         None => None,
1391//                     }
1392//                     .flatten()
1393//                 })
1394//                 .collect::<Vec<_>>()
1395//         });
1396//         opened_paths.sort();
1397//         assert_eq!(
1398//             opened_paths,
1399//             vec![
1400//                 None,
1401//                 Some(".git/HEAD".to_string()),
1402//                 Some("excluded_dir/file".to_string()),
1403//             ],
1404//             "Excluded files should get opened, excluded dir should not get opened"
1405//         );
1406
1407//         let entries = cx.read(|cx| workspace.file_project_paths(cx));
1408//         assert_eq!(
1409//             initial_entries, entries,
1410//             "Workspace entries should not change after opening excluded files and directories paths"
1411//         );
1412
1413//         cx.read(|cx| {
1414//             let pane = workspace.read(cx).active_pane().read(cx);
1415//             let mut opened_buffer_paths = pane
1416//                 .items()
1417//                 .map(|i| {
1418//                     i.project_path(cx)
1419//                         .expect("all excluded files that got open should have a path")
1420//                         .path
1421//                         .display()
1422//                         .to_string()
1423//                 })
1424//                 .collect::<Vec<_>>();
1425//             opened_buffer_paths.sort();
1426//             assert_eq!(
1427//                 opened_buffer_paths,
1428//                 vec![".git/HEAD".to_string(), "excluded_dir/file".to_string()],
1429//                 "Despite not being present in the worktrees, buffers for excluded files are opened and added to the pane"
1430//             );
1431//         });
1432//     }
1433
1434//     #[gpui::test]
1435//     async fn test_save_conflicting_item(cx: &mut TestAppContext) {
1436//         let app_state = init_test(cx);
1437//         app_state
1438//             .fs
1439//             .as_fake()
1440//             .insert_tree("/root", json!({ "a.txt": "" }))
1441//             .await;
1442
1443//         let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
1444//         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
1445//         let workspace = window.root(cx);
1446
1447//         // Open a file within an existing worktree.
1448//         workspace
1449//             .update(cx, |view, cx| {
1450//                 view.open_paths(vec![PathBuf::from("/root/a.txt")], true, cx)
1451//             })
1452//             .await;
1453//         let editor = cx.read(|cx| {
1454//             let pane = workspace.read(cx).active_pane().read(cx);
1455//             let item = pane.active_item().unwrap();
1456//             item.downcast::<Editor>().unwrap()
1457//         });
1458
1459//         editor.update(cx, |editor, cx| editor.handle_input("x", cx));
1460//         app_state
1461//             .fs
1462//             .as_fake()
1463//             .insert_file("/root/a.txt", "changed".to_string())
1464//             .await;
1465//         editor
1466//             .condition(cx, |editor, cx| editor.has_conflict(cx))
1467//             .await;
1468//         cx.read(|cx| assert!(editor.is_dirty(cx)));
1469
1470//         let save_task = workspace.update(cx, |workspace, cx| {
1471//             workspace.save_active_item(SaveIntent::Save, cx)
1472//         });
1473//         cx.foreground().run_until_parked();
1474//         window.simulate_prompt_answer(0, cx);
1475//         save_task.await.unwrap();
1476//         editor.read_with(cx, |editor, cx| {
1477//             assert!(!editor.is_dirty(cx));
1478//             assert!(!editor.has_conflict(cx));
1479//         });
1480//     }
1481
1482//     #[gpui::test]
1483//     async fn test_open_and_save_new_file(cx: &mut TestAppContext) {
1484//         let app_state = init_test(cx);
1485//         app_state.fs.create_dir(Path::new("/root")).await.unwrap();
1486
1487//         let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
1488//         project.update(cx, |project, _| project.languages().add(rust_lang()));
1489//         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
1490//         let workspace = window.root(cx);
1491//         let worktree = cx.read(|cx| workspace.read(cx).worktrees(cx).next().unwrap());
1492
1493//         // Create a new untitled buffer
1494//         cx.dispatch_action(window.into(), NewFile);
1495//         let editor = workspace.read_with(cx, |workspace, cx| {
1496//             workspace
1497//                 .active_item(cx)
1498//                 .unwrap()
1499//                 .downcast::<Editor>()
1500//                 .unwrap()
1501//         });
1502
1503//         editor.update(cx, |editor, cx| {
1504//             assert!(!editor.is_dirty(cx));
1505//             assert_eq!(editor.title(cx), "untitled");
1506//             assert!(Arc::ptr_eq(
1507//                 &editor.language_at(0, cx).unwrap(),
1508//                 &languages::PLAIN_TEXT
1509//             ));
1510//             editor.handle_input("hi", cx);
1511//             assert!(editor.is_dirty(cx));
1512//         });
1513
1514//         // Save the buffer. This prompts for a filename.
1515//         let save_task = workspace.update(cx, |workspace, cx| {
1516//             workspace.save_active_item(SaveIntent::Save, cx)
1517//         });
1518//         cx.foreground().run_until_parked();
1519//         cx.simulate_new_path_selection(|parent_dir| {
1520//             assert_eq!(parent_dir, Path::new("/root"));
1521//             Some(parent_dir.join("the-new-name.rs"))
1522//         });
1523//         cx.read(|cx| {
1524//             assert!(editor.is_dirty(cx));
1525//             assert_eq!(editor.read(cx).title(cx), "untitled");
1526//         });
1527
1528//         // When the save completes, the buffer's title is updated and the language is assigned based
1529//         // on the path.
1530//         save_task.await.unwrap();
1531//         editor.read_with(cx, |editor, cx| {
1532//             assert!(!editor.is_dirty(cx));
1533//             assert_eq!(editor.title(cx), "the-new-name.rs");
1534//             assert_eq!(editor.language_at(0, cx).unwrap().name().as_ref(), "Rust");
1535//         });
1536
1537//         // Edit the file and save it again. This time, there is no filename prompt.
1538//         editor.update(cx, |editor, cx| {
1539//             editor.handle_input(" there", cx);
1540//             assert!(editor.is_dirty(cx));
1541//         });
1542//         let save_task = workspace.update(cx, |workspace, cx| {
1543//             workspace.save_active_item(SaveIntent::Save, cx)
1544//         });
1545//         save_task.await.unwrap();
1546//         assert!(!cx.did_prompt_for_new_path());
1547//         editor.read_with(cx, |editor, cx| {
1548//             assert!(!editor.is_dirty(cx));
1549//             assert_eq!(editor.title(cx), "the-new-name.rs")
1550//         });
1551
1552//         // Open the same newly-created file in another pane item. The new editor should reuse
1553//         // the same buffer.
1554//         cx.dispatch_action(window.into(), NewFile);
1555//         workspace
1556//             .update(cx, |workspace, cx| {
1557//                 workspace.split_and_clone(
1558//                     workspace.active_pane().clone(),
1559//                     SplitDirection::Right,
1560//                     cx,
1561//                 );
1562//                 workspace.open_path((worktree.read(cx).id(), "the-new-name.rs"), None, true, cx)
1563//             })
1564//             .await
1565//             .unwrap();
1566//         let editor2 = workspace.update(cx, |workspace, cx| {
1567//             workspace
1568//                 .active_item(cx)
1569//                 .unwrap()
1570//                 .downcast::<Editor>()
1571//                 .unwrap()
1572//         });
1573//         cx.read(|cx| {
1574//             assert_eq!(
1575//                 editor2.read(cx).buffer().read(cx).as_singleton().unwrap(),
1576//                 editor.read(cx).buffer().read(cx).as_singleton().unwrap()
1577//             );
1578//         })
1579//     }
1580
1581//     #[gpui::test]
1582//     async fn test_setting_language_when_saving_as_single_file_worktree(cx: &mut TestAppContext) {
1583//         let app_state = init_test(cx);
1584//         app_state.fs.create_dir(Path::new("/root")).await.unwrap();
1585
1586//         let project = Project::test(app_state.fs.clone(), [], cx).await;
1587//         project.update(cx, |project, _| project.languages().add(rust_lang()));
1588//         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
1589//         let workspace = window.root(cx);
1590
1591//         // Create a new untitled buffer
1592//         cx.dispatch_action(window.into(), NewFile);
1593//         let editor = workspace.read_with(cx, |workspace, cx| {
1594//             workspace
1595//                 .active_item(cx)
1596//                 .unwrap()
1597//                 .downcast::<Editor>()
1598//                 .unwrap()
1599//         });
1600
1601//         editor.update(cx, |editor, cx| {
1602//             assert!(Arc::ptr_eq(
1603//                 &editor.language_at(0, cx).unwrap(),
1604//                 &languages::PLAIN_TEXT
1605//             ));
1606//             editor.handle_input("hi", cx);
1607//             assert!(editor.is_dirty(cx));
1608//         });
1609
1610//         // Save the buffer. This prompts for a filename.
1611//         let save_task = workspace.update(cx, |workspace, cx| {
1612//             workspace.save_active_item(SaveIntent::Save, cx)
1613//         });
1614//         cx.foreground().run_until_parked();
1615//         cx.simulate_new_path_selection(|_| Some(PathBuf::from("/root/the-new-name.rs")));
1616//         save_task.await.unwrap();
1617//         // The buffer is not dirty anymore and the language is assigned based on the path.
1618//         editor.read_with(cx, |editor, cx| {
1619//             assert!(!editor.is_dirty(cx));
1620//             assert_eq!(editor.language_at(0, cx).unwrap().name().as_ref(), "Rust")
1621//         });
1622//     }
1623
1624//     #[gpui::test]
1625//     async fn test_pane_actions(cx: &mut TestAppContext) {
1626//         let app_state = init_test(cx);
1627//         app_state
1628//             .fs
1629//             .as_fake()
1630//             .insert_tree(
1631//                 "/root",
1632//                 json!({
1633//                     "a": {
1634//                         "file1": "contents 1",
1635//                         "file2": "contents 2",
1636//                         "file3": "contents 3",
1637//                     },
1638//                 }),
1639//             )
1640//             .await;
1641
1642//         let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
1643//         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
1644//         let workspace = window.root(cx);
1645
1646//         let entries = cx.read(|cx| workspace.file_project_paths(cx));
1647//         let file1 = entries[0].clone();
1648
1649//         let pane_1 = cx.read(|cx| workspace.read(cx).active_pane().clone());
1650
1651//         workspace
1652//             .update(cx, |w, cx| w.open_path(file1.clone(), None, true, cx))
1653//             .await
1654//             .unwrap();
1655
1656//         let (editor_1, buffer) = pane_1.update(cx, |pane_1, cx| {
1657//             let editor = pane_1.active_item().unwrap().downcast::<Editor>().unwrap();
1658//             assert_eq!(editor.project_path(cx), Some(file1.clone()));
1659//             let buffer = editor.update(cx, |editor, cx| {
1660//                 editor.insert("dirt", cx);
1661//                 editor.buffer().downgrade()
1662//             });
1663//             (editor.downgrade(), buffer)
1664//         });
1665
1666//         cx.dispatch_action(window.into(), pane::SplitRight);
1667//         let editor_2 = cx.update(|cx| {
1668//             let pane_2 = workspace.read(cx).active_pane().clone();
1669//             assert_ne!(pane_1, pane_2);
1670
1671//             let pane2_item = pane_2.read(cx).active_item().unwrap();
1672//             assert_eq!(pane2_item.project_path(cx), Some(file1.clone()));
1673
1674//             pane2_item.downcast::<Editor>().unwrap().downgrade()
1675//         });
1676//         cx.dispatch_action(
1677//             window.into(),
1678//             workspace::CloseActiveItem { save_intent: None },
1679//         );
1680
1681//         cx.foreground().run_until_parked();
1682//         workspace.read_with(cx, |workspace, _| {
1683//             assert_eq!(workspace.panes().len(), 1);
1684//             assert_eq!(workspace.active_pane(), &pane_1);
1685//         });
1686
1687//         cx.dispatch_action(
1688//             window.into(),
1689//             workspace::CloseActiveItem { save_intent: None },
1690//         );
1691//         cx.foreground().run_until_parked();
1692//         window.simulate_prompt_answer(1, cx);
1693//         cx.foreground().run_until_parked();
1694
1695//         workspace.read_with(cx, |workspace, cx| {
1696//             assert_eq!(workspace.panes().len(), 1);
1697//             assert!(workspace.active_item(cx).is_none());
1698//         });
1699
1700//         cx.assert_dropped(editor_1);
1701//         cx.assert_dropped(editor_2);
1702//         cx.assert_dropped(buffer);
1703//     }
1704
1705//     #[gpui::test]
1706//     async fn test_navigation(cx: &mut TestAppContext) {
1707//         let app_state = init_test(cx);
1708//         app_state
1709//             .fs
1710//             .as_fake()
1711//             .insert_tree(
1712//                 "/root",
1713//                 json!({
1714//                     "a": {
1715//                         "file1": "contents 1\n".repeat(20),
1716//                         "file2": "contents 2\n".repeat(20),
1717//                         "file3": "contents 3\n".repeat(20),
1718//                     },
1719//                 }),
1720//             )
1721//             .await;
1722
1723//         let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
1724//         let workspace = cx
1725//             .add_window(|cx| Workspace::test_new(project.clone(), cx))
1726//             .root(cx);
1727//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
1728
1729//         let entries = cx.read(|cx| workspace.file_project_paths(cx));
1730//         let file1 = entries[0].clone();
1731//         let file2 = entries[1].clone();
1732//         let file3 = entries[2].clone();
1733
1734//         let editor1 = workspace
1735//             .update(cx, |w, cx| w.open_path(file1.clone(), None, true, cx))
1736//             .await
1737//             .unwrap()
1738//             .downcast::<Editor>()
1739//             .unwrap();
1740//         editor1.update(cx, |editor, cx| {
1741//             editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
1742//                 s.select_display_ranges([DisplayPoint::new(10, 0)..DisplayPoint::new(10, 0)])
1743//             });
1744//         });
1745//         let editor2 = workspace
1746//             .update(cx, |w, cx| w.open_path(file2.clone(), None, true, cx))
1747//             .await
1748//             .unwrap()
1749//             .downcast::<Editor>()
1750//             .unwrap();
1751//         let editor3 = workspace
1752//             .update(cx, |w, cx| w.open_path(file3.clone(), None, true, cx))
1753//             .await
1754//             .unwrap()
1755//             .downcast::<Editor>()
1756//             .unwrap();
1757
1758//         editor3
1759//             .update(cx, |editor, cx| {
1760//                 editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
1761//                     s.select_display_ranges([DisplayPoint::new(12, 0)..DisplayPoint::new(12, 0)])
1762//                 });
1763//                 editor.newline(&Default::default(), cx);
1764//                 editor.newline(&Default::default(), cx);
1765//                 editor.move_down(&Default::default(), cx);
1766//                 editor.move_down(&Default::default(), cx);
1767//                 editor.save(project.clone(), cx)
1768//             })
1769//             .await
1770//             .unwrap();
1771//         editor3.update(cx, |editor, cx| {
1772//             editor.set_scroll_position(vec2f(0., 12.5), cx)
1773//         });
1774//         assert_eq!(
1775//             active_location(&workspace, cx),
1776//             (file3.clone(), DisplayPoint::new(16, 0), 12.5)
1777//         );
1778
1779//         workspace
1780//             .update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
1781//             .await
1782//             .unwrap();
1783//         assert_eq!(
1784//             active_location(&workspace, cx),
1785//             (file3.clone(), DisplayPoint::new(0, 0), 0.)
1786//         );
1787
1788//         workspace
1789//             .update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
1790//             .await
1791//             .unwrap();
1792//         assert_eq!(
1793//             active_location(&workspace, cx),
1794//             (file2.clone(), DisplayPoint::new(0, 0), 0.)
1795//         );
1796
1797//         workspace
1798//             .update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
1799//             .await
1800//             .unwrap();
1801//         assert_eq!(
1802//             active_location(&workspace, cx),
1803//             (file1.clone(), DisplayPoint::new(10, 0), 0.)
1804//         );
1805
1806//         workspace
1807//             .update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
1808//             .await
1809//             .unwrap();
1810//         assert_eq!(
1811//             active_location(&workspace, cx),
1812//             (file1.clone(), DisplayPoint::new(0, 0), 0.)
1813//         );
1814
1815//         // Go back one more time and ensure we don't navigate past the first item in the history.
1816//         workspace
1817//             .update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
1818//             .await
1819//             .unwrap();
1820//         assert_eq!(
1821//             active_location(&workspace, cx),
1822//             (file1.clone(), DisplayPoint::new(0, 0), 0.)
1823//         );
1824
1825//         workspace
1826//             .update(cx, |w, cx| w.go_forward(w.active_pane().downgrade(), cx))
1827//             .await
1828//             .unwrap();
1829//         assert_eq!(
1830//             active_location(&workspace, cx),
1831//             (file1.clone(), DisplayPoint::new(10, 0), 0.)
1832//         );
1833
1834//         workspace
1835//             .update(cx, |w, cx| w.go_forward(w.active_pane().downgrade(), cx))
1836//             .await
1837//             .unwrap();
1838//         assert_eq!(
1839//             active_location(&workspace, cx),
1840//             (file2.clone(), DisplayPoint::new(0, 0), 0.)
1841//         );
1842
1843//         // Go forward to an item that has been closed, ensuring it gets re-opened at the same
1844//         // location.
1845//         pane.update(cx, |pane, cx| {
1846//             let editor3_id = editor3.id();
1847//             drop(editor3);
1848//             pane.close_item_by_id(editor3_id, SaveIntent::Close, cx)
1849//         })
1850//         .await
1851//         .unwrap();
1852//         workspace
1853//             .update(cx, |w, cx| w.go_forward(w.active_pane().downgrade(), cx))
1854//             .await
1855//             .unwrap();
1856//         assert_eq!(
1857//             active_location(&workspace, cx),
1858//             (file3.clone(), DisplayPoint::new(0, 0), 0.)
1859//         );
1860
1861//         workspace
1862//             .update(cx, |w, cx| w.go_forward(w.active_pane().downgrade(), cx))
1863//             .await
1864//             .unwrap();
1865//         assert_eq!(
1866//             active_location(&workspace, cx),
1867//             (file3.clone(), DisplayPoint::new(16, 0), 12.5)
1868//         );
1869
1870//         workspace
1871//             .update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
1872//             .await
1873//             .unwrap();
1874//         assert_eq!(
1875//             active_location(&workspace, cx),
1876//             (file3.clone(), DisplayPoint::new(0, 0), 0.)
1877//         );
1878
1879//         // Go back to an item that has been closed and removed from disk, ensuring it gets skipped.
1880//         pane.update(cx, |pane, cx| {
1881//             let editor2_id = editor2.id();
1882//             drop(editor2);
1883//             pane.close_item_by_id(editor2_id, SaveIntent::Close, cx)
1884//         })
1885//         .await
1886//         .unwrap();
1887//         app_state
1888//             .fs
1889//             .remove_file(Path::new("/root/a/file2"), Default::default())
1890//             .await
1891//             .unwrap();
1892//         cx.foreground().run_until_parked();
1893
1894//         workspace
1895//             .update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
1896//             .await
1897//             .unwrap();
1898//         assert_eq!(
1899//             active_location(&workspace, cx),
1900//             (file1.clone(), DisplayPoint::new(10, 0), 0.)
1901//         );
1902//         workspace
1903//             .update(cx, |w, cx| w.go_forward(w.active_pane().downgrade(), cx))
1904//             .await
1905//             .unwrap();
1906//         assert_eq!(
1907//             active_location(&workspace, cx),
1908//             (file3.clone(), DisplayPoint::new(0, 0), 0.)
1909//         );
1910
1911//         // Modify file to collapse multiple nav history entries into the same location.
1912//         // Ensure we don't visit the same location twice when navigating.
1913//         editor1.update(cx, |editor, cx| {
1914//             editor.change_selections(None, cx, |s| {
1915//                 s.select_display_ranges([DisplayPoint::new(15, 0)..DisplayPoint::new(15, 0)])
1916//             })
1917//         });
1918
1919//         for _ in 0..5 {
1920//             editor1.update(cx, |editor, cx| {
1921//                 editor.change_selections(None, cx, |s| {
1922//                     s.select_display_ranges([DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)])
1923//                 });
1924//             });
1925//             editor1.update(cx, |editor, cx| {
1926//                 editor.change_selections(None, cx, |s| {
1927//                     s.select_display_ranges([DisplayPoint::new(13, 0)..DisplayPoint::new(13, 0)])
1928//                 })
1929//             });
1930//         }
1931
1932//         editor1.update(cx, |editor, cx| {
1933//             editor.transact(cx, |editor, cx| {
1934//                 editor.change_selections(None, cx, |s| {
1935//                     s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(14, 0)])
1936//                 });
1937//                 editor.insert("", cx);
1938//             })
1939//         });
1940
1941//         editor1.update(cx, |editor, cx| {
1942//             editor.change_selections(None, cx, |s| {
1943//                 s.select_display_ranges([DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)])
1944//             })
1945//         });
1946//         workspace
1947//             .update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
1948//             .await
1949//             .unwrap();
1950//         assert_eq!(
1951//             active_location(&workspace, cx),
1952//             (file1.clone(), DisplayPoint::new(2, 0), 0.)
1953//         );
1954//         workspace
1955//             .update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
1956//             .await
1957//             .unwrap();
1958//         assert_eq!(
1959//             active_location(&workspace, cx),
1960//             (file1.clone(), DisplayPoint::new(3, 0), 0.)
1961//         );
1962
1963//         fn active_location(
1964//             workspace: &ViewHandle<Workspace>,
1965//             cx: &mut TestAppContext,
1966//         ) -> (ProjectPath, DisplayPoint, f32) {
1967//             workspace.update(cx, |workspace, cx| {
1968//                 let item = workspace.active_item(cx).unwrap();
1969//                 let editor = item.downcast::<Editor>().unwrap();
1970//                 let (selections, scroll_position) = editor.update(cx, |editor, cx| {
1971//                     (
1972//                         editor.selections.display_ranges(cx),
1973//                         editor.scroll_position(cx),
1974//                     )
1975//                 });
1976//                 (
1977//                     item.project_path(cx).unwrap(),
1978//                     selections[0].start,
1979//                     scroll_position.y(),
1980//                 )
1981//             })
1982//         }
1983//     }
1984
1985//     #[gpui::test]
1986//     async fn test_reopening_closed_items(cx: &mut TestAppContext) {
1987//         let app_state = init_test(cx);
1988//         app_state
1989//             .fs
1990//             .as_fake()
1991//             .insert_tree(
1992//                 "/root",
1993//                 json!({
1994//                     "a": {
1995//                         "file1": "",
1996//                         "file2": "",
1997//                         "file3": "",
1998//                         "file4": "",
1999//                     },
2000//                 }),
2001//             )
2002//             .await;
2003
2004//         let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
2005//         let workspace = cx
2006//             .add_window(|cx| Workspace::test_new(project, cx))
2007//             .root(cx);
2008//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
2009
2010//         let entries = cx.read(|cx| workspace.file_project_paths(cx));
2011//         let file1 = entries[0].clone();
2012//         let file2 = entries[1].clone();
2013//         let file3 = entries[2].clone();
2014//         let file4 = entries[3].clone();
2015
2016//         let file1_item_id = workspace
2017//             .update(cx, |w, cx| w.open_path(file1.clone(), None, true, cx))
2018//             .await
2019//             .unwrap()
2020//             .id();
2021//         let file2_item_id = workspace
2022//             .update(cx, |w, cx| w.open_path(file2.clone(), None, true, cx))
2023//             .await
2024//             .unwrap()
2025//             .id();
2026//         let file3_item_id = workspace
2027//             .update(cx, |w, cx| w.open_path(file3.clone(), None, true, cx))
2028//             .await
2029//             .unwrap()
2030//             .id();
2031//         let file4_item_id = workspace
2032//             .update(cx, |w, cx| w.open_path(file4.clone(), None, true, cx))
2033//             .await
2034//             .unwrap()
2035//             .id();
2036//         assert_eq!(active_path(&workspace, cx), Some(file4.clone()));
2037
2038//         // Close all the pane items in some arbitrary order.
2039//         pane.update(cx, |pane, cx| {
2040//             pane.close_item_by_id(file1_item_id, SaveIntent::Close, cx)
2041//         })
2042//         .await
2043//         .unwrap();
2044//         assert_eq!(active_path(&workspace, cx), Some(file4.clone()));
2045
2046//         pane.update(cx, |pane, cx| {
2047//             pane.close_item_by_id(file4_item_id, SaveIntent::Close, cx)
2048//         })
2049//         .await
2050//         .unwrap();
2051//         assert_eq!(active_path(&workspace, cx), Some(file3.clone()));
2052
2053//         pane.update(cx, |pane, cx| {
2054//             pane.close_item_by_id(file2_item_id, SaveIntent::Close, cx)
2055//         })
2056//         .await
2057//         .unwrap();
2058//         assert_eq!(active_path(&workspace, cx), Some(file3.clone()));
2059
2060//         pane.update(cx, |pane, cx| {
2061//             pane.close_item_by_id(file3_item_id, SaveIntent::Close, cx)
2062//         })
2063//         .await
2064//         .unwrap();
2065//         assert_eq!(active_path(&workspace, cx), None);
2066
2067//         // Reopen all the closed items, ensuring they are reopened in the same order
2068//         // in which they were closed.
2069//         workspace
2070//             .update(cx, Workspace::reopen_closed_item)
2071//             .await
2072//             .unwrap();
2073//         assert_eq!(active_path(&workspace, cx), Some(file3.clone()));
2074
2075//         workspace
2076//             .update(cx, Workspace::reopen_closed_item)
2077//             .await
2078//             .unwrap();
2079//         assert_eq!(active_path(&workspace, cx), Some(file2.clone()));
2080
2081//         workspace
2082//             .update(cx, Workspace::reopen_closed_item)
2083//             .await
2084//             .unwrap();
2085//         assert_eq!(active_path(&workspace, cx), Some(file4.clone()));
2086
2087//         workspace
2088//             .update(cx, Workspace::reopen_closed_item)
2089//             .await
2090//             .unwrap();
2091//         assert_eq!(active_path(&workspace, cx), Some(file1.clone()));
2092
2093//         // Reopening past the last closed item is a no-op.
2094//         workspace
2095//             .update(cx, Workspace::reopen_closed_item)
2096//             .await
2097//             .unwrap();
2098//         assert_eq!(active_path(&workspace, cx), Some(file1.clone()));
2099
2100//         // Reopening closed items doesn't interfere with navigation history.
2101//         workspace
2102//             .update(cx, |workspace, cx| {
2103//                 workspace.go_back(workspace.active_pane().downgrade(), cx)
2104//             })
2105//             .await
2106//             .unwrap();
2107//         assert_eq!(active_path(&workspace, cx), Some(file4.clone()));
2108
2109//         workspace
2110//             .update(cx, |workspace, cx| {
2111//                 workspace.go_back(workspace.active_pane().downgrade(), cx)
2112//             })
2113//             .await
2114//             .unwrap();
2115//         assert_eq!(active_path(&workspace, cx), Some(file2.clone()));
2116
2117//         workspace
2118//             .update(cx, |workspace, cx| {
2119//                 workspace.go_back(workspace.active_pane().downgrade(), cx)
2120//             })
2121//             .await
2122//             .unwrap();
2123//         assert_eq!(active_path(&workspace, cx), Some(file3.clone()));
2124
2125//         workspace
2126//             .update(cx, |workspace, cx| {
2127//                 workspace.go_back(workspace.active_pane().downgrade(), cx)
2128//             })
2129//             .await
2130//             .unwrap();
2131//         assert_eq!(active_path(&workspace, cx), Some(file4.clone()));
2132
2133//         workspace
2134//             .update(cx, |workspace, cx| {
2135//                 workspace.go_back(workspace.active_pane().downgrade(), cx)
2136//             })
2137//             .await
2138//             .unwrap();
2139//         assert_eq!(active_path(&workspace, cx), Some(file3.clone()));
2140
2141//         workspace
2142//             .update(cx, |workspace, cx| {
2143//                 workspace.go_back(workspace.active_pane().downgrade(), cx)
2144//             })
2145//             .await
2146//             .unwrap();
2147//         assert_eq!(active_path(&workspace, cx), Some(file2.clone()));
2148
2149//         workspace
2150//             .update(cx, |workspace, cx| {
2151//                 workspace.go_back(workspace.active_pane().downgrade(), cx)
2152//             })
2153//             .await
2154//             .unwrap();
2155//         assert_eq!(active_path(&workspace, cx), Some(file1.clone()));
2156
2157//         workspace
2158//             .update(cx, |workspace, cx| {
2159//                 workspace.go_back(workspace.active_pane().downgrade(), cx)
2160//             })
2161//             .await
2162//             .unwrap();
2163//         assert_eq!(active_path(&workspace, cx), Some(file1.clone()));
2164
2165//         fn active_path(
2166//             workspace: &ViewHandle<Workspace>,
2167//             cx: &TestAppContext,
2168//         ) -> Option<ProjectPath> {
2169//             workspace.read_with(cx, |workspace, cx| {
2170//                 let item = workspace.active_item(cx)?;
2171//                 item.project_path(cx)
2172//             })
2173//         }
2174//     }
2175
2176//     #[gpui::test]
2177//     async fn test_base_keymap(cx: &mut gpui::TestAppContext) {
2178//         struct TestView;
2179
2180//         impl Entity for TestView {
2181//             type Event = ();
2182//         }
2183
2184//         impl View for TestView {
2185//             fn ui_name() -> &'static str {
2186//                 "TestView"
2187//             }
2188
2189//             fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
2190//                 Empty::new().into_any()
2191//             }
2192//         }
2193
2194//         let executor = cx.background();
2195//         let fs = FakeFs::new(executor.clone());
2196
2197//         actions!(test, [A, B]);
2198//         // From the Atom keymap
2199//         actions!(workspace, [ActivatePreviousPane]);
2200//         // From the JetBrains keymap
2201//         actions!(pane, [ActivatePrevItem]);
2202
2203//         fs.save(
2204//             "/settings.json".as_ref(),
2205//             &r#"
2206//             {
2207//                 "base_keymap": "Atom"
2208//             }
2209//             "#
2210//             .into(),
2211//             Default::default(),
2212//         )
2213//         .await
2214//         .unwrap();
2215
2216//         fs.save(
2217//             "/keymap.json".as_ref(),
2218//             &r#"
2219//             [
2220//                 {
2221//                     "bindings": {
2222//                         "backspace": "test::A"
2223//                     }
2224//                 }
2225//             ]
2226//             "#
2227//             .into(),
2228//             Default::default(),
2229//         )
2230//         .await
2231//         .unwrap();
2232
2233//         cx.update(|cx| {
2234//             cx.set_global(SettingsStore::test(cx));
2235//             theme::init(Assets, cx);
2236//             welcome::init(cx);
2237
2238//             cx.add_global_action(|_: &A, _cx| {});
2239//             cx.add_global_action(|_: &B, _cx| {});
2240//             cx.add_global_action(|_: &ActivatePreviousPane, _cx| {});
2241//             cx.add_global_action(|_: &ActivatePrevItem, _cx| {});
2242
2243//             let settings_rx = watch_config_file(
2244//                 executor.clone(),
2245//                 fs.clone(),
2246//                 PathBuf::from("/settings.json"),
2247//             );
2248//             let keymap_rx =
2249//                 watch_config_file(executor.clone(), fs.clone(), PathBuf::from("/keymap.json"));
2250
2251//             handle_keymap_file_changes(keymap_rx, cx);
2252//             handle_settings_file_changes(settings_rx, cx);
2253//         });
2254
2255//         cx.foreground().run_until_parked();
2256
2257//         let window = cx.add_window(|_| TestView);
2258
2259//         // Test loading the keymap base at all
2260//         assert_key_bindings_for(
2261//             window.into(),
2262//             cx,
2263//             vec![("backspace", &A), ("k", &ActivatePreviousPane)],
2264//             line!(),
2265//         );
2266
2267//         // Test modifying the users keymap, while retaining the base keymap
2268//         fs.save(
2269//             "/keymap.json".as_ref(),
2270//             &r#"
2271//             [
2272//                 {
2273//                     "bindings": {
2274//                         "backspace": "test::B"
2275//                     }
2276//                 }
2277//             ]
2278//             "#
2279//             .into(),
2280//             Default::default(),
2281//         )
2282//         .await
2283//         .unwrap();
2284
2285//         cx.foreground().run_until_parked();
2286
2287//         assert_key_bindings_for(
2288//             window.into(),
2289//             cx,
2290//             vec![("backspace", &B), ("k", &ActivatePreviousPane)],
2291//             line!(),
2292//         );
2293
2294//         // Test modifying the base, while retaining the users keymap
2295//         fs.save(
2296//             "/settings.json".as_ref(),
2297//             &r#"
2298//             {
2299//                 "base_keymap": "JetBrains"
2300//             }
2301//             "#
2302//             .into(),
2303//             Default::default(),
2304//         )
2305//         .await
2306//         .unwrap();
2307
2308//         cx.foreground().run_until_parked();
2309
2310//         assert_key_bindings_for(
2311//             window.into(),
2312//             cx,
2313//             vec![("backspace", &B), ("[", &ActivatePrevItem)],
2314//             line!(),
2315//         );
2316
2317//         #[track_caller]
2318//         fn assert_key_bindings_for<'a>(
2319//             window: AnyWindowHandle,
2320//             cx: &TestAppContext,
2321//             actions: Vec<(&'static str, &'a dyn Action)>,
2322//             line: u32,
2323//         ) {
2324//             for (key, action) in actions {
2325//                 // assert that...
2326//                 assert!(
2327//                     cx.available_actions(window, 0)
2328//                         .into_iter()
2329//                         .any(|(_, bound_action, b)| {
2330//                             // action names match...
2331//                             bound_action.name() == action.name()
2332//                         && bound_action.namespace() == action.namespace()
2333//                         // and key strokes contain the given key
2334//                         && b.iter()
2335//                             .any(|binding| binding.keystrokes().iter().any(|k| k.key == key))
2336//                         }),
2337//                     "On {} Failed to find {} with key binding {}",
2338//                     line,
2339//                     action.name(),
2340//                     key
2341//                 );
2342//             }
2343//         }
2344//     }
2345
2346//     #[gpui::test]
2347//     async fn test_disabled_keymap_binding(cx: &mut gpui::TestAppContext) {
2348//         struct TestView;
2349
2350//         impl Entity for TestView {
2351//             type Event = ();
2352//         }
2353
2354//         impl View for TestView {
2355//             fn ui_name() -> &'static str {
2356//                 "TestView"
2357//             }
2358
2359//             fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
2360//                 Empty::new().into_any()
2361//             }
2362//         }
2363
2364//         let executor = cx.background();
2365//         let fs = FakeFs::new(executor.clone());
2366
2367//         actions!(test, [A, B]);
2368//         // From the Atom keymap
2369//         actions!(workspace, [ActivatePreviousPane]);
2370//         // From the JetBrains keymap
2371//         actions!(pane, [ActivatePrevItem]);
2372
2373//         fs.save(
2374//             "/settings.json".as_ref(),
2375//             &r#"
2376//             {
2377//                 "base_keymap": "Atom"
2378//             }
2379//             "#
2380//             .into(),
2381//             Default::default(),
2382//         )
2383//         .await
2384//         .unwrap();
2385
2386//         fs.save(
2387//             "/keymap.json".as_ref(),
2388//             &r#"
2389//             [
2390//                 {
2391//                     "bindings": {
2392//                         "backspace": "test::A"
2393//                     }
2394//                 }
2395//             ]
2396//             "#
2397//             .into(),
2398//             Default::default(),
2399//         )
2400//         .await
2401//         .unwrap();
2402
2403//         cx.update(|cx| {
2404//             cx.set_global(SettingsStore::test(cx));
2405//             theme::init(Assets, cx);
2406//             welcome::init(cx);
2407
2408//             cx.add_global_action(|_: &A, _cx| {});
2409//             cx.add_global_action(|_: &B, _cx| {});
2410//             cx.add_global_action(|_: &ActivatePreviousPane, _cx| {});
2411//             cx.add_global_action(|_: &ActivatePrevItem, _cx| {});
2412
2413//             let settings_rx = watch_config_file(
2414//                 executor.clone(),
2415//                 fs.clone(),
2416//                 PathBuf::from("/settings.json"),
2417//             );
2418//             let keymap_rx =
2419//                 watch_config_file(executor.clone(), fs.clone(), PathBuf::from("/keymap.json"));
2420
2421//             handle_keymap_file_changes(keymap_rx, cx);
2422//             handle_settings_file_changes(settings_rx, cx);
2423//         });
2424
2425//         cx.foreground().run_until_parked();
2426
2427//         let window = cx.add_window(|_| TestView);
2428
2429//         // Test loading the keymap base at all
2430//         assert_key_bindings_for(
2431//             window.into(),
2432//             cx,
2433//             vec![("backspace", &A), ("k", &ActivatePreviousPane)],
2434//             line!(),
2435//         );
2436
2437//         // Test disabling the key binding for the base keymap
2438//         fs.save(
2439//             "/keymap.json".as_ref(),
2440//             &r#"
2441//             [
2442//                 {
2443//                     "bindings": {
2444//                         "backspace": null
2445//                     }
2446//                 }
2447//             ]
2448//             "#
2449//             .into(),
2450//             Default::default(),
2451//         )
2452//         .await
2453//         .unwrap();
2454
2455//         cx.foreground().run_until_parked();
2456
2457//         assert_key_bindings_for(
2458//             window.into(),
2459//             cx,
2460//             vec![("k", &ActivatePreviousPane)],
2461//             line!(),
2462//         );
2463
2464//         // Test modifying the base, while retaining the users keymap
2465//         fs.save(
2466//             "/settings.json".as_ref(),
2467//             &r#"
2468//             {
2469//                 "base_keymap": "JetBrains"
2470//             }
2471//             "#
2472//             .into(),
2473//             Default::default(),
2474//         )
2475//         .await
2476//         .unwrap();
2477
2478//         cx.foreground().run_until_parked();
2479
2480//         assert_key_bindings_for(window.into(), cx, vec![("[", &ActivatePrevItem)], line!());
2481
2482//         #[track_caller]
2483//         fn assert_key_bindings_for<'a>(
2484//             window: AnyWindowHandle,
2485//             cx: &TestAppContext,
2486//             actions: Vec<(&'static str, &'a dyn Action)>,
2487//             line: u32,
2488//         ) {
2489//             for (key, action) in actions {
2490//                 // assert that...
2491//                 assert!(
2492//                     cx.available_actions(window, 0)
2493//                         .into_iter()
2494//                         .any(|(_, bound_action, b)| {
2495//                             // action names match...
2496//                             bound_action.name() == action.name()
2497//                         && bound_action.namespace() == action.namespace()
2498//                         // and key strokes contain the given key
2499//                         && b.iter()
2500//                             .any(|binding| binding.keystrokes().iter().any(|k| k.key == key))
2501//                         }),
2502//                     "On {} Failed to find {} with key binding {}",
2503//                     line,
2504//                     action.name(),
2505//                     key
2506//                 );
2507//             }
2508//         }
2509//     }
2510
2511//     #[gpui::test]
2512//     fn test_bundled_settings_and_themes(cx: &mut AppContext) {
2513//         cx.platform()
2514//             .fonts()
2515//             .add_fonts(&[
2516//                 Assets
2517//                     .load("fonts/zed-sans/zed-sans-extended.ttf")
2518//                     .unwrap()
2519//                     .to_vec()
2520//                     .into(),
2521//                 Assets
2522//                     .load("fonts/zed-mono/zed-mono-extended.ttf")
2523//                     .unwrap()
2524//                     .to_vec()
2525//                     .into(),
2526//                 Assets
2527//                     .load("fonts/plex/IBMPlexSans-Regular.ttf")
2528//                     .unwrap()
2529//                     .to_vec()
2530//                     .into(),
2531//             ])
2532//             .unwrap();
2533//         let themes = ThemeRegistry::new(Assets, cx.font_cache().clone());
2534//         let mut settings = SettingsStore::default();
2535//         settings
2536//             .set_default_settings(&settings::default_settings(), cx)
2537//             .unwrap();
2538//         cx.set_global(settings);
2539//         theme::init(Assets, cx);
2540
2541//         let mut has_default_theme = false;
2542//         for theme_name in themes.list(false).map(|meta| meta.name) {
2543//             let theme = themes.get(&theme_name).unwrap();
2544//             assert_eq!(theme.meta.name, theme_name);
2545//             if theme.meta.name == settings::get::<ThemeSettings>(cx).theme.meta.name {
2546//                 has_default_theme = true;
2547//             }
2548//         }
2549//         assert!(has_default_theme);
2550//     }
2551
2552//     #[gpui::test]
2553//     fn test_bundled_languages(cx: &mut AppContext) {
2554//         cx.set_global(SettingsStore::test(cx));
2555//         let mut languages = LanguageRegistry::test();
2556//         languages.set_executor(cx.background().clone());
2557//         let languages = Arc::new(languages);
2558//         let node_runtime = node_runtime::FakeNodeRuntime::new();
2559//         languages::init(languages.clone(), node_runtime, cx);
2560//         for name in languages.language_names() {
2561//             languages.language_for_name(&name);
2562//         }
2563//         cx.foreground().run_until_parked();
2564//     }
2565
2566//     fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
2567//         cx.foreground().forbid_parking();
2568//         cx.update(|cx| {
2569//             let mut app_state = AppState::test(cx);
2570//             let state = Arc::get_mut(&mut app_state).unwrap();
2571//             state.initialize_workspace = initialize_workspace;
2572//             state.build_window_options = build_window_options;
2573//             theme::init((), cx);
2574//             audio::init((), cx);
2575//             channel::init(&app_state.client, app_state.user_store.clone(), cx);
2576//             call::init(app_state.client.clone(), app_state.user_store.clone(), cx);
2577//             notifications::init(app_state.client.clone(), app_state.user_store.clone(), cx);
2578//             workspace::init(app_state.clone(), cx);
2579//             Project::init_settings(cx);
2580//             language::init(cx);
2581//             editor::init(cx);
2582//             project_panel::init_settings(cx);
2583//             collab_ui::init(&app_state, cx);
2584//             pane::init(cx);
2585//             project_panel::init((), cx);
2586//             terminal_view::init(cx);
2587//             assistant::init(cx);
2588//             app_state
2589//         })
2590//     }
2591
2592//     fn rust_lang() -> Arc<language::Language> {
2593//         Arc::new(language::Language::new(
2594//             language::LanguageConfig {
2595//                 name: "Rust".into(),
2596//                 path_suffixes: vec!["rs".to_string()],
2597//                 ..Default::default()
2598//             },
2599//             Some(tree_sitter_rust::language()),
2600//         ))
2601//     }
2602// }