use std::ops::Range;
use gpui::{Pixels, Point, WrappedLine, point, px};
const PAST_END: Pixels = px(1.0e6);
pub struct Layout {
lines: Vec<WrappedLine>,
starts: Vec<usize>,
rows: Vec<usize>,
total_rows: usize,
line_height: Pixels,
}
impl std::fmt::Debug for Layout {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("Layout")
.field("lines", &self.lines.len())
.field("rows", &self.total_rows)
.finish()
}
}
impl Layout {
pub fn new(lines: Vec<WrappedLine>, line_height: Pixels) -> Self {
let mut starts = Vec::with_capacity(lines.len());
let mut rows = Vec::with_capacity(lines.len());
let mut offset = 0;
let mut row = 0;
for line in &lines {
starts.push(offset);
rows.push(row);
offset += line.len() + '\n'.len_utf8();
row += line.wrap_boundaries().len() + 1;
}
Self {
lines,
starts,
rows,
total_rows: row.max(1),
line_height,
}
}
pub fn line_height(&self) -> Pixels {
self.line_height
}
pub fn total_rows(&self) -> usize {
self.total_rows
}
pub fn height(&self) -> Pixels {
self.line_height * self.total_rows as f32
}
pub fn painted_lines(&self) -> impl Iterator<Item = (&WrappedLine, Pixels)> {
self.lines
.iter()
.zip(&self.rows)
.map(|(line, row)| (line, self.line_height * *row as f32))
}
pub fn position_for_offset(&self, offset: usize) -> Point<Pixels> {
if self.lines.is_empty() {
return point(px(0.0), px(0.0));
}
let index = self.line_for_offset(offset);
let local = offset.saturating_sub(self.starts[index]);
let line = &self.lines[index];
let position = line
.position_for_index(local.min(line.len()), self.line_height)
.unwrap_or_default();
point(
position.x,
position.y + self.line_height * self.rows[index] as f32,
)
}
pub fn row_for_offset(&self, offset: usize) -> usize {
let y = self.position_for_offset(offset).y;
((y / self.line_height) as usize).min(self.total_rows - 1)
}
pub fn offset_for_position(&self, position: Point<Pixels>) -> usize {
let row = if position.y < px(0.0) {
0
} else {
((position.y / self.line_height) as usize).min(self.total_rows - 1)
};
self.offset_at_row(row, position.x)
}
pub fn offset_at_row(&self, row: usize, x: Pixels) -> usize {
if self.lines.is_empty() {
return 0;
}
let row = row.min(self.total_rows - 1);
let index = self.line_for_row(row);
let line = &self.lines[index];
let local = point(
x,
self.line_height * (row - self.rows[index]) as f32 + self.line_height / 2.0,
);
let offset = line
.closest_index_for_position(local, self.line_height)
.unwrap_or_else(|offset| offset);
self.starts[index] + offset
}
pub fn row_range(&self, row: usize) -> Range<usize> {
self.offset_at_row(row, px(-1.0))..self.offset_at_row(row, PAST_END)
}
pub(crate) fn accessible_rows(&self, text: &str) -> Vec<Range<usize>> {
(0..self.total_rows)
.map(|row| {
let mut range = self.row_range(row);
if range.start > range.end {
std::mem::swap(&mut range.start, &mut range.end);
}
range.start = range.start.min(text.len());
range.end = range.end.min(text.len());
if row + 1 == self.total_rows
|| self.line_for_row(row + 1) != self.line_for_row(row)
{
if text[range.end..].starts_with("\r\n") {
range.end += 2;
} else if text[range.end..].starts_with('\n') {
range.end += 1;
}
}
range
})
.collect()
}
fn line_for_offset(&self, offset: usize) -> usize {
self.starts
.iter()
.rposition(|start| *start <= offset)
.unwrap_or(0)
}
fn line_for_row(&self, row: usize) -> usize {
self.rows
.iter()
.rposition(|first| *first <= row)
.unwrap_or(0)
}
}