Separate WrappedLines from ShapedLines

ShapedLines are never wrapped, whereas WrappedLines are optionally wrapped if
they are associated with a wrap width. I tried to combine everything because
wrapping is inherently optional for the Text element, but we have a bunch of
APIs that don't make sense on a line that may wrap, so we need a distinct type
for that case.
This commit is contained in:
Nathan Sobo 2023-11-16 20:11:55 -07:00
parent e5ada92b7b
commit 9558da8681
12 changed files with 563 additions and 330 deletions

View file

@ -13,7 +13,8 @@ pub use block_map::{BlockMap, BlockPoint};
use collections::{BTreeMap, HashMap, HashSet};
use fold_map::FoldMap;
use gpui::{
Font, FontId, HighlightStyle, Hsla, Line, Model, ModelContext, Pixels, TextRun, UnderlineStyle,
Font, FontId, HighlightStyle, Hsla, LineLayout, Model, ModelContext, Pixels, ShapedLine,
TextRun, UnderlineStyle, WrappedLine,
};
use inlay_map::InlayMap;
use language::{
@ -561,7 +562,7 @@ impl DisplaySnapshot {
})
}
pub fn lay_out_line_for_row(
pub fn layout_row(
&self,
display_row: u32,
TextLayoutDetails {
@ -569,7 +570,7 @@ impl DisplaySnapshot {
editor_style,
rem_size,
}: &TextLayoutDetails,
) -> Line {
) -> Arc<LineLayout> {
let mut runs = Vec::new();
let mut line = String::new();
@ -598,29 +599,27 @@ impl DisplaySnapshot {
let font_size = editor_style.text.font_size.to_pixels(*rem_size);
text_system
.layout_text(&line, font_size, &runs, None)
.unwrap()
.pop()
.unwrap()
.layout_line(&line, font_size, &runs)
.expect("we expect the font to be loaded because it's rendered by the editor")
}
pub fn x_for_point(
pub fn x_for_display_point(
&self,
display_point: DisplayPoint,
text_layout_details: &TextLayoutDetails,
) -> Pixels {
let layout_line = self.lay_out_line_for_row(display_point.row(), text_layout_details);
layout_line.x_for_index(display_point.column() as usize)
let line = self.layout_row(display_point.row(), text_layout_details);
line.x_for_index(display_point.column() as usize)
}
pub fn column_for_x(
pub fn display_column_for_x(
&self,
display_row: u32,
x_coordinate: Pixels,
text_layout_details: &TextLayoutDetails,
x: Pixels,
details: &TextLayoutDetails,
) -> u32 {
let layout_line = self.lay_out_line_for_row(display_row, text_layout_details);
layout_line.closest_index_for_x(x_coordinate) as u32
let layout_line = self.layout_row(display_row, details);
layout_line.closest_index_for_x(x) as u32
}
pub fn chars_at(

View file

@ -5445,7 +5445,9 @@ impl Editor {
*head.column_mut() += 1;
head = display_map.clip_point(head, Bias::Right);
let goal = SelectionGoal::HorizontalPosition(
display_map.x_for_point(head, &text_layout_details).into(),
display_map
.x_for_display_point(head, &text_layout_details)
.into(),
);
selection.collapse_to(head, goal);
@ -6391,8 +6393,8 @@ impl Editor {
let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
let range = oldest_selection.display_range(&display_map).sorted();
let start_x = display_map.x_for_point(range.start, &text_layout_details);
let end_x = display_map.x_for_point(range.end, &text_layout_details);
let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
let positions = start_x.min(end_x)..start_x.max(end_x);
selections.clear();
@ -6431,15 +6433,16 @@ impl Editor {
let range = selection.display_range(&display_map).sorted();
debug_assert_eq!(range.start.row(), range.end.row());
let mut row = range.start.row();
let positions = if let SelectionGoal::HorizontalRange { start, end } =
selection.goal
{
px(start)..px(end)
} else {
let start_x = display_map.x_for_point(range.start, &text_layout_details);
let end_x = display_map.x_for_point(range.end, &text_layout_details);
start_x.min(end_x)..start_x.max(end_x)
};
let positions =
if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
px(start)..px(end)
} else {
let start_x =
display_map.x_for_display_point(range.start, &text_layout_details);
let end_x =
display_map.x_for_display_point(range.end, &text_layout_details);
start_x.min(end_x)..start_x.max(end_x)
};
while row != end_row {
if above {
@ -6992,7 +6995,7 @@ impl Editor {
let display_point = point.to_display_point(display_snapshot);
let goal = SelectionGoal::HorizontalPosition(
display_snapshot
.x_for_point(display_point, &text_layout_details)
.x_for_display_point(display_point, &text_layout_details)
.into(),
);
(display_point, goal)
@ -9755,7 +9758,8 @@ impl InputHandler for Editor {
let scroll_left = scroll_position.x * em_width;
let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
let x = snapshot.x_for_point(start, &text_layout_details) - scroll_left + self.gutter_width;
let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
+ self.gutter_width;
let y = line_height * (start.row() as f32 - scroll_position.y);
Some(Bounds {

View file

@ -20,10 +20,10 @@ use collections::{BTreeMap, HashMap};
use gpui::{
div, point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace,
BorrowWindow, Bounds, Component, ContentMask, Corners, DispatchPhase, Edges, Element,
ElementId, ElementInputHandler, Entity, EntityId, Hsla, InteractiveComponent, Line,
ElementId, ElementInputHandler, Entity, EntityId, Hsla, InteractiveComponent, LineLayout,
MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentComponent, Pixels,
ScrollWheelEvent, Size, StatefulInteractiveComponent, Style, Styled, TextRun, TextStyle, View,
ViewContext, WindowContext,
ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveComponent, Style, Styled,
TextRun, TextStyle, View, ViewContext, WindowContext, WrappedLine,
};
use itertools::Itertools;
use language::language_settings::ShowWhitespaceSetting;
@ -476,7 +476,7 @@ impl EditorElement {
Self::paint_diff_hunks(bounds, layout, cx);
}
for (ix, line) in layout.line_number_layouts.iter().enumerate() {
for (ix, line) in layout.line_numbers.iter().enumerate() {
if let Some(line) = line {
let line_origin = bounds.origin
+ point(
@ -775,21 +775,21 @@ impl EditorElement {
.chars_at(cursor_position)
.next()
.and_then(|(character, _)| {
let text = character.to_string();
let text = SharedString::from(character.to_string());
let len = text.len();
cx.text_system()
.layout_text(
&text,
.shape_line(
text,
cursor_row_layout.font_size,
&[TextRun {
len: text.len(),
len,
font: self.style.text.font(),
color: self.style.background,
background_color: None,
underline: None,
}],
None,
)
.unwrap()
.pop()
.log_err()
})
} else {
None
@ -1244,20 +1244,20 @@ impl EditorElement {
let font_size = style.text.font_size.to_pixels(cx.rem_size());
let layout = cx
.text_system()
.layout_text(
" ".repeat(column).as_str(),
.shape_line(
SharedString::from(" ".repeat(column)),
font_size,
&[TextRun {
len: column,
font: style.text.font(),
color: Hsla::default(),
background_color: None,
underline: None,
}],
None,
)
.unwrap();
layout[0].width
layout.width
}
fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &ViewContext<Editor>) -> Pixels {
@ -1338,7 +1338,7 @@ impl EditorElement {
relative_rows
}
fn layout_line_numbers(
fn shape_line_numbers(
&self,
rows: Range<u32>,
active_rows: &BTreeMap<u32, bool>,
@ -1347,12 +1347,12 @@ impl EditorElement {
snapshot: &EditorSnapshot,
cx: &ViewContext<Editor>,
) -> (
Vec<Option<gpui::Line>>,
Vec<Option<ShapedLine>>,
Vec<Option<(FoldStatus, BufferRow, bool)>>,
) {
let font_size = self.style.text.font_size.to_pixels(cx.rem_size());
let include_line_numbers = snapshot.mode == EditorMode::Full;
let mut line_number_layouts = Vec::with_capacity(rows.len());
let mut shaped_line_numbers = Vec::with_capacity(rows.len());
let mut fold_statuses = Vec::with_capacity(rows.len());
let mut line_number = String::new();
let is_relative = EditorSettings::get_global(cx).relative_line_numbers;
@ -1387,15 +1387,14 @@ impl EditorElement {
len: line_number.len(),
font: self.style.text.font(),
color,
background_color: None,
underline: None,
};
let layout = cx
let shaped_line = cx
.text_system()
.layout_text(&line_number, font_size, &[run], None)
.unwrap()
.pop()
.shape_line(line_number.clone().into(), font_size, &[run])
.unwrap();
line_number_layouts.push(Some(layout));
shaped_line_numbers.push(Some(shaped_line));
fold_statuses.push(
is_singleton
.then(|| {
@ -1408,17 +1407,17 @@ impl EditorElement {
}
} else {
fold_statuses.push(None);
line_number_layouts.push(None);
shaped_line_numbers.push(None);
}
}
(line_number_layouts, fold_statuses)
(shaped_line_numbers, fold_statuses)
}
fn layout_lines(
&mut self,
rows: Range<u32>,
line_number_layouts: &[Option<Line>],
line_number_layouts: &[Option<ShapedLine>],
snapshot: &EditorSnapshot,
cx: &ViewContext<Editor>,
) -> Vec<LineWithInvisibles> {
@ -1439,18 +1438,17 @@ impl EditorElement {
.chain(iter::repeat(""))
.take(rows.len());
placeholder_lines
.map(|line| {
.filter_map(move |line| {
let run = TextRun {
len: line.len(),
font: self.style.text.font(),
color: placeholder_color,
background_color: None,
underline: Default::default(),
};
cx.text_system()
.layout_text(line, font_size, &[run], None)
.unwrap()
.pop()
.unwrap()
.shape_line(line.to_string().into(), font_size, &[run])
.log_err()
})
.map(|line| LineWithInvisibles {
line,
@ -1726,7 +1724,7 @@ impl EditorElement {
.head
});
let (line_number_layouts, fold_statuses) = self.layout_line_numbers(
let (line_numbers, fold_statuses) = self.shape_line_numbers(
start_row..end_row,
&active_rows,
head_for_relative,
@ -1740,8 +1738,7 @@ impl EditorElement {
let scrollbar_row_range = scroll_position.y..(scroll_position.y + height_in_lines);
let mut max_visible_line_width = Pixels::ZERO;
let line_layouts =
self.layout_lines(start_row..end_row, &line_number_layouts, &snapshot, cx);
let line_layouts = self.layout_lines(start_row..end_row, &line_numbers, &snapshot, cx);
for line_with_invisibles in &line_layouts {
if line_with_invisibles.line.width > max_visible_line_width {
max_visible_line_width = line_with_invisibles.line.width;
@ -1879,35 +1876,31 @@ impl EditorElement {
let invisible_symbol_font_size = font_size / 2.;
let tab_invisible = cx
.text_system()
.layout_text(
"",
.shape_line(
"".into(),
invisible_symbol_font_size,
&[TextRun {
len: "".len(),
font: self.style.text.font(),
color: cx.theme().colors().editor_invisible,
background_color: None,
underline: None,
}],
None,
)
.unwrap()
.pop()
.unwrap();
let space_invisible = cx
.text_system()
.layout_text(
"",
.shape_line(
"".into(),
invisible_symbol_font_size,
&[TextRun {
len: "".len(),
font: self.style.text.font(),
color: cx.theme().colors().editor_invisible,
background_color: None,
underline: None,
}],
None,
)
.unwrap()
.pop()
.unwrap();
LayoutState {
@ -1939,7 +1932,7 @@ impl EditorElement {
active_rows,
highlighted_rows,
highlighted_ranges,
line_number_layouts,
line_numbers,
display_hunks,
blocks,
selections,
@ -2199,7 +2192,7 @@ impl EditorElement {
#[derive(Debug)]
pub struct LineWithInvisibles {
pub line: Line,
pub line: ShapedLine,
invisibles: Vec<Invisible>,
}
@ -2209,7 +2202,7 @@ impl LineWithInvisibles {
text_style: &TextStyle,
max_line_len: usize,
max_line_count: usize,
line_number_layouts: &[Option<Line>],
line_number_layouts: &[Option<ShapedLine>],
editor_mode: EditorMode,
cx: &WindowContext,
) -> Vec<Self> {
@ -2229,11 +2222,12 @@ impl LineWithInvisibles {
}]) {
for (ix, mut line_chunk) in highlighted_chunk.chunk.split('\n').enumerate() {
if ix > 0 {
let layout = cx
let shaped_line = cx
.text_system()
.layout_text(&line, font_size, &styles, None);
.shape_line(line.clone().into(), font_size, &styles)
.unwrap();
layouts.push(Self {
line: layout.unwrap().pop().unwrap(),
line: shaped_line,
invisibles: invisibles.drain(..).collect(),
});
@ -2267,6 +2261,7 @@ impl LineWithInvisibles {
len: line_chunk.len(),
font: text_style.font(),
color: text_style.color,
background_color: None,
underline: text_style.underline,
});
@ -3087,7 +3082,7 @@ pub struct LayoutState {
visible_display_row_range: Range<u32>,
active_rows: BTreeMap<u32, bool>,
highlighted_rows: Option<Range<u32>>,
line_number_layouts: Vec<Option<gpui::Line>>,
line_numbers: Vec<Option<ShapedLine>>,
display_hunks: Vec<DisplayDiffHunk>,
blocks: Vec<BlockLayout>,
highlighted_ranges: Vec<(Range<DisplayPoint>, Hsla)>,
@ -3100,8 +3095,8 @@ pub struct LayoutState {
code_actions_indicator: Option<CodeActionsIndicator>,
// hover_popovers: Option<(DisplayPoint, Vec<AnyElement<Editor>>)>,
fold_indicators: Vec<Option<AnyElement<Editor>>>,
tab_invisible: Line,
space_invisible: Line,
tab_invisible: ShapedLine,
space_invisible: ShapedLine,
}
struct CodeActionsIndicator {
@ -3201,7 +3196,7 @@ fn layout_line(
snapshot: &EditorSnapshot,
style: &EditorStyle,
cx: &WindowContext,
) -> Result<Line> {
) -> Result<ShapedLine> {
let mut line = snapshot.line(row);
if line.len() > MAX_LINE_LEN {
@ -3213,21 +3208,17 @@ fn layout_line(
line.truncate(len);
}
Ok(cx
.text_system()
.layout_text(
&line,
style.text.font_size.to_pixels(cx.rem_size()),
&[TextRun {
len: snapshot.line_len(row) as usize,
font: style.text.font(),
color: Hsla::default(),
underline: None,
}],
None,
)?
.pop()
.unwrap())
cx.text_system().shape_line(
line.into(),
style.text.font_size.to_pixels(cx.rem_size()),
&[TextRun {
len: snapshot.line_len(row) as usize,
font: style.text.font(),
color: Hsla::default(),
background_color: None,
underline: None,
}],
)
}
#[derive(Debug)]
@ -3237,7 +3228,7 @@ pub struct Cursor {
line_height: Pixels,
color: Hsla,
shape: CursorShape,
block_text: Option<Line>,
block_text: Option<ShapedLine>,
}
impl Cursor {
@ -3247,7 +3238,7 @@ impl Cursor {
line_height: Pixels,
color: Hsla,
shape: CursorShape,
block_text: Option<Line>,
block_text: Option<ShapedLine>,
) -> Cursor {
Cursor {
origin,

View file

@ -98,7 +98,7 @@ pub fn up_by_rows(
SelectionGoal::HorizontalPosition(x) => x.into(), // todo!("Can the fields in SelectionGoal by Pixels? We should extract a geometry crate and depend on that.")
SelectionGoal::WrappedHorizontalPosition((_, x)) => x.into(),
SelectionGoal::HorizontalRange { end, .. } => end.into(),
_ => map.x_for_point(start, text_layout_details),
_ => map.x_for_display_point(start, text_layout_details),
};
let prev_row = start.row().saturating_sub(row_count);
@ -107,7 +107,7 @@ pub fn up_by_rows(
Bias::Left,
);
if point.row() < start.row() {
*point.column_mut() = map.column_for_x(point.row(), goal_x, text_layout_details)
*point.column_mut() = map.display_column_for_x(point.row(), goal_x, text_layout_details)
} else if preserve_column_at_start {
return (start, goal);
} else {
@ -137,18 +137,18 @@ pub fn down_by_rows(
SelectionGoal::HorizontalPosition(x) => x.into(),
SelectionGoal::WrappedHorizontalPosition((_, x)) => x.into(),
SelectionGoal::HorizontalRange { end, .. } => end.into(),
_ => map.x_for_point(start, text_layout_details),
_ => map.x_for_display_point(start, text_layout_details),
};
let new_row = start.row() + row_count;
let mut point = map.clip_point(DisplayPoint::new(new_row, 0), Bias::Right);
if point.row() > start.row() {
*point.column_mut() = map.column_for_x(point.row(), goal_x, text_layout_details)
*point.column_mut() = map.display_column_for_x(point.row(), goal_x, text_layout_details)
} else if preserve_column_at_end {
return (start, goal);
} else {
point = map.max_point();
goal_x = map.x_for_point(point, text_layout_details)
goal_x = map.x_for_display_point(point, text_layout_details)
}
let mut clipped_point = map.clip_point(point, Bias::Right);

View file

@ -313,14 +313,14 @@ impl SelectionsCollection {
let is_empty = positions.start == positions.end;
let line_len = display_map.line_len(row);
let layed_out_line = display_map.lay_out_line_for_row(row, &text_layout_details);
let line = display_map.layout_row(row, &text_layout_details);
dbg!("****START COL****");
let start_col = layed_out_line.closest_index_for_x(positions.start) as u32;
if start_col < line_len || (is_empty && positions.start == layed_out_line.width) {
let start_col = line.closest_index_for_x(positions.start) as u32;
if start_col < line_len || (is_empty && positions.start == line.width) {
let start = DisplayPoint::new(row, start_col);
dbg!("****END COL****");
let end_col = layed_out_line.closest_index_for_x(positions.end) as u32;
let end_col = line.closest_index_for_x(positions.end) as u32;
let end = DisplayPoint::new(row, end_col);
dbg!(start_col, end_col);