From 8b4657707fa4b9265b68d634537a0b991ad55b02 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 25 Sep 2025 13:44:22 +0200 Subject: [PATCH] WIP2 Co-authored-by: Smit Barmase --- crates/project_panel/src/project_panel.rs | 549 ++++++++++++---------- 1 file changed, 294 insertions(+), 255 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 693b1e7a83cec931278bee18eeaa5ec829821833..096faf7c1f85259b2db480736e6ad904cc8b4478 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -88,6 +88,23 @@ struct State { // Currently selected leaf entry (see auto-folding for a definition of that) in a file tree selection: Option, edit_state: Option, + unfolded_dir_ids: HashSet, + expanded_dir_ids: HashMap>, +} + +impl State { + fn derive(old: &Self) -> Self { + Self { + last_worktree_root_id: None, + ancestors: Default::default(), + visible_entries: Default::default(), + max_width_item_index: None, + edit_state: old.edit_state.clone(), + unfolded_dir_ids: old.unfolded_dir_ids.clone(), + selection: old.selection.clone(), + expanded_dir_ids: old.expanded_dir_ids.clone(), + } + } } pub struct ProjectPanel { @@ -100,8 +117,6 @@ pub struct ProjectPanel { hover_scroll_task: Option>, folded_directory_drag_target: Option, drag_target_entry: Option, - expanded_dir_ids: HashMap>, - unfolded_dir_ids: HashSet, marked_entries: Vec, context_menu: Option<(Entity, Point, Subscription)>, filename_editor: Entity, @@ -298,7 +313,7 @@ actions!( ] ); -#[derive(Debug, Default)] +#[derive(Clone, Debug, Default)] struct FoldedAncestors { current_ancestor_depth: usize, ancestors: Vec, @@ -522,7 +537,7 @@ impl ProjectPanel { } } project::Event::WorktreeRemoved(id) => { - this.expanded_dir_ids.remove(id); + this.state.expanded_dir_ids.remove(id); this.update_visible_entries(None, cx); cx.notify(); } @@ -536,7 +551,7 @@ impl ProjectPanel { if let Some((worktree, expanded_dir_ids)) = project .read(cx) .worktree_for_id(*worktree_id, cx) - .zip(this.expanded_dir_ids.get_mut(worktree_id)) + .zip(this.state.expanded_dir_ids.get_mut(worktree_id)) { let worktree = worktree.read(cx); @@ -560,7 +575,7 @@ impl ProjectPanel { if let Err(ix) = expanded_dir_ids.binary_search(&child.id) { expanded_dir_ids.insert(ix, child.id); } - this.unfolded_dir_ids.insert(child.id); + this.state.unfolded_dir_ids.insert(child.id); } } this.update_visible_entries(None, cx); @@ -642,8 +657,7 @@ impl ProjectPanel { focus_handle, folded_directory_drag_target: None, drag_target_entry: None, - expanded_dir_ids: Default::default(), - unfolded_dir_ids: Default::default(), + marked_entries: Default::default(), context_menu: None, filename_editor, @@ -667,6 +681,8 @@ impl ProjectPanel { last_worktree_root_id: Default::default(), visible_entries: Default::default(), ancestors: Default::default(), + expanded_dir_ids: Default::default(), + unfolded_dir_ids: Default::default(), }, update_visible_entries_task: Task::ready(()), }; @@ -1022,7 +1038,7 @@ impl ProjectPanel { } fn is_unfoldable(&self, entry: &Entry, worktree: &Worktree) -> bool { - if !entry.is_dir() || self.unfolded_dir_ids.contains(&entry.id) { + if !entry.is_dir() || self.state.unfolded_dir_ids.contains(&entry.id) { return false; } @@ -1069,12 +1085,13 @@ impl ProjectPanel { if entry.is_dir() { let worktree_id = worktree.id(); let entry_id = entry.id; - let expanded_dir_ids = - if let Some(expanded_dir_ids) = self.expanded_dir_ids.get_mut(&worktree_id) { - expanded_dir_ids - } else { - return; - }; + let expanded_dir_ids = if let Some(expanded_dir_ids) = + self.state.expanded_dir_ids.get_mut(&worktree_id) + { + expanded_dir_ids + } else { + return; + }; match expanded_dir_ids.binary_search(&entry_id) { Ok(_) => self.select_next(&SelectNext, window, cx), @@ -1115,7 +1132,7 @@ impl ProjectPanel { } let worktree_id = worktree.id(); let expanded_dir_ids = - if let Some(expanded_dir_ids) = self.expanded_dir_ids.get_mut(&worktree_id) { + if let Some(expanded_dir_ids) = self.state.expanded_dir_ids.get_mut(&worktree_id) { expanded_dir_ids } else { return; @@ -1155,7 +1172,8 @@ impl ProjectPanel { let multiple_worktrees = self.project.read(cx).worktrees(cx).count() > 1; let project = self.project.read(cx); - self.expanded_dir_ids + self.state + .expanded_dir_ids .iter_mut() .for_each(|(worktree_id, expanded_entries)| { if multiple_worktrees { @@ -1189,7 +1207,7 @@ impl ProjectPanel { cx: &mut Context, ) { if let Some(worktree_id) = self.project.read(cx).worktree_id_for_entry(entry_id, cx) - && let Some(expanded_dir_ids) = self.expanded_dir_ids.get_mut(&worktree_id) + && let Some(expanded_dir_ids) = self.state.expanded_dir_ids.get_mut(&worktree_id) { self.project.update(cx, |project, cx| { match expanded_dir_ids.binary_search(&entry_id) { @@ -1215,7 +1233,7 @@ impl ProjectPanel { cx: &mut Context, ) { if let Some(worktree_id) = self.project.read(cx).worktree_id_for_entry(entry_id, cx) - && let Some(expanded_dir_ids) = self.expanded_dir_ids.get_mut(&worktree_id) + && let Some(expanded_dir_ids) = self.state.expanded_dir_ids.get_mut(&worktree_id) { match expanded_dir_ids.binary_search(&entry_id) { Ok(_ix) => { @@ -1240,7 +1258,7 @@ impl ProjectPanel { self.project.update(cx, |project, cx| { if let Some((worktree, expanded_dir_ids)) = project .worktree_for_id(worktree_id, cx) - .zip(self.expanded_dir_ids.get_mut(&worktree_id)) + .zip(self.state.expanded_dir_ids.get_mut(&worktree_id)) { if let Some(task) = project.expand_all_for_entry(worktree_id, entry_id, cx) { task.detach(); @@ -1276,7 +1294,7 @@ impl ProjectPanel { self.project.update(cx, |project, cx| { if let Some((worktree, expanded_dir_ids)) = project .worktree_for_id(worktree_id, cx) - .zip(self.expanded_dir_ids.get_mut(&worktree_id)) + .zip(self.state.expanded_dir_ids.get_mut(&worktree_id)) { let worktree = worktree.read(cx); let mut dirs_to_collapse = vec![entry_id]; @@ -1289,7 +1307,7 @@ impl ProjectPanel { expanded_dir_ids.remove(ix); } if auto_fold_enabled { - self.unfolded_dir_ids.remove(¤t_id); + self.state.unfolded_dir_ids.remove(¤t_id); } for child in worktree.child_entries(¤t_entry.path) { if child.is_dir() { @@ -1706,7 +1724,7 @@ impl ProjectPanel { .project .read(cx) .worktree_for_id(worktree_id, cx) - .zip(self.expanded_dir_ids.get_mut(&worktree_id)) + .zip(self.state.expanded_dir_ids.get_mut(&worktree_id)) { let worktree = worktree.read(cx); if let Some(mut entry) = worktree.entry_for_id(new_entry_id) { @@ -2039,7 +2057,7 @@ impl ProjectPanel { fn unfold_directory(&mut self, _: &UnfoldDirectory, _: &mut Window, cx: &mut Context) { if let Some((worktree, entry)) = self.selected_entry(cx) { - self.unfolded_dir_ids.insert(entry.id); + self.state.unfolded_dir_ids.insert(entry.id); let snapshot = worktree.snapshot(); let mut parent_path = entry.path.parent(); @@ -2051,7 +2069,7 @@ impl ProjectPanel { break; } - self.unfolded_dir_ids.insert(parent_entry.id); + self.state.unfolded_dir_ids.insert(parent_entry.id); parent_path = path.parent(); } else { break; @@ -2066,7 +2084,7 @@ impl ProjectPanel { fn fold_directory(&mut self, _: &FoldDirectory, _: &mut Window, cx: &mut Context) { if let Some((worktree, entry)) = self.selected_entry(cx) { - self.unfolded_dir_ids.remove(&entry.id); + self.state.unfolded_dir_ids.remove(&entry.id); let snapshot = worktree.snapshot(); let mut path = &*entry.path; @@ -2074,7 +2092,7 @@ impl ProjectPanel { let mut child_entries_iter = snapshot.child_entries(path); if let Some(child) = child_entries_iter.next() { if child_entries_iter.next().is_none() && child.is_dir() { - self.unfolded_dir_ids.remove(&child.id); + self.state.unfolded_dir_ids.remove(&child.id); path = &*child.path; } else { break; @@ -2990,7 +3008,11 @@ impl ProjectPanel { fn expand_to_selection(&mut self, cx: &mut Context) -> Option<()> { let (worktree, entry) = self.selected_entry(cx)?; - let expanded_dir_ids = self.expanded_dir_ids.entry(worktree.id()).or_default(); + let expanded_dir_ids = self + .state + .expanded_dir_ids + .entry(worktree.id()) + .or_default(); for path in entry.path.ancestors() { let Some(entry) = worktree.entry_for_path(path) else { @@ -3042,249 +3064,263 @@ impl ProjectPanel { let hide_gitignore = settings.hide_gitignore; let project = self.project.read(cx); let repo_snapshots = project.git_store().read(cx).repo_snapshots(cx); - self.state.last_worktree_root_id = project + + let old_ancestors = self.state.ancestors.clone(); + let mut new_state = State::derive(&self.state); + new_state.last_worktree_root_id = project .visible_worktrees(cx) .next_back() .and_then(|worktree| worktree.read(cx).root_entry()) .map(|entry| entry.id); - - let old_ancestors = std::mem::take(&mut self.state.ancestors); - self.state.visible_entries.clear(); let mut max_width_item = None; - let visible_worktrees: Vec<_> = project.visible_worktrees(cx).collect(); + let visible_worktrees: Vec<_> = project + .visible_worktrees(cx) + .map(|worktree| worktree.read(cx).snapshot()) + .collect(); let hide_root = settings.hide_root && visible_worktrees.len() == 1; - for worktree in visible_worktrees { - let worktree_snapshot = worktree.read(cx).snapshot(); - let worktree_id = worktree_snapshot.id(); - - let expanded_dir_ids = match self.expanded_dir_ids.entry(worktree_id) { - hash_map::Entry::Occupied(e) => e.into_mut(), - hash_map::Entry::Vacant(e) => { - // The first time a worktree's root entry becomes available, - // mark that root entry as expanded. - if let Some(entry) = worktree_snapshot.root_entry() { - e.insert(vec![entry.id]).as_slice() - } else { - &[] - } - } - }; - - let mut new_entry_parent_id = None; - let mut new_entry_kind = EntryKind::Dir; - if let Some(edit_state) = &self.state.edit_state - && edit_state.worktree_id == worktree_id - && edit_state.is_new_entry() - { - new_entry_parent_id = Some(edit_state.entry_id); - new_entry_kind = if edit_state.is_dir { - EntryKind::Dir - } else { - EntryKind::File - }; - } + self.update_visible_entries_task = cx.spawn(async move |this, cx| { + let new_state = cx + .background_spawn(async move { + for worktree_snapshot in visible_worktrees { + let worktree_id = worktree_snapshot.id(); + + let expanded_dir_ids = match new_state.expanded_dir_ids.entry(worktree_id) { + hash_map::Entry::Occupied(e) => e.into_mut(), + hash_map::Entry::Vacant(e) => { + // The first time a worktree's root entry becomes available, + // mark that root entry as expanded. + if let Some(entry) = worktree_snapshot.root_entry() { + e.insert(vec![entry.id]).as_slice() + } else { + &[] + } + } + }; - let mut visible_worktree_entries = Vec::new(); - let mut entry_iter = - GitTraversal::new(&repo_snapshots, worktree_snapshot.entries(true, 0)); - let mut auto_folded_ancestors = vec![]; - while let Some(entry) = entry_iter.entry() { - if hide_root && Some(entry.entry) == worktree.read(cx).root_entry() { - if new_entry_parent_id == Some(entry.id) { - visible_worktree_entries.push(Self::create_new_git_entry( - entry.entry, - entry.git_summary, - new_entry_kind, - )); - new_entry_parent_id = None; - } - entry_iter.advance(); - continue; - } - if auto_collapse_dirs && entry.kind.is_dir() { - auto_folded_ancestors.push(entry.id); - if !self.unfolded_dir_ids.contains(&entry.id) - && let Some(root_path) = worktree_snapshot.root_entry() - { - let mut child_entries = worktree_snapshot.child_entries(&entry.path); - if let Some(child) = child_entries.next() - && entry.path != root_path.path - && child_entries.next().is_none() - && child.kind.is_dir() + let mut new_entry_parent_id = None; + let mut new_entry_kind = EntryKind::Dir; + if let Some(edit_state) = &new_state.edit_state + && edit_state.worktree_id == worktree_id + && edit_state.is_new_entry() { - entry_iter.advance(); - - continue; + new_entry_parent_id = Some(edit_state.entry_id); + new_entry_kind = if edit_state.is_dir { + EntryKind::Dir + } else { + EntryKind::File + }; } - } - let depth = old_ancestors - .get(&entry.id) - .map(|ancestor| ancestor.current_ancestor_depth) - .unwrap_or_default() - .min(auto_folded_ancestors.len()); - if let Some(edit_state) = &mut self.state.edit_state - && edit_state.entry_id == entry.id - { - edit_state.depth = depth; - } - let mut ancestors = std::mem::take(&mut auto_folded_ancestors); - if ancestors.len() > 1 { - ancestors.reverse(); - self.state.ancestors.insert( - entry.id, - FoldedAncestors { - current_ancestor_depth: depth, - ancestors, - }, - ); - } - } - auto_folded_ancestors.clear(); - if !hide_gitignore || !entry.is_ignored { - visible_worktree_entries.push(entry.to_owned()); - } - let precedes_new_entry = if let Some(new_entry_id) = new_entry_parent_id { - entry.id == new_entry_id || { - self.state - .ancestors - .get(&entry.id) - .is_some_and(|entries| entries.ancestors.contains(&new_entry_id)) - } - } else { - false - }; - if precedes_new_entry && (!hide_gitignore || !entry.is_ignored) { - visible_worktree_entries.push(Self::create_new_git_entry( - entry.entry, - entry.git_summary, - new_entry_kind, - )); - } - let worktree_abs_path = worktree.read(cx).abs_path(); - let (depth, chars) = - if Some(entry.entry) == worktree.read(cx).root_entry() { - let Some(path_name) = worktree_abs_path.file_name() else { - continue; - }; - let depth = 0; - (depth, path_name.to_string_lossy().chars().count()) - } else if entry.is_file() { - let Some(path_name) = entry - .path - .file_name() - .with_context(|| format!("Non-root entry has no file name: {entry:?}")) - .log_err() - else { - continue; - }; - let depth = entry.path.ancestors().count() - 1; - (depth, path_name.chars().count()) - } else { - let path = - self.state - .ancestors - .get(&entry.id) - .and_then(|ancestors| { - let outermost_ancestor = ancestors.ancestors.last()?; - let root_folded_entry = worktree - .read(cx) - .entry_for_id(*outermost_ancestor)? - .path - .as_ref(); - entry.path.strip_prefix(root_folded_entry).ok().and_then( - |suffix| { - Some( - RelPath::new(root_folded_entry.file_name()?) - .unwrap() - .join(suffix), - ) + + let mut visible_worktree_entries = Vec::new(); + let mut entry_iter = + GitTraversal::new(&repo_snapshots, worktree_snapshot.entries(true, 0)); + let mut auto_folded_ancestors = vec![]; + let worktree_abs_path = worktree_snapshot.abs_path(); + while let Some(entry) = entry_iter.entry() { + if hide_root && Some(entry.entry) == worktree_snapshot.root_entry() { + if new_entry_parent_id == Some(entry.id) { + visible_worktree_entries.push(Self::create_new_git_entry( + entry.entry, + entry.git_summary, + new_entry_kind, + )); + new_entry_parent_id = None; + } + entry_iter.advance(); + continue; + } + if auto_collapse_dirs && entry.kind.is_dir() { + auto_folded_ancestors.push(entry.id); + if !new_state.unfolded_dir_ids.contains(&entry.id) + && let Some(root_path) = worktree_snapshot.root_entry() + { + let mut child_entries = + worktree_snapshot.child_entries(&entry.path); + if let Some(child) = child_entries.next() + && entry.path != root_path.path + && child_entries.next().is_none() + && child.kind.is_dir() + { + entry_iter.advance(); + + continue; + } + } + let depth = old_ancestors + .get(&entry.id) + .map(|ancestor| ancestor.current_ancestor_depth) + .unwrap_or_default() + .min(auto_folded_ancestors.len()); + if let Some(edit_state) = &mut new_state.edit_state + && edit_state.entry_id == entry.id + { + edit_state.depth = depth; + } + let mut ancestors = std::mem::take(&mut auto_folded_ancestors); + if ancestors.len() > 1 { + ancestors.reverse(); + new_state.ancestors.insert( + entry.id, + FoldedAncestors { + current_ancestor_depth: depth, + ancestors, }, - ) - }) - .or_else(|| { - entry - .path - .file_name() - .map(|file_name| RelPath::new(file_name).unwrap().into()) - }) - .unwrap_or_else(|| entry.path.clone()); - let depth = path.components().count(); - (depth, path.as_str().chars().count()) - }; - let width_estimate = - item_width_estimate(depth, chars, entry.canonical_path.is_some()); - - match max_width_item.as_mut() { - Some((id, worktree_id, width)) => { - if *width < width_estimate { - *id = entry.id; - *worktree_id = worktree.read(cx).id(); - *width = width_estimate; - } - } - None => { - max_width_item = Some((entry.id, worktree.read(cx).id(), width_estimate)) - } - } + ); + } + } + auto_folded_ancestors.clear(); + if !hide_gitignore || !entry.is_ignored { + visible_worktree_entries.push(entry.to_owned()); + } + let precedes_new_entry = if let Some(new_entry_id) = new_entry_parent_id + { + entry.id == new_entry_id || { + new_state.ancestors.get(&entry.id).is_some_and(|entries| { + entries.ancestors.contains(&new_entry_id) + }) + } + } else { + false + }; + if precedes_new_entry && (!hide_gitignore || !entry.is_ignored) { + visible_worktree_entries.push(Self::create_new_git_entry( + entry.entry, + entry.git_summary, + new_entry_kind, + )); + } - if expanded_dir_ids.binary_search(&entry.id).is_err() - && entry_iter.advance_to_sibling() - { - continue; - } - entry_iter.advance(); - } + let (depth, chars) = if Some(entry.entry) + == worktree_snapshot.root_entry() + { + let Some(path_name) = worktree_abs_path.file_name() else { + continue; + }; + let depth = 0; + (depth, path_name.to_string_lossy().chars().count()) + } else if entry.is_file() { + let Some(path_name) = entry + .path + .file_name() + .with_context(|| { + format!("Non-root entry has no file name: {entry:?}") + }) + .log_err() + else { + continue; + }; + let depth = entry.path.ancestors().count() - 1; + (depth, path_name.chars().count()) + } else { + let path = new_state + .ancestors + .get(&entry.id) + .and_then(|ancestors| { + let outermost_ancestor = ancestors.ancestors.last()?; + let root_folded_entry = worktree_snapshot + .entry_for_id(*outermost_ancestor)? + .path + .as_ref(); + entry.path.strip_prefix(root_folded_entry).ok().and_then( + |suffix| { + Some( + RelPath::new(root_folded_entry.file_name()?) + .unwrap() + .join(suffix), + ) + }, + ) + }) + .or_else(|| { + entry.path.file_name().map(|file_name| { + RelPath::new(file_name).unwrap().into() + }) + }) + .unwrap_or_else(|| entry.path.clone()); + let depth = path.components().count(); + (depth, path.as_str().chars().count()) + }; + let width_estimate = + item_width_estimate(depth, chars, entry.canonical_path.is_some()); + + match max_width_item.as_mut() { + Some((id, worktree_id, width)) => { + if *width < width_estimate { + *id = entry.id; + *worktree_id = worktree_snapshot.id(); + *width = width_estimate; + } + } + None => { + max_width_item = + Some((entry.id, worktree_snapshot.id(), width_estimate)) + } + } - project::sort_worktree_entries(&mut visible_worktree_entries); + if expanded_dir_ids.binary_search(&entry.id).is_err() + && entry_iter.advance_to_sibling() + { + continue; + } + entry_iter.advance(); + } - self.state.visible_entries.push(VisibleEntriesForWorktree { - worktree_id, - entries: visible_worktree_entries, - index: OnceCell::new(), - }) - } + project::sort_worktree_entries(&mut visible_worktree_entries); - if let Some((project_entry_id, worktree_id, _)) = max_width_item { - let mut visited_worktrees_length = 0; - let index = self - .state - .visible_entries - .iter() - .find_map(|visible_entries| { - if worktree_id == visible_entries.worktree_id { - visible_entries - .entries + new_state.visible_entries.push(VisibleEntriesForWorktree { + worktree_id, + entries: visible_worktree_entries, + index: OnceCell::new(), + }) + } + if let Some((project_entry_id, worktree_id, _)) = max_width_item { + let mut visited_worktrees_length = 0; + let index = new_state + .visible_entries .iter() - .position(|entry| entry.id == project_entry_id) - } else { - visited_worktrees_length += visible_entries.entries.len(); - None + .find_map(|visible_entries| { + if worktree_id == visible_entries.worktree_id { + visible_entries + .entries + .iter() + .position(|entry| entry.id == project_entry_id) + } else { + visited_worktrees_length += visible_entries.entries.len(); + None + } + }); + if let Some(index) = index { + new_state.max_width_item_index = Some(visited_worktrees_length + index); + } } - }); - if let Some(index) = index { - self.state.max_width_item_index = Some(visited_worktrees_length + index); - } - } - if let Some((worktree_id, entry_id)) = new_selected_entry { - self.state.selection = Some(SelectedEntry { - worktree_id, - entry_id, - }); - } - let elapsed = now.elapsed(); - if self.last_reported_update.elapsed() > Duration::from_secs(3600) { - telemetry::event!( - "Project Panel Updated", - elapsed_ms = elapsed.as_millis() as u64, - worktree_entries = self - .state - .visible_entries - .iter() - .map(|worktree| worktree.entries.len()) - .sum::(), - ) - } + if let Some((worktree_id, entry_id)) = new_selected_entry { + new_state.selection = Some(SelectedEntry { + worktree_id, + entry_id, + }); + } + new_state + }) + .await; + this.update(cx, |this, _| { + this.state = new_state; + let elapsed = now.elapsed(); + if this.last_reported_update.elapsed() > Duration::from_secs(3600) { + telemetry::event!( + "Project Panel Updated", + elapsed_ms = elapsed.as_millis() as u64, + worktree_entries = this + .state + .visible_entries + .iter() + .map(|worktree| worktree.entries.len()) + .sum::(), + ) + } + }) + .ok(); + }); } fn expand_entry( @@ -3296,7 +3332,7 @@ impl ProjectPanel { self.project.update(cx, |project, cx| { if let Some((worktree, expanded_dir_ids)) = project .worktree_for_id(worktree_id, cx) - .zip(self.expanded_dir_ids.get_mut(&worktree_id)) + .zip(self.state.expanded_dir_ids.get_mut(&worktree_id)) { project.expand_entry(worktree_id, entry_id, cx); let worktree = worktree.read(cx); @@ -4308,6 +4344,7 @@ impl ProjectPanel { if !kind.is_dir() || this + .state .expanded_dir_ids .get(&details.worktree_id) .is_some_and(|ids| ids.binary_search(&entry_id).is_ok()) @@ -4765,6 +4802,7 @@ impl ProjectPanel { }; let expanded_entry_ids = self + .state .expanded_dir_ids .get(&worktree_id) .map(Vec::as_slice) @@ -4921,6 +4959,7 @@ impl ProjectPanel { loop { let is_expanded_dir = entry.is_dir() && self + .state .expanded_dir_ids .get(&worktree.id()) .map(|ids| ids.binary_search(&entry.id).is_ok())