Introduce custom fold placeholders (#12214)

This pull request replaces the static `⋯` character we used to insert
when folding a range with a custom render function that return an
`AnyElement`. We plan to use this in the assistant, but for now this
should be behavior-preserving.

Release Notes:

- N/A

---------

Co-authored-by: Nathan <nathan@zed.dev>
Co-authored-by: Conrad <conrad@zed.dev>
This commit is contained in:
Antonio Scandurra 2024-05-23 23:22:30 +02:00 committed by GitHub
parent e0cfba43aa
commit 57d570c281
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 774 additions and 437 deletions

View File

@ -189,7 +189,7 @@ pub struct LanguageModelChoiceDelta {
struct MessageMetadata {
role: Role,
status: MessageStatus,
// todo!("delete this")
// TODO: Delete this
#[serde(skip)]
ambient_context: AmbientContextSnapshot,
}

View File

@ -19,6 +19,7 @@ use crate::{
use anyhow::{anyhow, Result};
use client::telemetry::Telemetry;
use collections::{hash_map, HashMap, HashSet, VecDeque};
use editor::FoldPlaceholder;
use editor::{
actions::{FoldAt, MoveDown, MoveUp},
display_map::{
@ -34,7 +35,7 @@ use fs::Fs;
use futures::StreamExt;
use gpui::{
canvas, div, point, relative, rems, uniform_list, Action, AnyElement, AnyView, AppContext,
AsyncAppContext, AsyncWindowContext, AvailableSpace, ClipboardItem, Context, Entity,
AsyncAppContext, AsyncWindowContext, AvailableSpace, ClipboardItem, Context, Empty, Entity,
EventEmitter, FocusHandle, FocusableView, FontStyle, FontWeight, HighlightStyle,
InteractiveElement, IntoElement, Model, ModelContext, ParentElement, Pixels, Render,
SharedString, StatefulInteractiveElement, Styled, Subscription, Task, TextStyle,
@ -2943,6 +2944,10 @@ impl ConversationEditor {
.insert_flaps(
[Flap::new(
start..end,
FoldPlaceholder {
render: Arc::new(|_, _, _| Empty.into_any()),
constrain_width: false,
},
render_slash_command_output_toggle,
render_slash_command_output_trailer,
)],

View File

@ -869,7 +869,6 @@ impl AssistantChat {
crate::ui::ChatMessage::new(
*id,
UserOrAssistant::User(self.user_store.read(cx).current_user()),
// todo!(): clean up the vec usage
vec![
body.clone().into_any_element(),
h_flex()

View File

@ -24,17 +24,27 @@ mod inlay_map;
mod tab_map;
mod wrap_map;
use crate::{hover_links::InlayHighlight, movement::TextLayoutDetails, InlayId};
use crate::{EditorStyle, RowExt};
pub use block_map::{BlockMap, BlockPoint};
use collections::{HashMap, HashSet};
use fold_map::FoldMap;
use gpui::{
AnyElement, Font, HighlightStyle, Hsla, LineLayout, Model, ModelContext, Pixels, UnderlineStyle,
use crate::{
hover_links::InlayHighlight, movement::TextLayoutDetails, EditorStyle, InlayId, RowExt,
};
use inlay_map::InlayMap;
pub use block_map::{
BlockBufferRows, BlockChunks as DisplayChunks, BlockContext, BlockDisposition, BlockId,
BlockMap, BlockPoint, BlockProperties, BlockStyle, RenderBlock, TransformBlock,
};
use block_map::{BlockRow, BlockSnapshot};
use collections::{HashMap, HashSet};
pub use flap_map::*;
pub use fold_map::{Fold, FoldId, FoldPlaceholder, FoldPoint};
use fold_map::{FoldMap, FoldSnapshot};
use gpui::{
AnyElement, Font, HighlightStyle, LineLayout, Model, ModelContext, Pixels, UnderlineStyle,
};
pub(crate) use inlay_map::Inlay;
use inlay_map::{InlayMap, InlaySnapshot};
pub use inlay_map::{InlayOffset, InlayPoint};
use language::{
language_settings::language_settings, OffsetUtf16, Point, Subscription as BufferSubscription,
language_settings::language_settings, ChunkRenderer, OffsetUtf16, Point,
Subscription as BufferSubscription,
};
use lsp::DiagnosticSeverity;
use multi_buffer::{
@ -42,27 +52,12 @@ use multi_buffer::{
ToOffset, ToPoint,
};
use serde::Deserialize;
use std::ops::Add;
use std::{any::TypeId, borrow::Cow, fmt::Debug, num::NonZeroU32, ops::Range, sync::Arc};
use sum_tree::{Bias, TreeMap};
use tab_map::TabMap;
use tab_map::{TabMap, TabSnapshot};
use ui::WindowContext;
use wrap_map::WrapMap;
pub use block_map::{
BlockBufferRows, BlockChunks as DisplayChunks, BlockContext, BlockDisposition, BlockId,
BlockProperties, BlockStyle, RenderBlock, TransformBlock,
};
pub use flap_map::*;
use self::block_map::{BlockRow, BlockSnapshot};
use self::fold_map::FoldSnapshot;
pub use self::fold_map::{Fold, FoldId, FoldPoint};
use self::inlay_map::InlaySnapshot;
pub use self::inlay_map::{InlayOffset, InlayPoint};
use self::tab_map::TabSnapshot;
use self::wrap_map::WrapSnapshot;
pub(crate) use inlay_map::Inlay;
use wrap_map::{WrapMap, WrapSnapshot};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum FoldStatus {
@ -105,10 +100,12 @@ pub struct DisplayMap {
inlay_highlights: InlayHighlights,
/// A container for explicitly foldable ranges, which supersede indentation based fold range suggestions.
flap_map: FlapMap,
fold_placeholder: FoldPlaceholder,
pub clip_at_line_ends: bool,
}
impl DisplayMap {
#[allow(clippy::too_many_arguments)]
pub fn new(
buffer: Model<MultiBuffer>,
font: Font,
@ -116,6 +113,7 @@ impl DisplayMap {
wrap_width: Option<Pixels>,
buffer_header_height: u8,
excerpt_header_height: u8,
fold_placeholder: FoldPlaceholder,
cx: &mut ModelContext<Self>,
) -> Self {
let buffer_subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
@ -138,6 +136,7 @@ impl DisplayMap {
wrap_map,
block_map,
flap_map,
fold_placeholder,
text_highlights: Default::default(),
inlay_highlights: Default::default(),
clip_at_line_ends: false,
@ -167,6 +166,7 @@ impl DisplayMap {
text_highlights: self.text_highlights.clone(),
inlay_highlights: self.inlay_highlights.clone(),
clip_at_line_ends: self.clip_at_line_ends,
fold_placeholder: self.fold_placeholder.clone(),
}
}
@ -174,14 +174,19 @@ impl DisplayMap {
self.fold(
other
.folds_in_range(0..other.buffer_snapshot.len())
.map(|fold| (fold.range.to_offset(&other.buffer_snapshot), fold.text)),
.map(|fold| {
(
fold.range.to_offset(&other.buffer_snapshot),
fold.placeholder.clone(),
)
}),
cx,
);
}
pub fn fold<T: ToOffset>(
&mut self,
ranges: impl IntoIterator<Item = (Range<T>, &'static str)>,
ranges: impl IntoIterator<Item = (Range<T>, FoldPlaceholder)>,
cx: &mut ModelContext<Self>,
) {
let snapshot = self.buffer.read(cx).snapshot(cx);
@ -324,10 +329,6 @@ impl DisplayMap {
.update(cx, |map, cx| map.set_font_with_size(font, font_size, cx))
}
pub fn set_fold_ellipses_color(&mut self, color: Hsla) -> bool {
self.fold_map.set_ellipses_color(color)
}
pub fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut ModelContext<Self>) -> bool {
self.wrap_map
.update(cx, |map, cx| map.set_wrap_width(width, cx))
@ -394,9 +395,10 @@ pub struct HighlightStyles {
}
pub struct HighlightedChunk<'a> {
pub chunk: &'a str,
pub text: &'a str,
pub style: Option<HighlightStyle>,
pub is_tab: bool,
pub renderer: Option<ChunkRenderer>,
}
#[derive(Clone)]
@ -411,6 +413,7 @@ pub struct DisplaySnapshot {
text_highlights: TextHighlights,
inlay_highlights: InlayHighlights,
clip_at_line_ends: bool,
pub(crate) fold_placeholder: FoldPlaceholder,
}
impl DisplaySnapshot {
@ -648,9 +651,10 @@ impl DisplaySnapshot {
}
HighlightedChunk {
chunk: chunk.text,
text: chunk.text,
style: highlight_style,
is_tab: chunk.is_tab,
renderer: chunk.renderer,
}
})
}
@ -672,7 +676,7 @@ impl DisplaySnapshot {
let range = display_row..display_row.next_row();
for chunk in self.highlighted_chunks(range, false, &editor_style) {
line.push_str(chunk.chunk);
line.push_str(chunk.text);
let text_style = if let Some(style) = chunk.style {
Cow::Owned(editor_style.text.clone().highlight(style))
@ -680,7 +684,7 @@ impl DisplaySnapshot {
Cow::Borrowed(&editor_style.text)
};
runs.push(text_style.to_run(chunk.chunk.len()))
runs.push(text_style.to_run(chunk.text.len()))
}
if line.ends_with('\n') {
@ -883,13 +887,16 @@ impl DisplaySnapshot {
pub fn foldable_range(
&self,
buffer_row: MultiBufferRow,
) -> Option<(Range<Point>, &'static str)> {
) -> Option<(Range<Point>, FoldPlaceholder)> {
let start = MultiBufferPoint::new(buffer_row.0, self.buffer_snapshot.line_len(buffer_row));
if let Some(flap) = self
.flap_snapshot
.query_row(buffer_row, &self.buffer_snapshot)
{
Some((flap.range.to_point(&self.buffer_snapshot), ""))
Some((
flap.range.to_point(&self.buffer_snapshot),
flap.placeholder.clone(),
))
} else if self.starts_indent(MultiBufferRow(start.row))
&& !self.is_line_folded(MultiBufferRow(start.row))
{
@ -909,7 +916,7 @@ impl DisplaySnapshot {
}
}
let end = end.unwrap_or(max_point);
Some((start..end, ""))
Some((start..end, self.fold_placeholder.clone()))
} else {
None
}
@ -950,6 +957,14 @@ impl Debug for DisplayPoint {
#[serde(transparent)]
pub struct DisplayRow(pub u32);
impl Add for DisplayRow {
type Output = Self;
fn add(self, other: Self) -> Self::Output {
DisplayRow(self.0 + other.0)
}
}
impl DisplayPoint {
pub fn new(row: DisplayRow, column: u32) -> Self {
Self(BlockPoint(Point::new(row.0, column)))
@ -1083,6 +1098,7 @@ pub mod tests {
wrap_width,
buffer_start_excerpt_header_height,
excerpt_header_height,
FoldPlaceholder::test(),
cx,
)
});
@ -1186,7 +1202,12 @@ pub mod tests {
} else {
log::info!("folding ranges: {:?}", ranges);
map.update(cx, |map, cx| {
map.fold(ranges.into_iter().map(|range| (range, "")), cx);
map.fold(
ranges
.into_iter()
.map(|range| (range, FoldPlaceholder::test())),
cx,
);
});
}
}
@ -1323,6 +1344,7 @@ pub mod tests {
wrap_width,
1,
1,
FoldPlaceholder::test(),
cx,
)
});
@ -1424,7 +1446,16 @@ pub mod tests {
let font_size = px(14.0);
let map = cx.new_model(|cx| {
DisplayMap::new(buffer.clone(), font("Helvetica"), font_size, None, 1, 1, cx)
DisplayMap::new(
buffer.clone(),
font("Helvetica"),
font_size,
None,
1,
1,
FoldPlaceholder::test(),
cx,
)
});
buffer.update(cx, |buffer, cx| {
@ -1510,8 +1541,18 @@ pub mod tests {
let font_size = px(14.0);
let map = cx
.new_model(|cx| DisplayMap::new(buffer, font("Helvetica"), font_size, None, 1, 1, cx));
let map = cx.new_model(|cx| {
DisplayMap::new(
buffer,
font("Helvetica"),
font_size,
None,
1,
1,
FoldPlaceholder::test(),
cx,
)
});
assert_eq!(
cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(5), &map, &theme, cx)),
vec![
@ -1536,7 +1577,7 @@ pub mod tests {
map.fold(
vec![(
MultiBufferPoint::new(0, 6)..MultiBufferPoint::new(3, 2),
"",
FoldPlaceholder::test(),
)],
cx,
)
@ -1602,7 +1643,16 @@ pub mod tests {
let font_size = px(16.0);
let map = cx.new_model(|cx| {
DisplayMap::new(buffer, font("Courier"), font_size, Some(px(40.0)), 1, 1, cx)
DisplayMap::new(
buffer,
font("Courier"),
font_size,
Some(px(40.0)),
1,
1,
FoldPlaceholder::test(),
cx,
)
});
assert_eq!(
cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(5), &map, &theme, cx)),
@ -1621,7 +1671,7 @@ pub mod tests {
map.fold(
vec![(
MultiBufferPoint::new(0, 6)..MultiBufferPoint::new(3, 2),
"",
FoldPlaceholder::test(),
)],
cx,
)
@ -1674,8 +1724,18 @@ pub mod tests {
let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
let font_size = px(16.0);
let map =
cx.new_model(|cx| DisplayMap::new(buffer, font("Courier"), font_size, None, 1, 1, cx));
let map = cx.new_model(|cx| {
DisplayMap::new(
buffer,
font("Courier"),
font_size,
None,
1,
1,
FoldPlaceholder::test(),
cx,
)
});
enum MyType {}
@ -1789,8 +1849,16 @@ pub mod tests {
let buffer = MultiBuffer::build_simple(text, cx);
let font_size = px(14.0);
cx.new_model(|cx| {
let mut map =
DisplayMap::new(buffer.clone(), font("Helvetica"), font_size, None, 1, 1, cx);
let mut map = DisplayMap::new(
buffer.clone(),
font("Helvetica"),
font_size,
None,
1,
1,
FoldPlaceholder::test(),
cx,
);
let snapshot = map.buffer.read(cx).snapshot(cx);
let range =
snapshot.anchor_before(Point::new(2, 0))..snapshot.anchor_after(Point::new(3, 3));
@ -1798,6 +1866,7 @@ pub mod tests {
map.flap_map.insert(
[Flap::new(
range,
FoldPlaceholder::test(),
|_row, _status, _toggle, _cx| div(),
|_row, _status, _cx| div(),
)],
@ -1817,7 +1886,16 @@ pub mod tests {
let font_size = px(14.0);
let map = cx.new_model(|cx| {
DisplayMap::new(buffer.clone(), font("Helvetica"), font_size, None, 1, 1, cx)
DisplayMap::new(
buffer.clone(),
font("Helvetica"),
font_size,
None,
1,
1,
FoldPlaceholder::test(),
cx,
)
});
let map = map.update(cx, |map, cx| map.snapshot(cx));
assert_eq!(map.text(), "α\nβ \n🏀β γ");
@ -1883,7 +1961,16 @@ pub mod tests {
let buffer = MultiBuffer::build_simple("aaa\n\t\tbbb", cx);
let font_size = px(14.0);
let map = cx.new_model(|cx| {
DisplayMap::new(buffer.clone(), font("Helvetica"), font_size, None, 1, 1, cx)
DisplayMap::new(
buffer.clone(),
font("Helvetica"),
font_size,
None,
1,
1,
FoldPlaceholder::test(),
cx,
)
});
assert_eq!(
map.update(cx, |map, cx| map.snapshot(cx)).max_point(),

View File

@ -875,7 +875,7 @@ impl<'a> Iterator for BlockChunks<'a> {
Some(Chunk {
text: prefix,
..self.input_chunk
..self.input_chunk.clone()
})
}
}

View File

@ -6,6 +6,8 @@ use sum_tree::{Bias, SeekTarget, SumTree};
use text::Point;
use ui::WindowContext;
use crate::FoldPlaceholder;
#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
pub struct FlapId(usize);
@ -76,6 +78,7 @@ type RenderTrailerFn =
#[derive(Clone)]
pub struct Flap {
pub range: Range<Anchor>,
pub placeholder: FoldPlaceholder,
pub render_toggle: RenderToggleFn,
pub render_trailer: RenderTrailerFn,
}
@ -83,6 +86,7 @@ pub struct Flap {
impl Flap {
pub fn new<RenderToggle, ToggleElement, RenderTrailer, TrailerElement>(
range: Range<Anchor>,
placeholder: FoldPlaceholder,
render_toggle: RenderToggle,
render_trailer: RenderTrailer,
) -> Self
@ -107,6 +111,7 @@ impl Flap {
{
Flap {
range,
placeholder,
render_toggle: Arc::new(move |row, folded, toggle, cx| {
render_toggle(row, folded, toggle, cx).into_any_element()
}),
@ -256,11 +261,13 @@ mod test {
let flaps = [
Flap::new(
snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(1, 5)),
FoldPlaceholder::test(),
|_row, _folded, _toggle, _cx| div(),
|_row, _folded, _cx| div(),
),
Flap::new(
snapshot.anchor_before(Point::new(3, 0))..snapshot.anchor_after(Point::new(3, 5)),
FoldPlaceholder::test(),
|_row, _folded, _toggle, _cx| div(),
|_row, _folded, _cx| div(),
),

View File

@ -2,17 +2,54 @@ use super::{
inlay_map::{InlayBufferRows, InlayChunks, InlayEdit, InlayOffset, InlayPoint, InlaySnapshot},
Highlights,
};
use gpui::{ElementId, HighlightStyle, Hsla};
use language::{Chunk, Edit, Point, TextSummary};
use gpui::{AnyElement, ElementId, WindowContext};
use language::{Chunk, ChunkRenderer, Edit, Point, TextSummary};
use multi_buffer::{Anchor, AnchorRangeExt, MultiBufferRow, MultiBufferSnapshot, ToOffset};
use std::{
cmp::{self, Ordering},
iter,
fmt, iter,
ops::{Add, AddAssign, Deref, DerefMut, Range, Sub},
sync::Arc,
};
use sum_tree::{Bias, Cursor, FilterCursor, SumTree};
use util::post_inc;
#[derive(Clone)]
pub struct FoldPlaceholder {
/// Creates an element to represent this fold's placeholder.
pub render: Arc<dyn Send + Sync + Fn(FoldId, Range<Anchor>, &mut WindowContext) -> AnyElement>,
/// If true, the element is constrained to the shaped width of an ellipsis.
pub constrain_width: bool,
}
impl FoldPlaceholder {
#[cfg(any(test, feature = "test-support"))]
pub fn test() -> Self {
use gpui::IntoElement;
Self {
render: Arc::new(|_id, _range, _cx| gpui::Empty.into_any_element()),
constrain_width: true,
}
}
}
impl fmt::Debug for FoldPlaceholder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FoldPlaceholder")
.field("constrain_width", &self.constrain_width)
.finish()
}
}
impl Eq for FoldPlaceholder {}
impl PartialEq for FoldPlaceholder {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.render, &other.render) && self.constrain_width == other.constrain_width
}
}
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
pub struct FoldPoint(pub Point);
@ -54,7 +91,7 @@ impl FoldPoint {
let mut offset = cursor.start().1.output.len;
if !overshoot.is_zero() {
let transform = cursor.item().expect("display point out of range");
assert!(transform.output_text.is_none());
assert!(transform.placeholder.is_none());
let end_inlay_offset = snapshot
.inlay_snapshot
.to_offset(InlayPoint(cursor.start().1.input.lines + overshoot));
@ -75,7 +112,7 @@ pub(crate) struct FoldMapWriter<'a>(&'a mut FoldMap);
impl<'a> FoldMapWriter<'a> {
pub(crate) fn fold<T: ToOffset>(
&mut self,
ranges: impl IntoIterator<Item = (Range<T>, &'static str)>,
ranges: impl IntoIterator<Item = (Range<T>, FoldPlaceholder)>,
) -> (FoldSnapshot, Vec<FoldEdit>) {
let mut edits = Vec::new();
let mut folds = Vec::new();
@ -99,7 +136,7 @@ impl<'a> FoldMapWriter<'a> {
folds.push(Fold {
id: FoldId(post_inc(&mut self.0.next_fold_id.0)),
range: fold_range,
text: fold_text,
placeholder: fold_text,
});
let inlay_range =
@ -183,7 +220,6 @@ impl<'a> FoldMapWriter<'a> {
/// See the [`display_map` module documentation](crate::display_map) for more information.
pub(crate) struct FoldMap {
snapshot: FoldSnapshot,
ellipses_color: Option<Hsla>,
next_fold_id: FoldId,
}
@ -198,15 +234,13 @@ impl FoldMap {
input: inlay_snapshot.text_summary(),
output: inlay_snapshot.text_summary(),
},
output_text: None,
placeholder: None,
},
&(),
),
inlay_snapshot: inlay_snapshot.clone(),
version: 0,
ellipses_color: None,
},
ellipses_color: None,
next_fold_id: FoldId::default(),
};
let snapshot = this.snapshot.clone();
@ -232,15 +266,6 @@ impl FoldMap {
(FoldMapWriter(self), snapshot, edits)
}
pub fn set_ellipses_color(&mut self, color: Hsla) -> bool {
if self.ellipses_color == Some(color) {
false
} else {
self.ellipses_color = Some(color);
true
}
}
fn check_invariants(&self) {
if cfg!(test) {
assert_eq!(
@ -329,9 +354,9 @@ impl FoldMap {
let buffer_start = fold.range.start.to_offset(&inlay_snapshot.buffer);
let buffer_end = fold.range.end.to_offset(&inlay_snapshot.buffer);
(
fold.clone(),
inlay_snapshot.to_inlay_offset(buffer_start)
..inlay_snapshot.to_inlay_offset(buffer_end),
fold.text,
)
});
folds_cursor.next(&inlay_snapshot.buffer);
@ -342,17 +367,17 @@ impl FoldMap {
while folds
.peek()
.map_or(false, |(fold_range, _)| fold_range.start < edit.new.end)
.map_or(false, |(_, fold_range)| fold_range.start < edit.new.end)
{
let (mut fold_range, fold_text) = folds.next().unwrap();
let (fold, mut fold_range) = folds.next().unwrap();
let sum = new_transforms.summary();
assert!(fold_range.start.0 >= sum.input.len);
while folds.peek().map_or(false, |(next_fold_range, _)| {
while folds.peek().map_or(false, |(_, next_fold_range)| {
next_fold_range.start <= fold_range.end
}) {
let (next_fold_range, _) = folds.next().unwrap();
let (_, next_fold_range) = folds.next().unwrap();
if next_fold_range.end > fold_range.end {
fold_range.end = next_fold_range.end;
}
@ -367,21 +392,36 @@ impl FoldMap {
output: text_summary.clone(),
input: text_summary,
},
output_text: None,
placeholder: None,
},
&(),
);
}
if fold_range.end > fold_range.start {
const ELLIPSIS: &'static str = "";
let fold_id = fold.id;
new_transforms.push(
Transform {
summary: TransformSummary {
output: TextSummary::from(fold_text),
output: TextSummary::from(ELLIPSIS),
input: inlay_snapshot
.text_summary_for_range(fold_range.start..fold_range.end),
},
output_text: Some(fold_text),
placeholder: Some(TransformPlaceholder {
text: ELLIPSIS,
renderer: ChunkRenderer {
render: Arc::new(move |cx| {
(fold.placeholder.render)(
fold_id,
fold.range.0.clone(),
cx,
)
}),
constrain_width: fold.placeholder.constrain_width,
},
}),
},
&(),
);
@ -398,7 +438,7 @@ impl FoldMap {
output: text_summary.clone(),
input: text_summary,
},
output_text: None,
placeholder: None,
},
&(),
);
@ -414,7 +454,7 @@ impl FoldMap {
output: text_summary.clone(),
input: text_summary,
},
output_text: None,
placeholder: None,
},
&(),
);
@ -484,7 +524,6 @@ pub struct FoldSnapshot {
folds: SumTree<Fold>,
pub inlay_snapshot: InlaySnapshot,
pub version: usize,
pub ellipses_color: Option<Hsla>,
}
impl FoldSnapshot {
@ -508,9 +547,9 @@ impl FoldSnapshot {
if let Some(transform) = cursor.item() {
let start_in_transform = range.start.0 - cursor.start().0 .0;
let end_in_transform = cmp::min(range.end, cursor.end(&()).0).0 - cursor.start().0 .0;
if let Some(output_text) = transform.output_text {
if let Some(placeholder) = transform.placeholder.as_ref() {
summary = TextSummary::from(
&output_text
&placeholder.text
[start_in_transform.column as usize..end_in_transform.column as usize],
);
} else {
@ -533,8 +572,9 @@ impl FoldSnapshot {
.output;
if let Some(transform) = cursor.item() {
let end_in_transform = range.end.0 - cursor.start().0 .0;
if let Some(output_text) = transform.output_text {
summary += TextSummary::from(&output_text[..end_in_transform.column as usize]);
if let Some(placeholder) = transform.placeholder.as_ref() {
summary +=
TextSummary::from(&placeholder.text[..end_in_transform.column as usize]);
} else {
let inlay_start = self.inlay_snapshot.to_offset(cursor.start().1);
let inlay_end = self
@ -631,7 +671,7 @@ impl FoldSnapshot {
let inlay_offset = self.inlay_snapshot.to_inlay_offset(buffer_offset);
let mut cursor = self.transforms.cursor::<InlayOffset>();
cursor.seek(&inlay_offset, Bias::Right, &());
cursor.item().map_or(false, |t| t.output_text.is_some())
cursor.item().map_or(false, |t| t.placeholder.is_some())
}
pub fn is_line_folded(&self, buffer_row: MultiBufferRow) -> bool {
@ -646,7 +686,7 @@ impl FoldSnapshot {
let buffer_point = self.inlay_snapshot.to_buffer_point(inlay_point);
if buffer_point.row != buffer_row.0 {
return false;
} else if transform.output_text.is_some() {
} else if transform.placeholder.is_some() {
return true;
}
}
@ -693,7 +733,6 @@ impl FoldSnapshot {
inlay_offset: inlay_start,
output_offset: range.start.0,
max_output_offset: range.end.0,
ellipses_color: self.ellipses_color,
}
}
@ -720,7 +759,7 @@ impl FoldSnapshot {
cursor.seek(&point, Bias::Right, &());
if let Some(transform) = cursor.item() {
let transform_start = cursor.start().0 .0;
if transform.output_text.is_some() {
if transform.placeholder.is_some() {
if point.0 == transform_start || matches!(bias, Bias::Left) {
FoldPoint(transform_start)
} else {
@ -810,15 +849,21 @@ fn consolidate_fold_edits(edits: &mut Vec<FoldEdit>) {
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
#[derive(Clone, Debug, Default)]
struct Transform {
summary: TransformSummary,
output_text: Option<&'static str>,
placeholder: Option<TransformPlaceholder>,
}
#[derive(Clone, Debug)]
struct TransformPlaceholder {
text: &'static str,
renderer: ChunkRenderer,
}
impl Transform {
fn is_fold(&self) -> bool {
self.output_text.is_some()
self.placeholder.is_some()
}
}
@ -858,7 +903,7 @@ impl Into<ElementId> for FoldId {
pub struct Fold {
pub id: FoldId,
pub range: FoldRange,
pub text: &'static str,
pub placeholder: FoldPlaceholder,
}
#[derive(Clone, Debug, Eq, PartialEq)]
@ -1004,7 +1049,6 @@ pub struct FoldChunks<'a> {
inlay_offset: InlayOffset,
output_offset: usize,
max_output_offset: usize,
ellipses_color: Option<Hsla>,
}
impl<'a> Iterator for FoldChunks<'a> {
@ -1019,7 +1063,7 @@ impl<'a> Iterator for FoldChunks<'a> {
// If we're in a fold, then return the fold's display text and
// advance the transform and buffer cursors to the end of the fold.
if let Some(output_text) = transform.output_text {
if let Some(placeholder) = transform.placeholder.as_ref() {
self.inlay_chunk.take();
self.inlay_offset += InlayOffset(transform.summary.input.len);
self.inlay_chunks.seek(self.inlay_offset);
@ -1030,13 +1074,10 @@ impl<'a> Iterator for FoldChunks<'a> {
self.transform_cursor.next(&());
}
self.output_offset += output_text.len();
self.output_offset += placeholder.text.len();
return Some(Chunk {
text: output_text,
highlight_style: self.ellipses_color.map(|color| HighlightStyle {
color: Some(color),
..Default::default()
}),
text: placeholder.text,
renderer: Some(placeholder.renderer.clone()),
..Default::default()
});
}
@ -1048,7 +1089,7 @@ impl<'a> Iterator for FoldChunks<'a> {
}
// Otherwise, take a chunk from the buffer's text.
if let Some((buffer_chunk_start, mut chunk)) = self.inlay_chunk {
if let Some((buffer_chunk_start, mut chunk)) = self.inlay_chunk.clone() {
let buffer_chunk_end = buffer_chunk_start + InlayOffset(chunk.text.len());
let transform_end = self.transform_cursor.end(&()).1;
let chunk_end = buffer_chunk_end.min(transform_end);
@ -1165,8 +1206,8 @@ mod tests {
let (mut writer, _, _) = map.write(inlay_snapshot, vec![]);
let (snapshot2, edits) = writer.fold(vec![
(Point::new(0, 2)..Point::new(2, 2), ""),
(Point::new(2, 4)..Point::new(4, 1), ""),
(Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()),
(Point::new(2, 4)..Point::new(4, 1), FoldPlaceholder::test()),
]);
assert_eq!(snapshot2.text(), "aa⋯cc⋯eeeee");
assert_eq!(
@ -1245,19 +1286,25 @@ mod tests {
let mut map = FoldMap::new(inlay_snapshot.clone()).0;
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
writer.fold(vec![(5..8, "")]);
writer.fold(vec![(5..8, FoldPlaceholder::test())]);
let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
assert_eq!(snapshot.text(), "abcde⋯ijkl");
// Create an fold adjacent to the start of the first fold.
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
writer.fold(vec![(0..1, ""), (2..5, "")]);
writer.fold(vec![
(0..1, FoldPlaceholder::test()),
(2..5, FoldPlaceholder::test()),
]);
let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
assert_eq!(snapshot.text(), "⋯b⋯ijkl");
// Create an fold adjacent to the end of the first fold.
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
writer.fold(vec![(11..11, ""), (8..10, "")]);
writer.fold(vec![
(11..11, FoldPlaceholder::test()),
(8..10, FoldPlaceholder::test()),
]);
let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
assert_eq!(snapshot.text(), "⋯b⋯kl");
}
@ -1267,7 +1314,10 @@ mod tests {
// Create two adjacent folds.
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
writer.fold(vec![(0..2, ""), (2..5, "")]);
writer.fold(vec![
(0..2, FoldPlaceholder::test()),
(2..5, FoldPlaceholder::test()),
]);
let (snapshot, _) = map.read(inlay_snapshot, vec![]);
assert_eq!(snapshot.text(), "⋯fghijkl");
@ -1291,10 +1341,10 @@ mod tests {
let mut map = FoldMap::new(inlay_snapshot.clone()).0;
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
writer.fold(vec![
(Point::new(0, 2)..Point::new(2, 2), ""),
(Point::new(0, 4)..Point::new(1, 0), ""),
(Point::new(1, 2)..Point::new(3, 2), ""),
(Point::new(3, 1)..Point::new(4, 1), ""),
(Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()),
(Point::new(0, 4)..Point::new(1, 0), FoldPlaceholder::test()),
(Point::new(1, 2)..Point::new(3, 2), FoldPlaceholder::test()),
(Point::new(3, 1)..Point::new(4, 1), FoldPlaceholder::test()),
]);
let (snapshot, _) = map.read(inlay_snapshot, vec![]);
assert_eq!(snapshot.text(), "aa⋯eeeee");
@ -1311,8 +1361,8 @@ mod tests {
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
writer.fold(vec![
(Point::new(0, 2)..Point::new(2, 2), ""),
(Point::new(3, 1)..Point::new(4, 1), ""),
(Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()),
(Point::new(3, 1)..Point::new(4, 1), FoldPlaceholder::test()),
]);
let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
assert_eq!(snapshot.text(), "aa⋯cccc\nd⋯eeeee");
@ -1336,10 +1386,10 @@ mod tests {
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
writer.fold(vec![
(Point::new(0, 2)..Point::new(2, 2), ""),
(Point::new(0, 4)..Point::new(1, 0), ""),
(Point::new(1, 2)..Point::new(3, 2), ""),
(Point::new(3, 1)..Point::new(4, 1), ""),
(Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()),
(Point::new(0, 4)..Point::new(1, 0), FoldPlaceholder::test()),
(Point::new(1, 2)..Point::new(3, 2), FoldPlaceholder::test()),
(Point::new(3, 1)..Point::new(4, 1), FoldPlaceholder::test()),
]);
let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
let fold_ranges = snapshot
@ -1414,10 +1464,10 @@ mod tests {
snapshot_edits.push((snapshot.clone(), edits));
let mut expected_text: String = inlay_snapshot.text().to_string();
for (fold_range, fold_text) in map.merged_folds().into_iter().rev() {
for fold_range in map.merged_folds().into_iter().rev() {
let fold_inlay_start = inlay_snapshot.to_inlay_offset(fold_range.start);
let fold_inlay_end = inlay_snapshot.to_inlay_offset(fold_range.end);
expected_text.replace_range(fold_inlay_start.0..fold_inlay_end.0, fold_text);
expected_text.replace_range(fold_inlay_start.0..fold_inlay_end.0, "");
}
assert_eq!(snapshot.text(), expected_text);
@ -1429,7 +1479,7 @@ mod tests {
let mut prev_row = 0;
let mut expected_buffer_rows = Vec::new();
for (fold_range, _fold_text) in map.merged_folds().into_iter() {
for fold_range in map.merged_folds() {
let fold_start = inlay_snapshot
.to_point(inlay_snapshot.to_inlay_offset(fold_range.start))
.row();
@ -1543,7 +1593,7 @@ mod tests {
let folded_buffer_rows = map
.merged_folds()
.iter()
.flat_map(|(fold_range, _)| {
.flat_map(|fold_range| {
let start_row = fold_range.start.to_point(&buffer_snapshot).row;
let end = fold_range.end.to_point(&buffer_snapshot);
if end.column == 0 {
@ -1640,8 +1690,8 @@ mod tests {
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
writer.fold(vec![
(Point::new(0, 2)..Point::new(2, 2), ""),
(Point::new(3, 1)..Point::new(4, 1), ""),
(Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()),
(Point::new(3, 1)..Point::new(4, 1), FoldPlaceholder::test()),
]);
let (snapshot, _) = map.read(inlay_snapshot, vec![]);
@ -1659,7 +1709,7 @@ mod tests {
}
impl FoldMap {
fn merged_folds(&self) -> Vec<(Range<usize>, &'static str)> {
fn merged_folds(&self) -> Vec<Range<usize>> {
let inlay_snapshot = self.snapshot.inlay_snapshot.clone();
let buffer = &inlay_snapshot.buffer;
let mut folds = self.snapshot.folds.items(buffer);
@ -1667,17 +1717,12 @@ mod tests {
folds.sort_by(|a, b| a.range.cmp(&b.range, buffer));
let mut folds = folds
.iter()
.map(|fold| {
(
fold.range.start.to_offset(buffer)..fold.range.end.to_offset(buffer),
fold.text,
)
})
.map(|fold| fold.range.start.to_offset(buffer)..fold.range.end.to_offset(buffer))
.peekable();
let mut merged_folds = Vec::new();
while let Some((mut fold_range, fold_text)) = folds.next() {
while let Some((next_range, _)) = folds.peek() {
while let Some(mut fold_range) = folds.next() {
while let Some(next_range) = folds.peek() {
if fold_range.end >= next_range.start {
if next_range.end > fold_range.end {
fold_range.end = next_range.end;
@ -1688,7 +1733,7 @@ mod tests {
}
}
if fold_range.end > fold_range.start {
merged_folds.push((fold_range, fold_text));
merged_folds.push(fold_range);
}
}
merged_folds
@ -1723,8 +1768,7 @@ mod tests {
for _ in 0..rng.gen_range(1..=2) {
let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right);
let start = buffer.clip_offset(rng.gen_range(0..=end), Left);
let text = if rng.gen() { "" } else { "" };
to_fold.push((start..end, text));
to_fold.push((start..end, FoldPlaceholder::test()));
}
log::info!("folding {:?}", to_fold);
let (mut writer, snapshot, edits) = self.write(inlay_snapshot, vec![]);

View File

@ -284,7 +284,7 @@ impl<'a> Iterator for InlayChunks<'a> {
self.output_offset.0 += prefix.len();
let mut prefix = Chunk {
text: prefix,
..*chunk
..chunk.clone()
};
if !self.active_highlights.is_empty() {
let mut highlight_style = HighlightStyle::default();

View File

@ -508,7 +508,7 @@ impl<'a> Iterator for TabChunks<'a> {
self.chunk.text = suffix;
return Some(Chunk {
text: prefix,
..self.chunk
..self.chunk.clone()
});
} else {
self.chunk.text = &self.chunk.text[1..];
@ -529,7 +529,7 @@ impl<'a> Iterator for TabChunks<'a> {
return Some(Chunk {
text: &SPACES[..len as usize],
is_tab: true,
..self.chunk
..self.chunk.clone()
});
}
}

View File

@ -812,7 +812,7 @@ impl<'a> Iterator for WrapChunks<'a> {
self.transforms.next(&());
return Some(Chunk {
text: &display_text[start_ix..end_ix],
..self.input_chunk
..self.input_chunk.clone()
});
}
@ -842,7 +842,7 @@ impl<'a> Iterator for WrapChunks<'a> {
self.input_chunk.text = suffix;
Some(Chunk {
text: prefix,
..self.input_chunk
..self.input_chunk.clone()
})
}
}

View File

@ -52,8 +52,8 @@ use clock::ReplicaId;
use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque};
use convert_case::{Case, Casing};
use debounced_delay::DebouncedDelay;
pub use display_map::DisplayPoint;
use display_map::*;
pub use display_map::{DisplayPoint, FoldPlaceholder};
use editor_settings::CurrentLineHighlight;
pub use editor_settings::EditorSettings;
use element::LineWithInvisibles;
@ -1577,8 +1577,48 @@ impl Editor {
) -> Self {
let style = cx.text_style();
let font_size = style.font_size.to_pixels(cx.rem_size());
let editor = cx.view().downgrade();
let fold_placeholder = FoldPlaceholder {
constrain_width: true,
render: Arc::new(move |fold_id, fold_range, cx| {
let editor = editor.clone();
div()
.id(fold_id)
.bg(cx.theme().colors().ghost_element_background)
.hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
.active(|style| style.bg(cx.theme().colors().ghost_element_active))
.rounded_sm()
.size_full()
.cursor_pointer()
.child("")
.on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
.on_click(move |_, cx| {
editor
.update(cx, |editor, cx| {
editor.unfold_ranges(
[fold_range.start..fold_range.end],
true,
false,
cx,
);
cx.stop_propagation();
})
.ok();
})
.into_any()
}),
};
let display_map = cx.new_model(|cx| {
DisplayMap::new(buffer.clone(), style.font(), font_size, None, 2, 1, cx)
DisplayMap::new(
buffer.clone(),
style.font(),
font_size,
None,
2,
1,
fold_placeholder,
cx,
)
});
let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
@ -5826,7 +5866,7 @@ impl Editor {
let mut end = fold.range.end.to_point(&buffer);
start.row -= row_delta;
end.row -= row_delta;
refold_ranges.push((start..end, fold.text));
refold_ranges.push((start..end, fold.placeholder.clone()));
}
}
}
@ -5920,7 +5960,7 @@ impl Editor {
let mut end = fold.range.end.to_point(&buffer);
start.row += row_delta;
end.row += row_delta;
refold_ranges.push((start..end, fold.text));
refold_ranges.push((start..end, fold.placeholder.clone()));
}
}
}
@ -9298,14 +9338,14 @@ impl Editor {
let buffer_row = fold_at.buffer_row;
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
if let Some((fold_range, fold_text)) = display_map.foldable_range(buffer_row) {
if let Some((fold_range, placeholder)) = display_map.foldable_range(buffer_row) {
let autoscroll = self
.selections
.all::<Point>(cx)
.iter()
.any(|selection| fold_range.overlaps(&selection.range()));
self.fold_ranges([(fold_range, fold_text)], autoscroll, cx);
self.fold_ranges([(fold_range, placeholder)], autoscroll, cx);
}
}
@ -9359,9 +9399,9 @@ impl Editor {
.buffer_snapshot
.line_len(MultiBufferRow(s.end.row)),
);
(start..end, "")
(start..end, display_map.fold_placeholder.clone())
} else {
(s.start..s.end, "")
(s.start..s.end, display_map.fold_placeholder.clone())
}
});
self.fold_ranges(ranges, true, cx);
@ -9369,7 +9409,7 @@ impl Editor {
pub fn fold_ranges<T: ToOffset + Clone>(
&mut self,
ranges: impl IntoIterator<Item = (Range<T>, &'static str)>,
ranges: impl IntoIterator<Item = (Range<T>, FoldPlaceholder)>,
auto_scroll: bool,
cx: &mut ViewContext<Self>,
) {

View File

@ -496,8 +496,8 @@ fn test_clone(cx: &mut TestAppContext) {
editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
editor.fold_ranges(
[
(Point::new(1, 0)..Point::new(2, 0), ""),
(Point::new(3, 0)..Point::new(4, 0), ""),
(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
],
true,
cx,
@ -905,9 +905,9 @@ fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
_ = view.update(cx, |view, cx| {
view.fold_ranges(
vec![
(Point::new(0, 6)..Point::new(0, 12), ""),
(Point::new(1, 2)..Point::new(1, 4), ""),
(Point::new(2, 4)..Point::new(2, 8), ""),
(Point::new(0, 6)..Point::new(0, 12), FoldPlaceholder::test()),
(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
],
true,
cx,
@ -3409,9 +3409,9 @@ fn test_move_line_up_down(cx: &mut TestAppContext) {
_ = view.update(cx, |view, cx| {
view.fold_ranges(
vec![
(Point::new(0, 2)..Point::new(1, 2), ""),
(Point::new(2, 3)..Point::new(4, 1), ""),
(Point::new(7, 0)..Point::new(8, 4), ""),
(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
],
true,
cx,
@ -3893,9 +3893,9 @@ fn test_split_selection_into_lines(cx: &mut TestAppContext) {
_ = view.update(cx, |view, cx| {
view.fold_ranges(
vec![
(Point::new(0, 2)..Point::new(1, 2), ""),
(Point::new(2, 3)..Point::new(4, 1), ""),
(Point::new(7, 0)..Point::new(8, 4), ""),
(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
],
true,
cx,
@ -4550,8 +4550,14 @@ async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
_ = view.update(cx, |view, cx| {
view.fold_ranges(
vec![
(Point::new(0, 21)..Point::new(0, 24), ""),
(Point::new(3, 20)..Point::new(3, 22), ""),
(
Point::new(0, 21)..Point::new(0, 24),
FoldPlaceholder::test(),
),
(
Point::new(3, 20)..Point::new(3, 22),
FoldPlaceholder::test(),
),
],
true,
cx,
@ -11973,6 +11979,7 @@ fn test_flap_insertion_and_rendering(cx: &mut TestAppContext) {
let flap = Flap::new(
range,
FoldPlaceholder::test(),
{
let toggle_callback = render_args.clone();
move |row, folded, callback, _cx| {

View File

@ -23,7 +23,6 @@ use crate::{
LineDown, LineUp, OpenExcerpts, PageDown, PageUp, Point, RowExt, RowRangeExt, SelectPhase,
Selection, SoftWrap, ToPoint, CURSORS_VISIBLE_FOR, MAX_LINE_LEN,
};
use anyhow::Result;
use client::ParticipantIndex;
use collections::{BTreeMap, HashMap};
use git::{blame::BlameEntry, diff::DiffHunkStatus, Oid};
@ -31,11 +30,11 @@ use gpui::{
anchored, deferred, div, fill, outline, point, px, quad, relative, size, svg,
transparent_black, Action, AnchorCorner, AnyElement, AvailableSpace, Bounds, ClipboardItem,
ContentMask, Corners, CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Entity,
GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement, ModifiersChangedEvent,
FontId, GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement, ModifiersChangedEvent,
MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, ParentElement, Pixels,
ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size, Stateful,
StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, TextStyleRefinement, View,
ViewContext, WeakView, WindowContext,
ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveElement,
Style, Styled, TextRun, TextStyle, TextStyleRefinement, View, ViewContext, WeakView,
WindowContext,
};
use itertools::Itertools;
use language::language_settings::{
@ -48,12 +47,12 @@ use project::{
ProjectPath,
};
use settings::Settings;
use smallvec::SmallVec;
use smallvec::{smallvec, SmallVec};
use std::{
any::TypeId,
borrow::Cow,
cmp::{self, Ordering},
fmt::Write,
fmt::{self, Write},
iter, mem,
ops::{Deref, Range},
sync::Arc,
@ -857,76 +856,6 @@ impl EditorElement {
(selections, active_rows, newest_selection_head)
}
#[allow(clippy::too_many_arguments)]
fn layout_folds(
&self,
snapshot: &EditorSnapshot,
content_origin: gpui::Point<Pixels>,
visible_anchor_range: Range<Anchor>,
visible_display_row_range: Range<DisplayRow>,
scroll_pixel_position: gpui::Point<Pixels>,
line_height: Pixels,
line_layouts: &[LineWithInvisibles],
cx: &mut WindowContext,
) -> Vec<FoldLayout> {
snapshot
.folds_in_range(visible_anchor_range.clone())
.filter_map(|fold| {
// Skip folds that have no text.
if fold.text.is_empty() {
return None;
}
let fold_range = fold.range.clone();
let display_range = fold.range.start.to_display_point(&snapshot)
..fold.range.end.to_display_point(&snapshot);
debug_assert_eq!(display_range.start.row(), display_range.end.row());
let row = display_range.start.row();
debug_assert!(row < visible_display_row_range.end);
let line_layout = line_layouts
.get(row.minus(visible_display_row_range.start) as usize)
.map(|l| &l.line)?;
let start_x = content_origin.x
+ line_layout.x_for_index(display_range.start.column() as usize)
- scroll_pixel_position.x;
let start_y =
content_origin.y + row.as_f32() * line_height - scroll_pixel_position.y;
let end_x = content_origin.x
+ line_layout.x_for_index(display_range.end.column() as usize)
- scroll_pixel_position.x;
let fold_bounds = Bounds {
origin: point(start_x, start_y),
size: size(end_x - start_x, line_height),
};
let mut hover_element = div()
.id(fold.id)
.size_full()
.cursor_pointer()
.on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
.on_click(
cx.listener_for(&self.editor, move |editor: &mut Editor, _, cx| {
editor.unfold_ranges(
[fold_range.start..fold_range.end],
true,
false,
cx,
);
cx.stop_propagation();
}),
)
.into_any();
hover_element.prepaint_as_root(fold_bounds.origin, fold_bounds.size.into(), cx);
Some(FoldLayout {
display_range,
hover_element,
})
})
.collect()
}
fn collect_cursors(
&self,
snapshot: &EditorSnapshot,
@ -994,8 +923,7 @@ impl EditorElement {
}
let cursor_row_layout = &line_layouts
[cursor_position.row().minus(visible_display_row_range.start) as usize]
.line;
[cursor_position.row().minus(visible_display_row_range.start) as usize];
let cursor_column = cursor_position.column() as usize;
let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
@ -1228,7 +1156,7 @@ impl EditorElement {
);
let size = element.layout_as_root(available_space, cx);
let line = &lines[ix].line;
let line = &lines[ix];
let padding = if line.width == Pixels::ZERO {
Pixels::ZERO
} else {
@ -1373,7 +1301,7 @@ impl EditorElement {
let line_end = if let Some(flap_trailer) = flap_trailer {
flap_trailer.bounds.right()
} else {
content_origin.x - scroll_pixel_position.x + line_layout.line.width
content_origin.x - scroll_pixel_position.x + line_layout.width
};
let padded_line_end = line_end + em_width * INLINE_BLAME_PADDING_EM_WIDTHS;
@ -1860,7 +1788,7 @@ impl EditorElement {
rows: Range<DisplayRow>,
line_number_layouts: &[Option<ShapedLine>],
snapshot: &EditorSnapshot,
cx: &WindowContext,
cx: &mut WindowContext,
) -> Vec<LineWithInvisibles> {
if rows.start >= rows.end {
return Vec::new();
@ -1894,8 +1822,11 @@ impl EditorElement {
.log_err()
})
.map(|line| LineWithInvisibles {
line,
width: line.width,
len: line.len,
fragments: smallvec![LineFragment::Text(line)],
invisibles: Vec::new(),
font_size,
})
.collect()
} else {
@ -1912,6 +1843,30 @@ impl EditorElement {
}
}
fn prepaint_lines(
&self,
start_row: DisplayRow,
line_layouts: &mut [LineWithInvisibles],
line_height: Pixels,
scroll_pixel_position: gpui::Point<Pixels>,
content_origin: gpui::Point<Pixels>,
cx: &mut WindowContext,
) -> SmallVec<[AnyElement; 1]> {
let mut line_elements = SmallVec::new();
for (ix, line) in line_layouts.iter_mut().enumerate() {
let row = start_row + DisplayRow(ix as u32);
line.prepaint(
line_height,
scroll_pixel_position,
row,
content_origin,
&mut line_elements,
cx,
);
}
line_elements
}
#[allow(clippy::too_many_arguments)]
fn build_blocks(
&self,
@ -1949,11 +1904,9 @@ impl EditorElement {
let anchor_x = text_x
+ if rows.contains(&align_to.row()) {
line_layouts[align_to.row().minus(rows.start) as usize]
.line
.x_for_index(align_to.column() as usize)
} else {
layout_line(align_to.row(), snapshot, &self.style, cx)
.unwrap()
.x_for_index(align_to.column() as usize)
};
@ -2310,7 +2263,7 @@ impl EditorElement {
let (x, y) = match position {
crate::ContextMenuOrigin::EditorPoint(point) => {
let cursor_row_layout = &line_layouts[point.row().minus(start_row) as usize].line;
let cursor_row_layout = &line_layouts[point.row().minus(start_row) as usize];
let x = cursor_row_layout.x_for_index(point.column() as usize)
- scroll_pixel_position.x;
let y = point.row().next_row().as_f32() * line_height - scroll_pixel_position.y;
@ -2406,7 +2359,7 @@ impl EditorElement {
// This is safe because we check on layout whether the required row is available
let hovered_row_layout =
&line_layouts[position.row().minus(visible_display_row_range.start) as usize].line;
&line_layouts[position.row().minus(visible_display_row_range.start) as usize];
// Compute Hovered Point
let x =
@ -2920,7 +2873,6 @@ impl EditorElement {
};
cx.set_cursor_style(cursor_style, &layout.text_hitbox);
cx.with_element_namespace("folds", |cx| self.paint_folds(layout, cx));
let invisible_display_ranges = self.paint_highlights(layout, cx);
self.paint_lines(&invisible_display_ranges, layout, cx);
self.paint_redactions(layout, cx);
@ -2979,7 +2931,7 @@ impl EditorElement {
fn paint_lines(
&mut self,
invisible_display_ranges: &[Range<DisplayPoint>],
layout: &EditorLayout,
layout: &mut EditorLayout,
cx: &mut WindowContext,
) {
let whitespace_setting = self
@ -3001,6 +2953,10 @@ impl EditorElement {
cx,
)
}
for line_element in &mut layout.line_elements {
line_element.paint(cx);
}
}
fn paint_redactions(&mut self, layout: &EditorLayout, cx: &mut WindowContext) {
@ -3378,7 +3334,7 @@ impl EditorElement {
.iter_rows()
.map(|row| {
let line_layout =
&layout.position_map.line_layouts[row.minus(start_row) as usize].line;
&layout.position_map.line_layouts[row.minus(start_row) as usize];
HighlightedRangeLine {
start_x: if row == range.start.row() {
layout.content_origin.x
@ -3405,37 +3361,6 @@ impl EditorElement {
}
}
fn paint_folds(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
if layout.folds.is_empty() {
return;
}
cx.paint_layer(layout.text_hitbox.bounds, |cx| {
let fold_corner_radius = 0.15 * layout.position_map.line_height;
for mut fold in mem::take(&mut layout.folds) {
fold.hover_element.paint(cx);
let hover_element = fold.hover_element.downcast_mut::<Stateful<Div>>().unwrap();
let fold_background = if hover_element.interactivity().active.unwrap() {
cx.theme().colors().ghost_element_active
} else if hover_element.interactivity().hovered.unwrap() {
cx.theme().colors().ghost_element_hover
} else {
cx.theme().colors().ghost_element_background
};
self.paint_highlighted_range(
fold.display_range.clone(),
fold_background,
fold_corner_radius,
fold_corner_radius * 2.,
layout,
cx,
);
}
})
}
fn paint_inline_blame(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
if let Some(mut inline_blame) = layout.inline_blame.take() {
cx.paint_layer(layout.text_hitbox.bounds, |cx| {
@ -3813,8 +3738,34 @@ fn deploy_blame_entry_context_menu(
#[derive(Debug)]
pub(crate) struct LineWithInvisibles {
pub line: ShapedLine,
fragments: SmallVec<[LineFragment; 1]>,
invisibles: Vec<Invisible>,
len: usize,
width: Pixels,
font_size: Pixels,
}
#[allow(clippy::large_enum_variant)]
enum LineFragment {
Text(ShapedLine),
Element {
element: Option<AnyElement>,
width: Pixels,
len: usize,
},
}
impl fmt::Debug for LineFragment {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
LineFragment::Text(shaped_line) => f.debug_tuple("Text").field(shaped_line).finish(),
LineFragment::Element { width, len, .. } => f
.debug_struct("Element")
.field("width", width)
.field("len", len)
.finish(),
}
}
}
impl LineWithInvisibles {
@ -3825,100 +3776,161 @@ impl LineWithInvisibles {
max_line_count: usize,
line_number_layouts: &[Option<ShapedLine>],
editor_mode: EditorMode,
cx: &WindowContext,
cx: &mut WindowContext,
) -> Vec<Self> {
let mut layouts = Vec::with_capacity(max_line_count);
let mut fragments: SmallVec<[LineFragment; 1]> = SmallVec::new();
let mut line = String::new();
let mut invisibles = Vec::new();
let mut width = Pixels::ZERO;
let mut len = 0;
let mut styles = Vec::new();
let mut non_whitespace_added = false;
let mut row = 0;
let mut line_exceeded_max_len = false;
let font_size = text_style.font_size.to_pixels(cx.rem_size());
let ellipsis = SharedString::from("");
for highlighted_chunk in chunks.chain([HighlightedChunk {
chunk: "\n",
text: "\n",
style: None,
is_tab: false,
renderer: None,
}]) {
for (ix, mut line_chunk) in highlighted_chunk.chunk.split('\n').enumerate() {
if ix > 0 {
if let Some(renderer) = highlighted_chunk.renderer {
if !line.is_empty() {
let shaped_line = cx
.text_system()
.shape_line(line.clone().into(), font_size, &styles)
.unwrap();
layouts.push(Self {
line: shaped_line,
invisibles: std::mem::take(&mut invisibles),
});
width += shaped_line.width;
len += shaped_line.len;
fragments.push(LineFragment::Text(shaped_line));
line.clear();
styles.clear();
row += 1;
line_exceeded_max_len = false;
non_whitespace_added = false;
if row == max_line_count {
return layouts;
}
}
if !line_chunk.is_empty() && !line_exceeded_max_len {
let text_style = if let Some(style) = highlighted_chunk.style {
Cow::Owned(text_style.clone().highlight(style))
let available_width = if renderer.constrain_width {
let chunk = if highlighted_chunk.text == ellipsis.as_ref() {
ellipsis.clone()
} else {
Cow::Borrowed(text_style)
SharedString::from(Arc::from(highlighted_chunk.text))
};
let shaped_line = cx
.text_system()
.shape_line(
chunk,
font_size,
&[text_style.to_run(highlighted_chunk.text.len())],
)
.unwrap();
AvailableSpace::Definite(shaped_line.width)
} else {
AvailableSpace::MinContent
};
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;
let mut element = (renderer.render)(cx);
let line_height = text_style.line_height_in_pixels(cx.rem_size());
let size = element.layout_as_root(
size(available_width, AvailableSpace::Definite(line_height)),
cx,
);
width += size.width;
len += highlighted_chunk.text.len();
fragments.push(LineFragment::Element {
element: Some(element),
width: size.width,
len: highlighted_chunk.text.len(),
});
} else {
for (ix, mut line_chunk) in highlighted_chunk.text.split('\n').enumerate() {
if ix > 0 {
let shaped_line = cx
.text_system()
.shape_line(line.clone().into(), font_size, &styles)
.unwrap();
width += shaped_line.width;
len += shaped_line.len;
fragments.push(LineFragment::Text(shaped_line));
layouts.push(Self {
width: mem::take(&mut width),
len: mem::take(&mut len),
fragments: mem::take(&mut fragments),
invisibles: std::mem::take(&mut invisibles),
font_size,
});
line.clear();
styles.clear();
row += 1;
line_exceeded_max_len = false;
non_whitespace_added = false;
if row == max_line_count {
return layouts;
}
line_chunk = &line_chunk[..chunk_len];
line_exceeded_max_len = true;
}
styles.push(TextRun {
len: line_chunk.len(),
font: text_style.font(),
color: text_style.color,
background_color: text_style.background_color,
underline: text_style.underline,
strikethrough: text_style.strikethrough,
});
if editor_mode == EditorMode::Full {
// Line wrap pads its contents with fake whitespaces,
// avoid printing them
let inside_wrapped_string = line_number_layouts
.get(row)
.and_then(|layout| layout.as_ref())
.is_none();
if highlighted_chunk.is_tab {
if non_whitespace_added || !inside_wrapped_string {
invisibles.push(Invisible::Tab {
line_start_offset: line.len(),
});
}
if !line_chunk.is_empty() && !line_exceeded_max_len {
let text_style = if let Some(style) = highlighted_chunk.style {
Cow::Owned(text_style.clone().highlight(style))
} else {
invisibles.extend(
line_chunk
.bytes()
.enumerate()
.filter(|(_, line_char)| {
let is_whitespace = (*line_char as char).is_whitespace();
non_whitespace_added |= !is_whitespace;
is_whitespace
&& (non_whitespace_added || !inside_wrapped_string)
})
.map(|(whitespace_index, _)| Invisible::Whitespace {
line_offset: line.len() + whitespace_index,
}),
)
}
}
Cow::Borrowed(text_style)
};
line.push_str(line_chunk);
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;
}
styles.push(TextRun {
len: line_chunk.len(),
font: text_style.font(),
color: text_style.color,
background_color: text_style.background_color,
underline: text_style.underline,
strikethrough: text_style.strikethrough,
});
if editor_mode == EditorMode::Full {
// Line wrap pads its contents with fake whitespaces,
// avoid printing them
let inside_wrapped_string = line_number_layouts
.get(row)
.and_then(|layout| layout.as_ref())
.is_none();
if highlighted_chunk.is_tab {
if non_whitespace_added || !inside_wrapped_string {
invisibles.push(Invisible::Tab {
line_start_offset: line.len(),
});
}
} else {
invisibles.extend(
line_chunk
.bytes()
.enumerate()
.filter(|(_, line_byte)| {
let is_whitespace =
(*line_byte as char).is_whitespace();
non_whitespace_added |= !is_whitespace;
is_whitespace
&& (non_whitespace_added || !inside_wrapped_string)
})
.map(|(whitespace_index, _)| Invisible::Whitespace {
line_offset: line.len() + whitespace_index,
}),
)
}
}
line.push_str(line_chunk);
}
}
}
}
@ -3926,6 +3938,34 @@ impl LineWithInvisibles {
layouts
}
fn prepaint(
&mut self,
line_height: Pixels,
scroll_pixel_position: gpui::Point<Pixels>,
row: DisplayRow,
content_origin: gpui::Point<Pixels>,
line_elements: &mut SmallVec<[AnyElement; 1]>,
cx: &mut WindowContext,
) {
let line_y = line_height * (row.as_f32() - scroll_pixel_position.y / line_height);
let mut fragment_origin = content_origin + gpui::point(-scroll_pixel_position.x, line_y);
for fragment in &mut self.fragments {
match fragment {
LineFragment::Text(line) => {
fragment_origin.x += line.width;
}
LineFragment::Element { element, width, .. } => {
let mut element = element
.take()
.expect("you can't prepaint LineWithInvisibles twice");
element.prepaint_at(fragment_origin, cx);
line_elements.push(element);
fragment_origin.x += *width;
}
}
}
}
fn draw(
&self,
layout: &EditorLayout,
@ -3939,9 +3979,20 @@ impl LineWithInvisibles {
let line_y = line_height
* (row.as_f32() - layout.position_map.scroll_pixel_position.y / line_height);
let line_origin =
let mut fragment_origin =
content_origin + gpui::point(-layout.position_map.scroll_pixel_position.x, line_y);
self.line.paint(line_origin, line_height, cx).log_err();
for fragment in &self.fragments {
match fragment {
LineFragment::Text(line) => {
line.paint(fragment_origin, line_height, cx).log_err();
fragment_origin.x += line.width;
}
LineFragment::Element { width, .. } => {
fragment_origin.x += *width;
}
}
}
self.draw_invisibles(
&selection_ranges,
@ -3979,7 +4030,7 @@ impl LineWithInvisibles {
Invisible::Whitespace { line_offset } => (line_offset, &layout.space_invisible),
};
let x_offset = self.line.x_for_index(token_offset);
let x_offset = self.x_for_index(token_offset);
let invisible_offset =
(layout.position_map.em_width - invisible_symbol.width).max(Pixels::ZERO) / 2.0;
let origin = content_origin
@ -4000,6 +4051,90 @@ impl LineWithInvisibles {
invisible_symbol.paint(origin, line_height, cx).log_err();
}
}
pub fn x_for_index(&self, index: usize) -> Pixels {
let mut fragment_start_x = Pixels::ZERO;
let mut fragment_start_index = 0;
for fragment in &self.fragments {
match fragment {
LineFragment::Text(shaped_line) => {
let fragment_end_index = fragment_start_index + shaped_line.len;
if index < fragment_end_index {
return fragment_start_x
+ shaped_line.x_for_index(index - fragment_start_index);
}
fragment_start_x += shaped_line.width;
fragment_start_index = fragment_end_index;
}
LineFragment::Element { len, width, .. } => {
let fragment_end_index = fragment_start_index + len;
if index < fragment_end_index {
return fragment_start_x;
}
fragment_start_x += *width;
fragment_start_index = fragment_end_index;
}
}
}
fragment_start_x
}
pub fn index_for_x(&self, x: Pixels) -> Option<usize> {
let mut fragment_start_x = Pixels::ZERO;
let mut fragment_start_index = 0;
for fragment in &self.fragments {
match fragment {
LineFragment::Text(shaped_line) => {
let fragment_end_x = fragment_start_x + shaped_line.width;
if x < fragment_end_x {
return Some(
fragment_start_index + shaped_line.index_for_x(x - fragment_start_x)?,
);
}
fragment_start_x = fragment_end_x;
fragment_start_index += shaped_line.len;
}
LineFragment::Element { len, width, .. } => {
let fragment_end_x = fragment_start_x + *width;
if x < fragment_end_x {
return Some(fragment_start_index);
}
fragment_start_index += len;
fragment_start_x = fragment_end_x;
}
}
}
None
}
pub fn font_id_for_index(&self, index: usize) -> Option<FontId> {
let mut fragment_start_index = 0;
for fragment in &self.fragments {
match fragment {
LineFragment::Text(shaped_line) => {
let fragment_end_index = fragment_start_index + shaped_line.len;
if index < fragment_end_index {
return shaped_line.font_id_for_index(index - fragment_start_index);
}
fragment_start_index = fragment_end_index;
}
LineFragment::Element { len, .. } => {
let fragment_end_index = fragment_start_index + len;
if index < fragment_end_index {
return None;
}
fragment_start_index = fragment_end_index;
}
}
}
None
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -4309,18 +4444,16 @@ impl Element for EditorElement {
);
let mut max_visible_line_width = Pixels::ZERO;
let line_layouts =
let mut 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;
if line_with_invisibles.width > max_visible_line_width {
max_visible_line_width = line_with_invisibles.width;
}
}
let longest_line_width =
layout_line(snapshot.longest_row(), &snapshot, &style, cx)
.unwrap()
.width;
layout_line(snapshot.longest_row(), &snapshot, &style, cx).width;
let mut scroll_width =
longest_line_width.max(max_visible_line_width) + overscroll.width;
@ -4430,6 +4563,15 @@ impl Element for EditorElement {
}
});
let line_elements = self.prepaint_lines(
start_row,
&mut line_layouts,
line_height,
scroll_pixel_position,
content_origin,
cx,
);
cx.with_element_namespace("blocks", |cx| {
self.layout_blocks(
&mut blocks,
@ -4470,19 +4612,6 @@ impl Element for EditorElement {
cx,
);
let folds = cx.with_element_namespace("folds", |cx| {
self.layout_folds(
&snapshot,
content_origin,
start_anchor..end_anchor,
start_row..end_row,
scroll_pixel_position,
line_height,
&line_layouts,
cx,
)
});
let gutter_settings = EditorSettings::get_global(cx).gutter;
let mut context_menu_visible = false;
@ -4628,11 +4757,11 @@ impl Element for EditorElement {
highlighted_rows,
highlighted_ranges,
redacted_ranges,
line_elements,
line_numbers,
display_hunks,
blamed_display_rows,
inline_blame,
folds,
blocks,
cursors,
visible_cursors,
@ -4750,11 +4879,11 @@ pub struct EditorLayout {
visible_display_row_range: Range<DisplayRow>,
active_rows: BTreeMap<DisplayRow, bool>,
highlighted_rows: BTreeMap<DisplayRow, Hsla>,
line_elements: SmallVec<[AnyElement; 1]>,
line_numbers: Vec<Option<ShapedLine>>,
display_hunks: Vec<(DisplayDiffHunk, Option<Hitbox>)>,
blamed_display_rows: Option<Vec<AnyElement>>,
inline_blame: Option<AnyElement>,
folds: Vec<FoldLayout>,
blocks: Vec<BlockLayout>,
highlighted_ranges: Vec<(Range<DisplayPoint>, Hsla)>,
redacted_ranges: Vec<Range<DisplayPoint>>,
@ -4893,11 +5022,6 @@ struct FlapTrailerLayout {
bounds: Bounds<Pixels>,
}
struct FoldLayout {
display_range: Range<DisplayPoint>,
hover_element: AnyElement,
}
struct PositionMap {
size: Size<Pixels>,
line_height: Pixels,
@ -4942,7 +5066,6 @@ impl PositionMap {
let (column, x_overshoot_after_line_end) = if let Some(line) = self
.line_layouts
.get(row as usize - scroll_position.y as usize)
.map(|LineWithInvisibles { line, .. }| line)
{
if let Some(ix) = line.index_for_x(x) {
(ix as u32, px(0.))
@ -4979,37 +5102,12 @@ fn layout_line(
row: DisplayRow,
snapshot: &EditorSnapshot,
style: &EditorStyle,
cx: &WindowContext,
) -> Result<ShapedLine> {
let mut line = snapshot.line(row);
let len = {
let line_len = line.len();
if line_len > MAX_LINE_LEN {
let mut len = MAX_LINE_LEN;
while !line.is_char_boundary(len) {
len -= 1;
}
line.truncate(len);
len
} else {
line_len
}
};
cx.text_system().shape_line(
line.into(),
style.text.font_size.to_pixels(cx.rem_size()),
&[TextRun {
len,
font: style.text.font(),
color: Hsla::default(),
background_color: None,
underline: None,
strikethrough: None,
}],
)
cx: &mut WindowContext,
) -> LineWithInvisibles {
let chunks = snapshot.highlighted_chunks(row..row + DisplayRow(1), true, style);
LineWithInvisibles::from_chunks(chunks, &style.text, MAX_LINE_LEN, 1, &[], snapshot.mode, cx)
.pop()
.unwrap()
}
#[derive(Debug)]

View File

@ -577,7 +577,7 @@ mod tests {
use crate::{
display_map::Inlay,
test::{editor_test_context::EditorTestContext, marked_display_snapshot},
Buffer, DisplayMap, DisplayRow, ExcerptRange, InlayId, MultiBuffer,
Buffer, DisplayMap, DisplayRow, ExcerptRange, FoldPlaceholder, InlayId, MultiBuffer,
};
use gpui::{font, Context as _};
use language::Capability;
@ -695,8 +695,18 @@ mod tests {
let font_size = px(14.0);
let buffer = MultiBuffer::build_simple(input_text, cx);
let buffer_snapshot = buffer.read(cx).snapshot(cx);
let display_map =
cx.new_model(|cx| DisplayMap::new(buffer, font, font_size, None, 1, 1, cx));
let display_map = cx.new_model(|cx| {
DisplayMap::new(
buffer,
font,
font_size,
None,
1,
1,
FoldPlaceholder::test(),
cx,
)
});
// add all kinds of inlays between two word boundaries: we should be able to cross them all, when looking for another boundary
let mut id = 0;
@ -901,8 +911,18 @@ mod tests {
);
multibuffer
});
let display_map =
cx.new_model(|cx| DisplayMap::new(multibuffer, font, px(14.0), None, 2, 2, cx));
let display_map = cx.new_model(|cx| {
DisplayMap::new(
multibuffer,
font,
px(14.0),
None,
2,
2,
FoldPlaceholder::test(),
cx,
)
});
let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx));
assert_eq!(snapshot.text(), "\n\nabc\ndefg\n\n\nhijkl\nmn");

View File

@ -251,12 +251,10 @@ impl Editor {
let end_column = cmp::min(display_map.line_len(head.row()), head.column() + 3);
target_left = target_left.min(
layouts[head.row().minus(start_row) as usize]
.line
.x_for_index(start_column as usize),
);
target_right = target_right.max(
layouts[head.row().minus(start_row) as usize]
.line
.x_for_index(end_column as usize)
+ max_glyph_width,
);

View File

@ -3,11 +3,9 @@ pub mod editor_test_context;
use crate::{
display_map::{DisplayMap, DisplaySnapshot, ToDisplayPoint},
DisplayPoint, Editor, EditorMode, MultiBuffer,
DisplayPoint, Editor, EditorMode, FoldPlaceholder, MultiBuffer,
};
use gpui::{Context, Font, FontFeatures, FontStyle, FontWeight, Model, Pixels, ViewContext};
use project::Project;
use util::test::{marked_text_offsets, marked_text_ranges};
@ -35,7 +33,18 @@ pub fn marked_display_snapshot(
let font_size: Pixels = 14usize.into();
let buffer = MultiBuffer::build_simple(&unmarked_text, cx);
let display_map = cx.new_model(|cx| DisplayMap::new(buffer, font, font_size, None, 1, 1, cx));
let display_map = cx.new_model(|cx| {
DisplayMap::new(
buffer,
font,
font_size,
None,
1,
1,
FoldPlaceholder::test(),
cx,
)
});
let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx));
let markers = markers
.into_iter()

View File

@ -143,7 +143,7 @@ impl StyledText {
}
}
/// todo!()
/// Get the layout for this element. This can be used to map indices to pixels and vice versa.
pub fn layout(&self) -> &TextLayout {
&self.layout
}
@ -232,7 +232,7 @@ impl IntoElement for StyledText {
}
}
/// todo!()
/// The Layout for TextElement. This can be used to map indices to pixels and vice versa.
#[derive(Default, Clone)]
pub struct TextLayout(Arc<Mutex<Option<TextLayoutInner>>>);
@ -358,7 +358,7 @@ impl TextLayout {
}
}
/// todo!()
/// Get the byte index into the input of the pixel position.
pub fn index_for_position(&self, mut position: Point<Pixels>) -> Result<usize, usize> {
let element_state = self.lock();
let element_state = element_state
@ -392,7 +392,7 @@ impl TextLayout {
Err(line_start_ix.saturating_sub(1))
}
/// todo!()
/// Get the pixel position for the given byte index.
pub fn position_for_index(&self, index: usize) -> Option<Point<Pixels>> {
let element_state = self.lock();
let element_state = element_state
@ -423,17 +423,17 @@ impl TextLayout {
None
}
/// todo!()
/// The bounds of this layout.
pub fn bounds(&self) -> Bounds<Pixels> {
self.0.lock().as_ref().unwrap().bounds.unwrap()
}
/// todo!()
/// The line height for this layout.
pub fn line_height(&self) -> Pixels {
self.0.lock().as_ref().unwrap().line_height
}
/// todo!()
/// The text for this layout.
pub fn text(&self) -> String {
self.0
.lock()

View File

@ -302,7 +302,7 @@ impl WrappedLineLayout {
}
}
/// todo!()
/// Returns the pixel position for the given byte index.
pub fn position_for_index(&self, index: usize, line_height: Pixels) -> Option<Point<Pixels>> {
let mut line_start_ix = 0;
let mut line_end_indices = self

View File

@ -19,7 +19,10 @@ use crate::{
use anyhow::{anyhow, Context, Result};
pub use clock::ReplicaId;
use futures::channel::oneshot;
use gpui::{AppContext, EventEmitter, HighlightStyle, ModelContext, Task, TaskLabel};
use gpui::{
AnyElement, AppContext, EventEmitter, HighlightStyle, ModelContext, Task, TaskLabel,
WindowContext,
};
use lazy_static::lazy_static;
use lsp::LanguageServerId;
use parking_lot::Mutex;
@ -31,6 +34,7 @@ use std::{
cmp::{self, Ordering},
collections::BTreeMap,
ffi::OsStr,
fmt,
future::Future,
iter::{self, Iterator, Peekable},
mem,
@ -461,7 +465,7 @@ pub struct BufferChunks<'a> {
/// A chunk of a buffer's text, along with its syntax highlight and
/// diagnostic status.
#[derive(Clone, Copy, Debug, Default)]
#[derive(Clone, Debug, Default)]
pub struct Chunk<'a> {
/// The text of the chunk.
pub text: &'a str,
@ -476,6 +480,25 @@ pub struct Chunk<'a> {
pub is_unnecessary: bool,
/// Whether this chunk of text was originally a tab character.
pub is_tab: bool,
/// An optional recipe for how the chunk should be presented.
pub renderer: Option<ChunkRenderer>,
}
/// A recipe for how the chunk should be presented.
#[derive(Clone)]
pub struct ChunkRenderer {
/// creates a custom element to represent this chunk.
pub render: Arc<dyn Send + Sync + Fn(&mut WindowContext) -> AnyElement>,
/// If true, the element is constrained to the shaped width of the text.
pub constrain_width: bool,
}
impl fmt::Debug for ChunkRenderer {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("ChunkRenderer")
.field("constrain_width", &self.constrain_width)
.finish()
}
}
/// A set of edits to a given version of a buffer, computed asynchronously.