zed2.rs

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