use ratatui::layout::Rect;
use ratatui::style::Stylize;
use ratatui::text::{Line, Span};
use ratatui::widgets::Paragraph;
use ratatui::Frame;
use unicode_width::UnicodeWidthStr;
use crate::app::App;
pub fn render(frame: &mut Frame, app: &App, area: Rect) {
let active = app.active_tab_index();
let n = app.tab_count();
let chips: Vec<String> = (0..n)
.map(|i| format!(" {}:{} ", i + 1, app.tab_label(i)))
.collect();
let widths: Vec<usize> = chips.iter().map(|c| c.width()).collect();
let (s, e) = visible_range(&widths, active, area.width as usize);
let mut spans: Vec<Span<'static>> = Vec::new();
if s > 0 {
spans.push(Span::from(format!("‹{} ", s)).dim());
}
for (k, chip) in chips.into_iter().enumerate().take(e).skip(s) {
if k > s {
spans.push(Span::from(" "));
}
if k == active {
spans.push(Span::from(chip).reversed().bold());
} else {
spans.push(Span::from(chip).dim());
}
}
if e < n {
spans.push(Span::from(format!(" {}›", n - e)).dim());
}
frame.render_widget(Paragraph::new(Line::from(spans)), area);
}
fn marker_width(hidden: usize) -> usize {
if hidden == 0 {
0
} else {
hidden.to_string().len() + 2
}
}
fn visible_range(widths: &[usize], active: usize, avail: usize) -> (usize, usize) {
let n = widths.len();
if n == 0 {
return (0, 0);
}
let total: usize = widths.iter().sum::<usize>() + n.saturating_sub(1);
if total <= avail {
return (0, n);
}
let active = active.min(n - 1);
let fits = |s: usize, e: usize| -> bool {
let w: usize = widths[s..e].iter().sum::<usize>()
+ (e - s - 1)
+ marker_width(s)
+ marker_width(n - e);
w <= avail
};
let (mut s, mut e) = (active, active + 1);
loop {
let prefer_right = (e - active) <= (active - s);
let first = if prefer_right && e < n {
Some((s, e + 1))
} else if s > 0 {
Some((s - 1, e))
} else if e < n {
Some((s, e + 1))
} else {
None
};
let second = if first == Some((s, e + 1)) {
(s > 0).then(|| (s - 1, e))
} else {
(e < n).then(|| (s, e + 1))
};
match (first, second) {
(Some((fs, fe)), _) if fits(fs, fe) => {
s = fs;
e = fe;
}
(_, Some((ss, se))) if fits(ss, se) => {
s = ss;
e = se;
}
_ => break,
}
}
(s, e)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn visible_range_keeps_active_visible_and_centers() {
let w = vec![8, 8, 8];
assert_eq!(visible_range(&w, 1, 80), (0, 3));
let w = vec![10; 9]; let (s, e) = visible_range(&w, 4, 40);
assert!((s..e).contains(&4), "アクティブは必ず可視: {s}..{e}");
assert!(s > 0 && e < 9, "両側に隠れタブ: {s}..{e}");
let used: usize =
w[s..e].iter().sum::<usize>() + (e - s - 1) + marker_width(s) + marker_width(9 - e);
assert!(used <= 40, "マーカー込みで幅内: {used}");
let (s, e) = visible_range(&w, 0, 40);
assert_eq!(s, 0);
assert!(e < 9);
let (s, e) = visible_range(&w, 8, 40);
assert_eq!(e, 9);
assert!(s > 0);
let (s, e) = visible_range(&w, 4, 8);
assert_eq!((s, e), (4, 5));
}
}