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