use ropey::{Rope, RopeSlice};
use unicode_width::UnicodeWidthChar;
use crate::buffer;
#[derive(Debug, Clone)]
pub struct WrapSegment {
pub doc_row: usize,
pub segment_index: usize,
pub char_start: usize,
pub char_end: usize,
}
fn char_width(ch: char) -> u16 {
if ch == '\t' {
1
} else {
UnicodeWidthChar::width(ch).unwrap_or(1) as u16
}
}
pub fn wrap_count(line: RopeSlice, text_width: u16) -> usize {
if text_width == 0 {
return 1;
}
let line_len = buffer::line_display_len(line);
if line_len == 0 {
return 1;
}
let mut segments = 1usize;
let mut col: u16 = 0;
for i in 0..line_len {
let ch = line.char(i);
let w = char_width(ch);
if w > 1 && col + w > text_width && col > 0 {
segments += 1;
col = w;
continue;
}
if col + w > text_width {
segments += 1;
col = w;
} else {
col += w;
}
}
segments
}
pub fn char_to_wrap_pos(line: RopeSlice, char_idx: usize, text_width: u16) -> (usize, u16) {
if text_width == 0 {
return (0, 0);
}
let line_len = buffer::line_display_len(line);
let target = char_idx.min(line_len);
let mut segment = 0usize;
let mut col: u16 = 0;
for i in 0..target {
let ch = line.char(i);
let w = char_width(ch);
if w > 1 && col + w > text_width && col > 0 {
segment += 1;
col = w;
continue;
}
if col + w > text_width {
segment += 1;
col = w;
} else {
col += w;
}
}
(segment, col)
}
pub fn wrap_pos_to_char(line: RopeSlice, segment: usize, target_col: u16, text_width: u16) -> usize {
if text_width == 0 {
return 0;
}
let line_len = buffer::line_display_len(line);
if line_len == 0 {
return 0;
}
let mut cur_segment = 0usize;
let mut col: u16 = 0;
let mut seg_start_char = 0usize;
for i in 0..line_len {
if cur_segment == segment {
let ch = line.char(i);
let w = char_width(ch);
let would_wrap = if w > 1 && col + w > text_width && col > 0 {
true
} else {
col + w > text_width
};
if would_wrap {
return i.saturating_sub(1).max(seg_start_char);
}
if col >= target_col {
return i;
}
col += w;
} else {
let ch = line.char(i);
let w = char_width(ch);
if w > 1 && col + w > text_width && col > 0 {
cur_segment += 1;
col = w;
if cur_segment == segment {
seg_start_char = i;
col = w;
if target_col == 0 {
return i;
}
}
continue;
}
if col + w > text_width {
cur_segment += 1;
col = w;
if cur_segment == segment {
seg_start_char = i;
col = w;
if target_col == 0 {
return i;
}
}
} else {
col += w;
}
}
}
if cur_segment < segment {
return line_len.saturating_sub(1);
}
line_len.saturating_sub(1)
}
pub fn build_screen_map(
rope: &Rope,
start_doc_row: usize,
start_wrap_segment: usize,
text_width: u16,
screen_height: u16,
) -> Vec<WrapSegment> {
let mut result = Vec::with_capacity(screen_height as usize);
let line_count = rope.len_lines();
let mut doc_row = start_doc_row;
if doc_row >= line_count {
return result;
}
let first_line = rope.line(doc_row);
let segments = build_line_segments(first_line, doc_row, text_width);
for seg in segments.into_iter().skip(start_wrap_segment) {
result.push(seg);
if result.len() >= screen_height as usize {
return result;
}
}
doc_row += 1;
while doc_row < line_count && result.len() < screen_height as usize {
let line = rope.line(doc_row);
let segments = build_line_segments(line, doc_row, text_width);
for seg in segments {
result.push(seg);
if result.len() >= screen_height as usize {
return result;
}
}
doc_row += 1;
}
result
}
fn build_line_segments(line: RopeSlice, doc_row: usize, text_width: u16) -> Vec<WrapSegment> {
let line_len = buffer::line_display_len(line);
if line_len == 0 || text_width == 0 {
return vec![WrapSegment {
doc_row,
segment_index: 0,
char_start: 0,
char_end: 0,
}];
}
let mut segments = Vec::new();
let mut seg_start = 0usize;
let mut seg_idx = 0usize;
let mut col: u16 = 0;
for i in 0..line_len {
let ch = line.char(i);
let w = char_width(ch);
let need_wrap = if w > 1 && col + w > text_width && col > 0 {
true
} else {
col + w > text_width
};
if need_wrap {
segments.push(WrapSegment {
doc_row,
segment_index: seg_idx,
char_start: seg_start,
char_end: i,
});
seg_idx += 1;
seg_start = i;
col = w;
} else {
col += w;
}
}
segments.push(WrapSegment {
doc_row,
segment_index: seg_idx,
char_start: seg_start,
char_end: line_len,
});
segments
}