ratatui_toolkit/widgets/code_diff/extensions/file_tree/methods/
navigation.rs

1//! Navigation methods for DiffFileTree.
2//!
3//! These methods delegate to TreeNavigator for centralized keyboard handling.
4
5use crate::primitives::tree_view::{get_visible_paths_filtered, matches_filter, TreeNavigator};
6use crate::widgets::code_diff::diff_file_tree::{DiffFileEntry, DiffFileTree};
7
8impl DiffFileTree {
9    /// Returns the filter matcher function for DiffFileEntry nodes.
10    fn filter_matcher() -> impl Fn(&DiffFileEntry, &Option<String>) -> bool + Copy {
11        |entry: &DiffFileEntry, filter: &Option<String>| matches_filter(&entry.name, filter)
12    }
13
14    /// Selects the next visible item in the tree.
15    ///
16    /// Respects the current filter (if any).
17    pub fn select_next(&mut self) {
18        let navigator = TreeNavigator::new();
19        navigator.select_next_filtered(&self.nodes, &mut self.state, Self::filter_matcher());
20    }
21
22    /// Selects the previous visible item in the tree.
23    ///
24    /// Respects the current filter (if any).
25    pub fn select_prev(&mut self) {
26        let navigator = TreeNavigator::new();
27        navigator.select_previous_filtered(&self.nodes, &mut self.state, Self::filter_matcher());
28    }
29
30    /// Toggles the expansion state of the currently selected node.
31    ///
32    /// Only has an effect if the selected node is a directory.
33    pub fn toggle_expand(&mut self) {
34        let navigator = TreeNavigator::new();
35        navigator.toggle_selected(&self.nodes, &mut self.state);
36    }
37
38    /// Expands the currently selected directory.
39    ///
40    /// Only has an effect if the selected node is a directory.
41    pub fn expand_selected(&mut self) {
42        let navigator = TreeNavigator::new();
43        navigator.expand_selected(&self.nodes, &mut self.state);
44    }
45
46    /// Collapses the currently selected directory if it's expanded.
47    ///
48    /// # Returns
49    ///
50    /// `true` if a directory was collapsed, `false` otherwise (not expanded
51    /// or not a directory).
52    pub fn collapse_selected(&mut self) -> bool {
53        if let Some(path) = self.state.selected_path.clone() {
54            if self.state.is_expanded(&path) {
55                // Check if it's a directory
56                if let Some(node) = self.get_node_at_path(&path) {
57                    if node.data.is_dir {
58                        self.state.collapse(path);
59                        return true;
60                    }
61                }
62            }
63        }
64        false
65    }
66
67    /// Moves selection to the parent directory of the currently selected node.
68    ///
69    /// If the selected node is already at the root level, this does nothing.
70    pub fn go_to_parent(&mut self) {
71        if let Some(path) = &self.state.selected_path {
72            if path.len() > 1 {
73                // Move to parent by removing the last index
74                let parent = path[..path.len() - 1].to_vec();
75                self.state.select(parent);
76            }
77        }
78    }
79
80    /// Goes to the first visible item in the tree.
81    ///
82    /// Respects the current filter (if any).
83    pub fn goto_top(&mut self) {
84        let navigator = TreeNavigator::new();
85        navigator.goto_top_filtered(&self.nodes, &mut self.state, Self::filter_matcher());
86    }
87
88    /// Goes to the last visible item in the tree.
89    ///
90    /// Respects the current filter (if any).
91    pub fn goto_bottom(&mut self) {
92        let navigator = TreeNavigator::new();
93        navigator.goto_bottom_filtered(&self.nodes, &mut self.state, Self::filter_matcher());
94    }
95
96    /// Returns whether the selected item is a directory.
97    ///
98    /// # Returns
99    ///
100    /// `Some(true)` if selected item is a directory, `Some(false)` if it's a file,
101    /// `None` if no item is selected.
102    #[must_use]
103    pub fn selected_is_dir(&self) -> Option<bool> {
104        let path = self.state.selected_path.as_ref()?;
105        let node = self.get_node_at_path(path)?;
106        Some(node.data.is_dir)
107    }
108
109    /// Returns the total number of visible items.
110    ///
111    /// Respects the current filter (if any).
112    #[must_use]
113    pub fn visible_count(&self) -> usize {
114        get_visible_paths_filtered(&self.nodes, &self.state, Self::filter_matcher()).len()
115    }
116
117    /// Sets the selection by visible index.
118    ///
119    /// Respects the current filter (if any).
120    ///
121    /// # Arguments
122    ///
123    /// * `index` - The 0-based index in the visible items list
124    pub fn set_selected_index(&mut self, index: usize) {
125        let visible_paths =
126            get_visible_paths_filtered(&self.nodes, &self.state, Self::filter_matcher());
127        if let Some(path) = visible_paths.get(index) {
128            self.state.select(path.clone());
129        }
130    }
131
132    /// Helper to get a node at a specific path.
133    pub(crate) fn get_node_at_path(
134        &self,
135        path: &[usize],
136    ) -> Option<
137        &crate::primitives::tree_view::TreeNode<
138            crate::widgets::code_diff::diff_file_tree::DiffFileEntry,
139        >,
140    > {
141        if path.is_empty() {
142            return None;
143        }
144
145        let mut current_nodes = &self.nodes;
146        let mut node = None;
147
148        for &idx in path {
149            node = current_nodes.get(idx);
150            if let Some(n) = node {
151                current_nodes = &n.children;
152            } else {
153                return None;
154            }
155        }
156
157        node
158    }
159}