diff --git a/crates/assistant/src/assistant.rs b/crates/assistant/src/assistant.rs index f8b36330ec..15bc7b330d 100644 --- a/crates/assistant/src/assistant.rs +++ b/crates/assistant/src/assistant.rs @@ -26,8 +26,8 @@ use semantic_index::{CloudEmbeddingProvider, SemanticIndex}; use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; use slash_command::{ - active_command, default_command, fetch_command, file_command, now_command, project_command, - prompt_command, rustdoc_command, search_command, tabs_command, + active_command, default_command, diagnostics_command, fetch_command, file_command, now_command, + project_command, prompt_command, rustdoc_command, search_command, tabs_command, }; use std::{ fmt::{self, Display}, @@ -316,6 +316,7 @@ fn register_slash_commands(cx: &mut AppContext) { slash_command_registry.register_command(prompt_command::PromptSlashCommand, true); slash_command_registry.register_command(default_command::DefaultSlashCommand, true); slash_command_registry.register_command(now_command::NowSlashCommand, true); + slash_command_registry.register_command(diagnostics_command::DiagnosticsCommand, true); slash_command_registry.register_command(rustdoc_command::RustdocSlashCommand, false); slash_command_registry.register_command(fetch_command::FetchSlashCommand, false); } diff --git a/crates/assistant/src/slash_command.rs b/crates/assistant/src/slash_command.rs index 4bb4e01c2f..ffd2d35016 100644 --- a/crates/assistant/src/slash_command.rs +++ b/crates/assistant/src/slash_command.rs @@ -18,6 +18,7 @@ use workspace::Workspace; pub mod active_command; pub mod default_command; +pub mod diagnostics_command; pub mod fetch_command; pub mod file_command; pub mod now_command; diff --git a/crates/assistant/src/slash_command/diagnostics_command.rs b/crates/assistant/src/slash_command/diagnostics_command.rs new file mode 100644 index 0000000000..0438ed2956 --- /dev/null +++ b/crates/assistant/src/slash_command/diagnostics_command.rs @@ -0,0 +1,444 @@ +use super::{SlashCommand, SlashCommandOutput}; +use anyhow::{anyhow, Result}; +use assistant_slash_command::SlashCommandOutputSection; +use fuzzy::{PathMatch, StringMatchCandidate}; +use gpui::{svg, AppContext, Model, RenderOnce, Task, View, WeakView}; +use language::{ + Anchor, BufferSnapshot, DiagnosticEntry, DiagnosticSeverity, LspAdapterDelegate, + OffsetRangeExt, ToOffset, +}; +use project::{DiagnosticSummary, PathMatchCandidateSet, Project}; +use rope::Point; +use std::fmt::Write; +use std::{ + ops::Range, + sync::{atomic::AtomicBool, Arc}, +}; +use ui::{prelude::*, ButtonLike, ElevationIndex}; +use util::paths::PathMatcher; +use util::ResultExt; +use workspace::Workspace; + +pub(crate) struct DiagnosticsCommand; + +impl DiagnosticsCommand { + fn search_paths( + &self, + query: String, + cancellation_flag: Arc, + workspace: &View, + cx: &mut AppContext, + ) -> Task> { + if query.is_empty() { + let workspace = workspace.read(cx); + let entries = workspace.recent_navigation_history(Some(10), cx); + let path_prefix: Arc = "".into(); + Task::ready( + entries + .into_iter() + .map(|(entry, _)| PathMatch { + score: 0., + positions: Vec::new(), + worktree_id: entry.worktree_id.to_usize(), + path: entry.path.clone(), + path_prefix: path_prefix.clone(), + distance_to_relative_ancestor: 0, + }) + .collect(), + ) + } else { + let worktrees = workspace.read(cx).visible_worktrees(cx).collect::>(); + let candidate_sets = worktrees + .into_iter() + .map(|worktree| { + let worktree = worktree.read(cx); + PathMatchCandidateSet { + snapshot: worktree.snapshot(), + include_ignored: worktree + .root_entry() + .map_or(false, |entry| entry.is_ignored), + include_root_name: false, + candidates: project::Candidates::Entries, + } + }) + .collect::>(); + + let executor = cx.background_executor().clone(); + cx.foreground_executor().spawn(async move { + fuzzy::match_path_sets( + candidate_sets.as_slice(), + query.as_str(), + None, + false, + 100, + &cancellation_flag, + executor, + ) + .await + }) + } + } +} + +impl SlashCommand for DiagnosticsCommand { + fn name(&self) -> String { + "diagnostics".into() + } + + fn description(&self) -> String { + "Insert diagnostics".into() + } + + fn menu_text(&self) -> String { + "Insert Diagnostics".into() + } + + fn requires_argument(&self) -> bool { + false + } + + fn complete_argument( + &self, + query: String, + cancellation_flag: Arc, + workspace: Option>, + cx: &mut AppContext, + ) -> Task>> { + let Some(workspace) = workspace.and_then(|workspace| workspace.upgrade()) else { + return Task::ready(Err(anyhow!("workspace was dropped"))); + }; + let query = query.split_whitespace().last().unwrap_or("").to_string(); + + let paths = self.search_paths(query.clone(), cancellation_flag.clone(), &workspace, cx); + let executor = cx.background_executor().clone(); + cx.background_executor().spawn(async move { + let mut matches: Vec = paths + .await + .into_iter() + .map(|path_match| { + format!( + "{}{}", + path_match.path_prefix, + path_match.path.to_string_lossy() + ) + }) + .collect(); + + matches.extend( + fuzzy::match_strings( + &Options::match_candidates_for_args(), + &query, + false, + 10, + &cancellation_flag, + executor, + ) + .await + .into_iter() + .map(|candidate| candidate.string), + ); + + Ok(matches) + }) + } + + fn run( + self: Arc, + argument: Option<&str>, + workspace: WeakView, + _delegate: Arc, + cx: &mut WindowContext, + ) -> Task> { + let Some(workspace) = workspace.upgrade() else { + return Task::ready(Err(anyhow!("workspace was dropped"))); + }; + + let options = Options::parse(argument); + + let task = collect_diagnostics(workspace.read(cx).project().clone(), options, cx); + cx.spawn(move |_| async move { + let (text, sections) = task.await?; + Ok(SlashCommandOutput { + text, + sections: sections + .into_iter() + .map(|(range, placeholder_type)| SlashCommandOutputSection { + range, + render_placeholder: Arc::new(move |id, unfold, _cx| { + DiagnosticsPlaceholder { + id, + unfold, + placeholder_type: placeholder_type.clone(), + } + .into_any_element() + }), + }) + .collect(), + run_commands_in_text: false, + }) + }) + } +} + +#[derive(Default)] +struct Options { + include_warnings: bool, + path_matcher: Option, +} + +const INCLUDE_WARNINGS_ARGUMENT: &str = "--include-warnings"; + +impl Options { + pub fn parse(arguments_line: Option<&str>) -> Self { + arguments_line + .map(|arguments_line| { + let args = arguments_line.split_whitespace().collect::>(); + let mut include_warnings = false; + let mut path_matcher = None; + for arg in args { + if arg == INCLUDE_WARNINGS_ARGUMENT { + include_warnings = true; + } else { + path_matcher = PathMatcher::new(arg).log_err(); + } + } + Self { + include_warnings, + path_matcher, + } + }) + .unwrap_or_default() + } + + fn match_candidates_for_args() -> [StringMatchCandidate; 1] { + [StringMatchCandidate::new( + 0, + INCLUDE_WARNINGS_ARGUMENT.to_string(), + )] + } +} + +fn collect_diagnostics( + project: Model, + options: Options, + cx: &mut AppContext, +) -> Task, PlaceholderType)>)>> { + let header = if let Some(path_matcher) = &options.path_matcher { + format!("diagnostics: {}", path_matcher.source()) + } else { + "diagnostics".to_string() + }; + + let project_handle = project.downgrade(); + let diagnostic_summaries: Vec<_> = project.read(cx).diagnostic_summaries(false, cx).collect(); + + cx.spawn(|mut cx| async move { + let mut text = String::new(); + writeln!(text, "{}", &header).unwrap(); + let mut sections: Vec<(Range, PlaceholderType)> = Vec::new(); + + let mut project_summary = DiagnosticSummary::default(); + for (project_path, _, summary) in diagnostic_summaries { + if let Some(path_matcher) = &options.path_matcher { + if !path_matcher.is_match(&project_path.path) { + continue; + } + } + + project_summary.error_count += summary.error_count; + if options.include_warnings { + project_summary.warning_count += summary.warning_count; + } else if summary.error_count == 0 { + continue; + } + + let last_end = text.len(); + let file_path = project_path.path.to_string_lossy().to_string(); + writeln!(&mut text, "{file_path}").unwrap(); + + if let Some(buffer) = project_handle + .update(&mut cx, |project, cx| project.open_buffer(project_path, cx))? + .await + .log_err() + { + collect_buffer_diagnostics( + &mut text, + &mut sections, + cx.read_model(&buffer, |buffer, _| buffer.snapshot())?, + options.include_warnings, + ); + } + + sections.push(( + last_end..text.len().saturating_sub(1), + PlaceholderType::File(file_path), + )) + } + sections.push(( + 0..text.len(), + PlaceholderType::Root(project_summary, header), + )); + + Ok((text, sections)) + }) +} + +fn collect_buffer_diagnostics( + text: &mut String, + sections: &mut Vec<(Range, PlaceholderType)>, + snapshot: BufferSnapshot, + include_warnings: bool, +) { + for (_, group) in snapshot.diagnostic_groups(None) { + let entry = &group.entries[group.primary_ix]; + collect_diagnostic(text, sections, entry, &snapshot, include_warnings) + } +} + +fn collect_diagnostic( + text: &mut String, + sections: &mut Vec<(Range, PlaceholderType)>, + entry: &DiagnosticEntry, + snapshot: &BufferSnapshot, + include_warnings: bool, +) { + const EXCERPT_EXPANSION_SIZE: u32 = 2; + const MAX_MESSAGE_LENGTH: usize = 2000; + + let ty = match entry.diagnostic.severity { + DiagnosticSeverity::WARNING => { + if !include_warnings { + return; + } + DiagnosticType::Warning + } + DiagnosticSeverity::ERROR => DiagnosticType::Error, + _ => return, + }; + let prev_len = text.len(); + + let range = entry.range.to_point(snapshot); + let diagnostic_row_number = range.start.row + 1; + + let start_row = range.start.row.saturating_sub(EXCERPT_EXPANSION_SIZE); + let end_row = (range.end.row + EXCERPT_EXPANSION_SIZE).min(snapshot.max_point().row) + 1; + let excerpt_range = + Point::new(start_row, 0).to_offset(&snapshot)..Point::new(end_row, 0).to_offset(&snapshot); + + text.push_str("```"); + if let Some(language_name) = snapshot.language().map(|l| l.code_fence_block_name()) { + text.push_str(&language_name); + } + text.push('\n'); + + let mut buffer_text = String::new(); + for chunk in snapshot.text_for_range(excerpt_range) { + buffer_text.push_str(chunk); + } + + for (i, line) in buffer_text.lines().enumerate() { + let line_number = start_row + i as u32 + 1; + writeln!(text, "{}", line).unwrap(); + + if line_number == diagnostic_row_number { + text.push_str("//"); + let prev_len = text.len(); + write!(text, " {}: ", ty.as_str()).unwrap(); + let padding = text.len() - prev_len; + + let message = util::truncate(&entry.diagnostic.message, MAX_MESSAGE_LENGTH) + .replace('\n', format!("\n//{:padding$}", "").as_str()); + + writeln!(text, "{message}").unwrap(); + } + } + + writeln!(text, "```").unwrap(); + sections.push(( + prev_len..text.len().saturating_sub(1), + PlaceholderType::Diagnostic(ty, entry.diagnostic.message.clone()), + )) +} + +#[derive(Clone)] +pub enum PlaceholderType { + Root(DiagnosticSummary, String), + File(String), + Diagnostic(DiagnosticType, String), +} + +#[derive(Copy, Clone, IntoElement)] +pub enum DiagnosticType { + Warning, + Error, +} + +impl DiagnosticType { + pub fn as_str(&self) -> &'static str { + match self { + DiagnosticType::Warning => "warning", + DiagnosticType::Error => "error", + } + } +} + +#[derive(IntoElement)] +pub struct DiagnosticsPlaceholder { + pub id: ElementId, + pub placeholder_type: PlaceholderType, + pub unfold: Arc, +} + +impl RenderOnce for DiagnosticsPlaceholder { + fn render(self, _cx: &mut WindowContext) -> impl IntoElement { + let unfold = self.unfold; + let (icon, content) = match self.placeholder_type { + PlaceholderType::Root(summary, title) => ( + h_flex() + .w_full() + .gap_0p5() + .when(summary.error_count > 0, |this| { + this.child(DiagnosticType::Error) + .child(Label::new(summary.error_count.to_string())) + }) + .when(summary.warning_count > 0, |this| { + this.child(DiagnosticType::Warning) + .child(Label::new(summary.warning_count.to_string())) + }) + .into_any_element(), + Label::new(title), + ), + PlaceholderType::File(file) => ( + Icon::new(IconName::File).into_any_element(), + Label::new(file), + ), + PlaceholderType::Diagnostic(diagnostic_type, message) => ( + diagnostic_type.into_any_element(), + Label::new(message).single_line(), + ), + }; + + ButtonLike::new(self.id) + .style(ButtonStyle::Filled) + .layer(ElevationIndex::ElevatedSurface) + .child(icon) + .child(content) + .on_click(move |_, cx| unfold(cx)) + } +} + +impl RenderOnce for DiagnosticType { + fn render(self, cx: &mut WindowContext) -> impl IntoElement { + svg() + .size(cx.text_style().font_size) + .flex_none() + .map(|icon| match self { + DiagnosticType::Error => icon + .path(IconName::XCircle.path()) + .text_color(Color::Error.color(cx)), + DiagnosticType::Warning => icon + .path(IconName::ExclamationTriangle.path()) + .text_color(Color::Warning.color(cx)), + }) + } +} diff --git a/crates/assistant/src/slash_command/file_command.rs b/crates/assistant/src/slash_command/file_command.rs index f9c9a66f1a..ed65a9dc96 100644 --- a/crates/assistant/src/slash_command/file_command.rs +++ b/crates/assistant/src/slash_command/file_command.rs @@ -58,7 +58,7 @@ impl FileSlashCommand { .root_entry() .map_or(false, |entry| entry.is_ignored), include_root_name: true, - directories_only: false, + candidates: project::Candidates::Files, } }) .collect::>(); diff --git a/crates/collab/src/tests/random_project_collaboration_tests.rs b/crates/collab/src/tests/random_project_collaboration_tests.rs index 9b0b6f0617..bd50b1a55b 100644 --- a/crates/collab/src/tests/random_project_collaboration_tests.rs +++ b/crates/collab/src/tests/random_project_collaboration_tests.rs @@ -305,7 +305,7 @@ impl RandomizedTest for ProjectCollaborationTest { .filter(|worktree| { let worktree = worktree.read(cx); worktree.is_visible() - && worktree.entries(false).any(|e| e.is_file()) + && worktree.entries(false, 0).any(|e| e.is_file()) && worktree.root_entry().map_or(false, |e| e.is_dir()) }) .choose(rng) @@ -427,14 +427,14 @@ impl RandomizedTest for ProjectCollaborationTest { .filter(|worktree| { let worktree = worktree.read(cx); worktree.is_visible() - && worktree.entries(false).any(|e| e.is_file()) + && worktree.entries(false, 0).any(|e| e.is_file()) }) .choose(rng) }); let Some(worktree) = worktree else { continue }; let full_path = worktree.read_with(cx, |worktree, _| { let entry = worktree - .entries(false) + .entries(false, 0) .filter(|e| e.is_file()) .choose(rng) .unwrap(); @@ -1206,8 +1206,8 @@ impl RandomizedTest for ProjectCollaborationTest { guest_project.remote_id(), ); assert_eq!( - guest_snapshot.entries(false).collect::>(), - host_snapshot.entries(false).collect::>(), + guest_snapshot.entries(false, 0).collect::>(), + host_snapshot.entries(false, 0).collect::>(), "{} has different snapshot than the host for worktree {:?} ({:?}) and project {:?}", client.username, host_snapshot.abs_path(), diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index b21fcbcb45..62f9ebf23d 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -483,7 +483,7 @@ impl FileFinderDelegate { .root_entry() .map_or(false, |entry| entry.is_ignored), include_root_name, - directories_only: false, + candidates: project::Candidates::Files, } }) .collect::>(); diff --git a/crates/file_finder/src/new_path_prompt.rs b/crates/file_finder/src/new_path_prompt.rs index d260792098..990072a2ac 100644 --- a/crates/file_finder/src/new_path_prompt.rs +++ b/crates/file_finder/src/new_path_prompt.rs @@ -278,7 +278,7 @@ impl PickerDelegate for NewPathDelegate { .root_entry() .map_or(false, |entry| entry.is_ignored), include_root_name, - directories_only: true, + candidates: project::Candidates::Directories, } }) .collect::>(); diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 35f01f1325..8a57ae30f2 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -7337,7 +7337,7 @@ impl Project { if query.include_ignored() { for (snapshot, settings) in snapshots { - for ignored_entry in snapshot.entries(true).filter(|e| e.is_ignored) { + for ignored_entry in snapshot.entries(true, 0).filter(|e| e.is_ignored) { let limiter = Arc::clone(&max_concurrent_workers); scope.spawn(async move { let _guard = limiter.acquire().await; @@ -11397,7 +11397,16 @@ pub struct PathMatchCandidateSet { pub snapshot: Snapshot, pub include_ignored: bool, pub include_root_name: bool, - pub directories_only: bool, + pub candidates: Candidates, +} + +pub enum Candidates { + /// Only consider directories. + Directories, + /// Only consider files. + Files, + /// Consider directories and files. + Entries, } impl<'a> fuzzy::PathMatchCandidateSet<'a> for PathMatchCandidateSet { @@ -11427,10 +11436,10 @@ impl<'a> fuzzy::PathMatchCandidateSet<'a> for PathMatchCandidateSet { fn candidates(&'a self, start: usize) -> Self::Candidates { PathMatchCandidateSetIter { - traversal: if self.directories_only { - self.snapshot.directories(self.include_ignored, start) - } else { - self.snapshot.files(self.include_ignored, start) + traversal: match self.candidates { + Candidates::Directories => self.snapshot.directories(self.include_ignored, start), + Candidates::Files => self.snapshot.files(self.include_ignored, start), + Candidates::Entries => self.snapshot.entries(self.include_ignored, start), }, } } diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 29a27faef2..918c075d76 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -765,7 +765,7 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon worktree .read(cx) .snapshot() - .entries(true) + .entries(true, 0) .map(|entry| (entry.path.as_ref(), entry.is_ignored)) .collect::>(), &[ @@ -839,7 +839,7 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon worktree .read(cx) .snapshot() - .entries(true) + .entries(true, 0) .map(|entry| (entry.path.as_ref(), entry.is_ignored)) .collect::>(), &[ diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 6b174cee31..417af04575 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1199,7 +1199,7 @@ impl ProjectPanel { if let Some(worktree) = worktree { let worktree = worktree.read(cx); let worktree_id = worktree.id(); - if let Some(last_entry) = worktree.entries(true).last() { + if let Some(last_entry) = worktree.entries(true, 0).last() { self.selection = Some(SelectedEntry { worktree_id, entry_id: last_entry.id, @@ -1578,7 +1578,7 @@ impl ProjectPanel { } let mut visible_worktree_entries = Vec::new(); - let mut entry_iter = snapshot.entries(true); + let mut entry_iter = snapshot.entries(true, 0); while let Some(entry) = entry_iter.entry() { if auto_collapse_dirs && entry.kind.is_dir() diff --git a/crates/worktree/src/worktree.rs b/crates/worktree/src/worktree.rs index f7fe575676..1b6e2f8194 100644 --- a/crates/worktree/src/worktree.rs +++ b/crates/worktree/src/worktree.rs @@ -2122,8 +2122,8 @@ impl Snapshot { self.traverse_from_offset(false, true, include_ignored, start) } - pub fn entries(&self, include_ignored: bool) -> Traversal { - self.traverse_from_offset(true, true, include_ignored, 0) + pub fn entries(&self, include_ignored: bool, start: usize) -> Traversal { + self.traverse_from_offset(true, true, include_ignored, start) } pub fn repositories(&self) -> impl Iterator, &RepositoryEntry)> { @@ -2555,7 +2555,7 @@ impl LocalSnapshot { assert_eq!(bfs_paths, dfs_paths_via_iter); let dfs_paths_via_traversal = self - .entries(true) + .entries(true, 0) .map(|e| e.path.as_ref()) .collect::>(); assert_eq!(dfs_paths_via_traversal, dfs_paths_via_iter); diff --git a/crates/worktree/src/worktree_tests.rs b/crates/worktree/src/worktree_tests.rs index 35b1cd043c..70d23dd287 100644 --- a/crates/worktree/src/worktree_tests.rs +++ b/crates/worktree/src/worktree_tests.rs @@ -45,7 +45,7 @@ async fn test_traversal(cx: &mut TestAppContext) { tree.read_with(cx, |tree, _| { assert_eq!( - tree.entries(false) + tree.entries(false, 0) .map(|entry| entry.path.as_ref()) .collect::>(), vec![ @@ -56,7 +56,7 @@ async fn test_traversal(cx: &mut TestAppContext) { ] ); assert_eq!( - tree.entries(true) + tree.entries(true, 0) .map(|entry| entry.path.as_ref()) .collect::>(), vec![ @@ -110,7 +110,7 @@ async fn test_circular_symlinks(cx: &mut TestAppContext) { tree.read_with(cx, |tree, _| { assert_eq!( - tree.entries(false) + tree.entries(false, 0) .map(|entry| entry.path.as_ref()) .collect::>(), vec![ @@ -136,7 +136,7 @@ async fn test_circular_symlinks(cx: &mut TestAppContext) { cx.executor().run_until_parked(); tree.read_with(cx, |tree, _| { assert_eq!( - tree.entries(false) + tree.entries(false, 0) .map(|entry| entry.path.as_ref()) .collect::>(), vec![ @@ -225,7 +225,7 @@ async fn test_symlinks_pointing_outside(cx: &mut TestAppContext) { // The symlinked directories are not scanned by default. tree.read_with(cx, |tree, _| { assert_eq!( - tree.entries(true) + tree.entries(true, 0) .map(|entry| (entry.path.as_ref(), entry.is_external)) .collect::>(), vec![ @@ -258,7 +258,7 @@ async fn test_symlinks_pointing_outside(cx: &mut TestAppContext) { // not scanned yet. tree.read_with(cx, |tree, _| { assert_eq!( - tree.entries(true) + tree.entries(true, 0) .map(|entry| (entry.path.as_ref(), entry.is_external)) .collect::>(), vec![ @@ -295,7 +295,7 @@ async fn test_symlinks_pointing_outside(cx: &mut TestAppContext) { // The expanded subdirectory's contents are loaded. tree.read_with(cx, |tree, _| { assert_eq!( - tree.entries(true) + tree.entries(true, 0) .map(|entry| (entry.path.as_ref(), entry.is_external)) .collect::>(), vec![ @@ -358,7 +358,7 @@ async fn test_renaming_case_only(cx: &mut TestAppContext) { .await; tree.read_with(cx, |tree, _| { assert_eq!( - tree.entries(true) + tree.entries(true, 0) .map(|entry| entry.path.as_ref()) .collect::>(), vec![Path::new(""), Path::new(OLD_NAME)] @@ -380,7 +380,7 @@ async fn test_renaming_case_only(cx: &mut TestAppContext) { tree.read_with(cx, |tree, _| { assert_eq!( - tree.entries(true) + tree.entries(true, 0) .map(|entry| entry.path.as_ref()) .collect::>(), vec![Path::new(""), Path::new(NEW_NAME)] @@ -435,7 +435,7 @@ async fn test_open_gitignored_files(cx: &mut TestAppContext) { tree.read_with(cx, |tree, _| { assert_eq!( - tree.entries(true) + tree.entries(true, 0) .map(|entry| (entry.path.as_ref(), entry.is_ignored)) .collect::>(), vec![ @@ -462,7 +462,7 @@ async fn test_open_gitignored_files(cx: &mut TestAppContext) { tree.read_with(cx, |tree, _| { assert_eq!( - tree.entries(true) + tree.entries(true, 0) .map(|entry| (entry.path.as_ref(), entry.is_ignored)) .collect::>(), vec![ @@ -502,7 +502,7 @@ async fn test_open_gitignored_files(cx: &mut TestAppContext) { tree.read_with(cx, |tree, _| { assert_eq!( - tree.entries(true) + tree.entries(true, 0) .map(|entry| (entry.path.as_ref(), entry.is_ignored)) .collect::>(), vec![ @@ -605,7 +605,7 @@ async fn test_dirs_no_longer_ignored(cx: &mut TestAppContext) { // Those subdirectories are now loaded. tree.read_with(cx, |tree, _| { assert_eq!( - tree.entries(true) + tree.entries(true, 0) .map(|e| (e.path.as_ref(), e.is_ignored)) .collect::>(), &[ @@ -637,7 +637,7 @@ async fn test_dirs_no_longer_ignored(cx: &mut TestAppContext) { // All of the directories that are no longer ignored are now loaded. tree.read_with(cx, |tree, _| { assert_eq!( - tree.entries(true) + tree.entries(true, 0) .map(|e| (e.path.as_ref(), e.is_ignored)) .collect::>(), &[ @@ -1203,8 +1203,8 @@ async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) { let snapshot2 = tree.update(cx, |tree, _| tree.as_local().unwrap().snapshot()); assert_eq!( - snapshot1.lock().entries(true).collect::>(), - snapshot2.entries(true).collect::>() + snapshot1.lock().entries(true, 0).collect::>(), + snapshot2.entries(true, 0).collect::>() ); } @@ -1411,8 +1411,8 @@ async fn test_random_worktree_operations_during_initial_scan( } assert_eq!( - updated_snapshot.entries(true).collect::>(), - final_snapshot.entries(true).collect::>(), + updated_snapshot.entries(true, 0).collect::>(), + final_snapshot.entries(true, 0).collect::>(), "wrong updates after snapshot {i}: {snapshot:#?} {updates:#?}", ); } @@ -1545,11 +1545,11 @@ async fn test_random_worktree_changes(cx: &mut TestAppContext, mut rng: StdRng) assert_eq!( prev_snapshot - .entries(true) + .entries(true, 0) .map(ignore_pending_dir) .collect::>(), snapshot - .entries(true) + .entries(true, 0) .map(ignore_pending_dir) .collect::>(), "wrong updates after snapshot {i}: {updates:#?}", @@ -1568,7 +1568,7 @@ async fn test_random_worktree_changes(cx: &mut TestAppContext, mut rng: StdRng) // The worktree's `UpdatedEntries` event can be used to follow along with // all changes to the worktree's snapshot. fn check_worktree_change_events(tree: &mut Worktree, cx: &mut ModelContext) { - let mut entries = tree.entries(true).cloned().collect::>(); + let mut entries = tree.entries(true, 0).cloned().collect::>(); cx.subscribe(&cx.handle(), move |tree, _, event, _| { if let Event::UpdatedEntries(changes) = event { for (path, _, change_type) in changes.iter() { @@ -1596,7 +1596,7 @@ fn check_worktree_change_events(tree: &mut Worktree, cx: &mut ModelContext>(); + let new_entries = tree.entries(true, 0).cloned().collect::>(); assert_eq!(entries, new_entries, "incorrect changes: {:?}", changes); } }) @@ -1611,7 +1611,7 @@ fn randomly_mutate_worktree( log::info!("mutating worktree"); let worktree = worktree.as_local_mut().unwrap(); let snapshot = worktree.snapshot(); - let entry = snapshot.entries(false).choose(rng).unwrap(); + let entry = snapshot.entries(false, 0).choose(rng).unwrap(); match rng.gen_range(0_u32..100) { 0..=33 if entry.path.as_ref() != Path::new("") => { @@ -1619,7 +1619,7 @@ fn randomly_mutate_worktree( worktree.delete_entry(entry.id, false, cx).unwrap() } ..=66 if entry.path.as_ref() != Path::new("") => { - let other_entry = snapshot.entries(false).choose(rng).unwrap(); + let other_entry = snapshot.entries(false, 0).choose(rng).unwrap(); let new_parent_path = if other_entry.is_dir() { other_entry.path.clone() } else {