Don't render invisibles with elements (#20841)
Turns out that in the case you have a somehow valid utf-8 file that contains almost all ascii control characters, we run out of element arena space. Fixes: #20652 Release Notes: - Fixed a crash when opening a file containing a very large number of ascii control characters on one line.
This commit is contained in:
parent
f0c7e62adc
commit
d4c5c0f05e
4 changed files with 119 additions and 65 deletions
|
@ -66,7 +66,7 @@ use std::{
|
||||||
use sum_tree::{Bias, TreeMap};
|
use sum_tree::{Bias, TreeMap};
|
||||||
use tab_map::{TabMap, TabSnapshot};
|
use tab_map::{TabMap, TabSnapshot};
|
||||||
use text::LineIndent;
|
use text::LineIndent;
|
||||||
use ui::{div, px, IntoElement, ParentElement, SharedString, Styled, WindowContext};
|
use ui::{px, SharedString, WindowContext};
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
use wrap_map::{WrapMap, WrapSnapshot};
|
use wrap_map::{WrapMap, WrapSnapshot};
|
||||||
|
|
||||||
|
@ -541,11 +541,17 @@ pub struct HighlightStyles {
|
||||||
pub suggestion: Option<HighlightStyle>,
|
pub suggestion: Option<HighlightStyle>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum ChunkReplacement {
|
||||||
|
Renderer(ChunkRenderer),
|
||||||
|
Str(SharedString),
|
||||||
|
}
|
||||||
|
|
||||||
pub struct HighlightedChunk<'a> {
|
pub struct HighlightedChunk<'a> {
|
||||||
pub text: &'a str,
|
pub text: &'a str,
|
||||||
pub style: Option<HighlightStyle>,
|
pub style: Option<HighlightStyle>,
|
||||||
pub is_tab: bool,
|
pub is_tab: bool,
|
||||||
pub renderer: Option<ChunkRenderer>,
|
pub replacement: Option<ChunkReplacement>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> HighlightedChunk<'a> {
|
impl<'a> HighlightedChunk<'a> {
|
||||||
|
@ -557,7 +563,7 @@ impl<'a> HighlightedChunk<'a> {
|
||||||
let mut text = self.text;
|
let mut text = self.text;
|
||||||
let style = self.style;
|
let style = self.style;
|
||||||
let is_tab = self.is_tab;
|
let is_tab = self.is_tab;
|
||||||
let renderer = self.renderer;
|
let renderer = self.replacement;
|
||||||
iter::from_fn(move || {
|
iter::from_fn(move || {
|
||||||
let mut prefix_len = 0;
|
let mut prefix_len = 0;
|
||||||
while let Some(&ch) = chars.peek() {
|
while let Some(&ch) = chars.peek() {
|
||||||
|
@ -573,30 +579,33 @@ impl<'a> HighlightedChunk<'a> {
|
||||||
text: prefix,
|
text: prefix,
|
||||||
style,
|
style,
|
||||||
is_tab,
|
is_tab,
|
||||||
renderer: renderer.clone(),
|
replacement: renderer.clone(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
chars.next();
|
chars.next();
|
||||||
let (prefix, suffix) = text.split_at(ch.len_utf8());
|
let (prefix, suffix) = text.split_at(ch.len_utf8());
|
||||||
text = suffix;
|
text = suffix;
|
||||||
if let Some(replacement) = replacement(ch) {
|
if let Some(replacement) = replacement(ch) {
|
||||||
let background = editor_style.status.hint_background;
|
let invisible_highlight = HighlightStyle {
|
||||||
let underline = editor_style.status.hint;
|
background_color: Some(editor_style.status.hint_background),
|
||||||
|
underline: Some(UnderlineStyle {
|
||||||
|
color: Some(editor_style.status.hint),
|
||||||
|
thickness: px(1.),
|
||||||
|
wavy: false,
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let invisible_style = if let Some(mut style) = style {
|
||||||
|
style.highlight(invisible_highlight);
|
||||||
|
style
|
||||||
|
} else {
|
||||||
|
invisible_highlight
|
||||||
|
};
|
||||||
return Some(HighlightedChunk {
|
return Some(HighlightedChunk {
|
||||||
text: prefix,
|
text: prefix,
|
||||||
style: None,
|
style: Some(invisible_style),
|
||||||
is_tab: false,
|
is_tab: false,
|
||||||
renderer: Some(ChunkRenderer {
|
replacement: Some(ChunkReplacement::Str(replacement.into())),
|
||||||
render: Arc::new(move |_| {
|
|
||||||
div()
|
|
||||||
.child(replacement)
|
|
||||||
.bg(background)
|
|
||||||
.text_decoration_1()
|
|
||||||
.text_decoration_color(underline)
|
|
||||||
.into_any_element()
|
|
||||||
}),
|
|
||||||
constrain_width: false,
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
let invisible_highlight = HighlightStyle {
|
let invisible_highlight = HighlightStyle {
|
||||||
|
@ -619,7 +628,7 @@ impl<'a> HighlightedChunk<'a> {
|
||||||
text: prefix,
|
text: prefix,
|
||||||
style: Some(invisible_style),
|
style: Some(invisible_style),
|
||||||
is_tab: false,
|
is_tab: false,
|
||||||
renderer: renderer.clone(),
|
replacement: renderer.clone(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -631,7 +640,7 @@ impl<'a> HighlightedChunk<'a> {
|
||||||
text: remainder,
|
text: remainder,
|
||||||
style,
|
style,
|
||||||
is_tab,
|
is_tab,
|
||||||
renderer: renderer.clone(),
|
replacement: renderer.clone(),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -895,7 +904,7 @@ impl DisplaySnapshot {
|
||||||
text: chunk.text,
|
text: chunk.text,
|
||||||
style: highlight_style,
|
style: highlight_style,
|
||||||
is_tab: chunk.is_tab,
|
is_tab: chunk.is_tab,
|
||||||
renderer: chunk.renderer,
|
replacement: chunk.renderer.map(ChunkReplacement::Renderer),
|
||||||
}
|
}
|
||||||
.highlight_invisibles(editor_style)
|
.highlight_invisibles(editor_style)
|
||||||
})
|
})
|
||||||
|
|
|
@ -16,8 +16,8 @@ use crate::{
|
||||||
items::BufferSearchHighlights,
|
items::BufferSearchHighlights,
|
||||||
mouse_context_menu::{self, MenuPosition, MouseContextMenu},
|
mouse_context_menu::{self, MenuPosition, MouseContextMenu},
|
||||||
scroll::scroll_amount::ScrollAmount,
|
scroll::scroll_amount::ScrollAmount,
|
||||||
BlockId, CodeActionsMenu, CursorShape, CustomBlockId, DisplayPoint, DisplayRow,
|
BlockId, ChunkReplacement, CodeActionsMenu, CursorShape, CustomBlockId, DisplayPoint,
|
||||||
DocumentHighlightRead, DocumentHighlightWrite, Editor, EditorMode, EditorSettings,
|
DisplayRow, DocumentHighlightRead, DocumentHighlightWrite, Editor, EditorMode, EditorSettings,
|
||||||
EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GutterDimensions, HalfPageDown,
|
EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GutterDimensions, HalfPageDown,
|
||||||
HalfPageUp, HandleInput, HoveredCursor, HoveredHunk, JumpData, LineDown, LineUp, OpenExcerpts,
|
HalfPageUp, HandleInput, HoveredCursor, HoveredHunk, JumpData, LineDown, LineUp, OpenExcerpts,
|
||||||
PageDown, PageUp, Point, RowExt, RowRangeExt, SelectPhase, Selection, SoftWrap, ToPoint,
|
PageDown, PageUp, Point, RowExt, RowRangeExt, SelectPhase, Selection, SoftWrap, ToPoint,
|
||||||
|
@ -34,8 +34,8 @@ use gpui::{
|
||||||
FontId, GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement, Length,
|
FontId, GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement, Length,
|
||||||
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad,
|
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad,
|
||||||
ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size,
|
ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size,
|
||||||
StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, TextStyleRefinement, View,
|
StatefulInteractiveElement, Style, Styled, TextRun, TextStyleRefinement, View, ViewContext,
|
||||||
ViewContext, WeakView, WindowContext,
|
WeakView, WindowContext,
|
||||||
};
|
};
|
||||||
use gpui::{ClickEvent, Subscription};
|
use gpui::{ClickEvent, Subscription};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
@ -2019,7 +2019,7 @@ impl EditorElement {
|
||||||
let chunks = snapshot.highlighted_chunks(rows.clone(), true, style);
|
let chunks = snapshot.highlighted_chunks(rows.clone(), true, style);
|
||||||
LineWithInvisibles::from_chunks(
|
LineWithInvisibles::from_chunks(
|
||||||
chunks,
|
chunks,
|
||||||
&style.text,
|
&style,
|
||||||
MAX_LINE_LEN,
|
MAX_LINE_LEN,
|
||||||
rows.len(),
|
rows.len(),
|
||||||
snapshot.mode,
|
snapshot.mode,
|
||||||
|
@ -4372,7 +4372,7 @@ impl LineWithInvisibles {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn from_chunks<'a>(
|
fn from_chunks<'a>(
|
||||||
chunks: impl Iterator<Item = HighlightedChunk<'a>>,
|
chunks: impl Iterator<Item = HighlightedChunk<'a>>,
|
||||||
text_style: &TextStyle,
|
editor_style: &EditorStyle,
|
||||||
max_line_len: usize,
|
max_line_len: usize,
|
||||||
max_line_count: usize,
|
max_line_count: usize,
|
||||||
editor_mode: EditorMode,
|
editor_mode: EditorMode,
|
||||||
|
@ -4380,6 +4380,7 @@ impl LineWithInvisibles {
|
||||||
is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
|
is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> Vec<Self> {
|
) -> Vec<Self> {
|
||||||
|
let text_style = &editor_style.text;
|
||||||
let mut layouts = Vec::with_capacity(max_line_count);
|
let mut layouts = Vec::with_capacity(max_line_count);
|
||||||
let mut fragments: SmallVec<[LineFragment; 1]> = SmallVec::new();
|
let mut fragments: SmallVec<[LineFragment; 1]> = SmallVec::new();
|
||||||
let mut line = String::new();
|
let mut line = String::new();
|
||||||
|
@ -4398,9 +4399,9 @@ impl LineWithInvisibles {
|
||||||
text: "\n",
|
text: "\n",
|
||||||
style: None,
|
style: None,
|
||||||
is_tab: false,
|
is_tab: false,
|
||||||
renderer: None,
|
replacement: None,
|
||||||
}]) {
|
}]) {
|
||||||
if let Some(renderer) = highlighted_chunk.renderer {
|
if let Some(replacement) = highlighted_chunk.replacement {
|
||||||
if !line.is_empty() {
|
if !line.is_empty() {
|
||||||
let shaped_line = cx
|
let shaped_line = cx
|
||||||
.text_system()
|
.text_system()
|
||||||
|
@ -4413,42 +4414,71 @@ impl LineWithInvisibles {
|
||||||
styles.clear();
|
styles.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
let available_width = if renderer.constrain_width {
|
match replacement {
|
||||||
let chunk = if highlighted_chunk.text == ellipsis.as_ref() {
|
ChunkReplacement::Renderer(renderer) => {
|
||||||
ellipsis.clone()
|
let available_width = if renderer.constrain_width {
|
||||||
} else {
|
let chunk = if highlighted_chunk.text == ellipsis.as_ref() {
|
||||||
SharedString::from(Arc::from(highlighted_chunk.text))
|
ellipsis.clone()
|
||||||
};
|
} else {
|
||||||
let shaped_line = cx
|
SharedString::from(Arc::from(highlighted_chunk.text))
|
||||||
.text_system()
|
};
|
||||||
.shape_line(
|
let shaped_line = cx
|
||||||
chunk,
|
.text_system()
|
||||||
font_size,
|
.shape_line(
|
||||||
&[text_style.to_run(highlighted_chunk.text.len())],
|
chunk,
|
||||||
)
|
font_size,
|
||||||
.unwrap();
|
&[text_style.to_run(highlighted_chunk.text.len())],
|
||||||
AvailableSpace::Definite(shaped_line.width)
|
)
|
||||||
} else {
|
.unwrap();
|
||||||
AvailableSpace::MinContent
|
AvailableSpace::Definite(shaped_line.width)
|
||||||
};
|
} else {
|
||||||
|
AvailableSpace::MinContent
|
||||||
|
};
|
||||||
|
|
||||||
let mut element = (renderer.render)(&mut ChunkRendererContext {
|
let mut element = (renderer.render)(&mut ChunkRendererContext {
|
||||||
context: cx,
|
context: cx,
|
||||||
max_width: text_width,
|
max_width: text_width,
|
||||||
});
|
});
|
||||||
let line_height = text_style.line_height_in_pixels(cx.rem_size());
|
let line_height = text_style.line_height_in_pixels(cx.rem_size());
|
||||||
let size = element.layout_as_root(
|
let size = element.layout_as_root(
|
||||||
size(available_width, AvailableSpace::Definite(line_height)),
|
size(available_width, AvailableSpace::Definite(line_height)),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
||||||
width += size.width;
|
width += size.width;
|
||||||
len += highlighted_chunk.text.len();
|
len += highlighted_chunk.text.len();
|
||||||
fragments.push(LineFragment::Element {
|
fragments.push(LineFragment::Element {
|
||||||
element: Some(element),
|
element: Some(element),
|
||||||
size,
|
size,
|
||||||
len: highlighted_chunk.text.len(),
|
len: highlighted_chunk.text.len(),
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
ChunkReplacement::Str(x) => {
|
||||||
|
let text_style = if let Some(style) = highlighted_chunk.style {
|
||||||
|
Cow::Owned(text_style.clone().highlight(style))
|
||||||
|
} else {
|
||||||
|
Cow::Borrowed(text_style)
|
||||||
|
};
|
||||||
|
|
||||||
|
let run = TextRun {
|
||||||
|
len: x.len(),
|
||||||
|
font: text_style.font(),
|
||||||
|
color: text_style.color,
|
||||||
|
background_color: text_style.background_color,
|
||||||
|
underline: text_style.underline,
|
||||||
|
strikethrough: text_style.strikethrough,
|
||||||
|
};
|
||||||
|
let line_layout = cx
|
||||||
|
.text_system()
|
||||||
|
.shape_line(x, font_size, &[run])
|
||||||
|
.unwrap()
|
||||||
|
.with_len(highlighted_chunk.text.len());
|
||||||
|
|
||||||
|
width += line_layout.width;
|
||||||
|
len += highlighted_chunk.text.len();
|
||||||
|
fragments.push(LineFragment::Text(line_layout))
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
for (ix, mut line_chunk) in highlighted_chunk.text.split('\n').enumerate() {
|
for (ix, mut line_chunk) in highlighted_chunk.text.split('\n').enumerate() {
|
||||||
if ix > 0 {
|
if ix > 0 {
|
||||||
|
@ -5992,7 +6022,7 @@ fn layout_line(
|
||||||
let chunks = snapshot.highlighted_chunks(row..row + DisplayRow(1), true, style);
|
let chunks = snapshot.highlighted_chunks(row..row + DisplayRow(1), true, style);
|
||||||
LineWithInvisibles::from_chunks(
|
LineWithInvisibles::from_chunks(
|
||||||
chunks,
|
chunks,
|
||||||
&style.text,
|
&style,
|
||||||
MAX_LINE_LEN,
|
MAX_LINE_LEN,
|
||||||
1,
|
1,
|
||||||
snapshot.mode,
|
snapshot.mode,
|
||||||
|
|
|
@ -44,6 +44,21 @@ impl ShapedLine {
|
||||||
self.layout.len
|
self.layout.len
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Override the len, useful if you're rendering text a
|
||||||
|
/// as text b (e.g. rendering invisibles).
|
||||||
|
pub fn with_len(mut self, len: usize) -> Self {
|
||||||
|
let layout = self.layout.as_ref();
|
||||||
|
self.layout = Arc::new(LineLayout {
|
||||||
|
font_size: layout.font_size,
|
||||||
|
width: layout.width,
|
||||||
|
ascent: layout.ascent,
|
||||||
|
descent: layout.descent,
|
||||||
|
runs: layout.runs.clone(),
|
||||||
|
len,
|
||||||
|
});
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Paint the line of text to the window.
|
/// Paint the line of text to the window.
|
||||||
pub fn paint(
|
pub fn paint(
|
||||||
&self,
|
&self,
|
||||||
|
|
|
@ -29,7 +29,7 @@ pub struct LineLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A run of text that has been shaped .
|
/// A run of text that has been shaped .
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ShapedRun {
|
pub struct ShapedRun {
|
||||||
/// The font id for this run
|
/// The font id for this run
|
||||||
pub font_id: FontId,
|
pub font_id: FontId,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue