Incorporate syntax highlighting into symbol outline view
Still need to figure out how to style the fuzzy match characters now that there's syntax highlighting. Right now, they are underlined in red.
This commit is contained in:
parent
7913a1ea22
commit
adeb7e6864
7 changed files with 369 additions and 124 deletions
|
@ -1,13 +1,16 @@
|
|||
use std::{ops::Range, sync::Arc};
|
||||
|
||||
use crate::{
|
||||
color::Color,
|
||||
fonts::TextStyle,
|
||||
fonts::{HighlightStyle, TextStyle},
|
||||
geometry::{
|
||||
rect::RectF,
|
||||
vector::{vec2f, Vector2F},
|
||||
},
|
||||
json::{ToJson, Value},
|
||||
text_layout::{Line, ShapedBoundary},
|
||||
DebugContext, Element, Event, EventContext, LayoutContext, PaintContext, SizeConstraint,
|
||||
text_layout::{Line, RunStyle, ShapedBoundary},
|
||||
DebugContext, Element, Event, EventContext, FontCache, LayoutContext, PaintContext,
|
||||
SizeConstraint, TextLayoutCache,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
|
@ -15,10 +18,12 @@ pub struct Text {
|
|||
text: String,
|
||||
style: TextStyle,
|
||||
soft_wrap: bool,
|
||||
highlights: Vec<(Range<usize>, HighlightStyle)>,
|
||||
}
|
||||
|
||||
pub struct LayoutState {
|
||||
lines: Vec<(Line, Vec<ShapedBoundary>)>,
|
||||
shaped_lines: Vec<Line>,
|
||||
wrap_boundaries: Vec<Vec<ShapedBoundary>>,
|
||||
line_height: f32,
|
||||
}
|
||||
|
||||
|
@ -28,6 +33,7 @@ impl Text {
|
|||
text,
|
||||
style,
|
||||
soft_wrap: true,
|
||||
highlights: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,6 +42,11 @@ impl Text {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn with_highlights(mut self, runs: Vec<(Range<usize>, HighlightStyle)>) -> Self {
|
||||
self.highlights = runs;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_soft_wrap(mut self, soft_wrap: bool) -> Self {
|
||||
self.soft_wrap = soft_wrap;
|
||||
self
|
||||
|
@ -51,32 +62,59 @@ impl Element for Text {
|
|||
constraint: SizeConstraint,
|
||||
cx: &mut LayoutContext,
|
||||
) -> (Vector2F, Self::LayoutState) {
|
||||
let font_id = self.style.font_id;
|
||||
let line_height = cx.font_cache.line_height(font_id, self.style.font_size);
|
||||
// Convert the string and highlight ranges into an iterator of highlighted chunks.
|
||||
let mut offset = 0;
|
||||
let mut highlight_ranges = self.highlights.iter().peekable();
|
||||
let chunks = std::iter::from_fn(|| {
|
||||
let result;
|
||||
if let Some((range, highlight)) = highlight_ranges.peek() {
|
||||
if offset < range.start {
|
||||
result = Some((&self.text[offset..range.start], None));
|
||||
offset = range.start;
|
||||
} else {
|
||||
result = Some((&self.text[range.clone()], Some(*highlight)));
|
||||
highlight_ranges.next();
|
||||
offset = range.end;
|
||||
}
|
||||
} else if offset < self.text.len() {
|
||||
result = Some((&self.text[offset..], None));
|
||||
offset = self.text.len();
|
||||
} else {
|
||||
result = None;
|
||||
}
|
||||
result
|
||||
});
|
||||
|
||||
let mut wrapper = cx.font_cache.line_wrapper(font_id, self.style.font_size);
|
||||
let mut lines = Vec::new();
|
||||
// Perform shaping on these highlighted chunks
|
||||
let shaped_lines = layout_highlighted_chunks(
|
||||
chunks,
|
||||
&self.style,
|
||||
cx.text_layout_cache,
|
||||
&cx.font_cache,
|
||||
usize::MAX,
|
||||
self.text.matches('\n').count() + 1,
|
||||
);
|
||||
|
||||
// If line wrapping is enabled, wrap each of the shaped lines.
|
||||
let font_id = self.style.font_id;
|
||||
let mut line_count = 0;
|
||||
let mut max_line_width = 0_f32;
|
||||
for line in self.text.lines() {
|
||||
let shaped_line = cx.text_layout_cache.layout_str(
|
||||
line,
|
||||
self.style.font_size,
|
||||
&[(line.len(), self.style.to_run())],
|
||||
);
|
||||
let wrap_boundaries = if self.soft_wrap {
|
||||
wrapper
|
||||
.wrap_shaped_line(line, &shaped_line, constraint.max.x())
|
||||
.collect::<Vec<_>>()
|
||||
let mut wrap_boundaries = Vec::new();
|
||||
let mut wrapper = cx.font_cache.line_wrapper(font_id, self.style.font_size);
|
||||
for (line, shaped_line) in self.text.lines().zip(&shaped_lines) {
|
||||
if self.soft_wrap {
|
||||
let boundaries = wrapper
|
||||
.wrap_shaped_line(line, shaped_line, constraint.max.x())
|
||||
.collect::<Vec<_>>();
|
||||
line_count += boundaries.len() + 1;
|
||||
wrap_boundaries.push(boundaries);
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
line_count += 1;
|
||||
}
|
||||
max_line_width = max_line_width.max(shaped_line.width());
|
||||
line_count += wrap_boundaries.len() + 1;
|
||||
lines.push((shaped_line, wrap_boundaries));
|
||||
}
|
||||
|
||||
let line_height = cx.font_cache.line_height(font_id, self.style.font_size);
|
||||
let size = vec2f(
|
||||
max_line_width
|
||||
.ceil()
|
||||
|
@ -84,7 +122,14 @@ impl Element for Text {
|
|||
.min(constraint.max.x()),
|
||||
(line_height * line_count as f32).ceil(),
|
||||
);
|
||||
(size, LayoutState { lines, line_height })
|
||||
(
|
||||
size,
|
||||
LayoutState {
|
||||
shaped_lines,
|
||||
wrap_boundaries,
|
||||
line_height,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn paint(
|
||||
|
@ -95,8 +140,10 @@ impl Element for Text {
|
|||
cx: &mut PaintContext,
|
||||
) -> Self::PaintState {
|
||||
let mut origin = bounds.origin();
|
||||
for (line, wrap_boundaries) in &layout.lines {
|
||||
let wrapped_line_boundaries = RectF::new(
|
||||
let empty = Vec::new();
|
||||
for (ix, line) in layout.shaped_lines.iter().enumerate() {
|
||||
let wrap_boundaries = layout.wrap_boundaries.get(ix).unwrap_or(&empty);
|
||||
let boundaries = RectF::new(
|
||||
origin,
|
||||
vec2f(
|
||||
bounds.width(),
|
||||
|
@ -104,16 +151,20 @@ impl Element for Text {
|
|||
),
|
||||
);
|
||||
|
||||
if wrapped_line_boundaries.intersects(visible_bounds) {
|
||||
line.paint_wrapped(
|
||||
origin,
|
||||
visible_bounds,
|
||||
layout.line_height,
|
||||
wrap_boundaries.iter().copied(),
|
||||
cx,
|
||||
);
|
||||
if boundaries.intersects(visible_bounds) {
|
||||
if self.soft_wrap {
|
||||
line.paint_wrapped(
|
||||
origin,
|
||||
visible_bounds,
|
||||
layout.line_height,
|
||||
wrap_boundaries.iter().copied(),
|
||||
cx,
|
||||
);
|
||||
} else {
|
||||
line.paint(origin, visible_bounds, layout.line_height, cx);
|
||||
}
|
||||
}
|
||||
origin.set_y(wrapped_line_boundaries.max_y());
|
||||
origin.set_y(boundaries.max_y());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -143,3 +194,71 @@ impl Element for Text {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform text layout on a series of highlighted chunks of text.
|
||||
pub fn layout_highlighted_chunks<'a>(
|
||||
chunks: impl Iterator<Item = (&'a str, Option<HighlightStyle>)>,
|
||||
style: &'a TextStyle,
|
||||
text_layout_cache: &'a TextLayoutCache,
|
||||
font_cache: &'a Arc<FontCache>,
|
||||
max_line_len: usize,
|
||||
max_line_count: usize,
|
||||
) -> Vec<Line> {
|
||||
let mut layouts = Vec::with_capacity(max_line_count);
|
||||
let mut prev_font_properties = style.font_properties.clone();
|
||||
let mut prev_font_id = style.font_id;
|
||||
let mut line = String::new();
|
||||
let mut styles = Vec::new();
|
||||
let mut row = 0;
|
||||
let mut line_exceeded_max_len = false;
|
||||
for (chunk, highlight_style) in chunks.chain([("\n", None)]) {
|
||||
for (ix, mut line_chunk) in chunk.split('\n').enumerate() {
|
||||
if ix > 0 {
|
||||
layouts.push(text_layout_cache.layout_str(&line, style.font_size, &styles));
|
||||
line.clear();
|
||||
styles.clear();
|
||||
row += 1;
|
||||
line_exceeded_max_len = false;
|
||||
if row == max_line_count {
|
||||
return layouts;
|
||||
}
|
||||
}
|
||||
|
||||
if !line_chunk.is_empty() && !line_exceeded_max_len {
|
||||
let highlight_style = highlight_style.unwrap_or(style.clone().into());
|
||||
|
||||
// Avoid a lookup if the font properties match the previous ones.
|
||||
let font_id = if highlight_style.font_properties == prev_font_properties {
|
||||
prev_font_id
|
||||
} else {
|
||||
font_cache
|
||||
.select_font(style.font_family_id, &highlight_style.font_properties)
|
||||
.unwrap_or(style.font_id)
|
||||
};
|
||||
|
||||
if line.len() + line_chunk.len() > max_line_len {
|
||||
let mut chunk_len = max_line_len - line.len();
|
||||
while !line_chunk.is_char_boundary(chunk_len) {
|
||||
chunk_len -= 1;
|
||||
}
|
||||
line_chunk = &line_chunk[..chunk_len];
|
||||
line_exceeded_max_len = true;
|
||||
}
|
||||
|
||||
line.push_str(line_chunk);
|
||||
styles.push((
|
||||
line_chunk.len(),
|
||||
RunStyle {
|
||||
font_id,
|
||||
color: highlight_style.color,
|
||||
underline: highlight_style.underline,
|
||||
},
|
||||
));
|
||||
prev_font_id = font_id;
|
||||
prev_font_properties = highlight_style.font_properties;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
layouts
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue