1use crate::file_manager::FileEntry;
2use std::collections::{HashMap, HashSet};
3use std::ffi::OsString;
4use std::path::{Path, PathBuf};
5
6pub struct NavState {
7 current_dir: PathBuf,
8 entries: Vec<FileEntry>,
9 selected: usize,
10 positions: HashMap<PathBuf, usize>,
11 markers: HashSet<PathBuf>,
12 filter: String,
13 request_id: u64,
14}
15
16impl NavState {
17 pub fn new(path: PathBuf) -> Self {
18 Self {
19 current_dir: path,
20 entries: Vec::new(),
21 selected: 0,
22 positions: HashMap::new(),
23 markers: HashSet::new(),
24 filter: String::new(),
25 request_id: 0,
26 }
27 }
28
29 pub fn current_dir(&self) -> &Path {
32 &self.current_dir
33 }
34
35 pub fn entries(&self) -> &[FileEntry] {
36 &self.entries
37 }
38
39 pub fn markers(&self) -> &HashSet<PathBuf> {
40 &self.markers
41 }
42
43 pub fn filter(&self) -> &str {
44 &self.filter
45 }
46
47 pub fn selected_idx(&self) -> usize {
48 self.selected
49 }
50
51 pub fn request_id(&self) -> u64 {
52 self.request_id
53 }
54
55 pub fn selected_entry(&self) -> Option<&FileEntry> {
56 self.entries.get(self.selected)
57 }
58
59 pub fn prepare_new_request(&mut self) -> u64 {
62 self.request_id += 1;
63 self.request_id
64 }
65
66 pub fn move_up(&mut self) -> bool {
67 let len = self.shown_entries_len();
68 if len == 0 {
69 return false;
70 }
71
72 if self.selected == 0 {
73 self.selected = len - 1;
74 } else {
75 self.selected -= 1;
76 }
77 true
78 }
79
80 pub fn move_down(&mut self) -> bool {
81 let len = self.shown_entries_len();
82 if len == 0 {
83 return false;
84 }
85
86 self.selected = (self.selected + 1) % len;
87 true
88 }
89
90 pub fn save_position(&mut self) {
91 self.positions
92 .insert(self.current_dir.clone(), self.selected);
93 }
94
95 pub fn get_position(&self) -> &HashMap<PathBuf, usize> {
96 &self.positions
97 }
98
99 pub fn set_path(&mut self, path: PathBuf) {
100 self.current_dir = path;
101 self.entries.clear();
102 self.selected = 0;
103 self.request_id += 1;
105 }
106
107 pub fn update_from_worker(
108 &mut self,
109 path: PathBuf,
110 entries: Vec<FileEntry>,
111 focus: Option<OsString>,
112 ) {
113 self.current_dir = path;
114 self.entries = entries;
115
116 if let Some(f) = focus {
117 self.selected = self
118 .entries
119 .iter()
120 .position(|e| e.name() == &f)
121 .unwrap_or(0);
122 } else {
123 self.selected = self.positions.get(&self.current_dir).cloned().unwrap_or(0);
124 }
125
126 self.selected = self.selected.min(self.entries.len().saturating_sub(1));
127 }
128
129 pub fn toggle_marker(&mut self) {
130 if let Some(entry) = self.selected_shown_entry() {
131 let path = self.current_dir().join(entry.name());
132 if !self.markers.remove(&path) {
133 self.markers.insert(path);
134 }
135 }
136 }
137
138 pub fn clear_markers(&mut self) {
139 self.markers.clear();
140 }
141
142 pub fn get_action_targets(&self) -> HashSet<PathBuf> {
143 if self.markers.is_empty() {
144 self.selected_entry()
145 .map(|e| self.current_dir.join(e.name()))
146 .into_iter()
147 .collect()
148 } else {
149 self.markers.iter().cloned().collect()
150 }
151 }
152
153 pub fn filtered_entries(&self) -> Vec<&FileEntry> {
156 if self.filter.is_empty() {
157 self.entries.iter().collect()
158 } else {
159 let filter_lower = self.filter.to_lowercase();
160 self.entries
161 .iter()
162 .filter(|e| {
163 e.name()
164 .to_string_lossy()
165 .to_lowercase()
166 .contains(&filter_lower)
167 })
168 .collect()
169 }
170 }
171
172 pub fn shown_entries(&self) -> Box<dyn Iterator<Item = &FileEntry> + '_> {
173 if self.filter.is_empty() {
174 Box::new(self.entries.iter())
175 } else {
176 let filter_lower = self.filter.to_lowercase();
177 Box::new(self.entries.iter().filter(move |e| {
178 e.name()
179 .to_string_lossy()
180 .to_lowercase()
181 .contains(&filter_lower)
182 }))
183 }
184 }
185
186 pub fn shown_entries_len(&self) -> usize {
187 if self.filter.is_empty() {
188 self.entries.len()
189 } else {
190 let filter_lower = self.filter.to_lowercase();
191 self.entries
192 .iter()
193 .filter(|e| {
194 e.name()
195 .to_string_lossy()
196 .to_lowercase()
197 .contains(&filter_lower)
198 })
199 .count()
200 }
201 }
202
203 pub fn selected_shown_entry(&self) -> Option<&FileEntry> {
204 self.shown_entries().nth(self.selected)
205 }
206
207 pub fn set_filter(&mut self, filter: String) {
208 self.filter = filter;
209 self.selected = 0;
210 }
211
212 pub fn clear_filters(&mut self) {
213 self.filter.clear();
214 }
215}