excel_cli/app/
navigation.rs1use 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 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 self.selected_cell = (new_row, new_col);
16
17 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; 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 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 < 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 > last_visible_col {
189 self.start_col = (column - scroll_cols_visible + 1).max(min_scroll_col);
190 return;
191 }
192
193 if column < EXCEL_MAX_COLS && column == last_visible_col && scroll_cols_visible > 1 {
196 self.start_col = (column - (scroll_cols_visible - 2)).max(min_scroll_col);
199 }
200 }
201}