use crate::segment::Segment;
pub fn chop_segments(
segments: &[Segment],
layer_x: u16,
cut_start: u16,
cut_width: u16,
) -> Vec<Segment> {
if cut_width == 0 {
return vec![];
}
let cut_end = cut_start + cut_width;
let mut result = Vec::new();
let mut current_x = layer_x;
for seg in segments {
if seg.is_empty() || seg.is_control {
continue;
}
let seg_width = seg.width() as u16;
let seg_end = current_x + seg_width;
if seg_end <= cut_start {
current_x = seg_end;
continue;
}
if current_x >= cut_end {
break;
}
let mut segment_to_add = seg.clone();
if current_x < cut_start {
let trim_left = (cut_start - current_x) as usize;
let (_left, right) = segment_to_add.split_at(trim_left);
segment_to_add = right;
current_x = cut_start;
}
let remaining_width = (cut_end - current_x) as usize;
if segment_to_add.width() > remaining_width {
let (left, _right) = segment_to_add.split_at(remaining_width);
segment_to_add = left;
}
if !segment_to_add.is_empty() {
current_x += segment_to_add.width() as u16;
result.push(segment_to_add);
}
if current_x >= cut_end {
break;
}
}
let total_width: usize = result.iter().map(|s| s.width()).sum();
if (total_width as u16) < cut_width {
let padding = " ".repeat((cut_width as usize) - total_width);
result.push(Segment::new(padding));
}
result
}
#[cfg(test)]
mod tests {
use super::*;
use crate::style::Style;
#[test]
fn full_segment_within_cut_range() {
let segments = vec![Segment::new("hello")];
let result = chop_segments(&segments, 0, 0, 5);
assert!(result.len() == 1);
assert!(result[0].text == "hello");
}
#[test]
fn segment_split_at_left_boundary() {
let segments = vec![Segment::new("hello world")];
let result = chop_segments(&segments, 0, 6, 5);
assert!(result.len() == 1);
assert!(result[0].text == "world");
}
#[test]
fn segment_split_at_right_boundary() {
let segments = vec![Segment::new("hello world")];
let result = chop_segments(&segments, 0, 0, 5);
assert!(result.len() == 1);
assert!(result[0].text == "hello");
}
#[test]
fn segment_split_at_both_boundaries() {
let segments = vec![Segment::new("hello world testing")];
let result = chop_segments(&segments, 0, 6, 5);
assert!(result.len() == 1);
assert!(result[0].text == "world");
}
#[test]
fn empty_segments_skipped() {
let segments = vec![Segment::new(""), Segment::new("hello"), Segment::new("")];
let result = chop_segments(&segments, 0, 0, 5);
assert!(result.len() == 1);
assert!(result[0].text == "hello");
}
#[test]
fn cut_range_beyond_segment_end() {
let segments = vec![Segment::new("hi")];
let result = chop_segments(&segments, 0, 0, 10);
assert!(result.len() == 2);
assert!(result[0].text == "hi");
assert!(result[1].text == " ");
}
#[test]
fn multiple_segments() {
let segments = vec![Segment::new("hello "), Segment::new("world")];
let result = chop_segments(&segments, 0, 0, 11);
assert!(result.len() == 2);
assert!(result[0].text == "hello ");
assert!(result[1].text == "world");
}
#[test]
fn layer_offset_before_cut() {
let segments = vec![Segment::new("hello")];
let result = chop_segments(&segments, 10, 15, 5);
assert!(result.len() == 1);
assert!(result[0].text == " ");
}
#[test]
fn layer_offset_overlapping_cut() {
let segments = vec![Segment::new("hello world")];
let result = chop_segments(&segments, 5, 10, 5);
assert!(result.len() == 1);
assert!(result[0].text == " worl");
}
#[test]
fn zero_width_cut() {
let segments = vec![Segment::new("hello")];
let result = chop_segments(&segments, 0, 0, 0);
assert!(result.is_empty());
}
#[test]
fn control_segments_ignored() {
let segments = vec![
Segment::new("hello"),
Segment::control("ESC[1m"),
Segment::new(" world"),
];
let result = chop_segments(&segments, 0, 0, 11);
assert!(result.len() == 2);
assert!(result[0].text == "hello");
assert!(result[1].text == " world");
}
#[test]
fn styled_segment_preserved() {
let style = Style::new().bold(true);
let segments = vec![Segment::styled("hello", style.clone())];
let result = chop_segments(&segments, 0, 0, 5);
assert!(result.len() == 1);
assert!(result[0].text == "hello");
assert!(result[0].style.bold);
}
#[test]
fn partial_overlap_at_start() {
let segments = vec![Segment::new("testing")];
let result = chop_segments(&segments, 0, 5, 5);
assert!(result.len() == 2);
assert!(result[0].text == "ng");
assert!(result[1].text == " ");
}
#[test]
fn partial_overlap_at_end() {
let segments = vec![Segment::new("testing")];
let result = chop_segments(&segments, 5, 5, 2);
assert!(result.len() == 1);
assert!(result[0].text == "te");
}
#[test]
fn chop_wide_char_at_cut_boundary() {
let segments = vec![Segment::new("A\u{4e16}B")];
let result = chop_segments(&segments, 0, 0, 2);
let total_width: usize = result.iter().map(|s| s.width()).sum();
assert_eq!(total_width, 2);
}
#[test]
fn chop_segment_with_combining_marks() {
let segments = vec![Segment::new("ae\u{0301}b")];
let result = chop_segments(&segments, 0, 0, 2);
let combined: String = result.iter().map(|s| s.text.as_str()).collect();
assert!(combined.contains("a"));
let total_width: usize = result.iter().map(|s| s.width()).sum();
assert_eq!(total_width, 2);
}
#[test]
fn chop_empty_segment() {
let segments = vec![Segment::new("")];
let result = chop_segments(&segments, 0, 0, 5);
assert!(!result.is_empty());
let total_width: usize = result.iter().map(|s| s.width()).sum();
assert_eq!(total_width, 5);
}
#[test]
fn chop_cut_exactly_aligns_with_wide_char_boundary() {
let segments = vec![Segment::new("\u{4e16}\u{754c}")];
let result = chop_segments(&segments, 0, 0, 2);
let combined: String = result.iter().map(|s| s.text.as_str()).collect();
assert!(combined.contains("\u{4e16}"));
let total_width: usize = result.iter().map(|s| s.width()).sum();
assert_eq!(total_width, 2);
let result2 = chop_segments(&segments, 0, 2, 2);
let combined2: String = result2.iter().map(|s| s.text.as_str()).collect();
assert!(combined2.contains("\u{754c}"));
let total_width2: usize = result2.iter().map(|s| s.width()).sum();
assert_eq!(total_width2, 2);
}
}