use ratatui::text::Span;
use std::isize;
use std::path::Path;
use crate::config::clean::theme::tab::TabTheme;
use crate::util::string::UnicodeTruncate;
use crate::HOME_DIR;
use unicode_width::UnicodeWidthStr;
// This file provides stuff to factor a tab-line from a list of paths.
//
// The tab-line logic uses these basic cases:
//
// * Case 0: Not enough space for anything useful. ⇒ Empty tab line
// * Case 1: All [1..n] tabs fit in long form
// * If only one tab exists, then...
// * Case 2: The only tab did _not_ fit in long form (would have been Case 1)...
// * Case 2a: Single tab fits in short form
// * Case 2b: Single tab fits only in short form, without pre- and postfix
// * Case 2c: Single tab fits only without pre- and postfix, and further
// shortened with an ellipsis
// * If more than one tab exist, then...
// * Case 3: The active tab fits in long form together with the others in short form
// * Case 4: Not all tabs can be shown; Scrolling needed...
// * Case 4a: The active tab fits between the scroll tags in long form
// * Case 4b: The active tab fits between the scroll tags only in short form
// * Case 4c: The active tab fits only without scroll tags and pre- and postfix
// * Case 4d: The active tab fits only without scroll tags and pre- and postfix
// and further shortened with ellipsis
pub struct TabLabel {
long: String,
short: String,
}
impl TabLabel {
fn from_path(path: &Path) -> TabLabel {
let mut full_path_str = path.as_os_str().to_str().unwrap_or_default().to_string();
if let Some(home_dir) = HOME_DIR.as_ref() {
let home_dir_str = home_dir.to_string_lossy().into_owned();
if full_path_str.starts_with(&home_dir_str) {
full_path_str = full_path_str.replacen(&home_dir_str, "~", 1);
}
}
// eprintln!("full_path_str: {:?}", full_path_str);
let last = Path::new(&full_path_str)
.file_name()
.unwrap_or_default()
.to_str()
.unwrap_or_default()
.to_string();
TabLabel {
long: full_path_str,
short: last,
}
}
}
#[derive(PartialEq, Debug)]
enum TabBarElement {
// Note: The Tab-Elements also store the index, eventhough it's not used
// for the tab-bar rendering at all. The reason is that this would allow
// an easier implementation for other features later on, like changing
// tabs by mouse clicks.
// It's a valuable information that comes for free here.
DividerII,
DividerIA,
DividerAI,
PrefixI,
PostfixI,
PrefixA,
PostfixA,
TabI(usize, String), // index, label-string
TabA(usize, String), // index, label-string
ScrollFrontPrefix,
ScrollFrontPostfix,
ScrollFront(usize), // number of hidden tabs
ScrollBackPrefix,
ScrollBackPostfix,
ScrollBack(usize), // number of hidden tab
PaddingPrefix,
PaddingPostfix,
PaddingFill(usize), // width of padding
}
fn check_fit_and_build_sequence(
labels: &[String],
available_width: usize,
extra_width: usize,
current_index: usize,
) -> Option<Vec<TabBarElement>> {
if labels.iter().map(|l| l.width()).sum::<usize>() + extra_width <= available_width {
let r = labels
.iter()
.enumerate()
.flat_map(|(ix, l)| {
let mut section = Vec::with_capacity(4);
if ix > 0 {
if ix == current_index {
section.push(TabBarElement::DividerIA);
} else if ix - 1 == current_index {
section.push(TabBarElement::DividerAI);
} else {
section.push(TabBarElement::DividerII);
}
};
if ix == current_index {
section.push(TabBarElement::PrefixA);
section.push(TabBarElement::TabA(ix, l.to_string()));
section.push(TabBarElement::PostfixA);
} else {
section.push(TabBarElement::PrefixI);
section.push(TabBarElement::TabI(ix, l.to_string()));
section.push(TabBarElement::PostfixI);
}
section
})
.collect();
Some(r)
} else {
None
}
}
fn factor_tab_bar_sequence(
available_width: usize,
tab_records: &Vec<&TabLabel>,
current_index: usize,
config: &TabTheme,
) -> Vec<TabBarElement> {
//##################################################################
//## Case 0: less available width than 2 units -> just show nothing
//##################################################################
if available_width < 2 {
return vec![];
}
//## Not case 0. Let's continue...
let tab_num = tab_records.len();
let labels_are_indexed =<