use super::*;
impl EditorCore {
pub fn get_headless_grid_styled(&self, start_visual_row: usize, count: usize) -> HeadlessGrid {
self.with_visual_row_index(|index| {
let mut grid = HeadlessGrid::new(start_visual_row, count);
if count == 0 {
return grid;
}
let total_visual = index.total_visual_lines();
if start_visual_row >= total_visual {
return grid;
}
let tab_width = self.layout_engine.tab_width();
let end_visual = start_visual_row.saturating_add(count).min(total_visual);
let regions = self.folding_manager.regions();
let Some((mut span, mut visual_in_line)) = index.span_for_visual_row(start_visual_row)
else {
return grid;
};
let mut current_visual = start_visual_row;
while current_visual < end_visual {
let logical_line = span.logical_line;
let Some(layout) = self.layout_engine.get_line_layout(logical_line) else {
let remaining_in_span = span.visual_line_count.saturating_sub(visual_in_line);
current_visual = current_visual.saturating_add(remaining_in_span);
let Some(next_span) = index.next_span_after_logical_line(logical_line) else {
break;
};
span = next_span;
visual_in_line = 0;
continue;
};
let line_text = self
.line_index
.get_line_text(logical_line)
.unwrap_or_default();
let line_char_len = line_text.chars().count();
let line_start_offset = self.line_index.position_to_char_offset(logical_line, 0);
let segment_start_col = if visual_in_line == 0 {
0
} else {
layout
.wrap_points
.get(visual_in_line - 1)
.map(|wp| wp.char_index)
.unwrap_or(0)
.min(line_char_len)
};
let segment_end_col = if visual_in_line < layout.wrap_points.len() {
layout.wrap_points[visual_in_line]
.char_index
.min(line_char_len)
} else {
line_char_len
};
let mut headless_line = HeadlessLine::new(logical_line, visual_in_line > 0);
let mut segment_x_start_cells = 0usize;
if visual_in_line > 0 {
let indent_cells = wrap_indent_cells_for_line_text(
&line_text,
self.layout_engine.wrap_indent(),
self.viewport_width,
tab_width,
);
segment_x_start_cells = indent_cells;
for _ in 0..indent_cells {
headless_line.add_cell(Cell::new(' ', 1));
}
}
let mut x_in_line = visual_x_for_column(&line_text, segment_start_col, tab_width);
for (col, ch) in line_text
.chars()
.enumerate()
.skip(segment_start_col)
.take(segment_end_col.saturating_sub(segment_start_col))
{
let offset = line_start_offset + col;
let styles = self.styles_at_offset(offset);
let w = cell_width_at(ch, x_in_line, tab_width);
x_in_line = x_in_line.saturating_add(w);
headless_line.add_cell(Cell::with_styles(ch, w, styles));
}
headless_line.set_visual_metadata(
visual_in_line,
line_start_offset.saturating_add(segment_start_col),
line_start_offset.saturating_add(segment_end_col),
segment_x_start_cells,
);
headless_line.set_fold_placeholder_appended(false);
if visual_in_line + 1 == layout.visual_line_count
&& let Some(region) = Self::collapsed_region_starting_at(regions, logical_line)
&& !region.placeholder.is_empty()
{
if !headless_line.cells.is_empty() {
x_in_line = x_in_line.saturating_add(char_width(' '));
headless_line.add_cell(Cell::with_styles(
' ',
char_width(' '),
vec![FOLD_PLACEHOLDER_STYLE_ID],
));
}
for ch in region.placeholder.chars() {
let w = cell_width_at(ch, x_in_line, tab_width);
x_in_line = x_in_line.saturating_add(w);
headless_line.add_cell(Cell::with_styles(
ch,
w,
vec![FOLD_PLACEHOLDER_STYLE_ID],
));
}
if let Some(right_bracket) = self.fold_right_boundary_bracket_char(region) {
x_in_line = x_in_line.saturating_add(char_width(' '));
headless_line.add_cell(Cell::with_styles(
' ',
char_width(' '),
vec![FOLD_PLACEHOLDER_STYLE_ID],
));
let w = cell_width_at(right_bracket, x_in_line, tab_width);
x_in_line = x_in_line.saturating_add(w);
headless_line.add_cell(Cell::with_styles(
right_bracket,
w,
vec![FOLD_PLACEHOLDER_STYLE_ID],
));
}
headless_line.set_fold_placeholder_appended(true);
}
grid.add_line(headless_line);
current_visual = current_visual.saturating_add(1);
visual_in_line = visual_in_line.saturating_add(1);
if visual_in_line >= span.visual_line_count {
let Some(next_span) = index.next_span_after_logical_line(logical_line) else {
break;
};
span = next_span;
visual_in_line = 0;
}
}
grid
})
}
pub fn get_minimap_grid(&self, start_visual_row: usize, count: usize) -> MinimapGrid {
self.with_visual_row_index(|index| {
let mut grid = MinimapGrid::new(start_visual_row, count);
if count == 0 {
return grid;
}
let total_visual = index.total_visual_lines();
if start_visual_row >= total_visual {
return grid;
}
let tab_width = self.layout_engine.tab_width();
let end_visual = start_visual_row.saturating_add(count).min(total_visual);
let regions = self.folding_manager.regions();
let Some((mut span, mut visual_in_line)) = index.span_for_visual_row(start_visual_row)
else {
return grid;
};
let mut current_visual = start_visual_row;
while current_visual < end_visual {
let logical_line = span.logical_line;
let Some(layout) = self.layout_engine.get_line_layout(logical_line) else {
let remaining_in_span = span.visual_line_count.saturating_sub(visual_in_line);
current_visual = current_visual.saturating_add(remaining_in_span);
let Some(next_span) = index.next_span_after_logical_line(logical_line) else {
break;
};
span = next_span;
visual_in_line = 0;
continue;
};
let line_text = self
.line_index
.get_line_text(logical_line)
.unwrap_or_default();
let line_char_len = line_text.chars().count();
let line_start_offset = self.line_index.position_to_char_offset(logical_line, 0);
let segment_start_col = if visual_in_line == 0 {
0
} else {
layout
.wrap_points
.get(visual_in_line - 1)
.map(|wp| wp.char_index)
.unwrap_or(0)
.min(line_char_len)
};
let segment_end_col = if visual_in_line < layout.wrap_points.len() {
layout.wrap_points[visual_in_line]
.char_index
.min(line_char_len)
} else {
line_char_len
};
let mut total_cells = 0usize;
let mut non_whitespace_cells = 0usize;
let mut dominant_style_counts: HashMap<StyleId, usize> = HashMap::new();
if visual_in_line > 0 {
let indent_cells = wrap_indent_cells_for_line_text(
&line_text,
self.layout_engine.wrap_indent(),
self.viewport_width,
tab_width,
);
total_cells = total_cells.saturating_add(indent_cells);
}
let mut x_in_line = visual_x_for_column(&line_text, segment_start_col, tab_width);
for (col, ch) in line_text
.chars()
.enumerate()
.skip(segment_start_col)
.take(segment_end_col.saturating_sub(segment_start_col))
{
let offset = line_start_offset + col;
let styles = self.styles_at_offset(offset);
let w = cell_width_at(ch, x_in_line, tab_width);
x_in_line = x_in_line.saturating_add(w);
total_cells = total_cells.saturating_add(w);
if !ch.is_whitespace() {
non_whitespace_cells = non_whitespace_cells.saturating_add(w);
}
if let Some(style) = styles.first().copied() {
let entry = dominant_style_counts.entry(style).or_insert(0);
*entry = entry.saturating_add(w);
}
}
let mut placeholder_appended = false;
if visual_in_line + 1 == layout.visual_line_count
&& let Some(region) = Self::collapsed_region_starting_at(regions, logical_line)
&& !region.placeholder.is_empty()
{
placeholder_appended = true;
if total_cells > 0 {
x_in_line = x_in_line.saturating_add(char_width(' '));
total_cells = total_cells.saturating_add(char_width(' '));
}
for ch in region.placeholder.chars() {
let w = cell_width_at(ch, x_in_line, tab_width);
x_in_line = x_in_line.saturating_add(w);
total_cells = total_cells.saturating_add(w);
if !ch.is_whitespace() {
non_whitespace_cells = non_whitespace_cells.saturating_add(w);
}
let entry = dominant_style_counts
.entry(FOLD_PLACEHOLDER_STYLE_ID)
.or_insert(0);
*entry = entry.saturating_add(w);
}
if let Some(right_bracket) = self.fold_right_boundary_bracket_char(region) {
x_in_line = x_in_line.saturating_add(char_width(' '));
total_cells = total_cells.saturating_add(char_width(' '));
let entry = dominant_style_counts
.entry(FOLD_PLACEHOLDER_STYLE_ID)
.or_insert(0);
*entry = entry.saturating_add(char_width(' '));
let w = cell_width_at(right_bracket, x_in_line, tab_width);
x_in_line = x_in_line.saturating_add(w);
total_cells = total_cells.saturating_add(w);
non_whitespace_cells = non_whitespace_cells.saturating_add(w);
let entry = dominant_style_counts
.entry(FOLD_PLACEHOLDER_STYLE_ID)
.or_insert(0);
*entry = entry.saturating_add(w);
}
}
let dominant_style = dominant_style_counts
.into_iter()
.max_by(|a, b| a.1.cmp(&b.1).then_with(|| b.0.cmp(&a.0)))
.map(|(style, _)| style);
grid.lines.push(MinimapLine {
logical_line_index: logical_line,
visual_in_logical: visual_in_line,
char_offset_start: line_start_offset.saturating_add(segment_start_col),
char_offset_end: line_start_offset.saturating_add(segment_end_col),
total_cells,
non_whitespace_cells,
dominant_style,
is_fold_placeholder_appended: placeholder_appended,
});
current_visual = current_visual.saturating_add(1);
visual_in_line = visual_in_line.saturating_add(1);
if visual_in_line >= span.visual_line_count {
let Some(next_span) = index.next_span_after_logical_line(logical_line) else {
break;
};
span = next_span;
visual_in_line = 0;
}
}
grid
})
}
pub fn get_headless_grid_composed(
&self,
start_visual_row: usize,
count: usize,
) -> ComposedGrid {
let mut grid = ComposedGrid::new(start_visual_row, count);
if count == 0 {
return grid;
}
#[derive(Debug, Clone)]
struct VirtualText {
anchor: usize,
text: String,
styles: Vec<StyleId>,
}
let mut inline_before: HashMap<usize, Vec<VirtualText>> = HashMap::new();
let mut inline_after: HashMap<usize, Vec<VirtualText>> = HashMap::new();
let mut above_by_line: BTreeMap<usize, Vec<VirtualText>> = BTreeMap::new();
for decorations in self.decorations.values() {
for deco in decorations {
let Some(text) = deco.text.as_ref() else {
continue;
};
if text.is_empty() {
continue;
}
let anchor = match deco.placement {
DecorationPlacement::After => deco.range.end,
DecorationPlacement::Before | DecorationPlacement::AboveLine => {
deco.range.start
}
};
let vt = VirtualText {
anchor,
text: text.clone(),
styles: deco.styles.clone(),
};
match deco.placement {
DecorationPlacement::Before => {
inline_before.entry(anchor).or_default().push(vt);
}
DecorationPlacement::After => {
inline_after.entry(anchor).or_default().push(vt);
}
DecorationPlacement::AboveLine => {
let line = self.line_index.char_offset_to_position(anchor).0;
above_by_line.entry(line).or_default().push(vt);
}
}
}
}
let regions = self.folding_manager.regions();
let line_count = self.layout_engine.logical_line_count();
let mut above_lines = Vec::new();
let mut above_prefix_counts = vec![0usize];
let mut total_above = 0usize;
for (&line, above) in &above_by_line {
if line >= line_count || Self::is_logical_line_hidden(regions, line) {
continue;
}
above_lines.push(line);
total_above = total_above.saturating_add(above.len());
above_prefix_counts.push(total_above);
}
let above_count_before_line = |line: usize| -> usize {
let idx = above_lines.partition_point(|candidate| *candidate < line);
above_prefix_counts[idx]
};
let (total_composed, start_logical_line, mut current_visual) =
self.with_visual_row_index(|index| {
let total_composed = index.total_visual_lines().saturating_add(total_above);
if line_count == 0 || start_visual_row >= total_composed {
return (total_composed, line_count, total_composed);
}
let composed_rows_before_line = |line: usize| -> usize {
index
.visual_rows_before_logical_line(line)
.saturating_add(above_count_before_line(line))
};
let mut low = 0usize;
let mut high = line_count;
while low < high {
let mid = (low + high).div_ceil(2);
if composed_rows_before_line(mid) <= start_visual_row {
low = mid;
} else {
high = mid.saturating_sub(1);
}
}
let mut start_line = low.min(line_count.saturating_sub(1));
if index.span_for_logical_line(start_line).is_none() {
let doc_visual = index.visual_rows_before_logical_line(start_line);
let Some((span, _)) = index.span_for_visual_row(doc_visual) else {
return (total_composed, line_count, total_composed);
};
start_line = span.logical_line;
}
(
total_composed,
start_line,
composed_rows_before_line(start_line),
)
});
if start_visual_row >= total_composed {
return grid;
}
let end_visual = start_visual_row.saturating_add(count).min(total_composed);
let tab_width = self.layout_engine.tab_width();
for logical_line in start_logical_line..line_count {
if Self::is_logical_line_hidden(regions, logical_line) {
continue;
}
if let Some(above) = above_by_line.get(&logical_line) {
for vt in above {
if current_visual >= end_visual {
return grid;
}
if current_visual >= start_visual_row {
let mut x_render = 0usize;
let mut cells: Vec<ComposedCell> = Vec::new();
for ch in vt.text.chars() {
let w = cell_width_at(ch, x_render, tab_width);
x_render = x_render.saturating_add(w);
cells.push(ComposedCell {
ch,
width: w,
styles: vt.styles.clone(),
source: ComposedCellSource::Virtual {
anchor_offset: vt.anchor,
},
});
}
grid.lines.push(ComposedLine {
kind: ComposedLineKind::VirtualAboveLine { logical_line },
char_offset_start: vt.anchor,
char_offset_end: vt.anchor,
cells,
});
}
current_visual = current_visual.saturating_add(1);
}
}
let Some(layout) = self.layout_engine.get_line_layout(logical_line) else {
continue;
};
let line_text = self
.line_index
.get_line_text(logical_line)
.unwrap_or_default();
let line_char_len = line_text.chars().count();
let line_start_offset = self.line_index.position_to_char_offset(logical_line, 0);
for visual_in_line in 0..layout.visual_line_count {
if current_visual >= end_visual {
return grid;
}
if current_visual < start_visual_row {
current_visual = current_visual.saturating_add(1);
continue;
}
let segment_start_col = if visual_in_line == 0 {
0
} else {
layout
.wrap_points
.get(visual_in_line - 1)
.map(|wp| wp.char_index)
.unwrap_or(0)
.min(line_char_len)
};
let segment_end_col = if visual_in_line < layout.wrap_points.len() {
layout.wrap_points[visual_in_line]
.char_index
.min(line_char_len)
} else {
line_char_len
};
let segment_start_offset = line_start_offset + segment_start_col;
let mut cells: Vec<ComposedCell> = Vec::new();
let mut x_render = 0usize;
if visual_in_line > 0 {
let indent_cells = wrap_indent_cells_for_line_text(
&line_text,
self.layout_engine.wrap_indent(),
self.viewport_width,
tab_width,
);
x_render = x_render.saturating_add(indent_cells);
for _ in 0..indent_cells {
cells.push(ComposedCell {
ch: ' ',
width: 1,
styles: Vec::new(),
source: ComposedCellSource::Virtual {
anchor_offset: segment_start_offset,
},
});
}
}
let mut x_in_line = visual_x_for_column(&line_text, segment_start_col, tab_width);
let push_virtual = |anchor: usize,
list: &[VirtualText],
cells: &mut Vec<ComposedCell>,
x_render: &mut usize| {
for vt in list {
for ch in vt.text.chars() {
let w = cell_width_at(ch, *x_render, tab_width);
*x_render = x_render.saturating_add(w);
cells.push(ComposedCell {
ch,
width: w,
styles: vt.styles.clone(),
source: ComposedCellSource::Virtual {
anchor_offset: anchor,
},
});
}
}
};
for (col, ch) in line_text
.chars()
.enumerate()
.skip(segment_start_col)
.take(segment_end_col.saturating_sub(segment_start_col))
{
let offset = line_start_offset + col;
if let Some(list) = inline_before.get(&offset) {
push_virtual(offset, list, &mut cells, &mut x_render);
}
if let Some(list) = inline_after.get(&offset) {
push_virtual(offset, list, &mut cells, &mut x_render);
}
let styles = self.styles_at_offset(offset);
let w = cell_width_at(ch, x_in_line, tab_width);
x_in_line = x_in_line.saturating_add(w);
x_render = x_render.saturating_add(w);
cells.push(ComposedCell {
ch,
width: w,
styles,
source: ComposedCellSource::Document { offset },
});
}
if visual_in_line + 1 == layout.visual_line_count {
let eol_offset = line_start_offset + line_char_len;
if let Some(list) = inline_before.get(&eol_offset) {
push_virtual(eol_offset, list, &mut cells, &mut x_render);
}
if let Some(list) = inline_after.get(&eol_offset) {
push_virtual(eol_offset, list, &mut cells, &mut x_render);
}
if let Some(region) = Self::collapsed_region_starting_at(regions, logical_line)
&& !region.placeholder.is_empty()
{
if !cells.is_empty() {
x_render = x_render.saturating_add(char_width(' '));
cells.push(ComposedCell {
ch: ' ',
width: char_width(' '),
styles: vec![FOLD_PLACEHOLDER_STYLE_ID],
source: ComposedCellSource::Virtual {
anchor_offset: eol_offset,
},
});
}
for ch in region.placeholder.chars() {
let w = cell_width_at(ch, x_render, tab_width);
x_render = x_render.saturating_add(w);
cells.push(ComposedCell {
ch,
width: w,
styles: vec![FOLD_PLACEHOLDER_STYLE_ID],
source: ComposedCellSource::Virtual {
anchor_offset: eol_offset,
},
});
}
if let Some(right_bracket) = self.fold_right_boundary_bracket_char(region) {
x_render = x_render.saturating_add(char_width(' '));
cells.push(ComposedCell {
ch: ' ',
width: char_width(' '),
styles: vec![FOLD_PLACEHOLDER_STYLE_ID],
source: ComposedCellSource::Virtual {
anchor_offset: eol_offset,
},
});
let w = cell_width_at(right_bracket, x_render, tab_width);
cells.push(ComposedCell {
ch: right_bracket,
width: w,
styles: vec![FOLD_PLACEHOLDER_STYLE_ID],
source: ComposedCellSource::Virtual {
anchor_offset: eol_offset,
},
});
}
}
}
grid.lines.push(ComposedLine {
kind: ComposedLineKind::Document {
logical_line,
visual_in_logical: visual_in_line,
},
char_offset_start: segment_start_offset,
char_offset_end: line_start_offset + segment_end_col,
cells,
});
current_visual = current_visual.saturating_add(1);
}
}
grid
}
}