fix: make explorer filtering explicitly modal
This commit is contained in:
+10
-10
@@ -1454,7 +1454,7 @@ impl Editor {
|
||||
Input::Backspace if picker.view == FileView::List || picker.searching => {
|
||||
picker.backspace()
|
||||
}
|
||||
Input::Char('/') if picker.view == FileView::Tree && !picker.searching => {
|
||||
Input::Char('i') if picker.view == FileView::Tree && !picker.searching => {
|
||||
picker.start_search()
|
||||
}
|
||||
Input::Char(ch)
|
||||
@@ -3729,7 +3729,7 @@ mod tests {
|
||||
let directory = Directory::new();
|
||||
fs::write(directory.path("keep.txt"), "text").unwrap();
|
||||
let mut editor = project_editor(&directory);
|
||||
type_text(&mut editor, " e/keep");
|
||||
type_text(&mut editor, " eikeep");
|
||||
editor.handle(Input::Char('\t'));
|
||||
assert!(!editor.file_picker().unwrap().focused);
|
||||
fs::write(directory.path("keep new.txt"), "new").unwrap();
|
||||
@@ -3774,7 +3774,7 @@ mod tests {
|
||||
let id = editor.buffer_id();
|
||||
editor.execute(&format!("edit {}", b.display()));
|
||||
fs::write(&a, b"\xff").unwrap();
|
||||
type_text(&mut editor, " e/a space 界");
|
||||
type_text(&mut editor, " eia space 界");
|
||||
editor.handle(Input::Enter);
|
||||
assert_eq!(editor.buffer_id(), id);
|
||||
assert_eq!(editor.buffer_count(), 3);
|
||||
@@ -3886,7 +3886,7 @@ mod tests {
|
||||
type_text(&mut editor, " o");
|
||||
editor.execute("buffers");
|
||||
assert!(editor.file_picker().is_none());
|
||||
type_text(&mut editor, " e/file");
|
||||
type_text(&mut editor, " eifile");
|
||||
editor.execute("buffers");
|
||||
assert!(!editor.file_picker().unwrap().focused);
|
||||
assert_eq!(editor.file_picker().unwrap().query, "file");
|
||||
@@ -3903,7 +3903,7 @@ mod tests {
|
||||
editor
|
||||
.apply(vec![Effect::Replace("a\nb\nc\nd".into())])
|
||||
.unwrap();
|
||||
type_text(&mut editor, " e/file");
|
||||
type_text(&mut editor, " eifile");
|
||||
editor.handle(Input::Char('\t'));
|
||||
type_text(&mut editor, "2j");
|
||||
assert_eq!(editor.cursor().row, 2);
|
||||
@@ -4081,7 +4081,7 @@ mod tests {
|
||||
.entries()
|
||||
.map(|row| row.path.to_owned())
|
||||
.collect::<Vec<_>>();
|
||||
type_text(&mut editor, "/deep/");
|
||||
type_text(&mut editor, "ideep/");
|
||||
assert!(editor.file_picker().unwrap().searching);
|
||||
assert_eq!(editor.file_picker().unwrap().query, "deep/");
|
||||
editor.handle(Input::Ctrl('H'));
|
||||
@@ -4172,7 +4172,7 @@ mod tests {
|
||||
assert_eq!(editor.file_picker().unwrap().selected, 1);
|
||||
editor.handle(Input::Ctrl('j'));
|
||||
assert_eq!(editor.file_picker().unwrap().selected, 0);
|
||||
editor.handle(Input::Char('/'));
|
||||
editor.handle(Input::Char('i'));
|
||||
assert_eq!(
|
||||
editor.file_navigation_hint(),
|
||||
editor.selection_navigation_hint()
|
||||
@@ -4271,7 +4271,7 @@ mod tests {
|
||||
let mut editor = project_editor(&directory);
|
||||
editor.set_file_picker_view("tree").unwrap();
|
||||
editor.open_file_picker(presentation);
|
||||
type_text(&mut editor, "/needle.txt");
|
||||
type_text(&mut editor, "ineedle.txt");
|
||||
let picker = editor.file_picker().unwrap();
|
||||
assert_eq!(
|
||||
picker.entries().nth(picker.selected).unwrap().path,
|
||||
@@ -4286,7 +4286,7 @@ mod tests {
|
||||
editor.open_file_picker(presentation);
|
||||
editor.handle(Input::Ctrl('w'));
|
||||
editor.open_file_picker(presentation);
|
||||
type_text(&mut editor, "/module");
|
||||
type_text(&mut editor, "imodule");
|
||||
assert!(
|
||||
editor
|
||||
.file_picker()
|
||||
@@ -4332,7 +4332,7 @@ mod tests {
|
||||
editor.edit_buffer(b.clone(), false).unwrap();
|
||||
fs::write(&a, b"\xff").unwrap();
|
||||
editor.open_file_picker(presentation);
|
||||
type_text(&mut editor, "/same");
|
||||
type_text(&mut editor, "isame");
|
||||
let picker = editor.file_picker.as_mut().unwrap();
|
||||
let labels = picker
|
||||
.entries()
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ Normal/Visual: Space is leader; Space Space opens palette; Esc cancels prefix
|
||||
Normal leader: Space w saves; Space b n/p/l/d next/previous/list/close buffer
|
||||
Files: Space o floating list; Space e docked compact tree (right by default)
|
||||
Ctrl+j/k select; tree Ctrl+l/h expand/parent, Enter toggles/opens
|
||||
/ tree search, Esc browse/edit, Ctrl+t view, Ctrl+w close, Ctrl+r refresh
|
||||
i filters in tree INSERT; Esc returns NORMAL, then editor; Ctrl+t view, Ctrl+w close, Ctrl+r refresh
|
||||
Normal: i/a insert, I/A insert at first nonblank/end, o/O new line
|
||||
h/j/k/l move, 0/$ line ends, x delete, J join lines
|
||||
w/b/e word next/previous/end, W/B/E whitespace-delimited WORDs
|
||||
|
||||
+2
-2
@@ -250,7 +250,7 @@ end
|
||||
crate::files::FileView::Tree
|
||||
);
|
||||
assert!(!session.editor().file_picker().unwrap().searching);
|
||||
session.handle(Input::Char('/'));
|
||||
session.handle(Input::Char('i'));
|
||||
for ch in "dir e\u{301}".chars() {
|
||||
session.handle(Input::Char(ch));
|
||||
}
|
||||
@@ -321,7 +321,7 @@ end
|
||||
assert!(!session.editor().file_picker().unwrap().searching);
|
||||
session.handle(Input::Char('x'));
|
||||
assert_eq!(session.editor().file_picker().unwrap().query, "");
|
||||
session.handle(Input::Char('/'));
|
||||
session.handle(Input::Char('i'));
|
||||
session.handle(Input::Char('x'));
|
||||
session.handle(Input::Ctrl('h'));
|
||||
assert_eq!(session.editor().file_picker().unwrap().query, "x");
|
||||
|
||||
+48
-53
@@ -496,10 +496,10 @@ fn draw_file_list(frame: &mut Frame, picker: &FilePicker, panel: Rect, navigatio
|
||||
let title = match (picker.presentation, picker.view, browsing) {
|
||||
(FilePresentation::Floating, FileView::List, _) => " Files · List ",
|
||||
(FilePresentation::Docked, FileView::List, _) => " Explorer · List ",
|
||||
(FilePresentation::Floating, FileView::Tree, true) => " Files · Tree browse ",
|
||||
(FilePresentation::Docked, FileView::Tree, true) => " Explorer · Tree browse ",
|
||||
(FilePresentation::Floating, FileView::Tree, false) => " Files · Tree search ",
|
||||
(FilePresentation::Docked, FileView::Tree, false) => " Explorer · Tree search ",
|
||||
(FilePresentation::Floating, FileView::Tree, true) => " Files · Tree NORMAL ",
|
||||
(FilePresentation::Docked, FileView::Tree, true) => " Explorer · Tree NORMAL ",
|
||||
(FilePresentation::Floating, FileView::Tree, false) => " Files · Tree INSERT ",
|
||||
(FilePresentation::Docked, FileView::Tree, false) => " Explorer · Tree INSERT ",
|
||||
};
|
||||
let block = Block::default()
|
||||
.borders(if bordered {
|
||||
@@ -525,7 +525,7 @@ fn draw_file_list(frame: &mut Frame, picker: &FilePicker, panel: Rect, navigatio
|
||||
])
|
||||
.areas(content);
|
||||
let query = if browsing {
|
||||
"browse · / search · ^T view".into()
|
||||
"NORMAL · i filter · ^T view".into()
|
||||
} else {
|
||||
format!("> {}", display_text(&picker.query))
|
||||
};
|
||||
@@ -584,31 +584,30 @@ fn draw_file_list(frame: &mut Frame, picker: &FilePicker, panel: Rect, navigatio
|
||||
.take(count)
|
||||
.map(|(index, entry)| {
|
||||
let selected = index == selected_index;
|
||||
let style = if selected {
|
||||
let mut style = if entry.is_dir {
|
||||
Style::default().fg(Color::Cyan)
|
||||
} else {
|
||||
Style::default()
|
||||
};
|
||||
if selected {
|
||||
style = style
|
||||
.bg(if picker.focused {
|
||||
Color::Blue
|
||||
} else {
|
||||
Color::DarkGray
|
||||
})
|
||||
.add_modifier(Modifier::BOLD)
|
||||
} else {
|
||||
Style::default()
|
||||
};
|
||||
.add_modifier(Modifier::BOLD);
|
||||
}
|
||||
let label = match picker.view {
|
||||
FileView::List => {
|
||||
format!("{} {}", if selected { ">" } else { " " }, entry.label)
|
||||
}
|
||||
FileView::Tree => format!(
|
||||
"{} {:indent$}{} {}",
|
||||
"{} {:indent$}{}{}",
|
||||
if selected { ">" } else { " " },
|
||||
"",
|
||||
if entry.is_dir {
|
||||
if entry.expanded { "▾" } else { "▸" }
|
||||
} else {
|
||||
" "
|
||||
},
|
||||
entry.label,
|
||||
if entry.is_dir { "/" } else { "" },
|
||||
indent = entry.depth * 2,
|
||||
),
|
||||
};
|
||||
@@ -624,16 +623,14 @@ fn draw_file_list(frame: &mut Frame, picker: &FilePicker, panel: Rect, navigatio
|
||||
}
|
||||
}
|
||||
let help = if picker.focused {
|
||||
let controls = match picker.presentation {
|
||||
FilePresentation::Floating => "Enter · Esc/^W close · ^R scan",
|
||||
FilePresentation::Docked => "Enter · Esc/Tab edit · ^W · ^R",
|
||||
let controls = match (picker.presentation, browsing) {
|
||||
(FilePresentation::Floating, true) => "Enter · i filter · Esc/^W close · ^R · ^T",
|
||||
(FilePresentation::Docked, true) => "Enter · i filter · Esc/Tab edit · ^W · ^R · ^T",
|
||||
(_, false) if picker.view == FileView::Tree => "Enter · Esc NORMAL · ^W · ^R · ^T",
|
||||
(FilePresentation::Floating, false) => "Enter · Esc/^W close · ^R · ^T",
|
||||
(FilePresentation::Docked, false) => "Enter · Esc/Tab edit · ^W · ^R · ^T",
|
||||
};
|
||||
let view_controls = if picker.view == FileView::Tree {
|
||||
"/ search · ^T view"
|
||||
} else {
|
||||
"^T view"
|
||||
};
|
||||
format!("{navigation_hint} · {view_controls} · {controls}")
|
||||
format!("{navigation_hint} · {controls}")
|
||||
} else {
|
||||
"Editor focus · invoke explorer to browse".into()
|
||||
};
|
||||
@@ -1011,7 +1008,6 @@ mod tests {
|
||||
.draw(|frame| draw_file_list(frame, &picker, area, editor.file_navigation_hint()))
|
||||
.unwrap();
|
||||
let text = screen_text(&terminal);
|
||||
assert!(text.contains("Tree browse"), "{text}");
|
||||
assert!(text.contains("No project files"), "{text}");
|
||||
assert_eq!(
|
||||
terminal.get_cursor_position().unwrap(),
|
||||
@@ -1155,20 +1151,32 @@ mod tests {
|
||||
.unwrap();
|
||||
let text = screen_text(&terminal);
|
||||
for label in [
|
||||
" ▾ chain/a/b",
|
||||
" leaf.txt",
|
||||
" ▸ empty",
|
||||
" ▾ mixed",
|
||||
" ▾ nested",
|
||||
" inner.txt",
|
||||
" sibling.txt",
|
||||
"> ▾ single",
|
||||
" only.txt",
|
||||
" root.txt",
|
||||
"/ search · ^T view",
|
||||
" chain/a/b/",
|
||||
" leaf.txt",
|
||||
" empty/",
|
||||
" mixed/",
|
||||
" nested/",
|
||||
" inner.txt",
|
||||
" sibling.txt",
|
||||
"> single/",
|
||||
" only.txt",
|
||||
" root.txt",
|
||||
] {
|
||||
assert!(text.contains(label), "{label}: {text}");
|
||||
}
|
||||
assert!(!text.contains('▸') && !text.contains('▾'), "{text}");
|
||||
let buffer = terminal.backend().buffer();
|
||||
for (index, entry) in picker.entries().enumerate() {
|
||||
let y = area.y + 3 + index as u16;
|
||||
assert_eq!(
|
||||
buffer[(area.x + 1, y)].fg,
|
||||
if entry.is_dir {
|
||||
Color::Cyan
|
||||
} else {
|
||||
Color::Reset
|
||||
},
|
||||
);
|
||||
}
|
||||
assert!(!text.contains("single/only.txt"), "{text}");
|
||||
assert!(!text.contains("mixed/nested/inner.txt"), "{text}");
|
||||
let cursor = terminal.get_cursor_position().unwrap();
|
||||
@@ -1185,7 +1193,6 @@ mod tests {
|
||||
.draw(|frame| draw_file_list(frame, &picker, area, editor.file_navigation_hint()))
|
||||
.unwrap();
|
||||
let text = screen_text(&terminal);
|
||||
assert!(text.contains("List"), "{text}");
|
||||
assert!(text.contains("chain/a/b/leaf.txt"), "{text}");
|
||||
assert!(text.contains("single/only.txt"), "{text}");
|
||||
assert!(!text.contains("▸"), "{text}");
|
||||
@@ -1236,7 +1243,7 @@ mod tests {
|
||||
}
|
||||
if width == 100 {
|
||||
let text = screen_text(&terminal);
|
||||
assert!(text.contains("> file.txt"), "{text}");
|
||||
assert!(text.contains("> file.txt"), "{text}");
|
||||
assert!(!text.contains("dir-00"), "{text}");
|
||||
}
|
||||
picker.start_search();
|
||||
@@ -1295,7 +1302,7 @@ mod tests {
|
||||
.draw(|frame| draw(frame, &editor, &mut top, &mut left))
|
||||
.unwrap();
|
||||
let browse_cursor = terminal.get_cursor_position().unwrap();
|
||||
editor.handle(Input::Char('/'));
|
||||
editor.handle(Input::Char('i'));
|
||||
assert!(editor.file_picker().unwrap().searching);
|
||||
assert_eq!(editor.file_picker().unwrap().query, "");
|
||||
assert!(
|
||||
@@ -1325,7 +1332,6 @@ mod tests {
|
||||
.draw(|frame| draw(frame, &editor, &mut top, &mut left))
|
||||
.unwrap();
|
||||
let text = screen_text(&terminal);
|
||||
assert!(text.contains("Tree search"), "{text}");
|
||||
assert!(text.contains("needle.txt"), "{text}");
|
||||
assert_eq!(terminal.get_cursor_position().unwrap().y, 1);
|
||||
editor.handle(Input::Escape);
|
||||
@@ -1353,7 +1359,7 @@ mod tests {
|
||||
assert_eq!(editor.lines(), ["unchanged"]);
|
||||
editor.handle(Input::Char(' '));
|
||||
editor.handle(Input::Char('e'));
|
||||
editor.handle(Input::Char('/'));
|
||||
editor.handle(Input::Char('i'));
|
||||
editor.handle(Input::Char('n'));
|
||||
editor.handle(Input::Char('\t'));
|
||||
assert!(!editor.file_picker().unwrap().focused);
|
||||
@@ -1376,7 +1382,6 @@ mod tests {
|
||||
terminal
|
||||
.draw(|frame| draw(frame, &editor, &mut top, &mut left))
|
||||
.unwrap();
|
||||
assert!(screen_text(&terminal).contains("Tree search"));
|
||||
assert_eq!(terminal.get_cursor_position().unwrap().y, 1);
|
||||
editor.handle(Input::Escape);
|
||||
assert!(editor.file_picker().unwrap().focused);
|
||||
@@ -1423,22 +1428,12 @@ mod tests {
|
||||
.draw(|frame| draw(frame, &editor, &mut top, &mut left))
|
||||
.unwrap();
|
||||
let text = screen_text(&terminal);
|
||||
assert!(
|
||||
text.contains(if view == FileView::Tree {
|
||||
"Tree browse"
|
||||
} else {
|
||||
"List"
|
||||
}),
|
||||
"{text}"
|
||||
);
|
||||
assert!(text.contains("^T view"), "{text}");
|
||||
assert!(text.contains(editor.file_navigation_hint()), "{text}");
|
||||
if view == FileView::Tree {
|
||||
assert_ne!(
|
||||
editor.file_navigation_hint(),
|
||||
editor.selection_navigation_hint()
|
||||
);
|
||||
assert!(text.contains("/ search"), "{text}");
|
||||
let cursor = terminal.get_cursor_position().unwrap();
|
||||
assert_eq!(
|
||||
terminal.backend().buffer()[(cursor.x, cursor.y)].symbol(),
|
||||
|
||||
Reference in New Issue
Block a user