mirror of
https://github.com/zed-industries/zed.git
synced 2026-06-01 03:14:56 +07:00
Make project panel to auto reveal multi buffer excerpts with latest selection (#57236)
Make non-singleton editors to return project paths by adding a `fn active_project_path`: this had been added as `fn project_path` and similar already, so the PR replaced those methods with the generic one now. Before: https://github.com/user-attachments/assets/d0773e18-3910-4c5b-bcb3-a742f9bf9691 After: https://github.com/user-attachments/assets/e7a3f13e-9649-4564-a7e6-dccf54f8c000 Release Notes: - Made project panel to auto reveal multi buffer excerpts with latest selection
This commit is contained in:
parent
13e7c11768
commit
d3a9fd96a3
22 changed files with 359 additions and 72 deletions
|
|
@ -566,6 +566,10 @@ impl Item for AgentDiffPane {
|
|||
.for_each_project_item(cx, f)
|
||||
}
|
||||
|
||||
fn active_project_path(&self, cx: &App) -> Option<ProjectPath> {
|
||||
self.editor.read(cx).active_project_path(cx)
|
||||
}
|
||||
|
||||
fn set_nav_history(
|
||||
&mut self,
|
||||
nav_history: ItemNavHistory,
|
||||
|
|
@ -2253,7 +2257,7 @@ mod tests {
|
|||
});
|
||||
|
||||
let editor2_path = editor2
|
||||
.read_with(cx, |editor, cx| editor.project_path(cx))
|
||||
.read_with(cx, |editor, cx| editor.active_project_path(cx))
|
||||
.unwrap();
|
||||
assert_eq!(editor2_path, buffer_path2);
|
||||
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ use settings::SettingsStore;
|
|||
use text::{Point, ToPoint};
|
||||
use util::{path, rel_path::rel_path, test::sample_text};
|
||||
use workspace::{
|
||||
CloseWindow, CollaboratorId, MultiWorkspace, ParticipantLocation, SplitDirection, Workspace,
|
||||
item::ItemHandle as _,
|
||||
CloseWindow, CollaboratorId, Item, MultiWorkspace, ParticipantLocation, SplitDirection,
|
||||
Workspace, item::ItemHandle as _,
|
||||
};
|
||||
|
||||
use super::TestClient;
|
||||
|
|
@ -154,7 +154,7 @@ async fn test_basic_following(
|
|||
.unwrap()
|
||||
});
|
||||
assert_eq!(
|
||||
cx_b.read(|cx| editor_b2.project_path(cx)),
|
||||
cx_b.read(|cx| editor_b2.read(cx).active_project_path(cx)),
|
||||
Some((worktree_id, rel_path("2.txt")).into())
|
||||
);
|
||||
assert_eq!(
|
||||
|
|
@ -1866,7 +1866,7 @@ async fn test_following_into_excluded_file(
|
|||
.unwrap()
|
||||
});
|
||||
assert_eq!(
|
||||
cx_b.read(|cx| editor_for_excluded_b.project_path(cx)),
|
||||
cx_b.read(|cx| editor_for_excluded_b.read(cx).active_project_path(cx)),
|
||||
Some((worktree_id, rel_path(".git/COMMIT_EDITMSG")).into())
|
||||
);
|
||||
assert_eq!(
|
||||
|
|
|
|||
|
|
@ -1962,7 +1962,7 @@ async fn test_breakpoint_jumps_only_in_proper_split_view(
|
|||
.read_with(cx, |_multi, cx| {
|
||||
let active = pane_a.read(cx).active_item().unwrap();
|
||||
let editor = active.to_any_view().downcast::<Editor>().unwrap();
|
||||
let path = editor.read(cx).project_path(cx).unwrap();
|
||||
let path = editor.read(cx).active_project_path(cx).unwrap();
|
||||
assert_eq!(
|
||||
path.path.file_name().unwrap(),
|
||||
"second.rs",
|
||||
|
|
@ -1976,7 +1976,7 @@ async fn test_breakpoint_jumps_only_in_proper_split_view(
|
|||
.read_with(cx, |_multi, cx| {
|
||||
let active = pane_b.read(cx).active_item().unwrap();
|
||||
let editor = active.to_any_view().downcast::<Editor>().unwrap();
|
||||
let path = editor.read(cx).project_path(cx).unwrap();
|
||||
let path = editor.read(cx).active_project_path(cx).unwrap();
|
||||
assert_eq!(
|
||||
path.path.file_name().unwrap(),
|
||||
"main.rs",
|
||||
|
|
@ -2056,7 +2056,7 @@ async fn test_breakpoint_jumps_only_in_proper_split_view(
|
|||
.read_with(cx, |_multi, cx| {
|
||||
let pane_a_active = pane_a.read(cx).active_item().unwrap();
|
||||
let pane_a_editor = pane_a_active.to_any_view().downcast::<Editor>().unwrap();
|
||||
let pane_a_path = pane_a_editor.read(cx).project_path(cx).unwrap();
|
||||
let pane_a_path = pane_a_editor.read(cx).active_project_path(cx).unwrap();
|
||||
assert_eq!(
|
||||
pane_a_path.path.file_name().unwrap(),
|
||||
"second.rs",
|
||||
|
|
@ -2161,7 +2161,7 @@ async fn test_breakpoint_jumps_only_in_proper_split_view(
|
|||
.read_with(cx, |_multi, cx| {
|
||||
let pane_b_active = pane_b.read(cx).active_item().unwrap();
|
||||
let pane_b_editor = pane_b_active.to_any_view().downcast::<Editor>().unwrap();
|
||||
let pane_b_path = pane_b_editor.read(cx).project_path(cx).unwrap();
|
||||
let pane_b_path = pane_b_editor.read(cx).active_project_path(cx).unwrap();
|
||||
assert_eq!(
|
||||
pane_b_path.path.file_name().unwrap(),
|
||||
"second.rs",
|
||||
|
|
@ -2232,7 +2232,7 @@ async fn test_breakpoint_jumps_only_in_proper_split_view(
|
|||
.read_with(cx, |_multi, cx| {
|
||||
let pane_c_active = pane_c.read(cx).active_item().unwrap();
|
||||
let pane_c_editor = pane_c_active.to_any_view().downcast::<Editor>().unwrap();
|
||||
let pane_c_path = pane_c_editor.read(cx).project_path(cx).unwrap();
|
||||
let pane_c_path = pane_c_editor.read(cx).active_project_path(cx).unwrap();
|
||||
assert_eq!(
|
||||
pane_c_path.path.file_name().unwrap(),
|
||||
"second.rs",
|
||||
|
|
@ -2294,7 +2294,7 @@ async fn test_breakpoint_jumps_only_in_proper_split_view(
|
|||
.read_with(cx, |_multi, cx| {
|
||||
let pane_c_active = pane_c.read(cx).active_item().unwrap();
|
||||
let pane_c_editor = pane_c_active.to_any_view().downcast::<Editor>().unwrap();
|
||||
let pane_c_path = pane_c_editor.read(cx).project_path(cx).unwrap();
|
||||
let pane_c_path = pane_c_editor.read(cx).active_project_path(cx).unwrap();
|
||||
assert_eq!(
|
||||
pane_c_path.path.file_name().unwrap(),
|
||||
"main.rs",
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ use serde_json::json;
|
|||
use std::sync::Arc;
|
||||
use unindent::Unindent as _;
|
||||
use util::{path, rel_path::rel_path};
|
||||
use workspace::Item;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_fetch_initial_stack_frames_and_go_to_stack_frame(
|
||||
|
|
@ -334,7 +335,7 @@ async fn test_select_stack_frame(executor: BackgroundExecutor, cx: &mut TestAppC
|
|||
assert_eq!(1, editors.len());
|
||||
|
||||
let project_path = editors[0]
|
||||
.update(cx, |editor, cx| editor.project_path(cx))
|
||||
.update(cx, |editor, cx| editor.active_project_path(cx))
|
||||
.unwrap();
|
||||
assert_eq!(rel_path("src/test.js"), project_path.path.as_ref());
|
||||
assert_eq!(test_file_content, editors[0].read(cx).text(cx));
|
||||
|
|
@ -397,7 +398,7 @@ async fn test_select_stack_frame(executor: BackgroundExecutor, cx: &mut TestAppC
|
|||
assert_eq!(1, editors.len());
|
||||
|
||||
let project_path = editors[0]
|
||||
.update(cx, |editor, cx| editor.project_path(cx))
|
||||
.update(cx, |editor, cx| editor.active_project_path(cx))
|
||||
.unwrap();
|
||||
assert_eq!(rel_path("src/module.js"), project_path.path.as_ref());
|
||||
assert_eq!(module_file_content, editors[0].read(cx).text(cx));
|
||||
|
|
|
|||
|
|
@ -220,7 +220,7 @@ impl BufferDiagnosticsEditor {
|
|||
// If there's no active editor with a project path, avoiding deploying
|
||||
// the buffer diagnostics view.
|
||||
if let Some(editor) = workspace.active_item_as::<Editor>(cx)
|
||||
&& let Some(project_path) = editor.project_path(cx)
|
||||
&& let Some(project_path) = editor.read(cx).active_project_path(cx)
|
||||
{
|
||||
// Check if there's already a `BufferDiagnosticsEditor` tab for this
|
||||
// same path, and if so, focus on that one instead of creating a new
|
||||
|
|
@ -749,6 +749,10 @@ impl Item for BufferDiagnosticsEditor {
|
|||
self.editor.for_each_project_item(cx, f);
|
||||
}
|
||||
|
||||
fn active_project_path(&self, _cx: &App) -> Option<ProjectPath> {
|
||||
Some(self.project_path.clone())
|
||||
}
|
||||
|
||||
fn has_conflict(&self, cx: &App) -> bool {
|
||||
self.multibuffer.read(cx).has_conflict(cx)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -807,6 +807,10 @@ impl Item for ProjectDiagnosticsEditor {
|
|||
self.editor.for_each_project_item(cx, f)
|
||||
}
|
||||
|
||||
fn active_project_path(&self, cx: &App) -> Option<ProjectPath> {
|
||||
self.editor.read(cx).active_project_path(cx)
|
||||
}
|
||||
|
||||
fn set_nav_history(
|
||||
&mut self,
|
||||
nav_history: ItemNavHistory,
|
||||
|
|
|
|||
|
|
@ -2650,16 +2650,6 @@ impl Editor {
|
|||
})
|
||||
}
|
||||
|
||||
/// Returns the project path for the editor's buffer, if any buffer is
|
||||
/// opened in the editor.
|
||||
pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
|
||||
if let Some(buffer) = self.buffer.read(cx).as_singleton() {
|
||||
buffer.read(cx).project_path(cx)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn selection_menu_enabled(&self, cx: &App) -> bool {
|
||||
self.show_selection_menu
|
||||
.unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
|
||||
|
|
|
|||
|
|
@ -28013,7 +28013,7 @@ async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
|
|||
)
|
||||
});
|
||||
|
||||
let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
|
||||
let project_path = editor.update(cx, |editor, cx| editor.active_project_path(cx).unwrap());
|
||||
let abs_path = project.read_with(cx, |project, cx| {
|
||||
project
|
||||
.absolute_path(&project_path, cx)
|
||||
|
|
@ -28163,7 +28163,8 @@ async fn test_breakpoint_after_save_as_existing_path(cx: &mut TestAppContext) {
|
|||
editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
|
||||
});
|
||||
|
||||
let project_path = first_editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
|
||||
let project_path =
|
||||
first_editor.update(cx, |editor, cx| editor.active_project_path(cx).unwrap());
|
||||
let abs_path = project.read_with(cx, |project, cx| {
|
||||
project
|
||||
.absolute_path(&project_path, cx)
|
||||
|
|
@ -28231,7 +28232,7 @@ async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
|
|||
)
|
||||
});
|
||||
|
||||
let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
|
||||
let project_path = editor.update(cx, |editor, cx| editor.active_project_path(cx).unwrap());
|
||||
let abs_path = project.read_with(cx, |project, cx| {
|
||||
project
|
||||
.absolute_path(&project_path, cx)
|
||||
|
|
@ -28402,7 +28403,7 @@ async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
|
|||
)
|
||||
});
|
||||
|
||||
let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
|
||||
let project_path = editor.update(cx, |editor, cx| editor.active_project_path(cx).unwrap());
|
||||
let abs_path = project.read_with(cx, |project, cx| {
|
||||
project
|
||||
.absolute_path(&project_path, cx)
|
||||
|
|
@ -28563,9 +28564,9 @@ impl BookmarkTestContext {
|
|||
}
|
||||
|
||||
fn abs_path(&self) -> Arc<Path> {
|
||||
let project_path = self
|
||||
.editor
|
||||
.read_with(&self.cx, |editor, cx| editor.project_path(cx).unwrap());
|
||||
let project_path = self.editor.read_with(&self.cx, |editor, cx| {
|
||||
editor.active_project_path(cx).unwrap()
|
||||
});
|
||||
self.project.read_with(&self.cx, |project, cx| {
|
||||
project
|
||||
.absolute_path(&project_path, cx)
|
||||
|
|
|
|||
|
|
@ -816,6 +816,10 @@ impl Item for Editor {
|
|||
}
|
||||
}
|
||||
|
||||
fn active_project_path(&self, cx: &App) -> Option<ProjectPath> {
|
||||
self.active_buffer(cx)?.read(cx).project_path(cx)
|
||||
}
|
||||
|
||||
fn can_save_as(&self, cx: &App) -> bool {
|
||||
self.buffer.read(cx).is_singleton()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1756,6 +1756,10 @@ impl Item for SplittableEditor {
|
|||
self.rhs_editor.read(cx).buffer_kind(cx)
|
||||
}
|
||||
|
||||
fn active_project_path(&self, cx: &App) -> Option<project::ProjectPath> {
|
||||
self.rhs_editor.read(cx).active_project_path(cx)
|
||||
}
|
||||
|
||||
fn is_dirty(&self, cx: &App) -> bool {
|
||||
self.rhs_editor.read(cx).is_dirty(cx)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ use serde_json::json;
|
|||
use settings::SettingsStore;
|
||||
use util::{path, rel_path::rel_path};
|
||||
use workspace::{
|
||||
AppState, CloseActiveItem, MultiWorkspace, OpenOptions, ToggleFileFinder, Workspace, open_paths,
|
||||
AppState, CloseActiveItem, Item, MultiWorkspace, OpenOptions, ToggleFileFinder, Workspace,
|
||||
open_paths,
|
||||
};
|
||||
|
||||
#[ctor::ctor]
|
||||
|
|
@ -1540,7 +1541,7 @@ async fn test_create_file_for_multiple_worktrees(cx: &mut TestAppContext) {
|
|||
cx.run_until_parked();
|
||||
cx.read(|cx| {
|
||||
let active_editor = workspace.read(cx).active_item_as::<Editor>(cx).unwrap();
|
||||
let project_path = active_editor.read(cx).project_path(cx);
|
||||
let project_path = active_editor.read(cx).active_project_path(cx);
|
||||
assert_eq!(
|
||||
project_path,
|
||||
Some(ProjectPath {
|
||||
|
|
@ -1618,7 +1619,7 @@ async fn test_create_file_focused_file_does_not_belong_to_available_worktrees(
|
|||
cx.read(|cx| {
|
||||
let active_editor = workspace.read(cx).active_item_as::<Editor>(cx).unwrap();
|
||||
|
||||
let project_path = active_editor.read(cx).project_path(cx);
|
||||
let project_path = active_editor.read(cx).active_project_path(cx);
|
||||
|
||||
assert!(
|
||||
project_path.is_some(),
|
||||
|
|
@ -1690,7 +1691,7 @@ async fn test_create_file_no_focused_with_multiple_worktrees(cx: &mut TestAppCon
|
|||
cx.run_until_parked();
|
||||
cx.read(|cx| {
|
||||
let active_editor = workspace.read(cx).active_item_as::<Editor>(cx).unwrap();
|
||||
let project_path = active_editor.read(cx).project_path(cx);
|
||||
let project_path = active_editor.read(cx).active_project_path(cx);
|
||||
assert_eq!(
|
||||
project_path,
|
||||
Some(ProjectPath {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ use language::{
|
|||
Point, ReplicaId, Rope, TextBuffer,
|
||||
};
|
||||
use multi_buffer::PathKey;
|
||||
use project::{Project, WorktreeId, git_store::Repository};
|
||||
use project::{Project, ProjectPath, WorktreeId, git_store::Repository};
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
collections::HashSet,
|
||||
|
|
@ -997,6 +997,10 @@ impl Item for CommitView {
|
|||
self.editor.for_each_project_item(cx, f)
|
||||
}
|
||||
|
||||
fn active_project_path(&self, cx: &App) -> Option<ProjectPath> {
|
||||
self.editor.read(cx).active_project_path(cx)
|
||||
}
|
||||
|
||||
fn set_nav_history(
|
||||
&mut self,
|
||||
nav_history: ItemNavHistory,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use gpui::{
|
|||
Focusable, Font, IntoElement, Render, Task, WeakEntity, Window,
|
||||
};
|
||||
use language::{Buffer, HighlightedText, LanguageRegistry};
|
||||
use project::Project;
|
||||
use project::{Project, ProjectPath};
|
||||
use settings::Settings;
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
|
|
@ -303,6 +303,10 @@ impl Item for FileDiffView {
|
|||
self.editor.for_each_project_item(cx, f)
|
||||
}
|
||||
|
||||
fn active_project_path(&self, cx: &App) -> Option<ProjectPath> {
|
||||
self.editor.read(cx).active_project_path(cx)
|
||||
}
|
||||
|
||||
fn set_nav_history(
|
||||
&mut self,
|
||||
nav_history: ItemNavHistory,
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ use util::paths::PathStyle;
|
|||
use util::{ResultExt, TryFutureExt, markdown::MarkdownInlineCode, maybe, rel_path::RelPath};
|
||||
use workspace::SERIALIZATION_THROTTLE_TIME;
|
||||
use workspace::{
|
||||
Workspace,
|
||||
Item, Workspace,
|
||||
dock::{DockPosition, Panel, PanelEvent},
|
||||
notifications::{DetachAndPromptErr, ErrorMessagePrompt, NotificationId, NotifyResultExt},
|
||||
};
|
||||
|
|
@ -1362,7 +1362,7 @@ impl GitPanel {
|
|||
let git_repo = self.active_repository.as_ref()?;
|
||||
|
||||
if let Some(project_diff) = workspace.read(cx).active_item_as::<ProjectDiff>(cx)
|
||||
&& let Some(project_path) = project_diff.read(cx).active_path(cx)
|
||||
&& let Some(project_path) = project_diff.read(cx).active_project_path(cx)
|
||||
&& Some(&entry.repo_path)
|
||||
== git_repo
|
||||
.read(cx)
|
||||
|
|
@ -8380,8 +8380,8 @@ mod tests {
|
|||
.item_of_type::<ProjectDiff>(cx)
|
||||
.expect("ProjectDiff should exist")
|
||||
.read(cx)
|
||||
.active_path(cx)
|
||||
.expect("active_path should exist");
|
||||
.active_project_path(cx)
|
||||
.expect("active_project_path should exist");
|
||||
|
||||
assert_eq!(active_path.path, rel_path("untracked").into_arc());
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use gpui::{
|
|||
};
|
||||
use language::{Buffer, Capability, HighlightedText, OffsetRangeExt};
|
||||
use multi_buffer::PathKey;
|
||||
use project::Project;
|
||||
use project::{Project, ProjectPath};
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
path::{Path, PathBuf},
|
||||
|
|
@ -313,6 +313,10 @@ impl Item for MultiDiffView {
|
|||
Some(Box::new(self.editor.clone()))
|
||||
}
|
||||
|
||||
fn active_project_path(&self, cx: &App) -> Option<ProjectPath> {
|
||||
self.editor.read(cx).active_project_path(cx)
|
||||
}
|
||||
|
||||
fn set_nav_history(
|
||||
&mut self,
|
||||
nav_history: ItemNavHistory,
|
||||
|
|
|
|||
|
|
@ -544,21 +544,6 @@ impl ProjectDiff {
|
|||
self.move_to_path(path_key, window, cx)
|
||||
}
|
||||
|
||||
pub fn active_path(&self, cx: &App) -> Option<ProjectPath> {
|
||||
let editor = self.editor.read(cx).focused_editor().read(cx);
|
||||
let multibuffer = editor.buffer().read(cx);
|
||||
let position = editor.selections.newest_anchor().head();
|
||||
let snapshot = multibuffer.snapshot(cx);
|
||||
let (text_anchor, _) = snapshot.anchor_to_buffer_anchor(position)?;
|
||||
let buffer = multibuffer.buffer(text_anchor.buffer_id)?;
|
||||
|
||||
let file = buffer.read(cx).file()?;
|
||||
Some(ProjectPath {
|
||||
worktree_id: file.worktree_id(cx),
|
||||
path: file.path().clone(),
|
||||
})
|
||||
}
|
||||
|
||||
fn move_to_beginning(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.rhs_editor().update(cx, |editor, cx| {
|
||||
|
|
@ -675,7 +660,7 @@ impl ProjectDiff {
|
|||
) {
|
||||
match event {
|
||||
EditorEvent::SelectionsChanged { local: true } => {
|
||||
let Some(project_path) = self.active_path(cx) else {
|
||||
let Some(project_path) = self.active_project_path(cx) else {
|
||||
return;
|
||||
};
|
||||
self.workspace
|
||||
|
|
@ -1048,6 +1033,21 @@ impl Item for ProjectDiff {
|
|||
.for_each_project_item(cx, f)
|
||||
}
|
||||
|
||||
fn active_project_path(&self, cx: &App) -> Option<ProjectPath> {
|
||||
let editor = self.editor.read(cx).focused_editor().read(cx);
|
||||
let multibuffer = editor.buffer().read(cx);
|
||||
let position = editor.selections.newest_anchor().head();
|
||||
let snapshot = multibuffer.snapshot(cx);
|
||||
let (text_anchor, _) = snapshot.anchor_to_buffer_anchor(position)?;
|
||||
let buffer = multibuffer.buffer(text_anchor.buffer_id)?;
|
||||
|
||||
let file = buffer.read(cx).file()?;
|
||||
Some(ProjectPath {
|
||||
worktree_id: file.worktree_id(cx),
|
||||
path: file.path().clone(),
|
||||
})
|
||||
}
|
||||
|
||||
fn set_nav_history(
|
||||
&mut self,
|
||||
nav_history: ItemNavHistory,
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ use gpui::{
|
|||
Focusable, IntoElement, Render, Task, Window,
|
||||
};
|
||||
use language::{self, Buffer, OffsetRangeExt, Point};
|
||||
use project::Project;
|
||||
use project::{Project, ProjectPath};
|
||||
use settings::Settings;
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
|
|
@ -377,6 +377,10 @@ impl Item for TextDiffView {
|
|||
self.diff_editor.read(cx).for_each_project_item(cx, f)
|
||||
}
|
||||
|
||||
fn active_project_path(&self, cx: &App) -> Option<ProjectPath> {
|
||||
self.diff_editor.read(cx).active_project_path(cx)
|
||||
}
|
||||
|
||||
fn set_nav_history(
|
||||
&mut self,
|
||||
nav_history: ItemNavHistory,
|
||||
|
|
|
|||
|
|
@ -5467,6 +5467,241 @@ async fn test_explicit_reveal(cx: &mut gpui::TestAppContext) {
|
|||
);
|
||||
}
|
||||
|
||||
/// Mirrors real multi-buffer views (`ProjectDiagnosticsEditor`, `ProjectDiff`,
|
||||
/// etc.): the workspace `Item` is a thin wrapper that holds an inner `Editor`
|
||||
/// and re-emits its events.
|
||||
mod multibuffer_wrapper {
|
||||
use editor::{Editor, EditorEvent};
|
||||
use gpui::{
|
||||
App, Context, Entity, EntityId, EventEmitter, FocusHandle, Focusable, IntoElement,
|
||||
ParentElement, Render, SharedString, Subscription, Window, div,
|
||||
};
|
||||
use workspace::item::{Item, ItemEvent, TabContentParams};
|
||||
|
||||
pub struct TestMultibufferWrapper {
|
||||
pub editor: Entity<Editor>,
|
||||
_subscription: Subscription,
|
||||
}
|
||||
|
||||
impl TestMultibufferWrapper {
|
||||
pub fn new(editor: Entity<Editor>, cx: &mut Context<Self>) -> Self {
|
||||
let _subscription = cx.subscribe(&editor, |_, _, event: &EditorEvent, cx| {
|
||||
cx.emit(event.clone());
|
||||
});
|
||||
Self {
|
||||
editor,
|
||||
_subscription,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<EditorEvent> for TestMultibufferWrapper {}
|
||||
|
||||
impl Focusable for TestMultibufferWrapper {
|
||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||
self.editor.read(cx).focus_handle(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for TestMultibufferWrapper {
|
||||
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
||||
div().child(self.editor.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl Item for TestMultibufferWrapper {
|
||||
type Event = EditorEvent;
|
||||
|
||||
fn tab_content_text(&self, _: usize, _: &App) -> SharedString {
|
||||
"wrapper".into()
|
||||
}
|
||||
|
||||
fn for_each_project_item(
|
||||
&self,
|
||||
cx: &App,
|
||||
f: &mut dyn FnMut(EntityId, &dyn project::ProjectItem),
|
||||
) {
|
||||
self.editor.read(cx).for_each_project_item(cx, f)
|
||||
}
|
||||
|
||||
fn active_project_path(&self, cx: &App) -> Option<project::ProjectPath> {
|
||||
self.editor.read(cx).active_project_path(cx)
|
||||
}
|
||||
|
||||
fn to_item_events(event: &EditorEvent, f: &mut dyn FnMut(ItemEvent)) {
|
||||
Editor::to_item_events(event, f)
|
||||
}
|
||||
|
||||
fn tab_content(&self, params: TabContentParams, _: &Window, cx: &App) -> gpui::AnyElement {
|
||||
ui::Label::new(self.tab_content_text(params.detail.unwrap_or_default(), cx))
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_autoreveal_follows_multibuffer_selection(cx: &mut gpui::TestAppContext) {
|
||||
use editor::{
|
||||
Editor, EditorEvent, EditorMode, MultiBuffer, PathKey, SelectionEffects, ToOffset,
|
||||
};
|
||||
use language::Point;
|
||||
use multibuffer_wrapper::TestMultibufferWrapper;
|
||||
|
||||
init_test_with_editor(cx);
|
||||
cx.update(|cx| {
|
||||
cx.update_global::<SettingsStore, _>(|store, cx| {
|
||||
store.update_user_settings(cx, |settings| {
|
||||
settings
|
||||
.project_panel
|
||||
.get_or_insert_default()
|
||||
.auto_reveal_entries = Some(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
let fs = FakeFs::new(cx.background_executor.clone());
|
||||
fs.insert_tree(
|
||||
path!("/project_root"),
|
||||
json!({
|
||||
"dir_1": { "file_1.py": "alpha 1\nalpha 2\nalpha 3\n" },
|
||||
"dir_2": { "file_2.py": "beta 1\nbeta 2\nbeta 3\n" },
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs.clone(), [path!("/project_root").as_ref()], cx).await;
|
||||
let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
|
||||
let workspace = window
|
||||
.read_with(cx, |mw, _| mw.workspace().clone())
|
||||
.unwrap();
|
||||
let cx = &mut VisualTestContext::from_window(window.into(), cx);
|
||||
let panel = workspace.update_in(cx, ProjectPanel::new);
|
||||
cx.run_until_parked();
|
||||
|
||||
let buffer_1 = project
|
||||
.update(cx, |project, cx| {
|
||||
let project_path = project
|
||||
.find_project_path("project_root/dir_1/file_1.py", cx)
|
||||
.unwrap();
|
||||
project.open_buffer(project_path, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let buffer_2 = project
|
||||
.update(cx, |project, cx| {
|
||||
let project_path = project
|
||||
.find_project_path("project_root/dir_2/file_2.py", cx)
|
||||
.unwrap();
|
||||
project.open_buffer(project_path, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let multi_buffer = cx.update(|_, cx| {
|
||||
cx.new(|cx| {
|
||||
let mut multi_buffer = MultiBuffer::new(language::Capability::ReadWrite);
|
||||
multi_buffer.set_excerpts_for_path(
|
||||
PathKey::sorted(0),
|
||||
buffer_1.clone(),
|
||||
[Point::new(0, 0)..Point::new(2, 0)],
|
||||
0,
|
||||
cx,
|
||||
);
|
||||
multi_buffer.set_excerpts_for_path(
|
||||
PathKey::sorted(1),
|
||||
buffer_2.clone(),
|
||||
[Point::new(0, 0)..Point::new(2, 0)],
|
||||
0,
|
||||
cx,
|
||||
);
|
||||
multi_buffer
|
||||
})
|
||||
});
|
||||
|
||||
let inner_editor = cx.update(|window, cx| {
|
||||
cx.new(|cx| {
|
||||
Editor::new(
|
||||
EditorMode::full(),
|
||||
multi_buffer.clone(),
|
||||
Some(project.clone()),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
});
|
||||
|
||||
// Wrap the multibuffer editor in an `Item`, mirroring real multibuffer
|
||||
// views (`ProjectDiagnosticsEditor`, `ProjectDiff`, etc.). Auto-reveal
|
||||
// should follow the inner editor's active buffer.
|
||||
workspace.update_in(cx, |workspace, window, cx| {
|
||||
let wrapper = cx.new(|cx| TestMultibufferWrapper::new(inner_editor.clone(), cx));
|
||||
workspace.add_item_to_active_pane(Box::new(wrapper), None, true, window, cx);
|
||||
});
|
||||
cx.run_until_parked();
|
||||
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..20, cx),
|
||||
&[
|
||||
"v project_root",
|
||||
" v dir_1",
|
||||
" file_1.py <== selected <== marked",
|
||||
" > dir_2",
|
||||
],
|
||||
"When a multibuffer becomes active, its first excerpt's file should be revealed"
|
||||
);
|
||||
|
||||
let buffer_2_offset = multi_buffer.read_with(cx, |multi_buffer, cx| {
|
||||
let snapshot = multi_buffer.snapshot(cx);
|
||||
let buffer_2_id = buffer_2.read(cx).remote_id();
|
||||
let excerpt = snapshot
|
||||
.excerpts_for_buffer(buffer_2_id)
|
||||
.next()
|
||||
.expect("buffer_2 excerpt must exist");
|
||||
snapshot
|
||||
.anchor_in_excerpt(excerpt.context.start)
|
||||
.expect("excerpt anchor must resolve")
|
||||
.to_offset(&snapshot)
|
||||
});
|
||||
|
||||
inner_editor.update_in(cx, |editor, window, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([buffer_2_offset..buffer_2_offset]);
|
||||
});
|
||||
});
|
||||
cx.run_until_parked();
|
||||
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..20, cx),
|
||||
&[
|
||||
"v project_root",
|
||||
" v dir_1",
|
||||
" file_1.py",
|
||||
" v dir_2",
|
||||
" file_2.py <== selected <== marked",
|
||||
],
|
||||
"Moving the cursor into a different excerpt buffer should reveal that buffer's entry"
|
||||
);
|
||||
|
||||
// Wrappers re-emit inner-editor events through `to_item_events`, so a
|
||||
// benign `TitleChanged` (e.g. diagnostic summary updates) ultimately
|
||||
// reaches `Workspace::active_item_path_changed`. The active path should be
|
||||
// recomputed from the wrapper instead of falling back to a stale selection.
|
||||
inner_editor.update(cx, |_, cx| cx.emit(EditorEvent::TitleChanged));
|
||||
cx.run_until_parked();
|
||||
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..20, cx),
|
||||
&[
|
||||
"v project_root",
|
||||
" v dir_1",
|
||||
" file_1.py",
|
||||
" v dir_2",
|
||||
" file_2.py <== selected <== marked",
|
||||
],
|
||||
"Wrapper-level title updates must not clobber the inner editor's reveal"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_reveal_in_project_panel_fallback(cx: &mut gpui::TestAppContext) {
|
||||
init_test_with_editor(cx);
|
||||
|
|
|
|||
|
|
@ -679,6 +679,10 @@ impl Item for ProjectSearchView {
|
|||
self.results_editor.for_each_project_item(cx, f)
|
||||
}
|
||||
|
||||
fn active_project_path(&self, cx: &App) -> Option<ProjectPath> {
|
||||
self.results_editor.read(cx).active_project_path(cx)
|
||||
}
|
||||
|
||||
fn can_save(&self, _: &App) -> bool {
|
||||
true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -237,6 +237,24 @@ pub trait Item: Focusable + EventEmitter<Self::Event> + Render + Sized {
|
|||
fn buffer_kind(&self, _cx: &App) -> ItemBufferKind {
|
||||
ItemBufferKind::None
|
||||
}
|
||||
|
||||
/// Returns the project path that should be treated as active for this item.
|
||||
///
|
||||
/// Singleton items use their only project item by default. Items backed by
|
||||
/// multiple buffers should override this to return the path for the buffer
|
||||
/// under the primary cursor or otherwise selected sub-item.
|
||||
fn active_project_path(&self, cx: &App) -> Option<ProjectPath> {
|
||||
if self.buffer_kind(cx) != ItemBufferKind::Singleton {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut result = None;
|
||||
self.for_each_project_item(cx, &mut |_, item| {
|
||||
result = item.project_path(cx);
|
||||
});
|
||||
result
|
||||
}
|
||||
|
||||
fn set_nav_history(&mut self, _: ItemNavHistory, _window: &mut Window, _: &mut Context<Self>) {}
|
||||
|
||||
fn can_split(&self) -> bool {
|
||||
|
|
@ -646,14 +664,7 @@ impl<T: Item> ItemHandle for Entity<T> {
|
|||
}
|
||||
|
||||
fn project_path(&self, cx: &App) -> Option<ProjectPath> {
|
||||
let this = self.read(cx);
|
||||
let mut result = None;
|
||||
if this.buffer_kind(cx) == ItemBufferKind::Singleton {
|
||||
this.for_each_project_item(cx, &mut |_, item| {
|
||||
result = item.project_path(cx);
|
||||
});
|
||||
}
|
||||
result
|
||||
<T as Item>::active_project_path(self.read(cx), cx)
|
||||
}
|
||||
|
||||
fn workspace_settings<'a>(&self, cx: &'a App) -> &'a WorkspaceSettings {
|
||||
|
|
@ -910,6 +921,16 @@ impl<T: Item> ItemHandle for Entity<T> {
|
|||
}
|
||||
}
|
||||
|
||||
ItemEvent::UpdateBreadcrumbs => {
|
||||
if &pane == workspace.active_pane()
|
||||
&& pane.read(cx).active_item().is_some_and(|active_item| {
|
||||
active_item.item_id() == item.item_id()
|
||||
})
|
||||
{
|
||||
workspace.active_item_path_changed(false, window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
ItemEvent::Edit => {
|
||||
let autosave = item.workspace_settings(cx).autosave;
|
||||
|
||||
|
|
@ -932,8 +953,6 @@ impl<T: Item> ItemHandle for Entity<T> {
|
|||
}
|
||||
pane.update(cx, |pane, cx| pane.handle_item_edit(item.item_id(), cx));
|
||||
}
|
||||
|
||||
_ => {}
|
||||
});
|
||||
},
|
||||
));
|
||||
|
|
|
|||
|
|
@ -5958,7 +5958,7 @@ impl Workspace {
|
|||
self.follower_states.contains_key(&id.into())
|
||||
}
|
||||
|
||||
fn active_item_path_changed(
|
||||
pub(crate) fn active_item_path_changed(
|
||||
&mut self,
|
||||
focus_changed: bool,
|
||||
window: &mut Window,
|
||||
|
|
|
|||
|
|
@ -4175,7 +4175,7 @@ mod tests {
|
|||
let (editor_1, buffer) = workspace.update_in(cx, |_, window, cx| {
|
||||
pane_1.update(cx, |pane_1, cx| {
|
||||
let editor = pane_1.active_item().unwrap().downcast::<Editor>().unwrap();
|
||||
assert_eq!(editor.project_path(cx), Some(file1.clone()));
|
||||
assert_eq!(editor.read(cx).active_project_path(cx), Some(file1.clone()));
|
||||
let buffer = editor.update(cx, |editor, cx| {
|
||||
editor.insert("dirt", window, cx);
|
||||
editor.buffer().downgrade()
|
||||
|
|
@ -4731,7 +4731,7 @@ mod tests {
|
|||
let scroll_position = editor_ref.scroll_position(cx);
|
||||
|
||||
(
|
||||
editor_ref.project_path(cx).unwrap(),
|
||||
editor_ref.active_project_path(cx).unwrap(),
|
||||
selections[0].start,
|
||||
scroll_position.y,
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue