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