use super::{ is_too_high, parse_indices, parse_selected, parse_text, stringify_text, Coordinates, Text, }; use crate::panes::terminal_character::{AnsiCode, RESET_STYLES}; use zellij_utils::data::Style; use unicode_width::UnicodeWidthChar; #[derive(Debug, Clone)] pub struct NestedListItem { pub text: Text, pub indentation_level: usize, } pub fn nested_list( mut contents: Vec, style: &Style, coordinates: Option, ) -> Vec { let mut stringified = String::new(); let max_width = coordinates .as_ref() .and_then(|c| c.width) .unwrap_or_else(|| max_nested_item_width(&contents)); for (line_index, line_item) in contents.drain(..).enumerate() { if is_too_high(line_index + 1, &coordinates) { break; } let mut reset_styles_for_item = RESET_STYLES; reset_styles_for_item.background = None; reset_styles_for_item.foreground = None; let padding = line_item.indentation_level * 2 + 1; let bulletin = if line_item.indentation_level % 2 == 0 { "> " } else { "- " }; let text_style = if line_item.text.selected { reset_styles_for_item .bold(Some(AnsiCode::On)) .foreground(Some(style.colors.white.into())) .background(Some(style.colors.bg.into())) } else { reset_styles_for_item .bold(Some(AnsiCode::On)) .foreground(Some(style.colors.white.into())) .background(Some(style.colors.black.into())) }; let (mut text, text_width) = stringify_text( &line_item.text, Some(padding + bulletin.len()), &coordinates, style, text_style, line_item.text.selected, ); text = pad_line(text, max_width, padding, text_width); let go_to_row_instruction = coordinates .as_ref() .map(|c| c.stringify_with_y_offset(line_index)) .unwrap_or_else(|| { if line_index != 0 { format!("\n\r") } else { "".to_owned() } }); let line_style = if line_item.text.selected { RESET_STYLES .foreground(Some(style.colors.white.into())) .background(Some(style.colors.bg.into())) } else { RESET_STYLES .foreground(Some(style.colors.white.into())) .background(Some(style.colors.black.into())) }; stringified.push_str(&format!( "{}{}{}{:padding$}{bulletin}{}{text}{}", go_to_row_instruction, line_style, reset_styles_for_item, " ", text_style, RESET_STYLES )); } stringified.as_bytes().to_vec() } pub fn parse_nested_list_items<'a>( params_iter: impl Iterator, ) -> Vec { params_iter .flat_map(|mut stringified| { let indentation_level = parse_indentation_level(&mut stringified); let selected = parse_selected(&mut stringified); let indices = parse_indices(&mut stringified); let text = parse_text(&mut stringified).map_err(|e| e.to_string())?; let text = Text { text, selected, indices, }; Ok::(NestedListItem { text, indentation_level, }) }) .collect::>() } fn parse_indentation_level(stringified: &mut String) -> usize { let mut indentation_level = 0; loop { if stringified.is_empty() { break; } if stringified.chars().next() == Some('|') { stringified.remove(0); indentation_level += 1; } else { break; } } indentation_level } fn max_nested_item_width(contents: &Vec) -> usize { let mut width_of_longest_line = 0; for line_item in contents.iter() { let mut line_item_text_width = 0; for character in line_item.text.text.chars() { let character_width = character.width().unwrap_or(0); line_item_text_width += character_width; } let bulletin_width = 2; let padding = line_item.indentation_level * 2 + 1; let total_width = line_item_text_width + bulletin_width + padding; if width_of_longest_line < total_width { width_of_longest_line = total_width; } } width_of_longest_line } fn pad_line(text: String, max_width: usize, padding: usize, text_width: usize) -> String { if max_width > text_width + padding + 2 { // 2 is the bulletin let end_padding = max_width.saturating_sub(text_width + padding + 2); return format!("{}{:end_padding$}", text, " "); } text }