feat: toggle diff explorer and show file change indicators

This commit is contained in:
2026-10-11 00:54:35 +02:00
parent 83c37f5a19
commit 2f595ca4ca
9 changed files with 454 additions and 38 deletions
+162 -3
View File
@@ -148,6 +148,7 @@ pub struct DiffState {
pub error: Option<String>,
pub comparison: Option<Comparison>,
pub files: Option<FilePicker>,
pub files_visible: bool,
pub file: usize,
pub focus: Focus,
pub top: usize,
@@ -177,6 +178,7 @@ impl DiffState {
error: None,
comparison: None,
files: None,
files_visible: true,
file: 0,
focus: Focus::Files,
top: 0,
@@ -434,6 +436,18 @@ impl DiffState {
}
fn handle_view(&mut self, input: Input) {
if input == Input::Ctrl('e') {
self.files_visible = !self.files_visible;
if self.files_visible {
self.focus = Focus::Files;
} else if self.focus == Focus::Files {
self.focus = Focus::Before;
}
if let Some(files) = &mut self.files {
files.focused = self.files_visible && self.focus == Focus::Files;
}
return;
}
if self.focus == Focus::Files {
let files = self.files.as_mut().expect("comparison file picker");
match input {
@@ -491,14 +505,16 @@ impl DiffState {
self.focus = match self.focus {
Focus::Files => Focus::Before,
Focus::Before => Focus::After,
Focus::After => Focus::Files,
Focus::After if self.files_visible => Focus::Files,
Focus::After => Focus::Before,
}
}
Input::BackTab => {
self.focus = match self.focus {
Focus::Files => Focus::After,
Focus::After => Focus::Before,
Focus::Before => Focus::Files,
Focus::Before if self.files_visible => Focus::Files,
Focus::Before => Focus::After,
}
}
Input::Up | Input::Ctrl('k') => self.navigate(false),
@@ -527,7 +543,7 @@ impl DiffState {
_ => {}
}
if let Some(files) = &mut self.files {
files.focused = self.focus == Focus::Files;
files.focused = self.files_visible && self.focus == Focus::Files;
}
}
@@ -1008,6 +1024,149 @@ mod tests {
assert!(editor.diff().is_none());
}
#[test]
fn explorer_toggle_preserves_filtered_tree_review_and_source_state() {
let mut editor = editor();
editor
.apply(vec![Effect::Replace("unsaved source".into())])
.unwrap();
let original = editor.context().text;
let carets = editor.carets();
let revision = editor.text_revision();
open(&mut editor);
let id = choose_pair(&mut editor);
let mut changes = comparison();
changes.files[0].path = "src/deep/renamed.txt".into();
changes.files[1].path = "src/deep/added.txt".into();
editor.apply_diff_result(id, Ok(Outcome::Comparison(changes)));
editor.handle(Input::Char('\t'));
editor.handle(Input::Down);
editor.handle(Input::Right);
editor.handle(Input::Char('\t'));
editor.handle(Input::Right);
editor.handle(Input::Char('\t'));
editor.handle(Input::Char('i'));
text(&mut editor, "src");
editor.handle(Input::Down);
let snapshot = |state: &DiffState| {
let files = state.files.as_ref().unwrap();
(
files.query.clone(),
files.selected,
files.view,
files.searching,
files
.entries()
.map(|row| (row.path.to_owned(), row.expanded))
.collect::<Vec<_>>(),
)
};
let tree = snapshot(editor.diff().unwrap());
let rows = editor
.diff()
.unwrap()
.rows
.iter()
.map(|row| (row.before, row.after, row.kind))
.collect::<Vec<_>>();
editor
.bind("tree", "ctrl+e", Some("core.down".into()))
.unwrap();
editor
.bind("selection", "ctrl+e", Some("core.up".into()))
.unwrap();
editor.handle(Input::Ctrl('e'));
let state = editor.diff().unwrap();
assert!(!state.files_visible);
assert_eq!(state.focus, Focus::Before);
assert!(!state.files.as_ref().unwrap().focused);
assert_eq!(
(state.file, state.top, state.left_before, state.left_after),
(0, 1, 1, 1)
);
assert_eq!(snapshot(state), tree);
for (input, focus) in [
(Input::Char('\t'), Focus::After),
(Input::Char('\t'), Focus::Before),
(Input::BackTab, Focus::After),
(Input::BackTab, Focus::Before),
] {
editor.handle(input);
let state = editor.diff().unwrap();
assert_eq!(state.focus, focus);
assert!(!state.files.as_ref().unwrap().focused);
}
for input in [
Input::Char('z'),
Input::Ctrl('t'),
Input::Backspace,
Input::Delete,
Input::Paste("not source".into()),
Input::Enter,
] {
editor.handle(input);
}
assert_eq!(snapshot(editor.diff().unwrap()), tree);
editor.handle(Input::Down);
assert_eq!(editor.diff().unwrap().top, 2);
editor.handle(Input::Ctrl('e'));
let state = editor.diff().unwrap();
assert!(state.files_visible && state.files.as_ref().unwrap().focused);
assert_eq!(state.focus, Focus::Files);
assert_eq!(snapshot(state), tree);
assert_eq!(
(state.file, state.top, state.left_before, state.left_after),
(0, 2, 1, 1)
);
assert_eq!(
state
.rows
.iter()
.map(|row| (row.before, row.after, row.kind))
.collect::<Vec<_>>(),
rows
);
editor.handle(Input::Char('\t'));
editor.handle(Input::Ctrl('e'));
assert_eq!(editor.diff().unwrap().focus, Focus::Before);
editor.handle(Input::Ctrl('e'));
editor.handle(Input::BackTab);
editor.handle(Input::Ctrl('e'));
assert_eq!(editor.diff().unwrap().focus, Focus::After);
assert!(!editor.diff().unwrap().files.as_ref().unwrap().focused);
editor.handle(Input::Ctrl('e'));
assert_eq!(editor.diff().unwrap().focus, Focus::Files);
assert_eq!(snapshot(editor.diff().unwrap()), tree);
assert_eq!(editor.context().text, original);
assert_eq!(editor.carets(), carets);
assert_eq!(editor.text_revision(), revision);
editor.handle(Input::Escape);
assert!(!editor.diff().unwrap().files.as_ref().unwrap().searching);
editor.handle(Input::Ctrl('e'));
editor.handle(Input::Escape);
assert!(editor.diff().is_none());
}
#[test]
fn explorer_toggle_is_not_a_revision_selector_control() {
let mut editor = editor();
open(&mut editor);
editor.handle(Input::Ctrl('e'));
assert_eq!(editor.diff().unwrap().selected, 0);
editor
.bind("selection", "ctrl+e", Some("core.down".into()))
.unwrap();
for phase in [Phase::Before, Phase::After] {
assert_eq!(editor.diff().unwrap().phase, phase);
editor.handle(Input::Ctrl('e'));
let state = editor.diff().unwrap();
assert!(state.files_visible);
assert_eq!(state.selected, 1);
assert_eq!(state.phase, phase);
editor.handle(Input::Enter);
}
}
#[test]
fn aligned_hunks_navigation_shared_top_independent_horizontal_and_file_reset() {
let mut editor = editor();
+8 -3
View File
@@ -1279,8 +1279,13 @@ impl Editor {
}
fn handle_diff(&mut self, input: Input) {
// Escape and Tab always retain their modal close/back/focus semantics.
if !matches!(input, Input::Escape | Input::Char('\t') | Input::BackTab) {
// Modal controls take precedence over configured browsing bindings.
let toggle_files = input == Input::Ctrl('e')
&& self
.diff
.as_ref()
.is_some_and(|diff| diff.phase == Phase::View);
if !toggle_files && !matches!(input, Input::Escape | Input::Char('\t') | Input::BackTab) {
if self.handle_tree_binding(&input) {
return;
}
@@ -2620,7 +2625,7 @@ impl Editor {
fn handle_tree_binding(&mut self, input: &Input) -> bool {
let picker = if let Some(diff) = &mut self.diff {
diff.files.as_mut()
diff.files.as_mut().filter(|_| diff.files_visible)
} else {
self.file_picker.as_mut()
};
+47 -5
View File
@@ -38,6 +38,8 @@ pub struct FileRow<'a> {
/// The actual project-relative OS path, never reconstructed from the label.
pub path: &'a Path,
pub label: &'a str,
/// Original virtual-catalog input index; absent for scanned entries and directories.
pub catalog_index: Option<usize>,
pub is_dir: bool,
pub depth: usize,
pub expanded: bool,
@@ -48,6 +50,7 @@ struct Node {
path: PathBuf,
label: String,
search_label: String,
catalog_index: Option<usize>,
is_dir: bool,
parent: Option<usize>,
children: Vec<usize>,
@@ -153,6 +156,7 @@ impl FilePicker {
path,
search_label: label.to_lowercase(),
label,
catalog_index: None,
is_dir: entry.file_type().unwrap().is_dir(),
parent: None,
children: Vec::new(),
@@ -163,25 +167,26 @@ impl FilePicker {
/// Builds the changed-path catalog without consulting the working tree.
pub fn from_paths(root: PathBuf, paths: impl IntoIterator<Item = PathBuf>) -> Self {
let mut catalog = HashSet::new();
for path in paths {
let mut catalog = HashMap::new();
for (index, path) in paths.into_iter().enumerate() {
for parent in path
.ancestors()
.skip(1)
.filter(|path| !path.as_os_str().is_empty())
{
catalog.insert((parent.to_owned(), true));
catalog.insert((parent.to_owned(), true), None);
}
catalog.insert((path, false));
catalog.entry((path, false)).or_insert(Some(index));
}
let nodes = catalog
.into_iter()
.map(|(path, is_dir)| {
.map(|((path, is_dir), catalog_index)| {
let label = label(&path);
Node {
path,
search_label: label.to_lowercase(),
label,
catalog_index,
is_dir,
parent: None,
children: Vec::new(),
@@ -310,6 +315,7 @@ impl FilePicker {
.tree
.map_or(node.label.as_str(), |tree| self.tree[tree].label.as_str()),
is_dir: node.is_dir,
catalog_index: node.catalog_index,
depth: row.depth,
expanded: row.expanded,
}
@@ -797,6 +803,30 @@ mod tests {
picker.reveal_path(Path::new("src/deep/deleted.rs"));
assert_eq!(picker.accept(), Some(PathBuf::from("src/deep/deleted.rs")));
assert_eq!(picker.entries().filter(|row| !row.is_dir).count(), 3);
let expected_index = |row: &FileRow<'_>| {
if row.is_dir {
None
} else {
Some(match row.path.to_str().unwrap() {
"x" => 0,
"x/y" => 1,
"src/deep/deleted.rs" => 2,
path => panic!("unexpected path {path}"),
})
}
};
for view in [FileView::List, FileView::Tree] {
picker.set_view(view);
for query in ["", "x", "deleted"] {
picker.query = query.into();
picker.refilter();
let rows: Vec<_> = picker.entries().collect();
assert!(!rows.is_empty());
for row in rows {
assert_eq!(row.catalog_index, expected_index(&row));
}
}
}
}
use std::sync::atomic::{AtomicU64, Ordering};
@@ -831,6 +861,18 @@ mod tests {
.collect()
}
#[test]
fn scanned_files_and_directories_have_no_catalog_index() {
let directory = Directory::new();
directory.put("nested/file.txt", "");
let mut picker = directory.scan();
for view in [FileView::List, FileView::Tree] {
picker.set_view(view);
picker.reveal_path(Path::new("nested/file.txt"));
assert!(picker.entries().all(|row| row.catalog_index.is_none()));
}
}
#[test]
fn nested_ignore_negation_hidden_files_and_vcs_metadata() {
let directory = Directory::new();
+1
View File
@@ -43,6 +43,7 @@ Markdown links: :follow_link (primary caret; local files only)
Diff: :diff (Normal); choose Before then After; Working checkout means disk, not unsaved buffers
Tab/Shift+Tab categories/focus; type to search IDs/refs; arrows navigate/scroll; Esc back/close
Remote refs/history load into temporary comparison repos without changing active refs/checkout
Changed-file tree: Ctrl+e hides/shows; A added, M modified, D removed, R renamed (old path shown)
Ruby config: $XDG_CONFIG_HOME/vamp/config.rb or ~/.config/vamp/config.rb
Set VAMP_RUBY to choose the Ruby executable.
+227 -26
View File
@@ -861,9 +861,23 @@ fn draw(
if picker.presentation == FilePresentation::Floating {
let panel = floating_panel(area);
frame.render_widget(Clear, panel);
draw_file_list(frame, picker, panel, editor.file_navigation_hint(), panels);
draw_file_list(
frame,
picker,
panel,
editor.file_navigation_hint(),
panels,
None,
);
} else if let Some(panel) = dock {
draw_file_list(frame, picker, panel, editor.file_navigation_hint(), panels);
draw_file_list(
frame,
picker,
panel,
editor.file_navigation_hint(),
panels,
None,
);
}
}
if let Some(palette) = editor.palette() {
@@ -1140,7 +1154,7 @@ fn draw_diff(
notice_area,
);
}
let (panes, navigator) = if diff.files.is_some() {
let (panes, navigator) = if diff.files_visible && diff.files.is_some() {
dock_layout(workspace, DockSide::Right, diff.focus == Focus::Files)
} else {
(workspace, None)
@@ -1150,7 +1164,14 @@ fn draw_diff(
if let Some(picker) = &diff.files
&& let Some(navigator) = navigator
{
draw_file_list(frame, picker, navigator, file_navigation_hint, panels);
draw_file_list(
frame,
picker,
navigator,
file_navigation_hint,
panels,
comparison.map(|comparison| comparison.files.as_slice()),
);
}
if let Some(file) = selected {
draw_diff_pane(frame, diff, file, before_area, true);
@@ -1167,7 +1188,7 @@ fn draw_diff(
}
frame.render_widget(
Paragraph::new(
"Tab focus · i filter · Enter open/toggle · ↑/↓ scroll/tree · ←/→ horizontal · Esc close",
"^E explorer · Tab focus · i filter · Enter open/toggle · ↑/↓ scroll/tree · ←/→ horizontal · Esc close",
)
.style(Style::default().fg(Color::DarkGray)),
help,
@@ -1740,6 +1761,7 @@ fn draw_file_list(
panel: Rect,
navigation_hint: &str,
panels: &mut WheelPanels,
changed_files: Option<&[ChangedFile]>,
) {
if panel.width == 0 || panel.height == 0 {
return;
@@ -1862,20 +1884,46 @@ fn draw_file_list(
})
.add_modifier(Modifier::BOLD);
}
let label = match picker.view {
FileView::List => {
format!("{} {}", if selected { ">" } else { " " }, entry.label)
}
FileView::Tree => format!(
"{} {:indent$}{}{}",
if selected { ">" } else { " " },
"",
entry.label,
if entry.is_dir { "/" } else { "" },
indent = entry.depth * 2,
),
};
Line::styled(label, style)
let changed = entry
.catalog_index
.and_then(|index| changed_files.and_then(|files| files.get(index)));
let prefix = format!(
"{} {:indent$}",
if selected { ">" } else { " " },
"",
indent = if picker.view == FileView::Tree {
entry.depth * 2
} else {
0
},
);
let mut spans = Vec::with_capacity(if changed.is_some() { 6 } else { 3 });
spans.push(Span::raw(prefix));
if let Some(file) = changed {
let color = match file.status {
FileStatus::Added => Color::Green,
FileStatus::Removed => Color::Red,
FileStatus::Modified => Color::Yellow,
FileStatus::Renamed => Color::Cyan,
};
spans.push(Span::styled(
diff_status(file.status),
Style::default().fg(color),
));
spans.push(Span::raw(" "));
}
spans.push(Span::raw(entry.label));
if entry.is_dir && picker.view == FileView::Tree {
spans.push(Span::raw("/"));
}
if let Some(file) = changed
&& file.status == FileStatus::Renamed
&& let Some(old_path) = &file.old_path
{
spans.push(Span::raw(" ← "));
spans.push(Span::raw(display_text(&old_path.to_string_lossy())));
}
Line::from(spans).style(style)
})
.collect();
frame.render_widget(Paragraph::new(lines), results_area);
@@ -1891,7 +1939,9 @@ fn draw_file_list(
));
}
}
let help = if picker.focused {
let help = if changed_files.is_some() {
"A added · M modified · D deleted · R renamed · ^E hide".into()
} else if picker.focused {
let controls = if picker.root.as_os_str().is_empty() {
if browsing {
"Enter · i filter · Esc close · Tab panes · ^T view"
@@ -2299,7 +2349,6 @@ mod tests {
assert_eq!(buffer[(after.x + 2, y)].symbol(), "+");
assert_eq!(buffer[(after.x + 4, y)].fg, Color::Green);
}
assert!(screen_text(&terminal).contains("> added.txt"));
}
#[test]
@@ -2328,7 +2377,6 @@ mod tests {
let mut panels = WheelPanels::default();
render_diff(&mut terminal, &diff, &mut panels);
let text = screen_text(&terminal);
assert!(text.contains("> new-name.txt"));
assert!(text.contains("Before · R · old-name.txt"));
assert!(text.contains("After · R · new-name.txt"));
let geometry = panels.diff.as_ref().unwrap();
@@ -2417,6 +2465,130 @@ mod tests {
);
}
#[test]
fn changed_file_markers_follow_catalog_indices_in_every_explorer_view() {
let files: Vec<_> = [
("z.txt", FileStatus::Added),
("chain/a/m.txt", FileStatus::Modified),
("d.txt", FileStatus::Removed),
("r.txt", FileStatus::Renamed),
]
.into_iter()
.map(|(path, status)| {
let mut file = deletion_file();
file.path = path.into();
file.status = status;
file.old_path = (status == FileStatus::Renamed).then(|| "old\nname.txt".into());
file
})
.collect();
let mut picker =
FilePicker::from_paths(PathBuf::new(), files.iter().map(|file| file.path.clone()));
picker.reveal_path(std::path::Path::new("chain/a/m.txt"));
let mut terminal = Terminal::new(ratatui::backend::TestBackend::new(100, 16)).unwrap();
for view in [FileView::Tree, FileView::List] {
picker.set_view(view);
for focused in [true, false] {
picker.focused = focused;
for index in 0..files.len() {
picker.reveal_path(&files[index].path);
terminal
.draw(|frame| {
draw_file_list(
frame,
&picker,
frame.area(),
"",
&mut WheelPanels::default(),
Some(&files),
);
})
.unwrap();
let buffer = terminal.backend().buffer();
for (file, color) in
files
.iter()
.zip([Color::Green, Color::Yellow, Color::Red, Color::Cyan])
{
let path = if view == FileView::Tree {
std::path::Path::new(file.path.file_name().unwrap())
} else {
file.path.as_path()
};
let expected =
format!("{} {}", diff_status(file.status), path.to_string_lossy());
let (x, y) = (0..16)
.find_map(|y| {
let row = (0..100)
.map(|x| buffer[(x, y)].symbol())
.collect::<String>();
row.find(&expected).map(|offset| {
(UnicodeWidthStr::width(&row[..offset]) as u16, y)
})
})
.unwrap_or_else(|| panic!("missing {expected}"));
assert_eq!(buffer[(x, y)].fg, color);
}
assert!(screen_text(&terminal).contains("r.txt ← old�name.txt"));
assert_eq!(picker.accept(), Some(files[index].path.clone()));
assert!(picker.query.is_empty());
}
}
}
picker.query = "m.txt".into();
picker.refilter();
terminal
.draw(|frame| {
draw_file_list(
frame,
&picker,
frame.area(),
"",
&mut WheelPanels::default(),
Some(&files),
);
})
.unwrap();
assert!(screen_text(&terminal).contains("M chain/a/m.txt"));
assert!(!screen_text(&terminal).contains("R r.txt"));
terminal
.draw(|frame| {
draw_file_list(
frame,
&picker,
frame.area(),
"",
&mut WheelPanels::default(),
None,
);
})
.unwrap();
assert!(!screen_text(&terminal).contains("M chain/a/m.txt"));
}
#[test]
fn hiding_diff_explorer_reclaims_workspace_and_restores_its_geometry() {
let mut diff = diff_comparison(vec![deletion_file()]);
let mut terminal = Terminal::new(ratatui::backend::TestBackend::new(120, 18)).unwrap();
let mut panels = WheelPanels::default();
render_diff(&mut terminal, &diff, &mut panels);
let original = panels.diff.as_ref().unwrap().regions;
diff.handle(Input::Ctrl('e'));
render_diff(&mut terminal, &diff, &mut panels);
let hidden = panels.diff.as_ref().unwrap().regions;
assert_eq!(hidden[0].0, Rect::default());
assert!(panels.files.is_none());
assert_eq!(hidden[1].0.x, 0);
assert_eq!(hidden[1].0.right(), hidden[2].0.x);
assert_eq!(hidden[2].0.right(), 120);
assert!(hidden[1].0.width + hidden[2].0.width > original[1].0.width + original[2].0.width);
assert!(!screen_text(&terminal).contains("Explorer"));
diff.handle(Input::Ctrl('e'));
render_diff(&mut terminal, &diff, &mut panels);
assert_eq!(panels.diff.as_ref().unwrap().regions, original);
assert_eq!(panels.files.unwrap().0, original[0].0);
}
#[test]
fn diff_notices_empty_comparisons_and_tiny_layouts_remain_renderable() {
let mut terminal = Terminal::new(ratatui::backend::TestBackend::new(100, 12)).unwrap();
@@ -2443,7 +2615,6 @@ mod tests {
}]);
render_diff(&mut terminal, &binary, &mut panels);
assert!(screen_text(&terminal).contains("Binary file"));
assert!(screen_text(&terminal).contains("> image.bin"));
let blank = diff_comparison(vec![ChangedFile {
path: "empty.txt".into(),
old_path: None,
@@ -2613,6 +2784,20 @@ mod tests {
assert_eq!(diff.focus, Focus::After);
assert_eq!(diff.top, 0);
assert_eq!(diff.file, 3);
let selected = diff.files.as_ref().unwrap().selected;
session.handle(Input::Ctrl('e'));
terminal
.draw(|frame| viewports.draw(frame, session.editor()))
.unwrap();
assert!(viewports.scroll(
&mut session,
document_wheel(MouseEventKind::ScrollDown, tree.x + 1, tree.y + 1),
));
let diff = session.editor().diff().unwrap();
assert_eq!(diff.focus, Focus::After);
assert_eq!(diff.files.as_ref().unwrap().selected, selected);
assert!(!diff.files.as_ref().unwrap().focused);
assert!(viewports.panels.files.is_none());
assert_eq!(session.editor().lines(), original);
assert_eq!(session.editor().cursor(), cursor);
assert!(viewports.document.geometry.is_none());
@@ -3892,7 +4077,7 @@ end
let area = Rect::new(3, 2, 40, 6);
let mut terminal = Terminal::new(ratatui::backend::TestBackend::new(60, 12)).unwrap();
terminal
.draw(|frame| draw_file_list(frame, picker, area, "", &mut panels))
.draw(|frame| draw_file_list(frame, picker, area, "", &mut panels, None))
.unwrap();
let mouse = MouseEvent {
kind: MouseEventKind::ScrollDown,
@@ -3919,6 +4104,7 @@ end
area,
"",
&mut panels,
None,
);
})
.unwrap();
@@ -3966,6 +4152,7 @@ end
area,
"",
&mut panels,
None,
)
})
.unwrap();
@@ -4010,7 +4197,7 @@ end
terminal
.draw(|frame| {
frame.set_cursor_position((59, 11));
draw_file_list(frame, &picker, area, "", &mut panels);
draw_file_list(frame, &picker, area, "", &mut panels, None);
})
.unwrap();
let text = screen_text(&terminal);
@@ -4027,7 +4214,7 @@ end
}
picker.navigate(false);
terminal
.draw(|frame| draw_file_list(frame, &picker, area, "", &mut panels))
.draw(|frame| draw_file_list(frame, &picker, area, "", &mut panels, None))
.unwrap();
assert!(panels.manual_files.is_none());
assert!(screen_text(&terminal).contains("> file-10"));
@@ -4060,6 +4247,7 @@ end
frame.area(),
editor.file_navigation_hint(),
&mut WheelPanels::default(),
None,
)
})
.unwrap();
@@ -4167,6 +4355,7 @@ end
area,
editor.selection_navigation_hint(),
&mut WheelPanels::default(),
None,
)
})
.unwrap();
@@ -4212,6 +4401,7 @@ end
area,
editor.selection_navigation_hint(),
&mut WheelPanels::default(),
None,
)
})
.unwrap();
@@ -4235,6 +4425,7 @@ end
area,
editor.selection_navigation_hint(),
&mut WheelPanels::default(),
None,
);
})
.unwrap();
@@ -4258,6 +4449,7 @@ end
area,
editor.selection_navigation_hint(),
&mut WheelPanels::default(),
None,
)
})
.unwrap();
@@ -4274,6 +4466,7 @@ end
area,
editor.file_navigation_hint(),
&mut WheelPanels::default(),
None,
)
})
.unwrap();
@@ -4294,6 +4487,7 @@ end
area,
editor.file_navigation_hint(),
&mut WheelPanels::default(),
None,
);
})
.unwrap();
@@ -4331,6 +4525,7 @@ end
area,
editor.selection_navigation_hint(),
&mut WheelPanels::default(),
None,
)
})
.unwrap();
@@ -4388,6 +4583,7 @@ end
frame.area(),
editor.selection_navigation_hint(),
&mut WheelPanels::default(),
None,
)
})
.unwrap();
@@ -4438,6 +4634,7 @@ end
area,
editor.file_navigation_hint(),
&mut WheelPanels::default(),
None,
)
})
.unwrap();
@@ -4489,6 +4686,7 @@ end
area,
editor.file_navigation_hint(),
&mut WheelPanels::default(),
None,
)
})
.unwrap();
@@ -4536,6 +4734,7 @@ end
area,
editor.file_navigation_hint(),
&mut WheelPanels::default(),
None,
)
})
.unwrap();
@@ -4567,6 +4766,7 @@ end
area,
editor.file_navigation_hint(),
&mut WheelPanels::default(),
None,
)
})
.unwrap();
@@ -4584,6 +4784,7 @@ end
area,
editor.file_navigation_hint(),
&mut WheelPanels::default(),
None,
)
})
.unwrap();