Skip to content

Commit adb6025

Browse files
authored
Fold folders up into parent if alone in directory (#288)
1 parent 59c6626 commit adb6025

File tree

3 files changed

+365
-89
lines changed

3 files changed

+365
-89
lines changed

Diff for: src/components/filetree.rs

+99-41
Original file line numberDiff line numberDiff line change
@@ -135,23 +135,36 @@ impl FileTreeComponent {
135135
changed
136136
}
137137

138+
const fn item_status_char(item_type: StatusItemType) -> char {
139+
match item_type {
140+
StatusItemType::Modified => 'M',
141+
StatusItemType::New => '+',
142+
StatusItemType::Deleted => '-',
143+
StatusItemType::Renamed => 'R',
144+
StatusItemType::Typechange => ' ',
145+
}
146+
}
147+
138148
fn item_to_text<'b>(
139-
item: &FileTreeItem,
149+
string: &str,
150+
indent: usize,
151+
visible: bool,
152+
file_item_kind: &FileTreeItemKind,
140153
width: u16,
141154
selected: bool,
142155
theme: &'b SharedTheme,
143156
) -> Option<Text<'b>> {
144-
let indent_str = if item.info.indent == 0 {
157+
let indent_str = if indent == 0 {
145158
String::from("")
146159
} else {
147-
format!("{:w$}", " ", w = (item.info.indent as usize) * 2)
160+
format!("{:w$}", " ", w = (indent as usize) * 2)
148161
};
149162

150-
if !item.info.visible {
163+
if !visible {
151164
return None;
152165
}
153166

154-
match &item.kind {
167+
match file_item_kind {
155168
FileTreeItemKind::File(status_item) => {
156169
let status_char =
157170
Self::item_status_char(status_item.status);
@@ -187,13 +200,13 @@ impl FileTreeComponent {
187200
" {}{}{:w$}",
188201
indent_str,
189202
collapse_char,
190-
item.info.path,
203+
string,
191204
w = width as usize
192205
)
193206
} else {
194207
format!(
195208
" {}{}{}",
196-
indent_str, collapse_char, item.info.path,
209+
indent_str, collapse_char, string,
197210
)
198211
};
199212

@@ -205,17 +218,80 @@ impl FileTreeComponent {
205218
}
206219
}
207220

208-
const fn item_status_char(item_type: StatusItemType) -> char {
209-
match item_type {
210-
StatusItemType::Modified => 'M',
211-
StatusItemType::New => '+',
212-
StatusItemType::Deleted => '-',
213-
StatusItemType::Renamed => 'R',
214-
StatusItemType::Typechange => ' ',
221+
/// Returns a Vec<TextDrawInfo> which is used to draw the `FileTreeComponent` correctly,
222+
/// allowing folders to be folded up if they are alone in their directory
223+
fn build_vec_text_draw_info_for_drawing(
224+
&self,
225+
) -> (Vec<TextDrawInfo>, usize) {
226+
let mut should_skip_over: usize = 0;
227+
let mut selection_offset: usize = 0;
228+
let mut vec_draw_text_info: Vec<TextDrawInfo> = vec![];
229+
let tree_items = self.tree.tree.items();
230+
for (index, item) in tree_items.iter().enumerate() {
231+
if should_skip_over > 0 {
232+
should_skip_over -= 1;
233+
continue;
234+
}
235+
236+
let index_above_select =
237+
index < self.tree.selection.unwrap_or(0);
238+
239+
vec_draw_text_info.push(TextDrawInfo {
240+
name: item.info.path.clone(),
241+
indent: item.info.indent,
242+
visible: item.info.visible,
243+
item_kind: &item.kind,
244+
});
245+
246+
let mut idx_temp = index;
247+
248+
while idx_temp < tree_items.len().saturating_sub(2)
249+
&& tree_items[idx_temp].info.indent
250+
< tree_items[idx_temp + 1].info.indent
251+
{
252+
// fold up the folder/file
253+
idx_temp += 1;
254+
should_skip_over += 1;
255+
256+
// don't fold files up
257+
if let FileTreeItemKind::File(_) =
258+
&tree_items[idx_temp].kind
259+
{
260+
should_skip_over -= 1;
261+
break;
262+
}
263+
264+
// don't fold up if more than one folder in folder
265+
if self.tree.tree.multiple_items_at_path(idx_temp) {
266+
should_skip_over -= 1;
267+
break;
268+
} else {
269+
// There is only one item at this level (i.e only one folder in the folder),
270+
// so do fold up
271+
272+
let vec_draw_text_info_len =
273+
vec_draw_text_info.len();
274+
vec_draw_text_info[vec_draw_text_info_len - 1]
275+
.name += &(String::from("/")
276+
+ &tree_items[idx_temp].info.path);
277+
if index_above_select {
278+
selection_offset += 1;
279+
}
280+
}
281+
}
215282
}
283+
(vec_draw_text_info, selection_offset)
216284
}
217285
}
218286

287+
/// Used for drawing the `FileTreeComponent`
288+
struct TextDrawInfo<'a> {
289+
name: String,
290+
indent: u8,
291+
visible: bool,
292+
item_kind: &'a FileTreeItemKind,
293+
}
294+
219295
impl DrawableComponent for FileTreeComponent {
220296
fn draw<B: Backend>(
221297
&self,
@@ -238,21 +314,8 @@ impl DrawableComponent for FileTreeComponent {
238314
&self.theme,
239315
);
240316
} else {
241-
let selection_offset =
242-
self.tree.tree.items().iter().enumerate().fold(
243-
0,
244-
|acc, (idx, e)| {
245-
let visible = e.info.visible;
246-
let index_above_select =
247-
idx < self.tree.selection.unwrap_or(0);
248-
249-
if !visible && index_above_select {
250-
acc + 1
251-
} else {
252-
acc
253-
}
254-
},
255-
);
317+
let (vec_draw_text_info, selection_offset) =
318+
self.build_vec_text_draw_info_for_drawing();
256319

257320
let select = self
258321
.tree
@@ -267,26 +330,21 @@ impl DrawableComponent for FileTreeComponent {
267330
select,
268331
));
269332

270-
let items = self
271-
.tree
272-
.tree
273-
.items()
333+
let items = vec_draw_text_info
274334
.iter()
275335
.enumerate()
276-
.filter_map(|(idx, e)| {
336+
.filter_map(|(index, draw_text_info)| {
277337
Self::item_to_text(
278-
e,
338+
&draw_text_info.name,
339+
draw_text_info.indent as usize,
340+
draw_text_info.visible,
341+
draw_text_info.item_kind,
279342
r.width,
280-
self.show_selection
281-
&& self
282-
.tree
283-
.selection
284-
.map_or(false, |e| e == idx),
343+
self.show_selection && select == index,
285344
&self.theme,
286345
)
287346
})
288347
.skip(self.scroll_top.get());
289-
290348
ui::draw_list(
291349
f,
292350
r,

Diff for: src/components/utils/filetree.rs

+29-30
Original file line numberDiff line numberDiff line change
@@ -185,27 +185,6 @@ impl FileTreeItems {
185185
self.file_count
186186
}
187187

188-
///
189-
pub(crate) fn find_parent_index(
190-
&self,
191-
path: &str,
192-
index: usize,
193-
) -> usize {
194-
if let Some(parent_path) = Path::new(path).parent() {
195-
let parent_path =
196-
parent_path.to_str().expect("invalid path");
197-
for i in (0..=index).rev() {
198-
let item = &self.items[i];
199-
let item_path = &item.info.full_path;
200-
if item_path == parent_path {
201-
return i;
202-
}
203-
}
204-
}
205-
206-
0
207-
}
208-
209188
fn push_dirs<'a>(
210189
item_path: &'a Path,
211190
nodes: &mut Vec<FileTreeItem>,
@@ -232,6 +211,25 @@ impl FileTreeItems {
232211

233212
Ok(())
234213
}
214+
215+
pub fn multiple_items_at_path(&self, index: usize) -> bool {
216+
let tree_items = self.items();
217+
let mut idx_temp_inner;
218+
if index + 2 < tree_items.len() {
219+
idx_temp_inner = index + 1;
220+
while idx_temp_inner < tree_items.len().saturating_sub(1)
221+
&& tree_items[index].info.indent
222+
< tree_items[idx_temp_inner].info.indent
223+
{
224+
idx_temp_inner += 1;
225+
}
226+
} else {
227+
return false;
228+
}
229+
230+
tree_items[idx_temp_inner].info.indent
231+
== tree_items[index].info.indent
232+
}
235233
}
236234

237235
impl IndexMut<usize> for FileTreeItems {
@@ -378,24 +376,25 @@ mod tests {
378376
}
379377

380378
#[test]
381-
fn test_find_parent() {
379+
fn test_multiple_items_at_path() {
382380
//0 a/
383381
//1 b/
384-
//2 c
385-
//3 d
382+
//2 c/
383+
//3 d
384+
//4 e/
385+
//5 f
386386

387387
let res = FileTreeItems::new(
388388
&string_vec_to_status(&[
389-
"a/b/c", //
390-
"a/b/d", //
389+
"a/b/c/d", //
390+
"a/b/e/f", //
391391
]),
392392
&BTreeSet::new(),
393393
)
394394
.unwrap();
395395

396-
assert_eq!(
397-
res.find_parent_index(&String::from("a/b/c"), 3),
398-
1
399-
);
396+
assert_eq!(res.multiple_items_at_path(0), false);
397+
assert_eq!(res.multiple_items_at_path(1), false);
398+
assert_eq!(res.multiple_items_at_path(2), true);
400399
}
401400
}

0 commit comments

Comments
 (0)