pub(crate) fn cursor_visual(text: &str, width: usize, cursor: usize) -> (usize, usize) {
if width == 0 {
return (0, 0);
}
let mut line_start = 0usize; let mut row = 0usize;
for logical in text.split('\n') {
let line_len = logical.chars().count();
let line_end = line_start + line_len; if cursor <= line_end {
let col = cursor - line_start;
let (r, c) = cursor_visual_in_line(logical, width, col);
return (row + r, c);
}
row += super::wrap::wrap_line(logical, width).len();
line_start = line_end + 1; }
(row, 0)
}
fn cursor_visual_in_line(text: &str, width: usize, col: usize) -> (usize, usize) {
if width == 0 {
return (0, 0);
}
let chars: Vec<char> = text.chars().collect();
let n = chars.len();
let mut row = 0usize;
let mut line_len = 0usize; let mut i = 0usize;
let target = col;
while i < n {
if chars[i].is_whitespace() {
let start = i;
while i < n && chars[i].is_whitespace() {
i += 1;
}
let ws = i - start;
if line_len + ws <= width {
if target < i {
return (row, line_len + (target - start));
}
line_len += ws;
} else {
let fits = width.saturating_sub(line_len);
let kept = fits.min(ws);
if target < start + kept {
return (row, line_len + (target - start));
}
if target < i {
return (row, line_len + kept);
}
row += 1;
line_len = 0;
}
continue;
}
let start = i;
while i < n && !chars[i].is_whitespace() {
i += 1;
}
let word_len = i - start;
if word_len > width {
let mut k = 0;
while k < word_len {
let take = width.min(word_len - k);
if line_len + take <= width {
if target < start + k + take {
return (row, line_len + (target - (start + k)));
}
line_len += take;
} else {
row += 1;
line_len = 0;
if target < start + k + take {
return (row, target - (start + k));
}
line_len += take;
}
k += take;
}
continue;
}
if line_len + word_len <= width {
if target < i {
return (row, line_len + (target - start));
}
line_len += word_len;
} else {
row += 1;
line_len = 0;
if target < i {
return (row, target - start);
}
line_len += word_len;
}
}
(row, line_len)
}