zed2.rs

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