use std::path::{Path, PathBuf};
use super::state::{LayoutMode, View};
use super::{App, SelectedInfo};
use crate::ui::TreeView;
impl App {
pub(super) fn toggle_mark(&mut self) {
let path = match self.view {
View::Explorer => {
if let Some((node, root_path)) = self.get_view_root_node() {
match self.layout_mode {
LayoutMode::Tree => {
let selected = TreeView::new(
node,
&root_path,
&self.theme,
&self.marked,
&self.clipboard,
)
.row(&self.nav.tree_state, self.nav.tree_state.selected);
selected.as_ref().map(|i| i.path.clone())
}
LayoutMode::Miller => node
.children
.get(self.nav.miller_state.selected)
.map(|child| self.nav.view_root.join(child.file_name())),
}
} else {
None
}
}
View::Duplicates => {
let Some(report) = self.duplicates.clone() else {
return;
};
let Some(group_index) = self
.duplicate_filter
.get(&report, &self.nav.view_root, &self.path)
.groups
.get(self.duplicates_state.selected_group)
.map(|(index, _)| *index)
else {
return;
};
let group = &report.groups[group_index];
let selected = self
.duplicates_state
.selected_item(self.duplicates_state.selected_group);
let is_on_header = !self
.duplicates_state
.is_expanded(self.duplicates_state.selected_group)
|| selected == 0;
if is_on_header {
if group.paths.iter().any(|path| self.marked.contains(path)) {
for path in &group.paths {
self.marked.remove(path);
}
} else {
for path in group.paths.iter().skip(1) {
self.mark(path.clone());
}
}
return;
}
group.paths.get(selected - 1).cloned()
}
View::Age => {
if let Some(filtered) = self.get_filtered_stale_dirs() {
let selected = self
.selected_stale_dir
.min(filtered.len().saturating_sub(1));
filtered.get(selected).map(|d| d.path.clone())
} else {
None
}
}
View::Errors => {
if let Some(warning) = self.displayed_warning(self.selected_warning) {
if warning.kind == gravityfile_core::WarningKind::BrokenSymlink {
Some(warning.path.clone())
} else {
None
}
} else {
None
}
}
View::Treemap => self.get_selected_treemap_child().map(|(_, path)| path),
};
if let Some(path) = path
&& !self.marked.remove(&path)
{
self.mark(path);
}
}
pub(super) fn get_path_size(&self, target: &Path) -> Option<u64> {
Some(self.tree.as_ref()?.find(target)?.size)
}
pub(super) fn move_up(&mut self) {
match self.view {
View::Explorer => match self.layout_mode {
LayoutMode::Tree => self.nav.tree_state.move_up(1),
LayoutMode::Miller => {
self.nav.miller_state.move_up(1);
self.update_preview();
}
},
View::Duplicates => {
let (group_count, file_counts) =
if let Some((filtered, _)) = self.get_filtered_duplicates() {
let counts: Vec<usize> = filtered.iter().map(|g| g.paths.len()).collect();
(filtered.len(), counts)
} else {
(0, Vec::new())
};
if group_count > 0 {
self.duplicates_state.move_up(group_count, |idx| {
file_counts.get(idx).copied().unwrap_or(0)
});
}
}
View::Age => {
self.selected_stale_dir = self.selected_stale_dir.saturating_sub(1);
}
View::Errors => {
self.selected_warning = self.selected_warning.saturating_sub(1);
}
View::Treemap => {
self.nav.treemap_state.move_prev();
}
}
}
pub(super) fn move_down(&mut self) {
match self.view {
View::Explorer => match self.layout_mode {
LayoutMode::Tree => {
self.nav.tree_state.move_down(1, self.cached_tree_len);
}
LayoutMode::Miller => {
self.nav.miller_state.move_down(1, self.cached_miller_len);
self.update_preview();
}
},
View::Duplicates => {
let (group_count, file_counts) =
if let Some((filtered, _)) = self.get_filtered_duplicates() {
let counts: Vec<usize> = filtered.iter().map(|g| g.paths.len()).collect();
(filtered.len(), counts)
} else {
(0, Vec::new())
};
if group_count > 0 {
self.duplicates_state.move_down(group_count, |idx| {
file_counts.get(idx).copied().unwrap_or(0)
});
}
}
View::Age => {
if let Some(filtered) = self.get_filtered_stale_dirs() {
let max = filtered.len().saturating_sub(1);
self.selected_stale_dir = (self.selected_stale_dir + 1).min(max);
}
}
View::Errors => {
let max = self.warning_count().saturating_sub(1);
self.selected_warning = (self.selected_warning + 1).min(max);
}
View::Treemap => {
self.nav.treemap_state.move_next(self.cached_treemap_len);
}
}
}
pub(super) fn page_size(&self) -> usize {
self.viewport_height.saturating_sub(1).max(1)
}
pub(super) fn page_up(&mut self) {
let page = self.page_size();
match self.view {
View::Explorer => match self.layout_mode {
LayoutMode::Tree => self.nav.tree_state.move_up(page),
LayoutMode::Miller => {
self.nav.miller_state.move_up(page);
self.update_preview();
}
},
View::Duplicates => {
self.duplicates_state.selected_group =
self.duplicates_state.selected_group.saturating_sub(page);
}
View::Age => {
self.selected_stale_dir = self.selected_stale_dir.saturating_sub(page);
}
View::Errors => {
self.selected_warning = self.selected_warning.saturating_sub(page);
}
View::Treemap => {
for _ in 0..page {
self.nav.treemap_state.move_prev();
}
}
}
}
pub(super) fn page_down(&mut self) {
let page = self.page_size();
match self.view {
View::Explorer => match self.layout_mode {
LayoutMode::Tree => {
self.nav.tree_state.move_down(page, self.cached_tree_len);
}
LayoutMode::Miller => {
self.nav
.miller_state
.move_down(page, self.cached_miller_len);
self.update_preview();
}
},
View::Duplicates => {
if let Some((filtered, _)) = self.get_filtered_duplicates() {
let max = filtered.len().saturating_sub(1);
self.duplicates_state.selected_group =
(self.duplicates_state.selected_group + page).min(max);
}
}
View::Age => {
if let Some(filtered) = self.get_filtered_stale_dirs() {
let max = filtered.len().saturating_sub(1);
self.selected_stale_dir = (self.selected_stale_dir + page).min(max);
}
}
View::Errors => {
let max = self.warning_count().saturating_sub(1);
self.selected_warning = (self.selected_warning + page).min(max);
}
View::Treemap => {
for _ in 0..page {
self.nav.treemap_state.move_next(self.cached_treemap_len);
}
}
}
}
pub(super) fn jump_to_top(&mut self) {
match self.view {
View::Explorer => match self.layout_mode {
LayoutMode::Tree => self.nav.tree_state.jump_to_top(),
LayoutMode::Miller => {
self.nav.miller_state.jump_to_top();
self.update_preview();
}
},
View::Duplicates => self.duplicates_state.reset(),
View::Age => self.selected_stale_dir = 0,
View::Errors => self.selected_warning = 0,
View::Treemap => self.nav.treemap_state.reset(),
}
}
pub(super) fn jump_to_bottom(&mut self) {
match self.view {
View::Explorer => match self.layout_mode {
LayoutMode::Tree => {
self.nav.tree_state.jump_to_bottom(self.cached_tree_len);
}
LayoutMode::Miller => {
self.nav.miller_state.jump_to_bottom(self.cached_miller_len);
self.update_preview();
}
},
View::Duplicates => {
if let Some((filtered, _)) = self.get_filtered_duplicates() {
self.duplicates_state.selected_group = filtered.len().saturating_sub(1);
}
}
View::Age => {
if let Some(filtered) = self.get_filtered_stale_dirs() {
self.selected_stale_dir = filtered.len().saturating_sub(1);
}
}
View::Errors => {
self.selected_warning = self.warning_count().saturating_sub(1);
}
View::Treemap => {
self.nav.treemap_state.selected = self.cached_treemap_len.saturating_sub(1);
}
}
}
pub(super) fn expand_selected(&mut self) {
if let Some((node, root_path)) = self.get_view_root_node() {
let selected =
TreeView::new(node, &root_path, &self.theme, &self.marked, &self.clipboard)
.row(&self.nav.tree_state, self.nav.tree_state.selected);
if let Some(item) = selected.as_ref() {
self.load_children(&item.path);
self.nav.tree_state.expand(&item.path);
self.update_cached_tree_len();
}
}
}
pub(super) fn collapse_selected(&mut self) {
if let Some((node, root_path)) = self.get_view_root_node() {
let selected =
TreeView::new(node, &root_path, &self.theme, &self.marked, &self.clipboard)
.row(&self.nav.tree_state, self.nav.tree_state.selected);
if let Some(item) = selected.as_ref() {
let is_expanded_dir = matches!(
item.node.kind,
crate::ui::VisibleNodeKind::Directory { expanded: true }
);
if is_expanded_dir {
self.nav.tree_state.collapse(&item.path);
self.update_cached_tree_len();
} else {
self.go_back();
}
}
}
}
pub(super) fn toggle_selected(&mut self) {
if let Some((node, root_path)) = self.get_view_root_node() {
let selected =
TreeView::new(node, &root_path, &self.theme, &self.marked, &self.clipboard)
.row(&self.nav.tree_state, self.nav.tree_state.selected);
if let Some(item) = selected.as_ref() {
self.nav.tree_state.toggle_expand(&item.path);
self.update_cached_tree_len();
}
}
}
pub(super) fn get_selected_info(&self) -> Option<SelectedInfo> {
let path = self.get_selected_path()?;
let tree = self.tree.as_ref()?;
let node = Self::find_node_at_path(&tree.root, &path, &tree.root_path)?;
Some(SelectedInfo {
name: node.display_name().into_owned(),
path,
size: node.size,
file_count: node.file_count(),
dir_count: node.dir_count(),
modified: node.timestamps.modified,
is_dir: node.is_dir(),
})
}
pub(super) fn is_selected_file(&self) -> bool {
self.get_selected_info().is_some_and(|info| !info.is_dir)
}
pub(super) fn get_selected_path(&self) -> Option<PathBuf> {
match self.view {
View::Explorer => {}
View::Treemap => return self.get_selected_treemap_child().map(|(_, path)| path),
View::Errors => {
return self
.displayed_warning(self.selected_warning)
.map(|warning| warning.path.clone());
}
View::Duplicates => {
let (groups, _) = self.get_filtered_duplicates()?;
return self.duplicates_state.selected_file_path(&groups).cloned();
}
View::Age => {
let directories = self.get_filtered_stale_dirs()?;
return directories
.get(self.selected_stale_dir)
.map(|directory| directory.path.clone());
}
}
match self.layout_mode {
LayoutMode::Tree => {
if let Some((view_node, view_path)) = self.get_view_root_node() {
return TreeView::new(
view_node,
&view_path,
&self.theme,
&self.marked,
&self.clipboard,
)
.row(&self.nav.tree_state, self.nav.tree_state.selected)
.map(|item| item.path);
}
None
}
LayoutMode::Miller => {
if let Some((view_node, _)) = self.get_view_root_node()
&& let Some(child) = view_node.children.get(self.nav.miller_state.selected)
{
return Some(self.nav.view_root.join(child.file_name()));
}
None
}
}
}
pub(super) fn sync_visual_cursor_to_view(&mut self) {
if let Some(state) = &self.visual_state {
match self.layout_mode {
LayoutMode::Tree => self.nav.tree_state.selected = state.cursor,
LayoutMode::Miller => self.nav.miller_state.selected = state.cursor,
}
}
}
pub(super) fn apply_visual_selection_as_marks(&mut self) {
let Some(state) = &self.visual_state else {
return;
};
let (start, end) = state.selection_range();
let paths_to_mark: Vec<PathBuf> = match self.layout_mode {
LayoutMode::Tree => {
if let Some((node, root_path)) = self.get_view_root_node() {
let items = crate::ui::TreeView::new(
node,
&root_path,
&self.theme,
&self.marked,
&self.clipboard,
)
.flatten(&self.nav.tree_state);
(start..=end)
.filter_map(|i| items.get(i).map(|item| item.path.clone()))
.collect()
} else {
Vec::new()
}
}
LayoutMode::Miller => {
if let Some((node, _)) = self.get_view_root_node() {
let view_root = self.nav.view_root.clone();
(start..=end)
.filter_map(|i| {
node.children
.get(i)
.map(|child| view_root.join(child.file_name()))
})
.collect()
} else {
Vec::new()
}
}
};
for path in paths_to_mark {
self.mark(path);
}
}
pub(super) fn adopt_rendered_offsets(&mut self) {
(self.nav.tree_state.offset, self.nav.miller_state.offset) = self.rendered_offsets.get();
}
pub(super) fn is_protected(&self, path: &Path) -> bool {
self.path.starts_with(path) || self.nav.view_root.starts_with(path)
}
pub(super) fn mark(&mut self, path: PathBuf) -> bool {
if self.is_protected(&path) {
return false;
}
self.marked.insert(path);
true
}
}