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