use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use super::braille::{dither_threshold_met, pack_braille_cell};
use super::{ratatui_color, set_cell};
use crate::primitives::minimap::{Minimap, MinimapLayout, MinimapSizing};
use crate::theme::Theme;
pub const LINES_PER_ROW: usize = 4;
pub const COLS_PER_CELL: usize = 2;
pub fn tui_minimap_layout(minimap: &Minimap, area: Rect) -> MinimapLayout {
tui_minimap_layout_with_sizing(minimap, area, MinimapSizing::FixedPitch(1.0))
}
pub fn tui_minimap_layout_with_sizing(
minimap: &Minimap,
area: Rect,
sizing: MinimapSizing,
) -> MinimapLayout {
let mut layout = minimap.layout_with_sizing(
crate::event::Rect::new(
area.x as f32,
area.y as f32,
area.width as f32,
area.height as f32,
),
LINES_PER_ROW,
sizing,
);
layout.cols_per_cell = resolve_cols_per_cell(minimap, area.width as usize);
layout
}
pub fn draw_minimap(
buf: &mut Buffer,
area: Rect,
minimap: &Minimap,
theme: &Theme,
) -> MinimapLayout {
let cols_per_cell = resolve_cols_per_cell(minimap, area.width as usize);
draw_minimap_with_scale(buf, area, minimap, theme, cols_per_cell)
}
pub fn resolve_cols_per_cell(minimap: &Minimap, width_cells: usize) -> usize {
let width_cells = width_cells.max(1);
let max_line_len = minimap
.lines
.iter()
.map(|l| l.text.chars().count())
.max()
.unwrap_or(0);
let capped = max_line_len.min(crate::primitives::minimap::COLUMN_CAPACITY);
let dot_budget = width_cells * 2;
let resolved_cols_per_dot = capped.div_ceil(dot_budget).max(1);
resolved_cols_per_dot * 2
}
pub fn draw_minimap_with_scale(
buf: &mut Buffer,
area: Rect,
minimap: &Minimap,
theme: &Theme,
cols_per_cell: usize,
) -> MinimapLayout {
let layout = tui_minimap_layout(minimap, area);
paint_minimap_rows(buf, area, minimap, theme, cols_per_cell, layout)
}
pub fn draw_minimap_with_sizing(
buf: &mut Buffer,
area: Rect,
minimap: &Minimap,
theme: &Theme,
cols_per_cell: usize,
sizing: MinimapSizing,
) -> MinimapLayout {
let layout = tui_minimap_layout_with_sizing(minimap, area, sizing);
paint_minimap_rows(buf, area, minimap, theme, cols_per_cell, layout)
}
fn paint_minimap_rows(
buf: &mut Buffer,
area: Rect,
minimap: &Minimap,
theme: &Theme,
cols_per_cell: usize,
mut layout: MinimapLayout,
) -> MinimapLayout {
let cols_per_cell = cols_per_cell.max(1);
layout.cols_per_cell = cols_per_cell;
if area.width == 0 || area.height == 0 {
return layout;
}
let bg = ratatui_color(theme.background);
let default_fg = ratatui_color(theme.foreground);
let highlight_bg = ratatui_color(theme.background.blend(theme.accent_bg, 0.25));
let hl = &layout.viewport_highlight;
let width_cells = area.width as usize;
for vline in &layout.visible_lines {
let row_y0 = vline.bounds.y.round();
let repeat_rows = vline.bounds.height.round().max(1.0) as u16;
let row_mid = vline.bounds.y + vline.bounds.height * 0.5;
let in_highlight = hl.height > 0.0 && row_mid >= hl.y && row_mid < hl.y + hl.height;
let row_bg = if in_highlight { highlight_bg } else { bg };
let row_lines: Vec<Option<Vec<char>>> = (0..LINES_PER_ROW)
.map(|dr| {
minimap
.lines
.get(vline.start_line_idx + dr)
.map(|l| l.text.chars().collect())
})
.collect();
for repeat in 0..repeat_rows {
let row_y = row_y0 + repeat as f32;
if row_y < 0.0 {
continue;
}
let row_y = row_y as u16;
if row_y < area.y || row_y >= area.y + area.height {
continue;
}
for col in 0..width_cells {
let ch = braille_char_for_cell(&row_lines, col, cols_per_cell);
let fg = cell_color(
minimap,
vline.start_line_idx,
col,
default_fg,
theme,
cols_per_cell,
);
set_cell(buf, area.x + col as u16, row_y, ch, fg, row_bg);
}
}
}
layout
}
fn cols_per_dot(cols_per_cell: usize) -> usize {
cols_per_cell.max(1).div_ceil(2).max(1)
}
fn braille_char_for_cell(
row_lines: &[Option<Vec<char>>],
col: usize,
cols_per_cell: usize,
) -> char {
let cols_per_dot = cols_per_dot(cols_per_cell);
pack_braille_cell(|dr, dc| {
let chars = match row_lines.get(dr).and_then(|o| o.as_ref()) {
Some(c) if !c.is_empty() => c,
_ => return false,
};
let dot_col = col * 2 + dc;
let c0 = dot_col * cols_per_dot;
if c0 >= chars.len() {
return false;
}
let c1 = (c0 + cols_per_dot).min(chars.len());
let covered = chars[c0..c1].iter().filter(|c| !c.is_whitespace()).count();
dither_threshold_met(covered, cols_per_dot, dr, dot_col)
})
}
fn cell_color(
minimap: &Minimap,
start_line_idx: usize,
col: usize,
default_fg: ratatui::style::Color,
theme: &Theme,
cols_per_cell: usize,
) -> ratatui::style::Color {
let cell_width = 2 * cols_per_dot(cols_per_cell);
let col_lo = col * cell_width;
let col_hi = col_lo + cell_width;
minimap
.syntax_spans
.iter()
.find(|s| s.line_idx == start_line_idx && s.start_col < col_hi && s.end_col > col_lo)
.map(|s| ratatui_color(s.color))
.unwrap_or({
let _ = theme; default_fg
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::primitives::minimap::{MinimapHit, MinimapLine, MinimapSpan};
use crate::types::{Color, WidgetId};
fn cell_char(buf: &Buffer, x: u16, y: u16) -> char {
buf[(x, y)].symbol().chars().next().unwrap_or(' ')
}
fn minimap_from(lines: Vec<&str>, total_buffer_lines: usize) -> Minimap {
Minimap {
id: WidgetId::new("mm"),
lines: lines
.into_iter()
.enumerate()
.map(|(i, t)| MinimapLine {
text: t.into(),
line_idx: i,
})
.collect(),
syntax_spans: Vec::new(),
visible_row_start: 0,
visible_row_count: 0,
total_buffer_lines,
}
}
fn eight_by_four() -> Minimap {
minimap_from(
vec![
"X ", " X ", " X ", " X", " ", " ", " ", " ",
],
8,
)
}
#[test]
fn braille_packing_exercises_all_dot_rows_and_both_dot_columns() {
let mm = eight_by_four();
let area = Rect::new(0, 0, 2, 2);
let mut buf = Buffer::empty(area);
let _layout = draw_minimap(&mut buf, area, &mm, &Theme::default());
assert_eq!(cell_char(&buf, 0, 0), '\u{2821}', "left cell mispacked");
assert_eq!(cell_char(&buf, 1, 0), '\u{2882}', "right cell mispacked");
}
#[test]
fn all_whitespace_group_packs_to_blank_braille_not_a_space() {
let mm = eight_by_four();
let area = Rect::new(0, 0, 2, 2);
let mut buf = Buffer::empty(area);
let _layout = draw_minimap(&mut buf, area, &mm, &Theme::default());
assert_eq!(cell_char(&buf, 0, 1), '\u{2800}');
assert_eq!(cell_char(&buf, 1, 1), '\u{2800}');
}
#[test]
fn cell_color_uses_the_aggregated_span_for_that_cell() {
let mut mm = eight_by_four();
let red = Color::rgb(255, 0, 0);
mm.syntax_spans.push(MinimapSpan {
line_idx: 0,
start_col: 0,
end_col: 2,
color: red,
});
let area = Rect::new(0, 0, 2, 2);
let mut buf = Buffer::empty(area);
let _layout = draw_minimap(&mut buf, area, &mm, &Theme::default());
assert_eq!(buf[(0u16, 0u16)].fg, ratatui_color(red));
assert_eq!(
buf[(1u16, 0u16)].fg,
ratatui_color(Theme::default().foreground)
);
}
#[test]
fn viewport_highlight_paints_a_background_band_not_a_foreground_overlay() {
let mut mm = eight_by_four();
mm.visible_row_start = 0;
mm.visible_row_count = 4; let theme = Theme {
accent_bg: Color::rgb(9, 9, 9),
..Theme::default()
};
let area = Rect::new(0, 0, 2, 2);
let mut buf = Buffer::empty(area);
let _layout = draw_minimap(&mut buf, area, &mm, &theme);
let expected_tint = ratatui_color(theme.background.blend(theme.accent_bg, 0.25));
assert_eq!(buf[(0u16, 0u16)].bg, expected_tint);
assert_ne!(buf[(0u16, 1u16)].bg, expected_tint);
}
#[test]
fn viewport_highlight_is_a_blended_tint_not_opaque_accent_bg() {
let mut mm = eight_by_four();
mm.visible_row_start = 0;
mm.visible_row_count = 4; let theme = Theme {
background: Color::rgb(20, 20, 20),
accent_bg: Color::rgb(0, 96, 191),
..Theme::default()
};
let area = Rect::new(0, 0, 2, 2);
let mut buf = Buffer::empty(area);
let _layout = draw_minimap(&mut buf, area, &mm, &theme);
let expected = theme.background.blend(theme.accent_bg, 0.25);
assert_eq!(buf[(0u16, 0u16)].bg, ratatui_color(expected));
assert_ne!(
buf[(0u16, 0u16)].bg,
ratatui_color(theme.accent_bg),
"the viewport slider must not paint opaque accent_bg at full strength"
);
}
#[test]
fn fully_visible_file_paints_no_viewport_band_at_all() {
let theme = Theme {
background: Color::rgb(20, 20, 20),
accent_bg: Color::rgb(0, 96, 191),
..Theme::default()
};
let tint = ratatui_color(theme.background.blend(theme.accent_bg, 0.25));
for (lines, label) in [
(vec![""], "single empty line"),
(vec!["fn main() {}", "", "// short"], "three-line file"),
] {
let total = lines.len();
let mut mm = minimap_from(lines, total);
mm.visible_row_start = 0;
mm.visible_row_count = 40;
let area = Rect::new(0, 0, 3, 6);
let mut buf = Buffer::empty(area);
let layout = draw_minimap(&mut buf, area, &mm, &theme);
assert_eq!(
layout.viewport_highlight.height, 0.0,
"{label}: layout must not report a viewport band"
);
assert!(
!layout.visible_lines.is_empty(),
"{label}: nothing was painted, so the tint check below is vacuous"
);
for y in area.y..area.y + area.height {
for x in area.x..area.x + area.width {
assert_ne!(buf[(x, y)].bg, tint, "{label}: cell ({x},{y}) is tinted");
}
}
}
}
fn paint_and_click_round_trip_at(origin_x: u16, origin_y: u16) {
let mm = minimap_from(vec!["x"; 8], 8);
let area = Rect::new(origin_x, origin_y, 4, 8); let mut buf = Buffer::empty(area);
let layout = draw_minimap(&mut buf, area, &mm, &Theme::default());
assert_eq!(
layout.hit_test(origin_x as f32 + 2.0, origin_y as f32 + 4.0),
MinimapHit::Seek { fraction: 0.5 }
);
assert_eq!(
layout.hit_test(origin_x as f32 + 2.0, origin_y as f32),
MinimapHit::Seek { fraction: 0.0 }
);
}
#[test]
fn paint_and_click_round_trip_returns_seek_for_the_clicked_fraction() {
paint_and_click_round_trip_at(0, 0);
}
#[test]
fn paint_and_click_round_trip_returns_seek_for_the_clicked_fraction_at_nonzero_origin() {
paint_and_click_round_trip_at(7, 13);
}
#[test]
fn short_file_in_a_tall_strip_paints_contiguous_rows_no_gaps() {
let mm = eight_by_four(); let area = Rect::new(0, 0, 2, 20); let mut buf = Buffer::empty(area);
let layout = draw_minimap(&mut buf, area, &mm, &Theme::default());
assert_eq!(layout.visible_lines.len(), 2);
assert_eq!(
layout.visible_lines[0].bounds.height, 1.0,
"row pitch must be exactly one cell row, not stretched toward MAX_ROW_PITCH"
);
assert_eq!(layout.visible_lines[1].bounds.height, 1.0);
assert_eq!(layout.visible_lines[0].bounds.y, 0.0);
assert_eq!(
layout.visible_lines[1].bounds.y, 1.0,
"row 1 must sit directly below row 0 with no gap"
);
assert_ne!(cell_char(&buf, 0, 0), ' ');
assert_ne!(cell_char(&buf, 0, 1), ' ');
assert_eq!(cell_char(&buf, 0, 2), ' ');
}
#[test]
fn zero_size_is_a_no_op() {
let mm = eight_by_four();
let mut buf = Buffer::empty(Rect::new(0, 0, 4, 4));
let _layout = draw_minimap(&mut buf, Rect::new(0, 0, 0, 4), &mm, &Theme::default());
assert_eq!(cell_char(&buf, 0, 0), ' ');
}
fn first_set_cell(chars: &[char], width_cells: usize) -> Option<usize> {
first_set_cell_at_scale(chars, width_cells, COLS_PER_CELL)
}
fn first_set_cell_at_scale(
chars: &[char],
width_cells: usize,
cols_per_cell: usize,
) -> Option<usize> {
let row_lines = [Some(chars.to_vec()), None, None, None];
(0..width_cells)
.find(|&col| braille_char_for_cell(&row_lines, col, cols_per_cell) != '\u{2800}')
}
#[test]
fn indentation_lands_at_distinct_columns_proportional_to_indent() {
let width_cells = 8;
let flush: Vec<char> = "x".chars().collect();
let indent_4: Vec<char> = " x".chars().collect();
let indent_8: Vec<char> = " x".chars().collect();
let c_flush = first_set_cell(&flush, width_cells).expect("flush line must set a dot");
let c_indent_4 =
first_set_cell(&indent_4, width_cells).expect("indent-4 line must set a dot");
let c_indent_8 =
first_set_cell(&indent_8, width_cells).expect("indent-8 line must set a dot");
assert!(
c_flush < c_indent_4 && c_indent_4 < c_indent_8,
"expected strictly increasing columns, got {c_flush} < {c_indent_4} < {c_indent_8}"
);
assert_eq!(c_indent_4, c_flush + 4 / COLS_PER_CELL);
assert_eq!(c_indent_8, c_flush + 8 / COLS_PER_CELL);
}
#[test]
fn a_long_neighbour_line_does_not_move_a_short_lines_dot() {
let width_cells = 8;
let indented: Vec<char> = " x".chars().collect();
let very_long: Vec<char> = format!("{}y", " ".repeat(200)).chars().collect();
let alone = {
let row_lines = [Some(indented.clone()), None, None, None];
(0..width_cells)
.find(|&col| braille_char_for_cell(&row_lines, col, COLS_PER_CELL) != '\u{2800}')
};
let with_long_neighbor = {
let row_lines = [Some(indented.clone()), Some(very_long), None, None];
(0..width_cells)
.find(|&col| braille_char_for_cell(&row_lines, col, COLS_PER_CELL) != '\u{2800}')
};
assert_eq!(
alone, with_long_neighbor,
"a long neighbouring line must not shift where the short line's dot lands"
);
}
#[test]
fn a_line_past_the_visible_range_is_clipped_not_compressed() {
let width_cells = 4;
let visible_cols = width_cells * COLS_PER_CELL;
let mut at_edge = vec![' '; visible_cols];
at_edge[visible_cols - 1] = 'x';
assert!(
first_set_cell(&at_edge, width_cells).is_some(),
"a dot within the visible range must be painted"
);
let mut past_edge = vec![' '; visible_cols + 1];
past_edge[visible_cols] = 'x';
assert_eq!(
first_set_cell(&past_edge, width_cells),
None,
"content past width_cells * COLS_PER_CELL must be clipped, not compressed into view"
);
}
#[test]
fn draw_minimap_matches_draw_minimap_with_scale_at_the_default_cols_per_cell() {
let mm = eight_by_four();
let area = Rect::new(0, 0, 2, 2);
let mut via_plain = Buffer::empty(area);
let plain_layout = draw_minimap(&mut via_plain, area, &mm, &Theme::default());
let mut via_scale = Buffer::empty(area);
let scale_layout =
draw_minimap_with_scale(&mut via_scale, area, &mm, &Theme::default(), COLS_PER_CELL);
assert_eq!(via_plain, via_scale);
assert_eq!(plain_layout, scale_layout);
}
#[test]
fn draw_minimap_with_scale_widens_the_representable_source_range() {
let width_cells = 11;
let mut indent_40 = vec![' '; 41];
indent_40[40] = 'x';
assert_eq!(
first_set_cell_at_scale(&indent_40, width_cells, COLS_PER_CELL),
None,
"column 40 must be unrepresentable at the default 2-cols-per-cell scale \
on an 11-cell strip (visible range is only 22 columns)"
);
let widened_cols_per_cell = 10;
assert_eq!(
first_set_cell_at_scale(&indent_40, width_cells, widened_cols_per_cell),
Some(4),
"column 40 must land in cell 4 once cols_per_cell is widened to 10 \
(5 source columns per dot, 11 cells -> 110 columns of coverage)"
);
}
#[test]
fn cell_color_survives_at_indent_40_and_80_with_a_widened_scale() {
let mut line = vec![' '; 90];
line[40] = 'a';
line[80] = 'b';
let text: String = line.into_iter().collect();
let mut mm = minimap_from(vec![&text], 8);
let red = Color::rgb(255, 0, 0);
let blue = Color::rgb(0, 0, 255);
mm.syntax_spans.push(MinimapSpan {
line_idx: 0,
start_col: 40,
end_col: 50,
color: red,
});
mm.syntax_spans.push(MinimapSpan {
line_idx: 0,
start_col: 80,
end_col: 90,
color: blue,
});
let area = Rect::new(0, 0, 11, 1);
let mut buf_narrow = Buffer::empty(area);
draw_minimap_with_scale(&mut buf_narrow, area, &mm, &Theme::default(), COLS_PER_CELL);
assert_eq!(
buf_narrow[(4u16, 0u16)].fg,
ratatui_color(Theme::default().foreground),
"at the fixed COLS_PER_CELL scale, column 40's span must be unreachable"
);
assert_eq!(
buf_narrow[(8u16, 0u16)].fg,
ratatui_color(Theme::default().foreground),
"at the fixed COLS_PER_CELL scale, column 80's span must be unreachable"
);
let mut buf_wide = Buffer::empty(area);
draw_minimap_with_scale(&mut buf_wide, area, &mm, &Theme::default(), 10);
assert_eq!(
buf_wide[(4u16, 0u16)].fg,
ratatui_color(red),
"colour must survive at indent 40"
);
assert_eq!(
buf_wide[(8u16, 0u16)].fg,
ratatui_color(blue),
"colour must survive at indent 80"
);
}
#[test]
fn draw_minimap_with_scale_clamps_zero_to_one_instead_of_matching_everything() {
let mm = eight_by_four();
let area = Rect::new(0, 0, 2, 2);
let mut via_zero = Buffer::empty(area);
draw_minimap_with_scale(&mut via_zero, area, &mm, &Theme::default(), 0);
let mut via_one = Buffer::empty(area);
draw_minimap_with_scale(&mut via_one, area, &mm, &Theme::default(), 1);
assert_eq!(
via_zero, via_one,
"cols_per_cell: 0 must behave like 1, not empty-match"
);
}
#[test]
fn odd_cols_per_cell_keeps_dot_and_colour_ranges_aligned() {
let width_cells = 8;
let area = Rect::new(0, 0, width_cells as u16, 1);
let red = Color::rgb(255, 0, 0);
{
let mut chars = vec![' '; 60];
chars[48..60].fill('x');
let text: String = chars.into_iter().collect();
let mut mm = minimap_from(vec![&text], 8);
mm.syntax_spans.push(MinimapSpan {
line_idx: 0,
start_col: 55,
end_col: 56,
color: red,
});
let mut buf = Buffer::empty(area);
draw_minimap_with_scale(&mut buf, area, &mm, &Theme::default(), 11);
assert_ne!(
cell_char(&buf, 4, 0),
'\u{2800}',
"a fully-covered [48, 60) bucket must set a dot in cell 4"
);
assert_eq!(
buf[(4u16, 0u16)].fg,
ratatui_color(red),
"column 55's span is inside cell 4's [48, 60) colour range and must be found"
);
}
{
let mut chars = vec![' '; 60];
chars[47] = 'x';
let text: String = chars.into_iter().collect();
let mut mm = minimap_from(vec![&text], 8);
mm.syntax_spans.push(MinimapSpan {
line_idx: 0,
start_col: 47,
end_col: 48,
color: red,
});
let mut buf = Buffer::empty(area);
draw_minimap_with_scale(&mut buf, area, &mm, &Theme::default(), 11);
assert_eq!(
cell_char(&buf, 4, 0),
'\u{2800}',
"column 47 is outside cell 4's [48, 60) range and must not set a dot"
);
assert_eq!(
buf[(4u16, 0u16)].fg,
ratatui_color(Theme::default().foreground),
"column 47's span is outside cell 4's [48, 60) colour range and must not be found"
);
}
}
fn last_set_cell_per_row(
lines: &[&str],
width_cells: usize,
rows: usize,
cols_per_cell: usize,
) -> Vec<Option<usize>> {
use crate::primitives::minimap::sample_blocks;
let sampled = sample_blocks(lines.len(), rows * LINES_PER_ROW, |i| lines[i].to_string());
let total = lines.len();
let mm = minimap_from_sampled(sampled, total);
let area = Rect::new(0, 0, width_cells as u16, rows as u16);
let mut buf = Buffer::empty(area);
draw_minimap_with_scale(&mut buf, area, &mm, &Theme::default(), cols_per_cell);
(0..rows as u16)
.map(|y| {
(0..width_cells as u16)
.rev()
.find(|&x| cell_char(&buf, x, y) != '\u{2800}')
.map(|x| x as usize)
})
.collect()
}
fn minimap_from_sampled(
sampled: Vec<crate::primitives::minimap::MinimapLine>,
total_buffer_lines: usize,
) -> Minimap {
Minimap {
id: WidgetId::new("mm"),
lines: sampled,
syntax_spans: Vec::new(),
visible_row_start: 0,
visible_row_count: 0,
total_buffer_lines,
}
}
#[test]
fn real_source_file_minimap_right_edge_tracks_line_length_not_saturated() {
let source = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/src/compose/tab_group.rs"
));
let lines: Vec<&str> = source.lines().collect();
assert!(
lines.len() >= 500,
"fixture must be a real, checked-in source file of at least 500 lines, got {}",
lines.len()
);
let width_cells = 12;
let rows = 33;
let cols_per_cell = 10;
let last_cols = last_set_cell_per_row(&lines, width_cells, rows, cols_per_cell);
let painted: Vec<usize> = last_cols.into_iter().flatten().collect();
assert!(
!painted.is_empty(),
"expected at least some painted rows from a real source file"
);
let right_edge_rows = painted.iter().filter(|&&c| c == width_cells - 1).count();
let fraction = right_edge_rows as f64 / painted.len() as f64;
assert!(
fraction <= 0.20,
"{right_edge_rows}/{} painted rows ({:.1}%) run to the strip's right edge, \
expected at most 20% -- the whole point of #1007 is that most rows should \
NOT saturate to the full strip width",
painted.len(),
fraction * 100.0
);
}
#[test]
fn last_set_cell_varies_across_rows_for_mixed_lengths_but_not_for_uniform_ones() {
let width_cells = 12;
let rows = 16;
let cols_per_cell = 10;
let uniform_lines: Vec<String> = (0..rows * LINES_PER_ROW)
.map(|_| " let value = compute_something(a, b, c);".to_string())
.collect();
let uniform_refs: Vec<&str> = uniform_lines.iter().map(String::as_str).collect();
let uniform_last_cols =
last_set_cell_per_row(&uniform_refs, width_cells, rows, cols_per_cell);
let uniform_painted: Vec<usize> = uniform_last_cols.into_iter().flatten().collect();
assert!(
uniform_painted.len() > 1,
"expected multiple painted rows from the uniform fixture"
);
assert!(
uniform_painted.iter().all(|&c| c == uniform_painted[0]),
"uniform-length lines must produce a uniform right edge, got {uniform_painted:?}"
);
let mixed_lines: Vec<String> = (0..rows)
.flat_map(|r| {
let line = if r % 2 == 0 {
"x;".to_string()
} else {
" let very_long_line_of_code_here = 12345;".to_string()
};
std::iter::repeat(line).take(LINES_PER_ROW)
})
.collect();
let mixed_refs: Vec<&str> = mixed_lines.iter().map(String::as_str).collect();
let mixed_last_cols = last_set_cell_per_row(&mixed_refs, width_cells, rows, cols_per_cell);
let mixed_painted: Vec<usize> = mixed_last_cols.into_iter().flatten().collect();
assert!(
mixed_painted.len() > 1,
"expected multiple painted rows from the mixed fixture"
);
assert!(
mixed_painted.iter().any(|&c| c != mixed_painted[0]),
"mixed-length lines must produce a varying right edge, got {mixed_painted:?}"
);
}
#[test]
fn resolve_cols_per_cell_stays_at_the_narrow_default_when_the_file_already_fits() {
let text = "x".repeat(20);
let mm = minimap_from(vec![text.as_str()], 1);
assert_eq!(resolve_cols_per_cell(&mm, 12), COLS_PER_CELL);
}
#[test]
fn resolve_cols_per_cell_widens_to_cover_a_wider_files_longest_line() {
let text = format!("{}x", " ".repeat(89));
let mm = minimap_from(vec![text.as_str()], 1);
assert_eq!(resolve_cols_per_cell(&mm, 11), 10);
}
#[test]
fn resolve_cols_per_cell_caps_at_column_capacity_for_an_extremely_long_line() {
use crate::primitives::minimap::COLUMN_CAPACITY;
let far_past_cap = "x".repeat(COLUMN_CAPACITY * 4);
let exactly_at_cap = "x".repeat(COLUMN_CAPACITY);
let mm_far = minimap_from(vec![far_past_cap.as_str()], 1);
let mm_cap = minimap_from(vec![exactly_at_cap.as_str()], 1);
assert_eq!(
resolve_cols_per_cell(&mm_far, 12),
resolve_cols_per_cell(&mm_cap, 12),
"a line far past COLUMN_CAPACITY must resolve the same scale as a line \
exactly at the cap -- VS Code parity, not unbounded widening"
);
}
#[test]
fn resolve_cols_per_cell_on_an_empty_buffer_does_not_divide_by_zero() {
let mm = minimap_from(vec![], 0);
assert_eq!(resolve_cols_per_cell(&mm, 12), COLS_PER_CELL);
}
#[test]
fn default_scale_distinguishes_a_24_column_line_from_a_300_column_line_in_the_same_file() {
let width_cells = 12;
let short_line = "x".repeat(24); let long_line = "x".repeat(300);
let mm = minimap_from(
vec![
short_line.as_str(),
"",
"",
"",
long_line.as_str(),
"",
"",
"",
],
8,
);
let area = Rect::new(0, 0, width_cells as u16, 2);
let mut buf = Buffer::empty(area);
draw_minimap(&mut buf, area, &mm, &Theme::default());
let last_set = |y: u16| -> Option<u16> {
(0..width_cells as u16)
.rev()
.find(|&x| cell_char(&buf, x, y) != '\u{2800}')
};
let short_last = last_set(0);
let long_last = last_set(1);
assert_ne!(
short_last, long_last,
"a 24-column line and a 300-column line in the same file must not paint \
identical-extent runs under the default (adaptive) scale -- got \
short={short_last:?} long={long_last:?}"
);
assert_eq!(
long_last,
Some(width_cells as u16 - 1),
"the 300-column line, clipped at COLUMN_CAPACITY like VS Code's own minimap, \
should still saturate the strip's right edge"
);
}
#[test]
fn draw_minimap_default_scale_reaches_deep_indents_on_a_wide_buffer() {
let mut line = vec![' '; 90];
line[40] = 'a';
line[80] = 'b';
let text: String = line.into_iter().collect();
let mut mm = minimap_from(vec![&text], 8);
let red = Color::rgb(255, 0, 0);
let blue = Color::rgb(0, 0, 255);
mm.syntax_spans.push(MinimapSpan {
line_idx: 0,
start_col: 40,
end_col: 50,
color: red,
});
mm.syntax_spans.push(MinimapSpan {
line_idx: 0,
start_col: 80,
end_col: 90,
color: blue,
});
let area = Rect::new(0, 0, 11, 1);
let mut buf = Buffer::empty(area);
let layout = draw_minimap(&mut buf, area, &mm, &Theme::default());
assert_eq!(
layout.cols_per_cell, 10,
"layout must report back the scale draw_minimap actually resolved and used"
);
assert_eq!(
buf[(4u16, 0u16)].fg,
ratatui_color(red),
"the default scale must reach indent 40 without any explicit widening"
);
assert_eq!(
buf[(8u16, 0u16)].fg,
ratatui_color(blue),
"the default scale must reach indent 80 without any explicit widening"
);
}
#[test]
fn tui_minimap_layout_resolves_the_same_scale_draw_minimap_paints_with() {
let mut line = vec![' '; 90];
line[40] = 'a';
let text: String = line.into_iter().collect();
let mm = minimap_from(vec![&text], 8);
let area = Rect::new(0, 0, 11, 1);
let no_paint_layout = tui_minimap_layout(&mm, area);
let mut buf = Buffer::empty(area);
let painted_layout = draw_minimap(&mut buf, area, &mm, &Theme::default());
assert_eq!(no_paint_layout.cols_per_cell, painted_layout.cols_per_cell);
assert_eq!(painted_layout.cols_per_cell, 10);
}
#[test]
fn draw_minimap_with_sizing_repeat_to_fill_paints_identical_content_across_every_repeated_row()
{
let mm = minimap_from(vec!["X ", " X ", " X ", " X"], 4);
let area = Rect::new(0, 0, 2, 20);
let mut buf = Buffer::empty(area);
let layout = draw_minimap_with_sizing(
&mut buf,
area,
&mm,
&Theme::default(),
2,
MinimapSizing::RepeatToFill(1.0),
);
assert_eq!(
layout.visible_lines.len(),
1,
"a 4-line file samples to exactly one braille row group"
);
assert_eq!(
layout.visible_lines[0].bounds.height, 20.0,
"the repeat factor must stretch the single row across the whole 20-row strip"
);
let expected_left = cell_char(&buf, 0, 0);
let expected_right = cell_char(&buf, 1, 0);
assert_ne!(
expected_left, '\u{2800}',
"the sampled row must actually paint content, not a blank braille glyph"
);
for y in 0..20u16 {
assert_eq!(
cell_char(&buf, 0, y),
expected_left,
"row {y} must repeat the same left-cell glyph as every other repeated row"
);
assert_eq!(
cell_char(&buf, 1, y),
expected_right,
"row {y} must repeat the same right-cell glyph as every other repeated row"
);
}
}
}