mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-25 08:55:10 +03:00
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:
parent
e0cfba43aa
commit
57d570c281
@ -189,7 +189,7 @@ pub struct LanguageModelChoiceDelta {
|
||||
struct MessageMetadata {
|
||||
role: Role,
|
||||
status: MessageStatus,
|
||||
// todo!("delete this")
|
||||
// TODO: Delete this
|
||||
#[serde(skip)]
|
||||
ambient_context: AmbientContextSnapshot,
|
||||
}
|
||||
|
@ -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,
|
||||
)],
|
||||
|
@ -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()
|
||||
|
@ -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(),
|
||||
|
@ -875,7 +875,7 @@ impl<'a> Iterator for BlockChunks<'a> {
|
||||
|
||||
Some(Chunk {
|
||||
text: prefix,
|
||||
..self.input_chunk
|
||||
..self.input_chunk.clone()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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(),
|
||||
),
|
||||
|
@ -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![]);
|
||||
|
@ -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();
|
||||
|
@ -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()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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>,
|
||||
) {
|
||||
|
@ -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| {
|
||||
|
@ -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)]
|
||||
|
@ -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");
|
||||
|
@ -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,
|
||||
);
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user