zed2.rs

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