feat: configure interchangeable top and bottom bars
This commit is contained in:
+478
-135
@@ -406,15 +406,9 @@ impl BufferViewports {
|
||||
self.parked
|
||||
.retain(|id, _| editor.pane_buffer_id(*id).is_some());
|
||||
self.syntax.retain(|id, _| editor.has_buffer(*id));
|
||||
let area = frame.area();
|
||||
let area = draw_tab_bar(frame, editor, area);
|
||||
let syntax = self.syntax.entry(editor.buffer_id()).or_default();
|
||||
let [full_workspace, prompt] = draw_bars(frame, editor, frame.area(), syntax);
|
||||
let multiple = editor.pane_layout().len() > 1;
|
||||
let chrome = if !multiple && editor.diff().is_some() {
|
||||
None
|
||||
} else {
|
||||
Some(document_layout(area, editor))
|
||||
};
|
||||
let full_workspace = chrome.map_or(area, |areas| areas[0]);
|
||||
let diff_header = editor
|
||||
.diff()
|
||||
.filter(|diff| !multiple && diff.phase == Phase::View)
|
||||
@@ -452,6 +446,10 @@ impl BufferViewports {
|
||||
.visit_rects(pane_rect(workspace), |id, area| {
|
||||
self.rects.push((id, terminal_rect(area)));
|
||||
});
|
||||
// An active prompt still needs its focused render pass when no document row fits.
|
||||
if self.rects.is_empty() && prompt.height > 0 {
|
||||
self.rects.push((focused, workspace));
|
||||
}
|
||||
// Paint focused overlays last, so an adjacent leaf cannot erase them.
|
||||
for active_pass in [false, true] {
|
||||
for index in 0..self.rects.len() {
|
||||
@@ -474,12 +472,11 @@ impl BufferViewports {
|
||||
frame.render_widget(block, leaf);
|
||||
leaf = inner;
|
||||
}
|
||||
let status = chrome
|
||||
.filter(|_| id == focused)
|
||||
.map_or(Rect::default(), |a| a[1]);
|
||||
let prompt = chrome
|
||||
.filter(|_| id == focused)
|
||||
.map_or(Rect::default(), |a| a[2]);
|
||||
let prompt = if id == focused {
|
||||
prompt
|
||||
} else {
|
||||
Rect::default()
|
||||
};
|
||||
editor.with_render_pane(id, |view| {
|
||||
self.activate_document(view.buffer_id());
|
||||
self.documents.retain(|id, _| view.has_buffer(*id));
|
||||
@@ -492,8 +489,8 @@ impl BufferViewports {
|
||||
draw_document(
|
||||
frame,
|
||||
view,
|
||||
[leaf, status, prompt],
|
||||
area,
|
||||
[leaf, prompt],
|
||||
full_workspace,
|
||||
top,
|
||||
left,
|
||||
syntax,
|
||||
@@ -544,14 +541,20 @@ impl BufferViewports {
|
||||
draw_diff(
|
||||
frame,
|
||||
diff,
|
||||
area,
|
||||
full_workspace,
|
||||
editor.selection_navigation_hint(),
|
||||
editor.file_navigation_hint(),
|
||||
&mut self.panels,
|
||||
Some(editor),
|
||||
);
|
||||
} else {
|
||||
draw_overlays(frame, editor, area, self.panels.caret, &mut self.panels);
|
||||
draw_overlays(
|
||||
frame,
|
||||
editor,
|
||||
full_workspace,
|
||||
self.panels.caret,
|
||||
&mut self.panels,
|
||||
);
|
||||
}
|
||||
self.diff_visible = self
|
||||
.panels
|
||||
@@ -1071,7 +1074,7 @@ fn chrome_items<'a>(
|
||||
editor: &'a Editor,
|
||||
line: &'a ChromeLine,
|
||||
names: &[String],
|
||||
syntax: Option<&SyntaxCache>,
|
||||
syntax: &SyntaxCache,
|
||||
) -> Vec<ChromeItem<'a>> {
|
||||
let mut items = Vec::new();
|
||||
for name in names {
|
||||
@@ -1100,31 +1103,28 @@ fn chrome_items<'a>(
|
||||
continue;
|
||||
}
|
||||
let mut text = match name.as_str() {
|
||||
"mode" if syntax.is_some() => Cow::Owned(if editor.visual_linewise() {
|
||||
"mode" => Cow::Owned(if editor.visual_linewise() {
|
||||
"VISUAL LINE".into()
|
||||
} else {
|
||||
editor.mode().to_string()
|
||||
}),
|
||||
"path" if syntax.is_some() => Cow::Owned(chrome_title(
|
||||
"path" => Cow::Owned(chrome_title(
|
||||
"{path}{modified}",
|
||||
editor.tab_index() + 1,
|
||||
editor.path(),
|
||||
editor.dirty(),
|
||||
)),
|
||||
"position" if syntax.is_some() => Cow::Owned(format!(
|
||||
"position" => Cow::Owned(format!(
|
||||
"{}:{}",
|
||||
editor.cursor().row + 1,
|
||||
editor.cursor().column + 1
|
||||
)),
|
||||
"language" if syntax.is_some() => {
|
||||
let syntax = syntax.unwrap();
|
||||
Cow::Owned(if let Some(error) = syntax.error() {
|
||||
format!("syntax: {error}")
|
||||
} else {
|
||||
syntax.language().unwrap_or_default().to_owned()
|
||||
})
|
||||
}
|
||||
"diagnostics" if syntax.is_some() => {
|
||||
"language" => Cow::Owned(if let Some(error) = syntax.error() {
|
||||
format!("syntax: {error}")
|
||||
} else {
|
||||
syntax.language().unwrap_or_default().to_owned()
|
||||
}),
|
||||
"diagnostics" => {
|
||||
let mut counts = [0; 4];
|
||||
for diagnostic in editor.diagnostics() {
|
||||
counts[diagnostic_severity(diagnostic.severity).0] += 1;
|
||||
@@ -1140,18 +1140,18 @@ fn chrome_items<'a>(
|
||||
}
|
||||
Cow::Owned(text)
|
||||
}
|
||||
"selections" if syntax.is_some() => Cow::Owned(if editor.selection_count() > 1 {
|
||||
"selections" => Cow::Owned(if editor.selection_count() > 1 {
|
||||
format!("{} selections", editor.selection_count())
|
||||
} else {
|
||||
String::new()
|
||||
}),
|
||||
"count" if syntax.is_some() => Cow::Owned(
|
||||
"count" => Cow::Owned(
|
||||
editor
|
||||
.count()
|
||||
.map(|count| format!("count: {count}"))
|
||||
.unwrap_or_default(),
|
||||
),
|
||||
"pending" if syntax.is_some() => {
|
||||
"pending" => {
|
||||
let mut text = editor.sequence_pending().unwrap_or_default().to_owned();
|
||||
for pending in [
|
||||
editor.g_pending().then_some('g'),
|
||||
@@ -1168,7 +1168,7 @@ fn chrome_items<'a>(
|
||||
}
|
||||
Cow::Owned(text)
|
||||
}
|
||||
"buffer" if syntax.is_some() => Cow::Owned(if editor.buffer_count() > 1 {
|
||||
"buffer" => Cow::Owned(if editor.buffer_count() > 1 {
|
||||
format!("[buffer {}/{}]", editor.buffer_id(), editor.buffer_count())
|
||||
} else {
|
||||
String::new()
|
||||
@@ -1348,47 +1348,69 @@ fn draw_chrome(frame: &mut Frame, mut groups: [Vec<ChromeItem<'_>>; 3], area: Re
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_tab_bar(frame: &mut Frame, editor: &Editor, area: Rect) -> Rect {
|
||||
if area.height == 0 {
|
||||
return area;
|
||||
}
|
||||
let line = editor.tabline_config();
|
||||
let anchors = [&line.left, &line.center, &line.right];
|
||||
if !anchors.iter().flat_map(|names| names.iter()).any(|name| {
|
||||
if name == "tabs" {
|
||||
editor.tab_count() > 1
|
||||
} else {
|
||||
!editor.label(name).is_empty()
|
||||
}
|
||||
}) {
|
||||
return area;
|
||||
}
|
||||
let groups = anchors.map(|names| chrome_items(editor, line, names, None));
|
||||
if !groups.iter().flatten().any(|item| item.tab.is_none())
|
||||
&& !(editor.tab_count() > 1 && groups.iter().any(|group| !group.is_empty()))
|
||||
{
|
||||
return area;
|
||||
}
|
||||
draw_chrome(frame, groups, Rect::new(area.x, area.y, area.width, 1));
|
||||
Rect::new(
|
||||
area.x,
|
||||
area.y.saturating_add(1),
|
||||
area.width,
|
||||
area.height - 1,
|
||||
)
|
||||
fn chrome_groups<'a>(
|
||||
editor: &'a Editor,
|
||||
line: &'a ChromeLine,
|
||||
syntax: &SyntaxCache,
|
||||
) -> [Vec<ChromeItem<'a>>; 3] {
|
||||
[&line.left, &line.center, &line.right].map(|names| chrome_items(editor, line, names, syntax))
|
||||
}
|
||||
|
||||
fn document_layout(area: Rect, editor: &Editor) -> [Rect; 3] {
|
||||
fn chrome_visible(editor: &Editor, groups: &[Vec<ChromeItem<'_>>; 3]) -> bool {
|
||||
groups.iter().flatten().any(|item| item.tab.is_none())
|
||||
|| (editor.tab_count() > 1 && groups.iter().any(|group| !group.is_empty()))
|
||||
}
|
||||
|
||||
fn document_layout(area: Rect, editor: &Editor, visible: [bool; 2]) -> [Rect; 4] {
|
||||
let prompting = matches!(editor.mode(), Mode::Command | Mode::Search);
|
||||
let dock_focused = editor
|
||||
.file_picker()
|
||||
.is_some_and(|picker| picker.focused && picker.presentation == FilePresentation::Docked);
|
||||
Layout::vertical([
|
||||
Constraint::Fill(1),
|
||||
Constraint::Length(u16::from(area.height >= 3)),
|
||||
Constraint::Length(u16::from(area.height >= 2 || (prompting && !dock_focused))),
|
||||
])
|
||||
.areas(area)
|
||||
let prompt =
|
||||
u16::from(editor.diff().is_none() && (area.height >= 2 || (prompting && !dock_focused)))
|
||||
.min(area.height);
|
||||
// Both bars yield to the separate prompt and the last document row.
|
||||
let available = area.height.saturating_sub(prompt + 1);
|
||||
let top = u16::from(visible[0]).min(available);
|
||||
let bottom = u16::from(visible[1]).min(available - top);
|
||||
let workspace = Rect::new(
|
||||
area.x,
|
||||
area.y.saturating_add(top),
|
||||
area.width,
|
||||
area.height - top - bottom - prompt,
|
||||
);
|
||||
[
|
||||
Rect::new(area.x, area.y, area.width, top),
|
||||
workspace,
|
||||
Rect::new(area.x, workspace.bottom(), area.width, bottom),
|
||||
Rect::new(area.x, area.bottom() - prompt, area.width, prompt),
|
||||
]
|
||||
}
|
||||
|
||||
fn draw_bars(
|
||||
frame: &mut Frame,
|
||||
editor: &Editor,
|
||||
area: Rect,
|
||||
syntax: &mut SyntaxCache,
|
||||
) -> [Rect; 2] {
|
||||
if area.width == 0 || area.height == 0 {
|
||||
let [_, workspace, _, prompt] = document_layout(area, editor, [false; 2]);
|
||||
return [workspace, prompt];
|
||||
}
|
||||
// Resolve the active buffer's language before computing either bar's visibility.
|
||||
syntax.update_visible(editor.lines(), editor.path(), editor.text_revision(), &[]);
|
||||
let groups = [
|
||||
chrome_groups(editor, editor.top_bar_config(), syntax),
|
||||
chrome_groups(editor, editor.bottom_bar_config(), syntax),
|
||||
];
|
||||
let visible = groups
|
||||
.each_ref()
|
||||
.map(|groups| chrome_visible(editor, groups));
|
||||
let [top, workspace, bottom, prompt] = document_layout(area, editor, visible);
|
||||
for (groups, area) in groups.into_iter().zip([top, bottom]) {
|
||||
draw_chrome(frame, groups, area);
|
||||
}
|
||||
[workspace, prompt]
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -1402,14 +1424,9 @@ fn draw(
|
||||
panels: &mut WheelPanels,
|
||||
) {
|
||||
let area = frame.area();
|
||||
let area = draw_tab_bar(frame, editor, area);
|
||||
let areas = if editor.diff().is_some() {
|
||||
[area, Rect::default(), Rect::default()]
|
||||
} else {
|
||||
document_layout(area, editor)
|
||||
};
|
||||
let areas = draw_bars(frame, editor, area, syntax);
|
||||
draw_document(
|
||||
frame, editor, areas, area, top, left, syntax, document, panels,
|
||||
frame, editor, areas, areas[0], top, left, syntax, document, panels,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1417,7 +1434,7 @@ fn draw(
|
||||
fn draw_document(
|
||||
frame: &mut Frame,
|
||||
editor: &Editor,
|
||||
[workspace, status_area, prompt_area]: [Rect; 3],
|
||||
[workspace, prompt_area]: [Rect; 2],
|
||||
overlay_area: Rect,
|
||||
top: &mut usize,
|
||||
left: &mut usize,
|
||||
@@ -1430,7 +1447,6 @@ fn draw_document(
|
||||
let area = workspace;
|
||||
let focused = editor.render_pane_focused();
|
||||
if let Some(diff) = editor.diff() {
|
||||
draw_status(frame, editor, syntax, status_area);
|
||||
if panels.workspace_managed && diff.phase != Phase::View {
|
||||
return;
|
||||
}
|
||||
@@ -1446,7 +1462,7 @@ fn draw_document(
|
||||
return;
|
||||
}
|
||||
panels.diff_syntax = None;
|
||||
if overlay_area.width == 0 || overlay_area.height == 0 {
|
||||
if overlay_area.width == 0 || (overlay_area.height == 0 && prompt_area.height == 0) {
|
||||
return;
|
||||
}
|
||||
let prompting = matches!(editor.mode(), Mode::Command | Mode::Search);
|
||||
@@ -1759,7 +1775,6 @@ fn draw_document(
|
||||
);
|
||||
}
|
||||
}
|
||||
draw_status(frame, editor, syntax, status_area);
|
||||
if prompting && prompt_area.height > 0 && prompt_area.width > 0 {
|
||||
let prefix = if editor.mode() == Mode::Search {
|
||||
'/'
|
||||
@@ -1895,13 +1910,6 @@ fn draw_overlays(
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_status(frame: &mut Frame, editor: &Editor, syntax: &SyntaxCache, status_area: Rect) {
|
||||
let line = editor.statusline_config();
|
||||
let groups = [&line.left, &line.center, &line.right]
|
||||
.map(|names| chrome_items(editor, line, names, Some(syntax)));
|
||||
draw_chrome(frame, groups, status_area);
|
||||
}
|
||||
|
||||
struct DiffGeometry {
|
||||
regions: [(Rect, Focus); 3],
|
||||
viewport: Option<(u64, usize, usize, usize, usize)>,
|
||||
@@ -5447,8 +5455,8 @@ mod tests {
|
||||
editor: &mut Editor,
|
||||
update: impl FnOnce(&mut ChromeLine, &mut ChromeLine),
|
||||
) {
|
||||
let mut top = editor.tabline_config().clone();
|
||||
let mut bottom = editor.statusline_config().clone();
|
||||
let mut top = editor.top_bar_config().clone();
|
||||
let mut bottom = editor.bottom_bar_config().clone();
|
||||
update(&mut top, &mut bottom);
|
||||
editor
|
||||
.set_chrome_config(top, bottom, BTreeMap::new())
|
||||
@@ -5490,7 +5498,11 @@ mod tests {
|
||||
let mut terminal = Terminal::new(ratatui::backend::TestBackend::new(160, 3)).unwrap();
|
||||
terminal
|
||||
.draw(|frame| {
|
||||
draw_tab_bar(frame, &editor, frame.area());
|
||||
draw_chrome(
|
||||
frame,
|
||||
chrome_groups(&editor, editor.top_bar_config(), &SyntaxCache::new()),
|
||||
frame.area(),
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
let titles = pane_screen(&terminal, Rect::new(0, 0, 160, 1));
|
||||
@@ -5508,7 +5520,11 @@ mod tests {
|
||||
});
|
||||
terminal
|
||||
.draw(|frame| {
|
||||
draw_tab_bar(frame, &editor, frame.area());
|
||||
draw_chrome(
|
||||
frame,
|
||||
chrome_groups(&editor, editor.top_bar_config(), &SyntaxCache::new()),
|
||||
frame.area(),
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
let titles = pane_screen(&terminal, Rect::new(0, 0, 160, 1));
|
||||
@@ -5554,8 +5570,16 @@ mod tests {
|
||||
let syntax = SyntaxCache::new();
|
||||
terminal
|
||||
.draw(|frame| {
|
||||
draw_tab_bar(frame, &editor, frame.area());
|
||||
draw_status(frame, &editor, &syntax, Rect::new(3, 2, 45, 1));
|
||||
draw_chrome(
|
||||
frame,
|
||||
chrome_groups(&editor, editor.top_bar_config(), &SyntaxCache::new()),
|
||||
frame.area(),
|
||||
);
|
||||
draw_chrome(
|
||||
frame,
|
||||
chrome_groups(&editor, editor.bottom_bar_config(), &syntax),
|
||||
Rect::new(3, 2, 45, 1),
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
let buffer = terminal.backend().buffer();
|
||||
@@ -5570,8 +5594,16 @@ mod tests {
|
||||
editor.set_label("left".into(), "L".repeat(30)).unwrap();
|
||||
terminal
|
||||
.draw(|frame| {
|
||||
draw_tab_bar(frame, &editor, frame.area());
|
||||
draw_status(frame, &editor, &syntax, Rect::new(3, 2, 45, 1));
|
||||
draw_chrome(
|
||||
frame,
|
||||
chrome_groups(&editor, editor.top_bar_config(), &SyntaxCache::new()),
|
||||
frame.area(),
|
||||
);
|
||||
draw_chrome(
|
||||
frame,
|
||||
chrome_groups(&editor, editor.bottom_bar_config(), &syntax),
|
||||
Rect::new(3, 2, 45, 1),
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
for (y, x, width) in [(0, 0, 51), (2, 3, 45)] {
|
||||
@@ -5585,7 +5617,7 @@ mod tests {
|
||||
fn active_tabs_keep_their_anchor_and_text_when_optional_groups_collide() {
|
||||
let mut editor = Editor::open(None, vec![]).unwrap();
|
||||
editor.execute("tab.new");
|
||||
configure_chrome(&mut editor, |top, _| {
|
||||
configure_chrome(&mut editor, |top, bottom| {
|
||||
top.left = vec!["prefix".into()];
|
||||
top.center = vec!["tabs".into()];
|
||||
top.right = vec!["suffix".into()];
|
||||
@@ -5594,6 +5626,7 @@ mod tests {
|
||||
tabs.padding = 64;
|
||||
tabs.leading_edge = "界".repeat(8);
|
||||
tabs.trailing_edge = "界".repeat(8);
|
||||
*bottom = top.clone();
|
||||
});
|
||||
editor
|
||||
.set_label("prefix".into(), "LEFT".repeat(32))
|
||||
@@ -5605,29 +5638,42 @@ mod tests {
|
||||
let mut terminal = Terminal::new(ratatui::backend::TestBackend::new(width, 2)).unwrap();
|
||||
terminal
|
||||
.draw(|frame| {
|
||||
draw_tab_bar(frame, &editor, frame.area());
|
||||
for (y, line) in [editor.top_bar_config(), editor.bottom_bar_config()]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
draw_chrome(
|
||||
frame,
|
||||
chrome_groups(&editor, line, &SyntaxCache::new()),
|
||||
Rect::new(0, y as u16, width, 1),
|
||||
);
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
if width > 0 {
|
||||
let row = pane_screen(&terminal, Rect::new(0, 0, width, 1));
|
||||
for y in 0..2 {
|
||||
if width == 0 {
|
||||
continue;
|
||||
}
|
||||
let row = pane_screen(&terminal, Rect::new(0, y, width, 1));
|
||||
assert!(
|
||||
row.trim()
|
||||
.starts_with(if width == 1 { "2" } else { "2:active" }),
|
||||
"{row}"
|
||||
);
|
||||
assert_eq!(
|
||||
terminal.backend().buffer()[((width - width.min(8)) / 2, 0)].symbol(),
|
||||
terminal.backend().buffer()[((width - width.min(8)) / 2, y)].symbol(),
|
||||
"2"
|
||||
);
|
||||
assert!(!row.contains("LEFT") && !row.contains("RIGHT"), "{row}");
|
||||
assert!(!row.contains('界'), "{row}");
|
||||
}
|
||||
}
|
||||
configure_chrome(&mut editor, |top, _| {
|
||||
configure_chrome(&mut editor, |top, bottom| {
|
||||
let tabs = top.segments.get_mut("tabs").unwrap();
|
||||
tabs.padding = 0;
|
||||
tabs.leading_edge.clear();
|
||||
tabs.trailing_edge.clear();
|
||||
*bottom = top.clone();
|
||||
});
|
||||
editor
|
||||
.set_label("prefix".into(), "LEFT".repeat(32))
|
||||
@@ -5638,11 +5684,22 @@ mod tests {
|
||||
let mut terminal = Terminal::new(ratatui::backend::TestBackend::new(40, 2)).unwrap();
|
||||
terminal
|
||||
.draw(|frame| {
|
||||
draw_tab_bar(frame, &editor, frame.area());
|
||||
for (y, line) in [editor.top_bar_config(), editor.bottom_bar_config()]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
draw_chrome(
|
||||
frame,
|
||||
chrome_groups(&editor, line, &SyntaxCache::new()),
|
||||
Rect::new(0, y as u16, 40, 1),
|
||||
);
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
let row = pane_screen(&terminal, Rect::new(0, 0, 40, 1));
|
||||
assert_eq!(&row[16..24], "2:active");
|
||||
for y in 0..2 {
|
||||
let row = pane_screen(&terminal, Rect::new(0, y, 40, 1));
|
||||
assert_eq!(&row[16..24], "2:active");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -5683,8 +5740,16 @@ mod tests {
|
||||
let syntax = SyntaxCache::new();
|
||||
terminal
|
||||
.draw(|frame| {
|
||||
draw_tab_bar(frame, &editor, frame.area());
|
||||
draw_status(frame, &editor, &syntax, Rect::new(0, 2, 40, 1));
|
||||
draw_chrome(
|
||||
frame,
|
||||
chrome_groups(&editor, editor.top_bar_config(), &SyntaxCache::new()),
|
||||
frame.area(),
|
||||
);
|
||||
draw_chrome(
|
||||
frame,
|
||||
chrome_groups(&editor, editor.bottom_bar_config(), &syntax),
|
||||
Rect::new(0, 2, 40, 1),
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
let buffer = terminal.backend().buffer();
|
||||
@@ -5726,7 +5791,11 @@ mod tests {
|
||||
editor.execute("tab.previous");
|
||||
terminal
|
||||
.draw(|frame| {
|
||||
draw_tab_bar(frame, &editor, frame.area());
|
||||
draw_chrome(
|
||||
frame,
|
||||
chrome_groups(&editor, editor.top_bar_config(), &SyntaxCache::new()),
|
||||
frame.area(),
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
let buffer = terminal.backend().buffer();
|
||||
@@ -5738,7 +5807,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_configured_chrome_does_not_allocate_a_tab_row() {
|
||||
fn empty_bar_content_reclaims_both_rows_without_hiding_the_prompt() {
|
||||
let mut editor = Editor::open(None, vec![]).unwrap();
|
||||
editor.execute("tab.new");
|
||||
configure_chrome(&mut editor, |top, bottom| {
|
||||
@@ -5755,28 +5824,292 @@ mod tests {
|
||||
first_leading_edge: None,
|
||||
},
|
||||
);
|
||||
bottom.left.clear();
|
||||
bottom.center.clear();
|
||||
bottom.right.clear();
|
||||
*bottom = top.clone();
|
||||
});
|
||||
let mut syntax = SyntaxCache::new();
|
||||
let mut terminal = Terminal::new(ratatui::backend::TestBackend::new(40, 5)).unwrap();
|
||||
terminal
|
||||
.draw(|frame| {
|
||||
let area = frame.area();
|
||||
assert_eq!(draw_tab_bar(frame, &editor, area), area);
|
||||
assert_eq!(document_layout(area, &editor)[1].height, 1);
|
||||
let [workspace, prompt] = draw_bars(frame, &editor, frame.area(), &mut syntax);
|
||||
assert_eq!(workspace, Rect::new(0, 0, 40, 4));
|
||||
assert_eq!(prompt, Rect::new(0, 4, 40, 1));
|
||||
})
|
||||
.unwrap();
|
||||
editor.set_label("empty".into(), "present".into()).unwrap();
|
||||
terminal
|
||||
.draw(|frame| {
|
||||
let area = frame.area();
|
||||
assert_eq!(draw_tab_bar(frame, &editor, area).y, 1);
|
||||
let [workspace, prompt] = draw_bars(frame, &editor, frame.area(), &mut syntax);
|
||||
assert_eq!(workspace, Rect::new(0, 1, 40, 2));
|
||||
assert_eq!(prompt, Rect::new(0, 4, 40, 1));
|
||||
})
|
||||
.unwrap();
|
||||
let title = pane_screen(&terminal, Rect::new(0, 0, 40, 1));
|
||||
assert!(title.contains("Not visible without a value"), "{title}");
|
||||
assert!(!title.contains('▓'), "{title}");
|
||||
for y in [0, 3] {
|
||||
let title = pane_screen(&terminal, Rect::new(0, y, 40, 1));
|
||||
assert!(title.contains("Not visible without a value"), "{title}");
|
||||
assert!(!title.contains('▓'), "{title}");
|
||||
}
|
||||
editor.handle(Input::Char(':'));
|
||||
terminal
|
||||
.draw(|frame| {
|
||||
let [workspace, prompt] = draw_bars(frame, &editor, frame.area(), &mut syntax);
|
||||
assert_eq!(workspace.height, 2);
|
||||
assert_eq!(prompt, Rect::new(0, 4, 40, 1));
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tiny_bars_yield_to_document_and_prompt_identically_in_either_position() {
|
||||
let directory = Directory::new();
|
||||
let path = directory.file("source.txt", "DOCUMENT");
|
||||
for segment in ["tabs", "mode"] {
|
||||
for at_top in [false, true] {
|
||||
for prompting in [false, true] {
|
||||
let mut editor = Editor::open(Some(path.clone()), vec![]).unwrap();
|
||||
editor.execute("tab.new");
|
||||
configure_chrome(&mut editor, |top, bottom| {
|
||||
for line in [&mut *top, &mut *bottom] {
|
||||
line.left.clear();
|
||||
line.center.clear();
|
||||
line.right.clear();
|
||||
}
|
||||
let line = if at_top { top } else { bottom };
|
||||
line.left = vec![segment.into()];
|
||||
});
|
||||
if prompting {
|
||||
editor.handle(Input::Char(':'));
|
||||
}
|
||||
for height in 1..=4 {
|
||||
let area = Rect::new(3, 2, 80, height);
|
||||
let mut terminal = Terminal::with_options(
|
||||
ratatui::backend::TestBackend::new(100, 10),
|
||||
ratatui::TerminalOptions {
|
||||
viewport: ratatui::Viewport::Fixed(area),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let mut views = BufferViewports::default();
|
||||
terminal
|
||||
.draw(|frame| views.draw(frame, &mut editor))
|
||||
.unwrap();
|
||||
let bar_text = match segment {
|
||||
"tabs" => "2 source.txt",
|
||||
_ if prompting => "COMMAND",
|
||||
_ => "NORMAL",
|
||||
};
|
||||
let visible = height >= 3;
|
||||
assert_eq!(screen_text(&terminal).contains(bar_text), visible);
|
||||
let cursor = terminal.get_cursor_position().unwrap();
|
||||
assert!(
|
||||
area.contains(cursor),
|
||||
"{segment} {at_top} prompting={prompting} {area:?}: {cursor:?}"
|
||||
);
|
||||
if prompting {
|
||||
assert_eq!(cursor.y, area.bottom() - 1);
|
||||
assert_eq!(
|
||||
terminal.backend().buffer()[(area.x, cursor.y)].symbol(),
|
||||
":",
|
||||
);
|
||||
}
|
||||
if !prompting || height > 1 {
|
||||
let viewport = views.document.geometry.unwrap().1;
|
||||
assert!(viewport.height > 0);
|
||||
assert!(pane_screen(&terminal, viewport).contains("DOCUMENT"));
|
||||
}
|
||||
if visible {
|
||||
let y = if at_top { area.y } else { area.bottom() - 2 };
|
||||
assert!(
|
||||
pane_screen(&terminal, Rect::new(area.x, y, area.width, 1))
|
||||
.contains(bar_text)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tabs_only_singletons_hide_in_either_bar_and_reappear_with_another_tab() {
|
||||
let mut editor = Editor::open(None, vec![]).unwrap();
|
||||
configure_chrome(&mut editor, |top, bottom| *bottom = top.clone());
|
||||
let mut views = BufferViewports::default();
|
||||
let mut terminal = Terminal::new(ratatui::backend::TestBackend::new(80, 12)).unwrap();
|
||||
terminal
|
||||
.draw(|frame| views.draw(frame, &mut editor))
|
||||
.unwrap();
|
||||
assert_eq!(views.rects[0].1, Rect::new(0, 0, 80, 11));
|
||||
assert!(views.scroll_document(&editor, document_wheel(MouseEventKind::ScrollDown, 2, 0),));
|
||||
|
||||
editor.execute("tab.new");
|
||||
terminal
|
||||
.draw(|frame| views.draw(frame, &mut editor))
|
||||
.unwrap();
|
||||
assert_eq!(views.rects[0].1, Rect::new(0, 1, 80, 9));
|
||||
for y in [0, 10] {
|
||||
assert!(
|
||||
!views.scroll_document(&editor, document_wheel(MouseEventKind::ScrollDown, 2, y),)
|
||||
);
|
||||
}
|
||||
let caret = terminal.get_cursor_position().unwrap();
|
||||
assert!(caret.y >= 1 && caret.y < 10);
|
||||
for y in [0, 10] {
|
||||
let bar = pane_screen(&terminal, Rect::new(0, y, 80, 1));
|
||||
assert!(
|
||||
bar.contains("1 [No Name]") && bar.contains("2 [No Name]"),
|
||||
"{bar}"
|
||||
);
|
||||
}
|
||||
editor.execute("tab.close");
|
||||
terminal
|
||||
.draw(|frame| views.draw(frame, &mut editor))
|
||||
.unwrap();
|
||||
assert_eq!(views.rects[0].1, Rect::new(0, 0, 80, 11));
|
||||
assert!(!screen_text(&terminal).contains("[No Name]"));
|
||||
for y in [0, 10] {
|
||||
assert!(
|
||||
views.scroll_document(&editor, document_wheel(MouseEventKind::ScrollDown, 2, y),)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn swapped_bars_render_active_metadata_and_language_on_the_first_paint() {
|
||||
let directory = Directory::new();
|
||||
let path = directory.file("active.rs", "fn main() {}");
|
||||
let mut editor = Editor::open(Some(path.clone()), vec![]).unwrap();
|
||||
editor.execute("tab.new");
|
||||
editor.handle(Input::Char('i'));
|
||||
editor.handle(Input::Paste("// changed\n".into()));
|
||||
editor.handle(Input::Escape);
|
||||
configure_chrome(&mut editor, std::mem::swap);
|
||||
let mut terminal = Terminal::new(ratatui::backend::TestBackend::new(240, 12)).unwrap();
|
||||
let mut views = BufferViewports::default();
|
||||
terminal
|
||||
.draw(|frame| views.draw(frame, &mut editor))
|
||||
.unwrap();
|
||||
let top = pane_screen(&terminal, Rect::new(0, 0, 240, 1));
|
||||
assert!(top.contains("NORMAL"), "{top}");
|
||||
assert!(top.contains(&path.display().to_string()), "{top}");
|
||||
assert!(top.contains("[+]") && top.contains("Rust"), "{top}");
|
||||
let cursor = editor.cursor();
|
||||
assert!(
|
||||
top.contains(&format!("{}:{}", cursor.row + 1, cursor.column + 1)),
|
||||
"{top}"
|
||||
);
|
||||
let bottom = pane_screen(&terminal, Rect::new(0, 10, 240, 1));
|
||||
assert!(
|
||||
bottom.contains("1 active.rs [+]") && bottom.contains("2 active.rs [+]"),
|
||||
"{bottom}"
|
||||
);
|
||||
assert_eq!(views.document.geometry.unwrap().1.y, 1);
|
||||
let before = views.rects[0].1;
|
||||
editor.handle(Input::Char(':'));
|
||||
terminal
|
||||
.draw(|frame| views.draw(frame, &mut editor))
|
||||
.unwrap();
|
||||
assert_eq!(views.rects[0].1, before);
|
||||
assert!(pane_screen(&terminal, Rect::new(0, 11, 240, 1)).starts_with(':'));
|
||||
assert_eq!(terminal.get_cursor_position().unwrap().y, 11);
|
||||
assert_eq!(pane_screen(&terminal, Rect::new(0, 10, 240, 1)), bottom);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn both_bars_bound_palettes_diff_selectors_docks_and_diff_prompts() {
|
||||
let directory = Directory::new();
|
||||
let mut editor =
|
||||
Editor::open(Some(directory.file("source.rs", "fn main() {}")), vec![]).unwrap();
|
||||
editor.execute("tab.new");
|
||||
configure_chrome(&mut editor, |top, bottom| {
|
||||
std::mem::swap(top, bottom);
|
||||
top.left = vec!["mode".into(), "language".into()];
|
||||
top.center = vec!["workspace".into()];
|
||||
top.right = vec!["position".into()];
|
||||
bottom.right = vec!["battery".into()];
|
||||
});
|
||||
editor
|
||||
.set_label("workspace".into(), "WORKSPACE".into())
|
||||
.unwrap();
|
||||
editor.set_label("battery".into(), "BOTTOM".into()).unwrap();
|
||||
let mut terminal = Terminal::new(ratatui::backend::TestBackend::new(120, 30)).unwrap();
|
||||
let mut views = BufferViewports::default();
|
||||
let assert_bars = |terminal: &Terminal<ratatui::backend::TestBackend>,
|
||||
views: &BufferViewports| {
|
||||
assert!(pane_screen(terminal, Rect::new(0, 0, 120, 1)).contains("WORKSPACE"));
|
||||
let bottom_y = if views.panels.diff.is_some() { 29 } else { 28 };
|
||||
assert!(pane_screen(terminal, Rect::new(0, bottom_y, 120, 1)).contains("BOTTOM"));
|
||||
for (_, rect) in &views.rects {
|
||||
assert!(rect.y >= 1 && rect.bottom() <= bottom_y);
|
||||
}
|
||||
if let Some(geometry) = &views.panels.diff {
|
||||
for (rect, _) in geometry.regions {
|
||||
if rect.height > 0 {
|
||||
assert!(rect.y >= 1 && rect.bottom() <= bottom_y);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some((rect, _, _)) = views.panels.files {
|
||||
assert!(rect.y >= 1 && rect.bottom() <= bottom_y);
|
||||
}
|
||||
};
|
||||
terminal
|
||||
.draw(|frame| views.draw(frame, &mut editor))
|
||||
.unwrap();
|
||||
assert!(pane_screen(&terminal, Rect::new(0, 0, 120, 1)).contains("Rust"));
|
||||
assert_bars(&terminal, &views);
|
||||
editor.handle(Input::Ctrl('p'));
|
||||
terminal
|
||||
.draw(|frame| views.draw(frame, &mut editor))
|
||||
.unwrap();
|
||||
assert!(screen_text(&terminal).contains("Commands"));
|
||||
assert_bars(&terminal, &views);
|
||||
editor.handle(Input::Escape);
|
||||
|
||||
editor.execute("diff");
|
||||
let (id, _) = editor.take_diff_request().unwrap();
|
||||
editor.apply_diff_result(id, Ok(crate::diff::Outcome::Catalog(diff_catalog())));
|
||||
terminal
|
||||
.draw(|frame| views.draw(frame, &mut editor))
|
||||
.unwrap();
|
||||
assert!(screen_text(&terminal).contains("Choose Before"));
|
||||
assert!(views.document.geometry.is_none());
|
||||
assert_bars(&terminal, &views);
|
||||
editor.handle(Input::Enter);
|
||||
editor.handle(Input::Enter);
|
||||
let (id, _) = editor.take_diff_request().unwrap();
|
||||
editor.apply_diff_result(
|
||||
id,
|
||||
Ok(crate::diff::Outcome::Comparison(crate::diff::Comparison {
|
||||
root: directory.0.clone(),
|
||||
editable_paths: Default::default(),
|
||||
before: "baseline".into(),
|
||||
after: "Working checkout".into(),
|
||||
files: vec![deletion_file()],
|
||||
})),
|
||||
);
|
||||
terminal
|
||||
.draw(|frame| views.draw(frame, &mut editor))
|
||||
.unwrap();
|
||||
assert!(views.panels.files.is_some());
|
||||
assert_bars(&terminal, &views);
|
||||
editor.handle(Input::Char(':'));
|
||||
terminal
|
||||
.draw(|frame| views.draw(frame, &mut editor))
|
||||
.unwrap();
|
||||
let cursor = terminal.get_cursor_position().unwrap();
|
||||
assert!(Rect::new(0, 1, 120, 28).contains(cursor), "{cursor:?}");
|
||||
assert_bars(&terminal, &views);
|
||||
editor.handle(Input::Escape);
|
||||
editor.handle(Input::Char(' '));
|
||||
editor.handle(Input::Char('o'));
|
||||
terminal
|
||||
.draw(|frame| views.draw(frame, &mut editor))
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
editor.file_picker().unwrap().presentation,
|
||||
FilePresentation::Floating
|
||||
);
|
||||
assert_bars(&terminal, &views);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -5802,11 +6135,16 @@ mod tests {
|
||||
terminal
|
||||
.draw(|frame| views.draw(frame, &mut editor))
|
||||
.unwrap();
|
||||
if width > 0 && height > 0 {
|
||||
if width > 0 && height >= 3 {
|
||||
let title = pane_screen(&terminal, Rect::new(0, 0, width, 1));
|
||||
assert!(title.trim_start().starts_with('3'), "{title}");
|
||||
assert!(!title.contains("status"), "{title}");
|
||||
}
|
||||
if width > 0 && height == 1 {
|
||||
let (_, viewport) = views.document.geometry.unwrap();
|
||||
assert!(viewport.contains(terminal.get_cursor_position().unwrap()));
|
||||
assert!(pane_screen(&terminal, viewport).contains("ACTIVE"));
|
||||
}
|
||||
}
|
||||
|
||||
let mut editor = Editor::open(None, vec![]).unwrap();
|
||||
@@ -5890,7 +6228,11 @@ mod tests {
|
||||
terminal
|
||||
.draw(|frame| {
|
||||
let area = frame.area();
|
||||
assert_eq!(draw_tab_bar(frame, &editor, area).y, 1);
|
||||
draw_chrome(
|
||||
frame,
|
||||
chrome_groups(&editor, editor.top_bar_config(), &SyntaxCache::new()),
|
||||
area,
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
let title = pane_screen(&terminal, Rect::new(0, 0, width, 1));
|
||||
@@ -5908,7 +6250,11 @@ mod tests {
|
||||
terminal
|
||||
.draw(|frame| {
|
||||
let area = frame.area();
|
||||
draw_tab_bar(frame, &editor, area);
|
||||
draw_chrome(
|
||||
frame,
|
||||
chrome_groups(&editor, editor.top_bar_config(), &SyntaxCache::new()),
|
||||
area,
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
let title = pane_screen(&terminal, Rect::new(0, 0, 80, 1));
|
||||
@@ -5979,10 +6325,6 @@ mod tests {
|
||||
terminal
|
||||
.draw(|frame| views.draw(frame, &mut editor))
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
views.panels.files.unwrap().0,
|
||||
floating_panel(Rect::new(0, 1, 120, 29))
|
||||
);
|
||||
assert_eq!(pane_screen(&terminal, Rect::new(0, 0, 120, 1)), title);
|
||||
}
|
||||
|
||||
@@ -6185,10 +6527,10 @@ mod tests {
|
||||
terminal
|
||||
.draw(|frame| views.draw(frame, &mut editor))
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
views.panels.files.unwrap().0,
|
||||
floating_panel(Rect::new(0, 0, 120, 30))
|
||||
);
|
||||
let panel = views.panels.files.unwrap().0;
|
||||
assert!(panel.x < 60 && panel.right() > 60);
|
||||
assert!(panel.y >= views.rects[0].1.y);
|
||||
assert!(panel.bottom() <= views.rects[0].1.bottom());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -6764,7 +7106,6 @@ mod tests {
|
||||
editor.handle(Input::Char(ch));
|
||||
}
|
||||
render_native_diff(&mut terminal, &mut views, &mut editor);
|
||||
assert_eq!(terminal.get_cursor_position().unwrap().y, 13);
|
||||
editor.handle(Input::Enter);
|
||||
let searched = render_native_diff(&mut terminal, &mut views, &mut editor);
|
||||
assert!(before.contains(searched));
|
||||
@@ -7071,7 +7412,6 @@ mod tests {
|
||||
terminal
|
||||
.draw(|frame| views.draw(frame, &mut editor))
|
||||
.unwrap();
|
||||
assert_eq!(terminal.get_cursor_position().unwrap().y, 13);
|
||||
assert!(screen_text(&terminal).contains(":diff.close"));
|
||||
editor.handle(Input::Enter);
|
||||
assert!(editor.diff().is_none());
|
||||
@@ -9989,6 +10329,7 @@ end
|
||||
let before = (top, left);
|
||||
editor.handle(Input::Char(' '));
|
||||
editor.handle(Input::Char('o'));
|
||||
let mut panels = WheelPanels::default();
|
||||
terminal
|
||||
.draw(|frame| {
|
||||
draw(
|
||||
@@ -9998,11 +10339,13 @@ end
|
||||
&mut left,
|
||||
&mut syntax,
|
||||
&mut DocumentViewport::default(),
|
||||
&mut WheelPanels::default(),
|
||||
&mut panels,
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
let panel = Rect::new(area.x + 5, area.y + 1, 100, 24);
|
||||
let panel = panels.files.unwrap().0;
|
||||
assert_eq!(panel.x - area.x, area.right() - panel.right());
|
||||
assert!(panel.y >= area.y && panel.bottom() <= area.bottom() - 2);
|
||||
assert_eq!((top, left), before);
|
||||
assert_eq!(
|
||||
terminal.get_cursor_position().unwrap(),
|
||||
|
||||
Reference in New Issue
Block a user