zed2.rs

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