Skip to main content

excel_cli/app/
navigation.rs

1use crate::app::AppState;
2use crate::excel::{EXCEL_MAX_COLS, EXCEL_MAX_ROWS};
3use crate::utils::find_non_empty_cell;
4use crate::utils::Direction;
5
6impl AppState<'_> {
7    pub fn move_cursor(&mut self, delta_row: isize, delta_col: isize) {
8        // Calculate new position
9        let new_row =
10            (self.selected_cell.0 as isize + delta_row).clamp(1, EXCEL_MAX_ROWS as isize) as usize;
11        let new_col =
12            (self.selected_cell.1 as isize + delta_col).clamp(1, EXCEL_MAX_COLS as isize) as usize;
13
14        // Update selected position
15        self.selected_cell = (new_row, new_col);
16
17        // Handle scrolling
18        self.handle_scrolling();
19    }
20
21    pub fn handle_scrolling(&mut self) {
22        let frozen_rows = self.workbook.get_current_sheet().freeze_panes.rows;
23        let min_scroll_row = frozen_rows + 1;
24
25        if frozen_rows > 0 && self.start_row < min_scroll_row {
26            self.start_row = min_scroll_row;
27        }
28
29        if self.selected_cell.0 > frozen_rows {
30            let frozen_rows_visible = if self.visible_rows > 1 {
31                frozen_rows.min(self.visible_rows - 1)
32            } else {
33                0
34            };
35            let scroll_rows_visible = self.visible_rows.saturating_sub(frozen_rows_visible).max(1);
36
37            if self.selected_cell.0 < self.start_row {
38                self.start_row = self.selected_cell.0.max(min_scroll_row);
39            } else if self.selected_cell.0 >= self.start_row + scroll_rows_visible {
40                self.start_row =
41                    (self.selected_cell.0 - scroll_rows_visible + 1).max(min_scroll_row);
42            }
43        }
44
45        self.handle_column_scrolling();
46    }
47
48    pub fn jump_to_first_row(&mut self) {
49        let current_col = self.selected_cell.1;
50        self.selected_cell = (1, current_col);
51        self.handle_scrolling();
52        self.add_notification("Jumped to first row".to_string());
53    }
54
55    pub fn jump_to_last_row(&mut self) {
56        let sheet = self.workbook.get_current_sheet();
57        let current_col = self.selected_cell.1;
58
59        let max_row = sheet.max_rows;
60
61        self.selected_cell = (max_row, current_col);
62        self.handle_scrolling();
63        self.add_notification("Jumped to last row".to_string());
64    }
65
66    pub fn jump_to_first_column(&mut self) {
67        let current_row = self.selected_cell.0;
68        self.selected_cell = (current_row, 1);
69        self.handle_scrolling();
70        self.add_notification("Jumped to first column".to_string());
71    }
72
73    pub fn jump_to_first_non_empty_column(&mut self) {
74        let sheet = self.workbook.get_current_sheet();
75        let current_row = self.selected_cell.0;
76
77        let mut first_non_empty_col = 1; // Default to first column
78
79        if current_row < sheet.data.len() {
80            for col in 1..=sheet.max_cols {
81                if col < sheet.data[current_row].len()
82                    && !sheet.data[current_row][col].value.is_empty()
83                {
84                    first_non_empty_col = col;
85                    break;
86                }
87            }
88        }
89
90        self.selected_cell = (current_row, first_non_empty_col);
91        self.handle_scrolling();
92        self.add_notification("Jumped to first non-empty column".to_string());
93    }
94
95    pub fn jump_to_last_column(&mut self) {
96        let sheet = self.workbook.get_current_sheet();
97        let current_row = self.selected_cell.0;
98
99        let max_col = sheet.max_cols;
100
101        self.selected_cell = (current_row, max_col);
102        self.handle_scrolling();
103        self.add_notification("Jumped to last column".to_string());
104    }
105
106    fn jump_to_non_empty_cell(&mut self, direction: Direction) {
107        let sheet = self.workbook.get_current_sheet();
108        let max_bounds = (sheet.max_rows, sheet.max_cols);
109        let current_pos = self.selected_cell;
110
111        if let Some(new_pos) = find_non_empty_cell(sheet, current_pos, direction, max_bounds) {
112            self.selected_cell = new_pos;
113            self.handle_scrolling();
114
115            let dir_name = match direction {
116                Direction::Left => "left",
117                Direction::Right => "right",
118                Direction::Up => "up",
119                Direction::Down => "down",
120            };
121
122            // Re-fetch sheet to avoid borrow conflict
123            let sheet = self.workbook.get_current_sheet();
124
125            let (row, col) = self.selected_cell;
126            let is_cell_empty = row >= sheet.data.len()
127                || col >= sheet.data[row].len()
128                || sheet.data[row][col].value.is_empty();
129
130            let message = if is_cell_empty {
131                format!("Jumped to first non-empty cell ({dir_name})")
132            } else {
133                format!("Jumped to last non-empty cell ({dir_name})")
134            };
135
136            self.add_notification(message);
137        }
138    }
139
140    pub fn jump_to_prev_non_empty_cell_left(&mut self) {
141        self.jump_to_non_empty_cell(Direction::Left);
142    }
143
144    pub fn jump_to_prev_non_empty_cell_right(&mut self) {
145        self.jump_to_non_empty_cell(Direction::Right);
146    }
147
148    pub fn jump_to_prev_non_empty_cell_up(&mut self) {
149        self.jump_to_non_empty_cell(Direction::Up);
150    }
151
152    pub fn jump_to_prev_non_empty_cell_down(&mut self) {
153        self.jump_to_non_empty_cell(Direction::Down);
154    }
155
156    fn handle_column_scrolling(&mut self) {
157        self.ensure_column_visible(self.selected_cell.1);
158    }
159
160    pub fn ensure_column_visible(&mut self, column: usize) {
161        let column = column.min(EXCEL_MAX_COLS);
162        let frozen_cols = self.workbook.get_current_sheet().freeze_panes.cols;
163        let min_scroll_col = frozen_cols + 1;
164
165        if frozen_cols > 0 && self.start_col < min_scroll_col {
166            self.start_col = min_scroll_col;
167        }
168
169        if column <= frozen_cols {
170            return;
171        }
172
173        // If column is to the left of visible area, adjust start_col
174        if column < self.start_col {
175            self.start_col = column.max(min_scroll_col);
176            return;
177        }
178
179        let frozen_cols_visible = if self.visible_cols > 1 {
180            frozen_cols.min(self.visible_cols - 1)
181        } else {
182            0
183        };
184        let scroll_cols_visible = self.visible_cols.saturating_sub(frozen_cols_visible).max(1);
185        let last_visible_col = self.start_col + scroll_cols_visible - 1;
186
187        // If column is to the right of visible area, adjust start_col to make it visible
188        if column > last_visible_col {
189            self.start_col = (column - scroll_cols_visible + 1).max(min_scroll_col);
190            return;
191        }
192
193        // If the column is already visible but at the right edge, try to add a margin
194        // Only apply margin logic if not at the max column
195        if column < EXCEL_MAX_COLS && column == last_visible_col && scroll_cols_visible > 1 {
196            // Adjust start column to show more columns to the left
197            // This creates a margin on the right
198            self.start_col = (column - (scroll_cols_visible - 2)).max(min_scroll_col);
199        }
200    }
201}