use crate::security::{safe_repeat, MAX_ANSI_SEQ_LEN};
use crate::style::{properties::*, Style};
#[allow(dead_code)]
impl Style {
pub fn maybe_convert_tabs(&self, s: &str) -> String {
if !self.is_set(TAB_WIDTH_KEY) || self.tab_width <= 0 {
return s.to_string();
}
let tab_spaces = safe_repeat(' ', self.tab_width as usize);
s.replace('\t', &tab_spaces)
}
pub fn truncate_visible_line(s: &str, maxw: usize) -> String {
if maxw == 0 {
return String::new();
}
let mut result = String::new();
let mut width = 0;
let mut chars = s.chars().peekable();
while let Some(ch) = chars.next() {
if ch == '\x1b' {
result.push(ch);
let mut scanned = 0usize;
for esc_ch in chars.by_ref() {
result.push(esc_ch);
scanned += 1;
if esc_ch == 'm'
|| (esc_ch != '[' && ('@'..='~').contains(&esc_ch))
|| scanned >= MAX_ANSI_SEQ_LEN
{
break;
}
}
continue;
}
let char_width = unicode_width::UnicodeWidthChar::width(ch).unwrap_or(0);
if width + char_width > maxw {
break;
}
result.push(ch);
width += char_width;
}
result
}
pub fn truncate_height(&self, s: &str) -> String {
let lines: Vec<&str> = s.split('\n').collect();
if lines.len() <= self.max_height as usize {
return s.to_string();
}
lines[0..self.max_height as usize].join("\n")
}
pub fn truncate_width(&self, s: &str) -> String {
let lines: Vec<&str> = s.split('\n').collect();
let truncated: Vec<String> = lines
.iter()
.map(|line| Self::truncate_visible_line(line, self.max_width as usize))
.collect();
truncated.join("\n")
}
pub fn word_wrap_ansi_aware(text: &str, width: usize) -> Vec<String> {
if width == 0 {
return vec![String::new()];
}
let mut lines = Vec::new();
let tokens = Self::tokenize_with_breakpoints(text, &[' ']);
if tokens.is_empty() {
return vec![text.to_string()];
}
let mut current_line = String::new();
let mut current_width = 0;
for token in tokens {
let token_width = crate::width_visible(&token);
if token_width > width {
if !current_line.is_empty() {
lines.push(current_line);
}
let mut hard_wrapped = Self::hard_wrap_ansi_aware(&token, width);
if !hard_wrapped.is_empty() {
current_line = hard_wrapped.pop().unwrap();
lines.extend(hard_wrapped);
current_width = crate::width_visible(¤t_line);
} else {
current_line = String::new();
current_width = 0;
}
continue;
}
if !current_line.is_empty() && current_width + token_width > width {
lines.push(current_line);
current_line = token;
current_width = token_width;
} else {
current_line.push_str(&token);
current_width += token_width;
}
}
if !current_line.is_empty() {
lines.push(current_line);
}
if lines.is_empty() {
vec![String::new()]
} else {
lines
}
}
pub fn hard_wrap_ansi_aware(text: &str, width: usize) -> Vec<String> {
if width == 0 {
return vec![String::new()];
}
let mut lines = Vec::new();
let mut current_line = String::new();
let mut current_width = 0;
let mut chars = text.chars().peekable();
while let Some(ch) = chars.next() {
if ch == '\x1b' {
current_line.push(ch);
let mut scanned = 0usize;
for esc_ch in chars.by_ref() {
current_line.push(esc_ch);
scanned += 1;
if esc_ch == 'm'
|| (esc_ch != '[' && ('@'..='~').contains(&esc_ch))
|| scanned >= MAX_ANSI_SEQ_LEN
{
break;
}
}
continue;
}
let char_width = unicode_width::UnicodeWidthChar::width(ch).unwrap_or(0);
if current_width + char_width > width && current_width > 0 {
lines.push(current_line);
current_line = String::new();
current_width = 0;
}
current_line.push(ch);
current_width += char_width;
}
if !current_line.is_empty() {
lines.push(current_line);
}
if lines.is_empty() {
vec![String::new()]
} else {
lines
}
}
pub fn tokenize_with_breakpoints(s: &str, break_chars: &[char]) -> Vec<String> {
let mut tokens: Vec<String> = Vec::new();
let mut current = String::new();
let mut chars = s.chars().peekable();
while let Some(ch) = chars.next() {
if ch == '\x1b' {
current.push(ch);
let mut scanned = 0usize;
for esc_ch in chars.by_ref() {
current.push(esc_ch);
scanned += 1;
if esc_ch == 'm'
|| (esc_ch != '[' && ('@'..='~').contains(&esc_ch))
|| scanned >= MAX_ANSI_SEQ_LEN
{
break;
}
}
} else if break_chars.contains(&ch) {
if !current.is_empty() {
tokens.push(current);
current = String::new();
}
tokens.push(ch.to_string());
} else {
current.push(ch);
}
}
if !current.is_empty() {
tokens.push(current);
}
tokens
}
}