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
19 changed files with 774 additions and 437 deletions
|
@ -189,7 +189,7 @@ pub struct LanguageModelChoiceDelta {
|
||||||
struct MessageMetadata {
|
struct MessageMetadata {
|
||||||
role: Role,
|
role: Role,
|
||||||
status: MessageStatus,
|
status: MessageStatus,
|
||||||
// todo!("delete this")
|
// TODO: Delete this
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
ambient_context: AmbientContextSnapshot,
|
ambient_context: AmbientContextSnapshot,
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ use crate::{
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use client::telemetry::Telemetry;
|
use client::telemetry::Telemetry;
|
||||||
use collections::{hash_map, HashMap, HashSet, VecDeque};
|
use collections::{hash_map, HashMap, HashSet, VecDeque};
|
||||||
|
use editor::FoldPlaceholder;
|
||||||
use editor::{
|
use editor::{
|
||||||
actions::{FoldAt, MoveDown, MoveUp},
|
actions::{FoldAt, MoveDown, MoveUp},
|
||||||
display_map::{
|
display_map::{
|
||||||
|
@ -34,7 +35,7 @@ use fs::Fs;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
canvas, div, point, relative, rems, uniform_list, Action, AnyElement, AnyView, AppContext,
|
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,
|
EventEmitter, FocusHandle, FocusableView, FontStyle, FontWeight, HighlightStyle,
|
||||||
InteractiveElement, IntoElement, Model, ModelContext, ParentElement, Pixels, Render,
|
InteractiveElement, IntoElement, Model, ModelContext, ParentElement, Pixels, Render,
|
||||||
SharedString, StatefulInteractiveElement, Styled, Subscription, Task, TextStyle,
|
SharedString, StatefulInteractiveElement, Styled, Subscription, Task, TextStyle,
|
||||||
|
@ -2943,6 +2944,10 @@ impl ConversationEditor {
|
||||||
.insert_flaps(
|
.insert_flaps(
|
||||||
[Flap::new(
|
[Flap::new(
|
||||||
start..end,
|
start..end,
|
||||||
|
FoldPlaceholder {
|
||||||
|
render: Arc::new(|_, _, _| Empty.into_any()),
|
||||||
|
constrain_width: false,
|
||||||
|
},
|
||||||
render_slash_command_output_toggle,
|
render_slash_command_output_toggle,
|
||||||
render_slash_command_output_trailer,
|
render_slash_command_output_trailer,
|
||||||
)],
|
)],
|
||||||
|
|
|
@ -869,7 +869,6 @@ impl AssistantChat {
|
||||||
crate::ui::ChatMessage::new(
|
crate::ui::ChatMessage::new(
|
||||||
*id,
|
*id,
|
||||||
UserOrAssistant::User(self.user_store.read(cx).current_user()),
|
UserOrAssistant::User(self.user_store.read(cx).current_user()),
|
||||||
// todo!(): clean up the vec usage
|
|
||||||
vec![
|
vec![
|
||||||
body.clone().into_any_element(),
|
body.clone().into_any_element(),
|
||||||
h_flex()
|
h_flex()
|
||||||
|
|
|
@ -24,17 +24,27 @@ mod inlay_map;
|
||||||
mod tab_map;
|
mod tab_map;
|
||||||
mod wrap_map;
|
mod wrap_map;
|
||||||
|
|
||||||
use crate::{hover_links::InlayHighlight, movement::TextLayoutDetails, InlayId};
|
use crate::{
|
||||||
use crate::{EditorStyle, RowExt};
|
hover_links::InlayHighlight, movement::TextLayoutDetails, EditorStyle, InlayId, 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 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::{
|
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 lsp::DiagnosticSeverity;
|
||||||
use multi_buffer::{
|
use multi_buffer::{
|
||||||
|
@ -42,27 +52,12 @@ use multi_buffer::{
|
||||||
ToOffset, ToPoint,
|
ToOffset, ToPoint,
|
||||||
};
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use std::ops::Add;
|
||||||
use std::{any::TypeId, borrow::Cow, fmt::Debug, num::NonZeroU32, ops::Range, sync::Arc};
|
use std::{any::TypeId, borrow::Cow, fmt::Debug, num::NonZeroU32, ops::Range, sync::Arc};
|
||||||
use sum_tree::{Bias, TreeMap};
|
use sum_tree::{Bias, TreeMap};
|
||||||
use tab_map::TabMap;
|
use tab_map::{TabMap, TabSnapshot};
|
||||||
use ui::WindowContext;
|
use ui::WindowContext;
|
||||||
|
use wrap_map::{WrapMap, WrapSnapshot};
|
||||||
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;
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum FoldStatus {
|
pub enum FoldStatus {
|
||||||
|
@ -105,10 +100,12 @@ pub struct DisplayMap {
|
||||||
inlay_highlights: InlayHighlights,
|
inlay_highlights: InlayHighlights,
|
||||||
/// A container for explicitly foldable ranges, which supersede indentation based fold range suggestions.
|
/// A container for explicitly foldable ranges, which supersede indentation based fold range suggestions.
|
||||||
flap_map: FlapMap,
|
flap_map: FlapMap,
|
||||||
|
fold_placeholder: FoldPlaceholder,
|
||||||
pub clip_at_line_ends: bool,
|
pub clip_at_line_ends: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DisplayMap {
|
impl DisplayMap {
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
buffer: Model<MultiBuffer>,
|
buffer: Model<MultiBuffer>,
|
||||||
font: Font,
|
font: Font,
|
||||||
|
@ -116,6 +113,7 @@ impl DisplayMap {
|
||||||
wrap_width: Option<Pixels>,
|
wrap_width: Option<Pixels>,
|
||||||
buffer_header_height: u8,
|
buffer_header_height: u8,
|
||||||
excerpt_header_height: u8,
|
excerpt_header_height: u8,
|
||||||
|
fold_placeholder: FoldPlaceholder,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let buffer_subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
|
let buffer_subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
|
||||||
|
@ -138,6 +136,7 @@ impl DisplayMap {
|
||||||
wrap_map,
|
wrap_map,
|
||||||
block_map,
|
block_map,
|
||||||
flap_map,
|
flap_map,
|
||||||
|
fold_placeholder,
|
||||||
text_highlights: Default::default(),
|
text_highlights: Default::default(),
|
||||||
inlay_highlights: Default::default(),
|
inlay_highlights: Default::default(),
|
||||||
clip_at_line_ends: false,
|
clip_at_line_ends: false,
|
||||||
|
@ -167,6 +166,7 @@ impl DisplayMap {
|
||||||
text_highlights: self.text_highlights.clone(),
|
text_highlights: self.text_highlights.clone(),
|
||||||
inlay_highlights: self.inlay_highlights.clone(),
|
inlay_highlights: self.inlay_highlights.clone(),
|
||||||
clip_at_line_ends: self.clip_at_line_ends,
|
clip_at_line_ends: self.clip_at_line_ends,
|
||||||
|
fold_placeholder: self.fold_placeholder.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,14 +174,19 @@ impl DisplayMap {
|
||||||
self.fold(
|
self.fold(
|
||||||
other
|
other
|
||||||
.folds_in_range(0..other.buffer_snapshot.len())
|
.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,
|
cx,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fold<T: ToOffset>(
|
pub fn fold<T: ToOffset>(
|
||||||
&mut self,
|
&mut self,
|
||||||
ranges: impl IntoIterator<Item = (Range<T>, &'static str)>,
|
ranges: impl IntoIterator<Item = (Range<T>, FoldPlaceholder)>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) {
|
||||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
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))
|
.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 {
|
pub fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut ModelContext<Self>) -> bool {
|
||||||
self.wrap_map
|
self.wrap_map
|
||||||
.update(cx, |map, cx| map.set_wrap_width(width, cx))
|
.update(cx, |map, cx| map.set_wrap_width(width, cx))
|
||||||
|
@ -394,9 +395,10 @@ pub struct HighlightStyles {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct HighlightedChunk<'a> {
|
pub struct HighlightedChunk<'a> {
|
||||||
pub chunk: &'a str,
|
pub text: &'a str,
|
||||||
pub style: Option<HighlightStyle>,
|
pub style: Option<HighlightStyle>,
|
||||||
pub is_tab: bool,
|
pub is_tab: bool,
|
||||||
|
pub renderer: Option<ChunkRenderer>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -411,6 +413,7 @@ pub struct DisplaySnapshot {
|
||||||
text_highlights: TextHighlights,
|
text_highlights: TextHighlights,
|
||||||
inlay_highlights: InlayHighlights,
|
inlay_highlights: InlayHighlights,
|
||||||
clip_at_line_ends: bool,
|
clip_at_line_ends: bool,
|
||||||
|
pub(crate) fold_placeholder: FoldPlaceholder,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DisplaySnapshot {
|
impl DisplaySnapshot {
|
||||||
|
@ -648,9 +651,10 @@ impl DisplaySnapshot {
|
||||||
}
|
}
|
||||||
|
|
||||||
HighlightedChunk {
|
HighlightedChunk {
|
||||||
chunk: chunk.text,
|
text: chunk.text,
|
||||||
style: highlight_style,
|
style: highlight_style,
|
||||||
is_tab: chunk.is_tab,
|
is_tab: chunk.is_tab,
|
||||||
|
renderer: chunk.renderer,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -672,7 +676,7 @@ impl DisplaySnapshot {
|
||||||
|
|
||||||
let range = display_row..display_row.next_row();
|
let range = display_row..display_row.next_row();
|
||||||
for chunk in self.highlighted_chunks(range, false, &editor_style) {
|
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 {
|
let text_style = if let Some(style) = chunk.style {
|
||||||
Cow::Owned(editor_style.text.clone().highlight(style))
|
Cow::Owned(editor_style.text.clone().highlight(style))
|
||||||
|
@ -680,7 +684,7 @@ impl DisplaySnapshot {
|
||||||
Cow::Borrowed(&editor_style.text)
|
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') {
|
if line.ends_with('\n') {
|
||||||
|
@ -883,13 +887,16 @@ impl DisplaySnapshot {
|
||||||
pub fn foldable_range(
|
pub fn foldable_range(
|
||||||
&self,
|
&self,
|
||||||
buffer_row: MultiBufferRow,
|
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));
|
let start = MultiBufferPoint::new(buffer_row.0, self.buffer_snapshot.line_len(buffer_row));
|
||||||
if let Some(flap) = self
|
if let Some(flap) = self
|
||||||
.flap_snapshot
|
.flap_snapshot
|
||||||
.query_row(buffer_row, &self.buffer_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))
|
} else if self.starts_indent(MultiBufferRow(start.row))
|
||||||
&& !self.is_line_folded(MultiBufferRow(start.row))
|
&& !self.is_line_folded(MultiBufferRow(start.row))
|
||||||
{
|
{
|
||||||
|
@ -909,7 +916,7 @@ impl DisplaySnapshot {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let end = end.unwrap_or(max_point);
|
let end = end.unwrap_or(max_point);
|
||||||
Some((start..end, "⋯"))
|
Some((start..end, self.fold_placeholder.clone()))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -950,6 +957,14 @@ impl Debug for DisplayPoint {
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
pub struct DisplayRow(pub u32);
|
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 {
|
impl DisplayPoint {
|
||||||
pub fn new(row: DisplayRow, column: u32) -> Self {
|
pub fn new(row: DisplayRow, column: u32) -> Self {
|
||||||
Self(BlockPoint(Point::new(row.0, column)))
|
Self(BlockPoint(Point::new(row.0, column)))
|
||||||
|
@ -1083,6 +1098,7 @@ pub mod tests {
|
||||||
wrap_width,
|
wrap_width,
|
||||||
buffer_start_excerpt_header_height,
|
buffer_start_excerpt_header_height,
|
||||||
excerpt_header_height,
|
excerpt_header_height,
|
||||||
|
FoldPlaceholder::test(),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
@ -1186,7 +1202,12 @@ pub mod tests {
|
||||||
} else {
|
} else {
|
||||||
log::info!("folding ranges: {:?}", ranges);
|
log::info!("folding ranges: {:?}", ranges);
|
||||||
map.update(cx, |map, cx| {
|
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,
|
wrap_width,
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
|
FoldPlaceholder::test(),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
@ -1424,7 +1446,16 @@ pub mod tests {
|
||||||
|
|
||||||
let font_size = px(14.0);
|
let font_size = px(14.0);
|
||||||
let map = cx.new_model(|cx| {
|
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| {
|
buffer.update(cx, |buffer, cx| {
|
||||||
|
@ -1510,8 +1541,18 @@ pub mod tests {
|
||||||
|
|
||||||
let font_size = px(14.0);
|
let font_size = px(14.0);
|
||||||
|
|
||||||
let map = cx
|
let map = cx.new_model(|cx| {
|
||||||
.new_model(|cx| DisplayMap::new(buffer, font("Helvetica"), font_size, None, 1, 1, cx));
|
DisplayMap::new(
|
||||||
|
buffer,
|
||||||
|
font("Helvetica"),
|
||||||
|
font_size,
|
||||||
|
None,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
FoldPlaceholder::test(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(5), &map, &theme, cx)),
|
cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(5), &map, &theme, cx)),
|
||||||
vec![
|
vec![
|
||||||
|
@ -1536,7 +1577,7 @@ pub mod tests {
|
||||||
map.fold(
|
map.fold(
|
||||||
vec![(
|
vec![(
|
||||||
MultiBufferPoint::new(0, 6)..MultiBufferPoint::new(3, 2),
|
MultiBufferPoint::new(0, 6)..MultiBufferPoint::new(3, 2),
|
||||||
"⋯",
|
FoldPlaceholder::test(),
|
||||||
)],
|
)],
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
@ -1602,7 +1643,16 @@ pub mod tests {
|
||||||
let font_size = px(16.0);
|
let font_size = px(16.0);
|
||||||
|
|
||||||
let map = cx.new_model(|cx| {
|
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!(
|
assert_eq!(
|
||||||
cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(5), &map, &theme, cx)),
|
cx.update(|cx| syntax_chunks(DisplayRow(0)..DisplayRow(5), &map, &theme, cx)),
|
||||||
|
@ -1621,7 +1671,7 @@ pub mod tests {
|
||||||
map.fold(
|
map.fold(
|
||||||
vec![(
|
vec![(
|
||||||
MultiBufferPoint::new(0, 6)..MultiBufferPoint::new(3, 2),
|
MultiBufferPoint::new(0, 6)..MultiBufferPoint::new(3, 2),
|
||||||
"⋯",
|
FoldPlaceholder::test(),
|
||||||
)],
|
)],
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
@ -1674,8 +1724,18 @@ pub mod tests {
|
||||||
let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
|
let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
|
||||||
|
|
||||||
let font_size = px(16.0);
|
let font_size = px(16.0);
|
||||||
let map =
|
let map = cx.new_model(|cx| {
|
||||||
cx.new_model(|cx| DisplayMap::new(buffer, font("Courier"), font_size, None, 1, 1, cx));
|
DisplayMap::new(
|
||||||
|
buffer,
|
||||||
|
font("Courier"),
|
||||||
|
font_size,
|
||||||
|
None,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
FoldPlaceholder::test(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
enum MyType {}
|
enum MyType {}
|
||||||
|
|
||||||
|
@ -1789,8 +1849,16 @@ pub mod tests {
|
||||||
let buffer = MultiBuffer::build_simple(text, cx);
|
let buffer = MultiBuffer::build_simple(text, cx);
|
||||||
let font_size = px(14.0);
|
let font_size = px(14.0);
|
||||||
cx.new_model(|cx| {
|
cx.new_model(|cx| {
|
||||||
let mut map =
|
let mut map = DisplayMap::new(
|
||||||
DisplayMap::new(buffer.clone(), font("Helvetica"), font_size, None, 1, 1, cx);
|
buffer.clone(),
|
||||||
|
font("Helvetica"),
|
||||||
|
font_size,
|
||||||
|
None,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
FoldPlaceholder::test(),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
let snapshot = map.buffer.read(cx).snapshot(cx);
|
let snapshot = map.buffer.read(cx).snapshot(cx);
|
||||||
let range =
|
let range =
|
||||||
snapshot.anchor_before(Point::new(2, 0))..snapshot.anchor_after(Point::new(3, 3));
|
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(
|
map.flap_map.insert(
|
||||||
[Flap::new(
|
[Flap::new(
|
||||||
range,
|
range,
|
||||||
|
FoldPlaceholder::test(),
|
||||||
|_row, _status, _toggle, _cx| div(),
|
|_row, _status, _toggle, _cx| div(),
|
||||||
|_row, _status, _cx| div(),
|
|_row, _status, _cx| div(),
|
||||||
)],
|
)],
|
||||||
|
@ -1817,7 +1886,16 @@ pub mod tests {
|
||||||
let font_size = px(14.0);
|
let font_size = px(14.0);
|
||||||
|
|
||||||
let map = cx.new_model(|cx| {
|
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));
|
let map = map.update(cx, |map, cx| map.snapshot(cx));
|
||||||
assert_eq!(map.text(), "✅ α\nβ \n🏀β γ");
|
assert_eq!(map.text(), "✅ α\nβ \n🏀β γ");
|
||||||
|
@ -1883,7 +1961,16 @@ pub mod tests {
|
||||||
let buffer = MultiBuffer::build_simple("aaa\n\t\tbbb", cx);
|
let buffer = MultiBuffer::build_simple("aaa\n\t\tbbb", cx);
|
||||||
let font_size = px(14.0);
|
let font_size = px(14.0);
|
||||||
let map = cx.new_model(|cx| {
|
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!(
|
assert_eq!(
|
||||||
map.update(cx, |map, cx| map.snapshot(cx)).max_point(),
|
map.update(cx, |map, cx| map.snapshot(cx)).max_point(),
|
||||||
|
|
|
@ -875,7 +875,7 @@ impl<'a> Iterator for BlockChunks<'a> {
|
||||||
|
|
||||||
Some(Chunk {
|
Some(Chunk {
|
||||||
text: prefix,
|
text: prefix,
|
||||||
..self.input_chunk
|
..self.input_chunk.clone()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,8 @@ use sum_tree::{Bias, SeekTarget, SumTree};
|
||||||
use text::Point;
|
use text::Point;
|
||||||
use ui::WindowContext;
|
use ui::WindowContext;
|
||||||
|
|
||||||
|
use crate::FoldPlaceholder;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
|
#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
|
||||||
pub struct FlapId(usize);
|
pub struct FlapId(usize);
|
||||||
|
|
||||||
|
@ -76,6 +78,7 @@ type RenderTrailerFn =
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Flap {
|
pub struct Flap {
|
||||||
pub range: Range<Anchor>,
|
pub range: Range<Anchor>,
|
||||||
|
pub placeholder: FoldPlaceholder,
|
||||||
pub render_toggle: RenderToggleFn,
|
pub render_toggle: RenderToggleFn,
|
||||||
pub render_trailer: RenderTrailerFn,
|
pub render_trailer: RenderTrailerFn,
|
||||||
}
|
}
|
||||||
|
@ -83,6 +86,7 @@ pub struct Flap {
|
||||||
impl Flap {
|
impl Flap {
|
||||||
pub fn new<RenderToggle, ToggleElement, RenderTrailer, TrailerElement>(
|
pub fn new<RenderToggle, ToggleElement, RenderTrailer, TrailerElement>(
|
||||||
range: Range<Anchor>,
|
range: Range<Anchor>,
|
||||||
|
placeholder: FoldPlaceholder,
|
||||||
render_toggle: RenderToggle,
|
render_toggle: RenderToggle,
|
||||||
render_trailer: RenderTrailer,
|
render_trailer: RenderTrailer,
|
||||||
) -> Self
|
) -> Self
|
||||||
|
@ -107,6 +111,7 @@ impl Flap {
|
||||||
{
|
{
|
||||||
Flap {
|
Flap {
|
||||||
range,
|
range,
|
||||||
|
placeholder,
|
||||||
render_toggle: Arc::new(move |row, folded, toggle, cx| {
|
render_toggle: Arc::new(move |row, folded, toggle, cx| {
|
||||||
render_toggle(row, folded, toggle, cx).into_any_element()
|
render_toggle(row, folded, toggle, cx).into_any_element()
|
||||||
}),
|
}),
|
||||||
|
@ -256,11 +261,13 @@ mod test {
|
||||||
let flaps = [
|
let flaps = [
|
||||||
Flap::new(
|
Flap::new(
|
||||||
snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(1, 5)),
|
snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(1, 5)),
|
||||||
|
FoldPlaceholder::test(),
|
||||||
|_row, _folded, _toggle, _cx| div(),
|
|_row, _folded, _toggle, _cx| div(),
|
||||||
|_row, _folded, _cx| div(),
|
|_row, _folded, _cx| div(),
|
||||||
),
|
),
|
||||||
Flap::new(
|
Flap::new(
|
||||||
snapshot.anchor_before(Point::new(3, 0))..snapshot.anchor_after(Point::new(3, 5)),
|
snapshot.anchor_before(Point::new(3, 0))..snapshot.anchor_after(Point::new(3, 5)),
|
||||||
|
FoldPlaceholder::test(),
|
||||||
|_row, _folded, _toggle, _cx| div(),
|
|_row, _folded, _toggle, _cx| div(),
|
||||||
|_row, _folded, _cx| div(),
|
|_row, _folded, _cx| div(),
|
||||||
),
|
),
|
||||||
|
|
|
@ -2,17 +2,54 @@ use super::{
|
||||||
inlay_map::{InlayBufferRows, InlayChunks, InlayEdit, InlayOffset, InlayPoint, InlaySnapshot},
|
inlay_map::{InlayBufferRows, InlayChunks, InlayEdit, InlayOffset, InlayPoint, InlaySnapshot},
|
||||||
Highlights,
|
Highlights,
|
||||||
};
|
};
|
||||||
use gpui::{ElementId, HighlightStyle, Hsla};
|
use gpui::{AnyElement, ElementId, WindowContext};
|
||||||
use language::{Chunk, Edit, Point, TextSummary};
|
use language::{Chunk, ChunkRenderer, Edit, Point, TextSummary};
|
||||||
use multi_buffer::{Anchor, AnchorRangeExt, MultiBufferRow, MultiBufferSnapshot, ToOffset};
|
use multi_buffer::{Anchor, AnchorRangeExt, MultiBufferRow, MultiBufferSnapshot, ToOffset};
|
||||||
use std::{
|
use std::{
|
||||||
cmp::{self, Ordering},
|
cmp::{self, Ordering},
|
||||||
iter,
|
fmt, iter,
|
||||||
ops::{Add, AddAssign, Deref, DerefMut, Range, Sub},
|
ops::{Add, AddAssign, Deref, DerefMut, Range, Sub},
|
||||||
|
sync::Arc,
|
||||||
};
|
};
|
||||||
use sum_tree::{Bias, Cursor, FilterCursor, SumTree};
|
use sum_tree::{Bias, Cursor, FilterCursor, SumTree};
|
||||||
use util::post_inc;
|
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)]
|
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
|
||||||
pub struct FoldPoint(pub Point);
|
pub struct FoldPoint(pub Point);
|
||||||
|
|
||||||
|
@ -54,7 +91,7 @@ impl FoldPoint {
|
||||||
let mut offset = cursor.start().1.output.len;
|
let mut offset = cursor.start().1.output.len;
|
||||||
if !overshoot.is_zero() {
|
if !overshoot.is_zero() {
|
||||||
let transform = cursor.item().expect("display point out of range");
|
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
|
let end_inlay_offset = snapshot
|
||||||
.inlay_snapshot
|
.inlay_snapshot
|
||||||
.to_offset(InlayPoint(cursor.start().1.input.lines + overshoot));
|
.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> {
|
impl<'a> FoldMapWriter<'a> {
|
||||||
pub(crate) fn fold<T: ToOffset>(
|
pub(crate) fn fold<T: ToOffset>(
|
||||||
&mut self,
|
&mut self,
|
||||||
ranges: impl IntoIterator<Item = (Range<T>, &'static str)>,
|
ranges: impl IntoIterator<Item = (Range<T>, FoldPlaceholder)>,
|
||||||
) -> (FoldSnapshot, Vec<FoldEdit>) {
|
) -> (FoldSnapshot, Vec<FoldEdit>) {
|
||||||
let mut edits = Vec::new();
|
let mut edits = Vec::new();
|
||||||
let mut folds = Vec::new();
|
let mut folds = Vec::new();
|
||||||
|
@ -99,7 +136,7 @@ impl<'a> FoldMapWriter<'a> {
|
||||||
folds.push(Fold {
|
folds.push(Fold {
|
||||||
id: FoldId(post_inc(&mut self.0.next_fold_id.0)),
|
id: FoldId(post_inc(&mut self.0.next_fold_id.0)),
|
||||||
range: fold_range,
|
range: fold_range,
|
||||||
text: fold_text,
|
placeholder: fold_text,
|
||||||
});
|
});
|
||||||
|
|
||||||
let inlay_range =
|
let inlay_range =
|
||||||
|
@ -183,7 +220,6 @@ impl<'a> FoldMapWriter<'a> {
|
||||||
/// See the [`display_map` module documentation](crate::display_map) for more information.
|
/// See the [`display_map` module documentation](crate::display_map) for more information.
|
||||||
pub(crate) struct FoldMap {
|
pub(crate) struct FoldMap {
|
||||||
snapshot: FoldSnapshot,
|
snapshot: FoldSnapshot,
|
||||||
ellipses_color: Option<Hsla>,
|
|
||||||
next_fold_id: FoldId,
|
next_fold_id: FoldId,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,15 +234,13 @@ impl FoldMap {
|
||||||
input: inlay_snapshot.text_summary(),
|
input: inlay_snapshot.text_summary(),
|
||||||
output: inlay_snapshot.text_summary(),
|
output: inlay_snapshot.text_summary(),
|
||||||
},
|
},
|
||||||
output_text: None,
|
placeholder: None,
|
||||||
},
|
},
|
||||||
&(),
|
&(),
|
||||||
),
|
),
|
||||||
inlay_snapshot: inlay_snapshot.clone(),
|
inlay_snapshot: inlay_snapshot.clone(),
|
||||||
version: 0,
|
version: 0,
|
||||||
ellipses_color: None,
|
|
||||||
},
|
},
|
||||||
ellipses_color: None,
|
|
||||||
next_fold_id: FoldId::default(),
|
next_fold_id: FoldId::default(),
|
||||||
};
|
};
|
||||||
let snapshot = this.snapshot.clone();
|
let snapshot = this.snapshot.clone();
|
||||||
|
@ -232,15 +266,6 @@ impl FoldMap {
|
||||||
(FoldMapWriter(self), snapshot, edits)
|
(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) {
|
fn check_invariants(&self) {
|
||||||
if cfg!(test) {
|
if cfg!(test) {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -329,9 +354,9 @@ impl FoldMap {
|
||||||
let buffer_start = fold.range.start.to_offset(&inlay_snapshot.buffer);
|
let buffer_start = fold.range.start.to_offset(&inlay_snapshot.buffer);
|
||||||
let buffer_end = fold.range.end.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_start)
|
||||||
..inlay_snapshot.to_inlay_offset(buffer_end),
|
..inlay_snapshot.to_inlay_offset(buffer_end),
|
||||||
fold.text,
|
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
folds_cursor.next(&inlay_snapshot.buffer);
|
folds_cursor.next(&inlay_snapshot.buffer);
|
||||||
|
@ -342,17 +367,17 @@ impl FoldMap {
|
||||||
|
|
||||||
while folds
|
while folds
|
||||||
.peek()
|
.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();
|
let sum = new_transforms.summary();
|
||||||
|
|
||||||
assert!(fold_range.start.0 >= sum.input.len);
|
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
|
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 {
|
if next_fold_range.end > fold_range.end {
|
||||||
fold_range.end = next_fold_range.end;
|
fold_range.end = next_fold_range.end;
|
||||||
}
|
}
|
||||||
|
@ -367,21 +392,36 @@ impl FoldMap {
|
||||||
output: text_summary.clone(),
|
output: text_summary.clone(),
|
||||||
input: text_summary,
|
input: text_summary,
|
||||||
},
|
},
|
||||||
output_text: None,
|
placeholder: None,
|
||||||
},
|
},
|
||||||
&(),
|
&(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if fold_range.end > fold_range.start {
|
if fold_range.end > fold_range.start {
|
||||||
|
const ELLIPSIS: &'static str = "⋯";
|
||||||
|
|
||||||
|
let fold_id = fold.id;
|
||||||
new_transforms.push(
|
new_transforms.push(
|
||||||
Transform {
|
Transform {
|
||||||
summary: TransformSummary {
|
summary: TransformSummary {
|
||||||
output: TextSummary::from(fold_text),
|
output: TextSummary::from(ELLIPSIS),
|
||||||
input: inlay_snapshot
|
input: inlay_snapshot
|
||||||
.text_summary_for_range(fold_range.start..fold_range.end),
|
.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(),
|
output: text_summary.clone(),
|
||||||
input: text_summary,
|
input: text_summary,
|
||||||
},
|
},
|
||||||
output_text: None,
|
placeholder: None,
|
||||||
},
|
},
|
||||||
&(),
|
&(),
|
||||||
);
|
);
|
||||||
|
@ -414,7 +454,7 @@ impl FoldMap {
|
||||||
output: text_summary.clone(),
|
output: text_summary.clone(),
|
||||||
input: text_summary,
|
input: text_summary,
|
||||||
},
|
},
|
||||||
output_text: None,
|
placeholder: None,
|
||||||
},
|
},
|
||||||
&(),
|
&(),
|
||||||
);
|
);
|
||||||
|
@ -484,7 +524,6 @@ pub struct FoldSnapshot {
|
||||||
folds: SumTree<Fold>,
|
folds: SumTree<Fold>,
|
||||||
pub inlay_snapshot: InlaySnapshot,
|
pub inlay_snapshot: InlaySnapshot,
|
||||||
pub version: usize,
|
pub version: usize,
|
||||||
pub ellipses_color: Option<Hsla>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FoldSnapshot {
|
impl FoldSnapshot {
|
||||||
|
@ -508,9 +547,9 @@ impl FoldSnapshot {
|
||||||
if let Some(transform) = cursor.item() {
|
if let Some(transform) = cursor.item() {
|
||||||
let start_in_transform = range.start.0 - cursor.start().0 .0;
|
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;
|
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(
|
summary = TextSummary::from(
|
||||||
&output_text
|
&placeholder.text
|
||||||
[start_in_transform.column as usize..end_in_transform.column as usize],
|
[start_in_transform.column as usize..end_in_transform.column as usize],
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
@ -533,8 +572,9 @@ impl FoldSnapshot {
|
||||||
.output;
|
.output;
|
||||||
if let Some(transform) = cursor.item() {
|
if let Some(transform) = cursor.item() {
|
||||||
let end_in_transform = range.end.0 - cursor.start().0 .0;
|
let end_in_transform = range.end.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[..end_in_transform.column as usize]);
|
summary +=
|
||||||
|
TextSummary::from(&placeholder.text[..end_in_transform.column as usize]);
|
||||||
} else {
|
} else {
|
||||||
let inlay_start = self.inlay_snapshot.to_offset(cursor.start().1);
|
let inlay_start = self.inlay_snapshot.to_offset(cursor.start().1);
|
||||||
let inlay_end = self
|
let inlay_end = self
|
||||||
|
@ -631,7 +671,7 @@ impl FoldSnapshot {
|
||||||
let inlay_offset = self.inlay_snapshot.to_inlay_offset(buffer_offset);
|
let inlay_offset = self.inlay_snapshot.to_inlay_offset(buffer_offset);
|
||||||
let mut cursor = self.transforms.cursor::<InlayOffset>();
|
let mut cursor = self.transforms.cursor::<InlayOffset>();
|
||||||
cursor.seek(&inlay_offset, Bias::Right, &());
|
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 {
|
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);
|
let buffer_point = self.inlay_snapshot.to_buffer_point(inlay_point);
|
||||||
if buffer_point.row != buffer_row.0 {
|
if buffer_point.row != buffer_row.0 {
|
||||||
return false;
|
return false;
|
||||||
} else if transform.output_text.is_some() {
|
} else if transform.placeholder.is_some() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -693,7 +733,6 @@ impl FoldSnapshot {
|
||||||
inlay_offset: inlay_start,
|
inlay_offset: inlay_start,
|
||||||
output_offset: range.start.0,
|
output_offset: range.start.0,
|
||||||
max_output_offset: range.end.0,
|
max_output_offset: range.end.0,
|
||||||
ellipses_color: self.ellipses_color,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -720,7 +759,7 @@ impl FoldSnapshot {
|
||||||
cursor.seek(&point, Bias::Right, &());
|
cursor.seek(&point, Bias::Right, &());
|
||||||
if let Some(transform) = cursor.item() {
|
if let Some(transform) = cursor.item() {
|
||||||
let transform_start = cursor.start().0 .0;
|
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) {
|
if point.0 == transform_start || matches!(bias, Bias::Left) {
|
||||||
FoldPoint(transform_start)
|
FoldPoint(transform_start)
|
||||||
} else {
|
} 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 {
|
struct Transform {
|
||||||
summary: TransformSummary,
|
summary: TransformSummary,
|
||||||
output_text: Option<&'static str>,
|
placeholder: Option<TransformPlaceholder>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct TransformPlaceholder {
|
||||||
|
text: &'static str,
|
||||||
|
renderer: ChunkRenderer,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Transform {
|
impl Transform {
|
||||||
fn is_fold(&self) -> bool {
|
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 struct Fold {
|
||||||
pub id: FoldId,
|
pub id: FoldId,
|
||||||
pub range: FoldRange,
|
pub range: FoldRange,
|
||||||
pub text: &'static str,
|
pub placeholder: FoldPlaceholder,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
@ -1004,7 +1049,6 @@ pub struct FoldChunks<'a> {
|
||||||
inlay_offset: InlayOffset,
|
inlay_offset: InlayOffset,
|
||||||
output_offset: usize,
|
output_offset: usize,
|
||||||
max_output_offset: usize,
|
max_output_offset: usize,
|
||||||
ellipses_color: Option<Hsla>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Iterator for FoldChunks<'a> {
|
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
|
// 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.
|
// 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_chunk.take();
|
||||||
self.inlay_offset += InlayOffset(transform.summary.input.len);
|
self.inlay_offset += InlayOffset(transform.summary.input.len);
|
||||||
self.inlay_chunks.seek(self.inlay_offset);
|
self.inlay_chunks.seek(self.inlay_offset);
|
||||||
|
@ -1030,13 +1074,10 @@ impl<'a> Iterator for FoldChunks<'a> {
|
||||||
self.transform_cursor.next(&());
|
self.transform_cursor.next(&());
|
||||||
}
|
}
|
||||||
|
|
||||||
self.output_offset += output_text.len();
|
self.output_offset += placeholder.text.len();
|
||||||
return Some(Chunk {
|
return Some(Chunk {
|
||||||
text: output_text,
|
text: placeholder.text,
|
||||||
highlight_style: self.ellipses_color.map(|color| HighlightStyle {
|
renderer: Some(placeholder.renderer.clone()),
|
||||||
color: Some(color),
|
|
||||||
..Default::default()
|
|
||||||
}),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1048,7 +1089,7 @@ impl<'a> Iterator for FoldChunks<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, take a chunk from the buffer's text.
|
// 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 buffer_chunk_end = buffer_chunk_start + InlayOffset(chunk.text.len());
|
||||||
let transform_end = self.transform_cursor.end(&()).1;
|
let transform_end = self.transform_cursor.end(&()).1;
|
||||||
let chunk_end = buffer_chunk_end.min(transform_end);
|
let chunk_end = buffer_chunk_end.min(transform_end);
|
||||||
|
@ -1165,8 +1206,8 @@ mod tests {
|
||||||
|
|
||||||
let (mut writer, _, _) = map.write(inlay_snapshot, vec![]);
|
let (mut writer, _, _) = map.write(inlay_snapshot, vec![]);
|
||||||
let (snapshot2, edits) = writer.fold(vec![
|
let (snapshot2, edits) = writer.fold(vec![
|
||||||
(Point::new(0, 2)..Point::new(2, 2), "⋯"),
|
(Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()),
|
||||||
(Point::new(2, 4)..Point::new(4, 1), "⋯"),
|
(Point::new(2, 4)..Point::new(4, 1), FoldPlaceholder::test()),
|
||||||
]);
|
]);
|
||||||
assert_eq!(snapshot2.text(), "aa⋯cc⋯eeeee");
|
assert_eq!(snapshot2.text(), "aa⋯cc⋯eeeee");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -1245,19 +1286,25 @@ mod tests {
|
||||||
let mut map = FoldMap::new(inlay_snapshot.clone()).0;
|
let mut map = FoldMap::new(inlay_snapshot.clone()).0;
|
||||||
|
|
||||||
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
|
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![]);
|
let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
|
||||||
assert_eq!(snapshot.text(), "abcde⋯ijkl");
|
assert_eq!(snapshot.text(), "abcde⋯ijkl");
|
||||||
|
|
||||||
// Create an fold adjacent to the start of the first fold.
|
// Create an fold adjacent to the start of the first fold.
|
||||||
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
|
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![]);
|
let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
|
||||||
assert_eq!(snapshot.text(), "⋯b⋯ijkl");
|
assert_eq!(snapshot.text(), "⋯b⋯ijkl");
|
||||||
|
|
||||||
// Create an fold adjacent to the end of the first fold.
|
// Create an fold adjacent to the end of the first fold.
|
||||||
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
|
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![]);
|
let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
|
||||||
assert_eq!(snapshot.text(), "⋯b⋯kl");
|
assert_eq!(snapshot.text(), "⋯b⋯kl");
|
||||||
}
|
}
|
||||||
|
@ -1267,7 +1314,10 @@ mod tests {
|
||||||
|
|
||||||
// Create two adjacent folds.
|
// Create two adjacent folds.
|
||||||
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
|
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![]);
|
let (snapshot, _) = map.read(inlay_snapshot, vec![]);
|
||||||
assert_eq!(snapshot.text(), "⋯fghijkl");
|
assert_eq!(snapshot.text(), "⋯fghijkl");
|
||||||
|
|
||||||
|
@ -1291,10 +1341,10 @@ mod tests {
|
||||||
let mut map = FoldMap::new(inlay_snapshot.clone()).0;
|
let mut map = FoldMap::new(inlay_snapshot.clone()).0;
|
||||||
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
|
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
|
||||||
writer.fold(vec![
|
writer.fold(vec![
|
||||||
(Point::new(0, 2)..Point::new(2, 2), "⋯"),
|
(Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()),
|
||||||
(Point::new(0, 4)..Point::new(1, 0), "⋯"),
|
(Point::new(0, 4)..Point::new(1, 0), FoldPlaceholder::test()),
|
||||||
(Point::new(1, 2)..Point::new(3, 2), "⋯"),
|
(Point::new(1, 2)..Point::new(3, 2), FoldPlaceholder::test()),
|
||||||
(Point::new(3, 1)..Point::new(4, 1), "⋯"),
|
(Point::new(3, 1)..Point::new(4, 1), FoldPlaceholder::test()),
|
||||||
]);
|
]);
|
||||||
let (snapshot, _) = map.read(inlay_snapshot, vec![]);
|
let (snapshot, _) = map.read(inlay_snapshot, vec![]);
|
||||||
assert_eq!(snapshot.text(), "aa⋯eeeee");
|
assert_eq!(snapshot.text(), "aa⋯eeeee");
|
||||||
|
@ -1311,8 +1361,8 @@ mod tests {
|
||||||
|
|
||||||
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
|
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
|
||||||
writer.fold(vec![
|
writer.fold(vec![
|
||||||
(Point::new(0, 2)..Point::new(2, 2), "⋯"),
|
(Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()),
|
||||||
(Point::new(3, 1)..Point::new(4, 1), "⋯"),
|
(Point::new(3, 1)..Point::new(4, 1), FoldPlaceholder::test()),
|
||||||
]);
|
]);
|
||||||
let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
|
let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
|
||||||
assert_eq!(snapshot.text(), "aa⋯cccc\nd⋯eeeee");
|
assert_eq!(snapshot.text(), "aa⋯cccc\nd⋯eeeee");
|
||||||
|
@ -1336,10 +1386,10 @@ mod tests {
|
||||||
|
|
||||||
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
|
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
|
||||||
writer.fold(vec![
|
writer.fold(vec![
|
||||||
(Point::new(0, 2)..Point::new(2, 2), "⋯"),
|
(Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()),
|
||||||
(Point::new(0, 4)..Point::new(1, 0), "⋯"),
|
(Point::new(0, 4)..Point::new(1, 0), FoldPlaceholder::test()),
|
||||||
(Point::new(1, 2)..Point::new(3, 2), "⋯"),
|
(Point::new(1, 2)..Point::new(3, 2), FoldPlaceholder::test()),
|
||||||
(Point::new(3, 1)..Point::new(4, 1), "⋯"),
|
(Point::new(3, 1)..Point::new(4, 1), FoldPlaceholder::test()),
|
||||||
]);
|
]);
|
||||||
let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
|
let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
|
||||||
let fold_ranges = snapshot
|
let fold_ranges = snapshot
|
||||||
|
@ -1414,10 +1464,10 @@ mod tests {
|
||||||
snapshot_edits.push((snapshot.clone(), edits));
|
snapshot_edits.push((snapshot.clone(), edits));
|
||||||
|
|
||||||
let mut expected_text: String = inlay_snapshot.text().to_string();
|
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_start = inlay_snapshot.to_inlay_offset(fold_range.start);
|
||||||
let fold_inlay_end = inlay_snapshot.to_inlay_offset(fold_range.end);
|
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);
|
assert_eq!(snapshot.text(), expected_text);
|
||||||
|
@ -1429,7 +1479,7 @@ mod tests {
|
||||||
|
|
||||||
let mut prev_row = 0;
|
let mut prev_row = 0;
|
||||||
let mut expected_buffer_rows = Vec::new();
|
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
|
let fold_start = inlay_snapshot
|
||||||
.to_point(inlay_snapshot.to_inlay_offset(fold_range.start))
|
.to_point(inlay_snapshot.to_inlay_offset(fold_range.start))
|
||||||
.row();
|
.row();
|
||||||
|
@ -1543,7 +1593,7 @@ mod tests {
|
||||||
let folded_buffer_rows = map
|
let folded_buffer_rows = map
|
||||||
.merged_folds()
|
.merged_folds()
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|(fold_range, _)| {
|
.flat_map(|fold_range| {
|
||||||
let start_row = fold_range.start.to_point(&buffer_snapshot).row;
|
let start_row = fold_range.start.to_point(&buffer_snapshot).row;
|
||||||
let end = fold_range.end.to_point(&buffer_snapshot);
|
let end = fold_range.end.to_point(&buffer_snapshot);
|
||||||
if end.column == 0 {
|
if end.column == 0 {
|
||||||
|
@ -1640,8 +1690,8 @@ mod tests {
|
||||||
|
|
||||||
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
|
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
|
||||||
writer.fold(vec![
|
writer.fold(vec![
|
||||||
(Point::new(0, 2)..Point::new(2, 2), "⋯"),
|
(Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()),
|
||||||
(Point::new(3, 1)..Point::new(4, 1), "⋯"),
|
(Point::new(3, 1)..Point::new(4, 1), FoldPlaceholder::test()),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let (snapshot, _) = map.read(inlay_snapshot, vec![]);
|
let (snapshot, _) = map.read(inlay_snapshot, vec![]);
|
||||||
|
@ -1659,7 +1709,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FoldMap {
|
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 inlay_snapshot = self.snapshot.inlay_snapshot.clone();
|
||||||
let buffer = &inlay_snapshot.buffer;
|
let buffer = &inlay_snapshot.buffer;
|
||||||
let mut folds = self.snapshot.folds.items(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));
|
folds.sort_by(|a, b| a.range.cmp(&b.range, buffer));
|
||||||
let mut folds = folds
|
let mut folds = folds
|
||||||
.iter()
|
.iter()
|
||||||
.map(|fold| {
|
.map(|fold| fold.range.start.to_offset(buffer)..fold.range.end.to_offset(buffer))
|
||||||
(
|
|
||||||
fold.range.start.to_offset(buffer)..fold.range.end.to_offset(buffer),
|
|
||||||
fold.text,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.peekable();
|
.peekable();
|
||||||
|
|
||||||
let mut merged_folds = Vec::new();
|
let mut merged_folds = Vec::new();
|
||||||
while let Some((mut fold_range, fold_text)) = folds.next() {
|
while let Some(mut fold_range) = folds.next() {
|
||||||
while let Some((next_range, _)) = folds.peek() {
|
while let Some(next_range) = folds.peek() {
|
||||||
if fold_range.end >= next_range.start {
|
if fold_range.end >= next_range.start {
|
||||||
if next_range.end > fold_range.end {
|
if next_range.end > fold_range.end {
|
||||||
fold_range.end = next_range.end;
|
fold_range.end = next_range.end;
|
||||||
|
@ -1688,7 +1733,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if fold_range.end > fold_range.start {
|
if fold_range.end > fold_range.start {
|
||||||
merged_folds.push((fold_range, fold_text));
|
merged_folds.push(fold_range);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
merged_folds
|
merged_folds
|
||||||
|
@ -1723,8 +1768,7 @@ mod tests {
|
||||||
for _ in 0..rng.gen_range(1..=2) {
|
for _ in 0..rng.gen_range(1..=2) {
|
||||||
let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right);
|
let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right);
|
||||||
let start = buffer.clip_offset(rng.gen_range(0..=end), Left);
|
let start = buffer.clip_offset(rng.gen_range(0..=end), Left);
|
||||||
let text = if rng.gen() { "⋯" } else { "" };
|
to_fold.push((start..end, FoldPlaceholder::test()));
|
||||||
to_fold.push((start..end, text));
|
|
||||||
}
|
}
|
||||||
log::info!("folding {:?}", to_fold);
|
log::info!("folding {:?}", to_fold);
|
||||||
let (mut writer, snapshot, edits) = self.write(inlay_snapshot, vec![]);
|
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();
|
self.output_offset.0 += prefix.len();
|
||||||
let mut prefix = Chunk {
|
let mut prefix = Chunk {
|
||||||
text: prefix,
|
text: prefix,
|
||||||
..*chunk
|
..chunk.clone()
|
||||||
};
|
};
|
||||||
if !self.active_highlights.is_empty() {
|
if !self.active_highlights.is_empty() {
|
||||||
let mut highlight_style = HighlightStyle::default();
|
let mut highlight_style = HighlightStyle::default();
|
||||||
|
|
|
@ -508,7 +508,7 @@ impl<'a> Iterator for TabChunks<'a> {
|
||||||
self.chunk.text = suffix;
|
self.chunk.text = suffix;
|
||||||
return Some(Chunk {
|
return Some(Chunk {
|
||||||
text: prefix,
|
text: prefix,
|
||||||
..self.chunk
|
..self.chunk.clone()
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
self.chunk.text = &self.chunk.text[1..];
|
self.chunk.text = &self.chunk.text[1..];
|
||||||
|
@ -529,7 +529,7 @@ impl<'a> Iterator for TabChunks<'a> {
|
||||||
return Some(Chunk {
|
return Some(Chunk {
|
||||||
text: &SPACES[..len as usize],
|
text: &SPACES[..len as usize],
|
||||||
is_tab: true,
|
is_tab: true,
|
||||||
..self.chunk
|
..self.chunk.clone()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -812,7 +812,7 @@ impl<'a> Iterator for WrapChunks<'a> {
|
||||||
self.transforms.next(&());
|
self.transforms.next(&());
|
||||||
return Some(Chunk {
|
return Some(Chunk {
|
||||||
text: &display_text[start_ix..end_ix],
|
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;
|
self.input_chunk.text = suffix;
|
||||||
Some(Chunk {
|
Some(Chunk {
|
||||||
text: prefix,
|
text: prefix,
|
||||||
..self.input_chunk
|
..self.input_chunk.clone()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,8 +52,8 @@ use clock::ReplicaId;
|
||||||
use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque};
|
use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque};
|
||||||
use convert_case::{Case, Casing};
|
use convert_case::{Case, Casing};
|
||||||
use debounced_delay::DebouncedDelay;
|
use debounced_delay::DebouncedDelay;
|
||||||
pub use display_map::DisplayPoint;
|
|
||||||
use display_map::*;
|
use display_map::*;
|
||||||
|
pub use display_map::{DisplayPoint, FoldPlaceholder};
|
||||||
use editor_settings::CurrentLineHighlight;
|
use editor_settings::CurrentLineHighlight;
|
||||||
pub use editor_settings::EditorSettings;
|
pub use editor_settings::EditorSettings;
|
||||||
use element::LineWithInvisibles;
|
use element::LineWithInvisibles;
|
||||||
|
@ -1577,8 +1577,48 @@ impl Editor {
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let style = cx.text_style();
|
let style = cx.text_style();
|
||||||
let font_size = style.font_size.to_pixels(cx.rem_size());
|
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| {
|
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());
|
let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
|
||||||
|
@ -5826,7 +5866,7 @@ impl Editor {
|
||||||
let mut end = fold.range.end.to_point(&buffer);
|
let mut end = fold.range.end.to_point(&buffer);
|
||||||
start.row -= row_delta;
|
start.row -= row_delta;
|
||||||
end.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);
|
let mut end = fold.range.end.to_point(&buffer);
|
||||||
start.row += row_delta;
|
start.row += row_delta;
|
||||||
end.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 buffer_row = fold_at.buffer_row;
|
||||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
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
|
let autoscroll = self
|
||||||
.selections
|
.selections
|
||||||
.all::<Point>(cx)
|
.all::<Point>(cx)
|
||||||
.iter()
|
.iter()
|
||||||
.any(|selection| fold_range.overlaps(&selection.range()));
|
.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
|
.buffer_snapshot
|
||||||
.line_len(MultiBufferRow(s.end.row)),
|
.line_len(MultiBufferRow(s.end.row)),
|
||||||
);
|
);
|
||||||
(start..end, "⋯")
|
(start..end, display_map.fold_placeholder.clone())
|
||||||
} else {
|
} else {
|
||||||
(s.start..s.end, "⋯")
|
(s.start..s.end, display_map.fold_placeholder.clone())
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
self.fold_ranges(ranges, true, cx);
|
self.fold_ranges(ranges, true, cx);
|
||||||
|
@ -9369,7 +9409,7 @@ impl Editor {
|
||||||
|
|
||||||
pub fn fold_ranges<T: ToOffset + Clone>(
|
pub fn fold_ranges<T: ToOffset + Clone>(
|
||||||
&mut self,
|
&mut self,
|
||||||
ranges: impl IntoIterator<Item = (Range<T>, &'static str)>,
|
ranges: impl IntoIterator<Item = (Range<T>, FoldPlaceholder)>,
|
||||||
auto_scroll: bool,
|
auto_scroll: bool,
|
||||||
cx: &mut ViewContext<Self>,
|
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.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
|
||||||
editor.fold_ranges(
|
editor.fold_ranges(
|
||||||
[
|
[
|
||||||
(Point::new(1, 0)..Point::new(2, 0), "⋯"),
|
(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
|
||||||
(Point::new(3, 0)..Point::new(4, 0), "⋯"),
|
(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
|
||||||
],
|
],
|
||||||
true,
|
true,
|
||||||
cx,
|
cx,
|
||||||
|
@ -905,9 +905,9 @@ fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
|
||||||
_ = view.update(cx, |view, cx| {
|
_ = view.update(cx, |view, cx| {
|
||||||
view.fold_ranges(
|
view.fold_ranges(
|
||||||
vec![
|
vec![
|
||||||
(Point::new(0, 6)..Point::new(0, 12), "⋯"),
|
(Point::new(0, 6)..Point::new(0, 12), FoldPlaceholder::test()),
|
||||||
(Point::new(1, 2)..Point::new(1, 4), "⋯"),
|
(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
|
||||||
(Point::new(2, 4)..Point::new(2, 8), "⋯"),
|
(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
|
||||||
],
|
],
|
||||||
true,
|
true,
|
||||||
cx,
|
cx,
|
||||||
|
@ -3409,9 +3409,9 @@ fn test_move_line_up_down(cx: &mut TestAppContext) {
|
||||||
_ = view.update(cx, |view, cx| {
|
_ = view.update(cx, |view, cx| {
|
||||||
view.fold_ranges(
|
view.fold_ranges(
|
||||||
vec![
|
vec![
|
||||||
(Point::new(0, 2)..Point::new(1, 2), "⋯"),
|
(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
|
||||||
(Point::new(2, 3)..Point::new(4, 1), "⋯"),
|
(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
|
||||||
(Point::new(7, 0)..Point::new(8, 4), "⋯"),
|
(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
|
||||||
],
|
],
|
||||||
true,
|
true,
|
||||||
cx,
|
cx,
|
||||||
|
@ -3893,9 +3893,9 @@ fn test_split_selection_into_lines(cx: &mut TestAppContext) {
|
||||||
_ = view.update(cx, |view, cx| {
|
_ = view.update(cx, |view, cx| {
|
||||||
view.fold_ranges(
|
view.fold_ranges(
|
||||||
vec![
|
vec![
|
||||||
(Point::new(0, 2)..Point::new(1, 2), "⋯"),
|
(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
|
||||||
(Point::new(2, 3)..Point::new(4, 1), "⋯"),
|
(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
|
||||||
(Point::new(7, 0)..Point::new(8, 4), "⋯"),
|
(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
|
||||||
],
|
],
|
||||||
true,
|
true,
|
||||||
cx,
|
cx,
|
||||||
|
@ -4550,8 +4550,14 @@ async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
|
||||||
_ = view.update(cx, |view, cx| {
|
_ = view.update(cx, |view, cx| {
|
||||||
view.fold_ranges(
|
view.fold_ranges(
|
||||||
vec![
|
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,
|
true,
|
||||||
cx,
|
cx,
|
||||||
|
@ -11973,6 +11979,7 @@ fn test_flap_insertion_and_rendering(cx: &mut TestAppContext) {
|
||||||
|
|
||||||
let flap = Flap::new(
|
let flap = Flap::new(
|
||||||
range,
|
range,
|
||||||
|
FoldPlaceholder::test(),
|
||||||
{
|
{
|
||||||
let toggle_callback = render_args.clone();
|
let toggle_callback = render_args.clone();
|
||||||
move |row, folded, callback, _cx| {
|
move |row, folded, callback, _cx| {
|
||||||
|
|
|
@ -23,7 +23,6 @@ use crate::{
|
||||||
LineDown, LineUp, OpenExcerpts, PageDown, PageUp, Point, RowExt, RowRangeExt, SelectPhase,
|
LineDown, LineUp, OpenExcerpts, PageDown, PageUp, Point, RowExt, RowRangeExt, SelectPhase,
|
||||||
Selection, SoftWrap, ToPoint, CURSORS_VISIBLE_FOR, MAX_LINE_LEN,
|
Selection, SoftWrap, ToPoint, CURSORS_VISIBLE_FOR, MAX_LINE_LEN,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
|
||||||
use client::ParticipantIndex;
|
use client::ParticipantIndex;
|
||||||
use collections::{BTreeMap, HashMap};
|
use collections::{BTreeMap, HashMap};
|
||||||
use git::{blame::BlameEntry, diff::DiffHunkStatus, Oid};
|
use git::{blame::BlameEntry, diff::DiffHunkStatus, Oid};
|
||||||
|
@ -31,11 +30,11 @@ use gpui::{
|
||||||
anchored, deferred, div, fill, outline, point, px, quad, relative, size, svg,
|
anchored, deferred, div, fill, outline, point, px, quad, relative, size, svg,
|
||||||
transparent_black, Action, AnchorCorner, AnyElement, AvailableSpace, Bounds, ClipboardItem,
|
transparent_black, Action, AnchorCorner, AnyElement, AvailableSpace, Bounds, ClipboardItem,
|
||||||
ContentMask, Corners, CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Entity,
|
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,
|
MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, ParentElement, Pixels,
|
||||||
ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size, Stateful,
|
ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveElement,
|
||||||
StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, TextStyleRefinement, View,
|
Style, Styled, TextRun, TextStyle, TextStyleRefinement, View, ViewContext, WeakView,
|
||||||
ViewContext, WeakView, WindowContext,
|
WindowContext,
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use language::language_settings::{
|
use language::language_settings::{
|
||||||
|
@ -48,12 +47,12 @@ use project::{
|
||||||
ProjectPath,
|
ProjectPath,
|
||||||
};
|
};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use smallvec::SmallVec;
|
use smallvec::{smallvec, SmallVec};
|
||||||
use std::{
|
use std::{
|
||||||
any::TypeId,
|
any::TypeId,
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
cmp::{self, Ordering},
|
cmp::{self, Ordering},
|
||||||
fmt::Write,
|
fmt::{self, Write},
|
||||||
iter, mem,
|
iter, mem,
|
||||||
ops::{Deref, Range},
|
ops::{Deref, Range},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
|
@ -857,76 +856,6 @@ impl EditorElement {
|
||||||
(selections, active_rows, newest_selection_head)
|
(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(
|
fn collect_cursors(
|
||||||
&self,
|
&self,
|
||||||
snapshot: &EditorSnapshot,
|
snapshot: &EditorSnapshot,
|
||||||
|
@ -994,8 +923,7 @@ impl EditorElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
let cursor_row_layout = &line_layouts
|
let cursor_row_layout = &line_layouts
|
||||||
[cursor_position.row().minus(visible_display_row_range.start) as usize]
|
[cursor_position.row().minus(visible_display_row_range.start) as usize];
|
||||||
.line;
|
|
||||||
let cursor_column = cursor_position.column() as usize;
|
let cursor_column = cursor_position.column() as usize;
|
||||||
|
|
||||||
let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
|
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 size = element.layout_as_root(available_space, cx);
|
||||||
|
|
||||||
let line = &lines[ix].line;
|
let line = &lines[ix];
|
||||||
let padding = if line.width == Pixels::ZERO {
|
let padding = if line.width == Pixels::ZERO {
|
||||||
Pixels::ZERO
|
Pixels::ZERO
|
||||||
} else {
|
} else {
|
||||||
|
@ -1373,7 +1301,7 @@ impl EditorElement {
|
||||||
let line_end = if let Some(flap_trailer) = flap_trailer {
|
let line_end = if let Some(flap_trailer) = flap_trailer {
|
||||||
flap_trailer.bounds.right()
|
flap_trailer.bounds.right()
|
||||||
} else {
|
} 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;
|
let padded_line_end = line_end + em_width * INLINE_BLAME_PADDING_EM_WIDTHS;
|
||||||
|
|
||||||
|
@ -1860,7 +1788,7 @@ impl EditorElement {
|
||||||
rows: Range<DisplayRow>,
|
rows: Range<DisplayRow>,
|
||||||
line_number_layouts: &[Option<ShapedLine>],
|
line_number_layouts: &[Option<ShapedLine>],
|
||||||
snapshot: &EditorSnapshot,
|
snapshot: &EditorSnapshot,
|
||||||
cx: &WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> Vec<LineWithInvisibles> {
|
) -> Vec<LineWithInvisibles> {
|
||||||
if rows.start >= rows.end {
|
if rows.start >= rows.end {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
|
@ -1894,8 +1822,11 @@ impl EditorElement {
|
||||||
.log_err()
|
.log_err()
|
||||||
})
|
})
|
||||||
.map(|line| LineWithInvisibles {
|
.map(|line| LineWithInvisibles {
|
||||||
line,
|
width: line.width,
|
||||||
|
len: line.len,
|
||||||
|
fragments: smallvec![LineFragment::Text(line)],
|
||||||
invisibles: Vec::new(),
|
invisibles: Vec::new(),
|
||||||
|
font_size,
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
} else {
|
} 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)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn build_blocks(
|
fn build_blocks(
|
||||||
&self,
|
&self,
|
||||||
|
@ -1949,11 +1904,9 @@ impl EditorElement {
|
||||||
let anchor_x = text_x
|
let anchor_x = text_x
|
||||||
+ if rows.contains(&align_to.row()) {
|
+ if rows.contains(&align_to.row()) {
|
||||||
line_layouts[align_to.row().minus(rows.start) as usize]
|
line_layouts[align_to.row().minus(rows.start) as usize]
|
||||||
.line
|
|
||||||
.x_for_index(align_to.column() as usize)
|
.x_for_index(align_to.column() as usize)
|
||||||
} else {
|
} else {
|
||||||
layout_line(align_to.row(), snapshot, &self.style, cx)
|
layout_line(align_to.row(), snapshot, &self.style, cx)
|
||||||
.unwrap()
|
|
||||||
.x_for_index(align_to.column() as usize)
|
.x_for_index(align_to.column() as usize)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2310,7 +2263,7 @@ impl EditorElement {
|
||||||
|
|
||||||
let (x, y) = match position {
|
let (x, y) = match position {
|
||||||
crate::ContextMenuOrigin::EditorPoint(point) => {
|
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)
|
let x = cursor_row_layout.x_for_index(point.column() as usize)
|
||||||
- scroll_pixel_position.x;
|
- scroll_pixel_position.x;
|
||||||
let y = point.row().next_row().as_f32() * line_height - scroll_pixel_position.y;
|
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
|
// This is safe because we check on layout whether the required row is available
|
||||||
let hovered_row_layout =
|
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
|
// Compute Hovered Point
|
||||||
let x =
|
let x =
|
||||||
|
@ -2920,7 +2873,6 @@ impl EditorElement {
|
||||||
};
|
};
|
||||||
cx.set_cursor_style(cursor_style, &layout.text_hitbox);
|
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);
|
let invisible_display_ranges = self.paint_highlights(layout, cx);
|
||||||
self.paint_lines(&invisible_display_ranges, layout, cx);
|
self.paint_lines(&invisible_display_ranges, layout, cx);
|
||||||
self.paint_redactions(layout, cx);
|
self.paint_redactions(layout, cx);
|
||||||
|
@ -2979,7 +2931,7 @@ impl EditorElement {
|
||||||
fn paint_lines(
|
fn paint_lines(
|
||||||
&mut self,
|
&mut self,
|
||||||
invisible_display_ranges: &[Range<DisplayPoint>],
|
invisible_display_ranges: &[Range<DisplayPoint>],
|
||||||
layout: &EditorLayout,
|
layout: &mut EditorLayout,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) {
|
) {
|
||||||
let whitespace_setting = self
|
let whitespace_setting = self
|
||||||
|
@ -3001,6 +2953,10 @@ impl EditorElement {
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for line_element in &mut layout.line_elements {
|
||||||
|
line_element.paint(cx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint_redactions(&mut self, layout: &EditorLayout, cx: &mut WindowContext) {
|
fn paint_redactions(&mut self, layout: &EditorLayout, cx: &mut WindowContext) {
|
||||||
|
@ -3378,7 +3334,7 @@ impl EditorElement {
|
||||||
.iter_rows()
|
.iter_rows()
|
||||||
.map(|row| {
|
.map(|row| {
|
||||||
let line_layout =
|
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 {
|
HighlightedRangeLine {
|
||||||
start_x: if row == range.start.row() {
|
start_x: if row == range.start.row() {
|
||||||
layout.content_origin.x
|
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) {
|
fn paint_inline_blame(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
|
||||||
if let Some(mut inline_blame) = layout.inline_blame.take() {
|
if let Some(mut inline_blame) = layout.inline_blame.take() {
|
||||||
cx.paint_layer(layout.text_hitbox.bounds, |cx| {
|
cx.paint_layer(layout.text_hitbox.bounds, |cx| {
|
||||||
|
@ -3813,8 +3738,34 @@ fn deploy_blame_entry_context_menu(
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct LineWithInvisibles {
|
pub(crate) struct LineWithInvisibles {
|
||||||
pub line: ShapedLine,
|
fragments: SmallVec<[LineFragment; 1]>,
|
||||||
invisibles: Vec<Invisible>,
|
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 {
|
impl LineWithInvisibles {
|
||||||
|
@ -3825,100 +3776,161 @@ impl LineWithInvisibles {
|
||||||
max_line_count: usize,
|
max_line_count: usize,
|
||||||
line_number_layouts: &[Option<ShapedLine>],
|
line_number_layouts: &[Option<ShapedLine>],
|
||||||
editor_mode: EditorMode,
|
editor_mode: EditorMode,
|
||||||
cx: &WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> Vec<Self> {
|
) -> Vec<Self> {
|
||||||
let mut layouts = Vec::with_capacity(max_line_count);
|
let mut layouts = Vec::with_capacity(max_line_count);
|
||||||
|
let mut fragments: SmallVec<[LineFragment; 1]> = SmallVec::new();
|
||||||
let mut line = String::new();
|
let mut line = String::new();
|
||||||
let mut invisibles = Vec::new();
|
let mut invisibles = Vec::new();
|
||||||
|
let mut width = Pixels::ZERO;
|
||||||
|
let mut len = 0;
|
||||||
let mut styles = Vec::new();
|
let mut styles = Vec::new();
|
||||||
let mut non_whitespace_added = false;
|
let mut non_whitespace_added = false;
|
||||||
let mut row = 0;
|
let mut row = 0;
|
||||||
let mut line_exceeded_max_len = false;
|
let mut line_exceeded_max_len = false;
|
||||||
let font_size = text_style.font_size.to_pixels(cx.rem_size());
|
let font_size = text_style.font_size.to_pixels(cx.rem_size());
|
||||||
|
|
||||||
|
let ellipsis = SharedString::from("⋯");
|
||||||
|
|
||||||
for highlighted_chunk in chunks.chain([HighlightedChunk {
|
for highlighted_chunk in chunks.chain([HighlightedChunk {
|
||||||
chunk: "\n",
|
text: "\n",
|
||||||
style: None,
|
style: None,
|
||||||
is_tab: false,
|
is_tab: false,
|
||||||
|
renderer: None,
|
||||||
}]) {
|
}]) {
|
||||||
for (ix, mut line_chunk) in highlighted_chunk.chunk.split('\n').enumerate() {
|
if let Some(renderer) = highlighted_chunk.renderer {
|
||||||
if ix > 0 {
|
if !line.is_empty() {
|
||||||
let shaped_line = cx
|
let shaped_line = cx
|
||||||
.text_system()
|
.text_system()
|
||||||
.shape_line(line.clone().into(), font_size, &styles)
|
.shape_line(line.clone().into(), font_size, &styles)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
layouts.push(Self {
|
width += shaped_line.width;
|
||||||
line: shaped_line,
|
len += shaped_line.len;
|
||||||
invisibles: std::mem::take(&mut invisibles),
|
fragments.push(LineFragment::Text(shaped_line));
|
||||||
});
|
|
||||||
|
|
||||||
line.clear();
|
line.clear();
|
||||||
styles.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 available_width = if renderer.constrain_width {
|
||||||
let text_style = if let Some(style) = highlighted_chunk.style {
|
let chunk = if highlighted_chunk.text == ellipsis.as_ref() {
|
||||||
Cow::Owned(text_style.clone().highlight(style))
|
ellipsis.clone()
|
||||||
} else {
|
} 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 element = (renderer.render)(cx);
|
||||||
let mut chunk_len = max_line_len - line.len();
|
let line_height = text_style.line_height_in_pixels(cx.rem_size());
|
||||||
while !line_chunk.is_char_boundary(chunk_len) {
|
let size = element.layout_as_root(
|
||||||
chunk_len -= 1;
|
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 {
|
if !line_chunk.is_empty() && !line_exceeded_max_len {
|
||||||
len: line_chunk.len(),
|
let text_style = if let Some(style) = highlighted_chunk.style {
|
||||||
font: text_style.font(),
|
Cow::Owned(text_style.clone().highlight(style))
|
||||||
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 {
|
} else {
|
||||||
invisibles.extend(
|
Cow::Borrowed(text_style)
|
||||||
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,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
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(
|
fn draw(
|
||||||
&self,
|
&self,
|
||||||
layout: &EditorLayout,
|
layout: &EditorLayout,
|
||||||
|
@ -3939,9 +3979,20 @@ impl LineWithInvisibles {
|
||||||
let line_y = line_height
|
let line_y = line_height
|
||||||
* (row.as_f32() - layout.position_map.scroll_pixel_position.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);
|
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(
|
self.draw_invisibles(
|
||||||
&selection_ranges,
|
&selection_ranges,
|
||||||
|
@ -3979,7 +4030,7 @@ impl LineWithInvisibles {
|
||||||
Invisible::Whitespace { line_offset } => (line_offset, &layout.space_invisible),
|
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 =
|
let invisible_offset =
|
||||||
(layout.position_map.em_width - invisible_symbol.width).max(Pixels::ZERO) / 2.0;
|
(layout.position_map.em_width - invisible_symbol.width).max(Pixels::ZERO) / 2.0;
|
||||||
let origin = content_origin
|
let origin = content_origin
|
||||||
|
@ -4000,6 +4051,90 @@ impl LineWithInvisibles {
|
||||||
invisible_symbol.paint(origin, line_height, cx).log_err();
|
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)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
@ -4309,18 +4444,16 @@ impl Element for EditorElement {
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut max_visible_line_width = Pixels::ZERO;
|
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);
|
self.layout_lines(start_row..end_row, &line_numbers, &snapshot, cx);
|
||||||
for line_with_invisibles in &line_layouts {
|
for line_with_invisibles in &line_layouts {
|
||||||
if line_with_invisibles.line.width > max_visible_line_width {
|
if line_with_invisibles.width > max_visible_line_width {
|
||||||
max_visible_line_width = line_with_invisibles.line.width;
|
max_visible_line_width = line_with_invisibles.width;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let longest_line_width =
|
let longest_line_width =
|
||||||
layout_line(snapshot.longest_row(), &snapshot, &style, cx)
|
layout_line(snapshot.longest_row(), &snapshot, &style, cx).width;
|
||||||
.unwrap()
|
|
||||||
.width;
|
|
||||||
let mut scroll_width =
|
let mut scroll_width =
|
||||||
longest_line_width.max(max_visible_line_width) + overscroll.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| {
|
cx.with_element_namespace("blocks", |cx| {
|
||||||
self.layout_blocks(
|
self.layout_blocks(
|
||||||
&mut blocks,
|
&mut blocks,
|
||||||
|
@ -4470,19 +4612,6 @@ impl Element for EditorElement {
|
||||||
cx,
|
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 gutter_settings = EditorSettings::get_global(cx).gutter;
|
||||||
|
|
||||||
let mut context_menu_visible = false;
|
let mut context_menu_visible = false;
|
||||||
|
@ -4628,11 +4757,11 @@ impl Element for EditorElement {
|
||||||
highlighted_rows,
|
highlighted_rows,
|
||||||
highlighted_ranges,
|
highlighted_ranges,
|
||||||
redacted_ranges,
|
redacted_ranges,
|
||||||
|
line_elements,
|
||||||
line_numbers,
|
line_numbers,
|
||||||
display_hunks,
|
display_hunks,
|
||||||
blamed_display_rows,
|
blamed_display_rows,
|
||||||
inline_blame,
|
inline_blame,
|
||||||
folds,
|
|
||||||
blocks,
|
blocks,
|
||||||
cursors,
|
cursors,
|
||||||
visible_cursors,
|
visible_cursors,
|
||||||
|
@ -4750,11 +4879,11 @@ pub struct EditorLayout {
|
||||||
visible_display_row_range: Range<DisplayRow>,
|
visible_display_row_range: Range<DisplayRow>,
|
||||||
active_rows: BTreeMap<DisplayRow, bool>,
|
active_rows: BTreeMap<DisplayRow, bool>,
|
||||||
highlighted_rows: BTreeMap<DisplayRow, Hsla>,
|
highlighted_rows: BTreeMap<DisplayRow, Hsla>,
|
||||||
|
line_elements: SmallVec<[AnyElement; 1]>,
|
||||||
line_numbers: Vec<Option<ShapedLine>>,
|
line_numbers: Vec<Option<ShapedLine>>,
|
||||||
display_hunks: Vec<(DisplayDiffHunk, Option<Hitbox>)>,
|
display_hunks: Vec<(DisplayDiffHunk, Option<Hitbox>)>,
|
||||||
blamed_display_rows: Option<Vec<AnyElement>>,
|
blamed_display_rows: Option<Vec<AnyElement>>,
|
||||||
inline_blame: Option<AnyElement>,
|
inline_blame: Option<AnyElement>,
|
||||||
folds: Vec<FoldLayout>,
|
|
||||||
blocks: Vec<BlockLayout>,
|
blocks: Vec<BlockLayout>,
|
||||||
highlighted_ranges: Vec<(Range<DisplayPoint>, Hsla)>,
|
highlighted_ranges: Vec<(Range<DisplayPoint>, Hsla)>,
|
||||||
redacted_ranges: Vec<Range<DisplayPoint>>,
|
redacted_ranges: Vec<Range<DisplayPoint>>,
|
||||||
|
@ -4893,11 +5022,6 @@ struct FlapTrailerLayout {
|
||||||
bounds: Bounds<Pixels>,
|
bounds: Bounds<Pixels>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FoldLayout {
|
|
||||||
display_range: Range<DisplayPoint>,
|
|
||||||
hover_element: AnyElement,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct PositionMap {
|
struct PositionMap {
|
||||||
size: Size<Pixels>,
|
size: Size<Pixels>,
|
||||||
line_height: Pixels,
|
line_height: Pixels,
|
||||||
|
@ -4942,7 +5066,6 @@ impl PositionMap {
|
||||||
let (column, x_overshoot_after_line_end) = if let Some(line) = self
|
let (column, x_overshoot_after_line_end) = if let Some(line) = self
|
||||||
.line_layouts
|
.line_layouts
|
||||||
.get(row as usize - scroll_position.y as usize)
|
.get(row as usize - scroll_position.y as usize)
|
||||||
.map(|LineWithInvisibles { line, .. }| line)
|
|
||||||
{
|
{
|
||||||
if let Some(ix) = line.index_for_x(x) {
|
if let Some(ix) = line.index_for_x(x) {
|
||||||
(ix as u32, px(0.))
|
(ix as u32, px(0.))
|
||||||
|
@ -4979,37 +5102,12 @@ fn layout_line(
|
||||||
row: DisplayRow,
|
row: DisplayRow,
|
||||||
snapshot: &EditorSnapshot,
|
snapshot: &EditorSnapshot,
|
||||||
style: &EditorStyle,
|
style: &EditorStyle,
|
||||||
cx: &WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> Result<ShapedLine> {
|
) -> LineWithInvisibles {
|
||||||
let mut line = snapshot.line(row);
|
let chunks = snapshot.highlighted_chunks(row..row + DisplayRow(1), true, style);
|
||||||
|
LineWithInvisibles::from_chunks(chunks, &style.text, MAX_LINE_LEN, 1, &[], snapshot.mode, cx)
|
||||||
let len = {
|
.pop()
|
||||||
let line_len = line.len();
|
.unwrap()
|
||||||
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,
|
|
||||||
}],
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
|
@ -577,7 +577,7 @@ mod tests {
|
||||||
use crate::{
|
use crate::{
|
||||||
display_map::Inlay,
|
display_map::Inlay,
|
||||||
test::{editor_test_context::EditorTestContext, marked_display_snapshot},
|
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 gpui::{font, Context as _};
|
||||||
use language::Capability;
|
use language::Capability;
|
||||||
|
@ -695,8 +695,18 @@ mod tests {
|
||||||
let font_size = px(14.0);
|
let font_size = px(14.0);
|
||||||
let buffer = MultiBuffer::build_simple(input_text, cx);
|
let buffer = MultiBuffer::build_simple(input_text, cx);
|
||||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||||
let display_map =
|
let display_map = cx.new_model(|cx| {
|
||||||
cx.new_model(|cx| DisplayMap::new(buffer, font, font_size, None, 1, 1, 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
|
// 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;
|
let mut id = 0;
|
||||||
|
@ -901,8 +911,18 @@ mod tests {
|
||||||
);
|
);
|
||||||
multibuffer
|
multibuffer
|
||||||
});
|
});
|
||||||
let display_map =
|
let display_map = cx.new_model(|cx| {
|
||||||
cx.new_model(|cx| DisplayMap::new(multibuffer, font, px(14.0), None, 2, 2, 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));
|
let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||||
|
|
||||||
assert_eq!(snapshot.text(), "\n\nabc\ndefg\n\n\nhijkl\nmn");
|
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);
|
let end_column = cmp::min(display_map.line_len(head.row()), head.column() + 3);
|
||||||
target_left = target_left.min(
|
target_left = target_left.min(
|
||||||
layouts[head.row().minus(start_row) as usize]
|
layouts[head.row().minus(start_row) as usize]
|
||||||
.line
|
|
||||||
.x_for_index(start_column as usize),
|
.x_for_index(start_column as usize),
|
||||||
);
|
);
|
||||||
target_right = target_right.max(
|
target_right = target_right.max(
|
||||||
layouts[head.row().minus(start_row) as usize]
|
layouts[head.row().minus(start_row) as usize]
|
||||||
.line
|
|
||||||
.x_for_index(end_column as usize)
|
.x_for_index(end_column as usize)
|
||||||
+ max_glyph_width,
|
+ max_glyph_width,
|
||||||
);
|
);
|
||||||
|
|
|
@ -3,11 +3,9 @@ pub mod editor_test_context;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
display_map::{DisplayMap, DisplaySnapshot, ToDisplayPoint},
|
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 gpui::{Context, Font, FontFeatures, FontStyle, FontWeight, Model, Pixels, ViewContext};
|
||||||
|
|
||||||
use project::Project;
|
use project::Project;
|
||||||
use util::test::{marked_text_offsets, marked_text_ranges};
|
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 font_size: Pixels = 14usize.into();
|
||||||
|
|
||||||
let buffer = MultiBuffer::build_simple(&unmarked_text, cx);
|
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 snapshot = display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||||
let markers = markers
|
let markers = markers
|
||||||
.into_iter()
|
.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 {
|
pub fn layout(&self) -> &TextLayout {
|
||||||
&self.layout
|
&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)]
|
#[derive(Default, Clone)]
|
||||||
pub struct TextLayout(Arc<Mutex<Option<TextLayoutInner>>>);
|
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> {
|
pub fn index_for_position(&self, mut position: Point<Pixels>) -> Result<usize, usize> {
|
||||||
let element_state = self.lock();
|
let element_state = self.lock();
|
||||||
let element_state = element_state
|
let element_state = element_state
|
||||||
|
@ -392,7 +392,7 @@ impl TextLayout {
|
||||||
Err(line_start_ix.saturating_sub(1))
|
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>> {
|
pub fn position_for_index(&self, index: usize) -> Option<Point<Pixels>> {
|
||||||
let element_state = self.lock();
|
let element_state = self.lock();
|
||||||
let element_state = element_state
|
let element_state = element_state
|
||||||
|
@ -423,17 +423,17 @@ impl TextLayout {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// todo!()
|
/// The bounds of this layout.
|
||||||
pub fn bounds(&self) -> Bounds<Pixels> {
|
pub fn bounds(&self) -> Bounds<Pixels> {
|
||||||
self.0.lock().as_ref().unwrap().bounds.unwrap()
|
self.0.lock().as_ref().unwrap().bounds.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// todo!()
|
/// The line height for this layout.
|
||||||
pub fn line_height(&self) -> Pixels {
|
pub fn line_height(&self) -> Pixels {
|
||||||
self.0.lock().as_ref().unwrap().line_height
|
self.0.lock().as_ref().unwrap().line_height
|
||||||
}
|
}
|
||||||
|
|
||||||
/// todo!()
|
/// The text for this layout.
|
||||||
pub fn text(&self) -> String {
|
pub fn text(&self) -> String {
|
||||||
self.0
|
self.0
|
||||||
.lock()
|
.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>> {
|
pub fn position_for_index(&self, index: usize, line_height: Pixels) -> Option<Point<Pixels>> {
|
||||||
let mut line_start_ix = 0;
|
let mut line_start_ix = 0;
|
||||||
let mut line_end_indices = self
|
let mut line_end_indices = self
|
||||||
|
|
|
@ -19,7 +19,10 @@ use crate::{
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
pub use clock::ReplicaId;
|
pub use clock::ReplicaId;
|
||||||
use futures::channel::oneshot;
|
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 lazy_static::lazy_static;
|
||||||
use lsp::LanguageServerId;
|
use lsp::LanguageServerId;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
@ -31,6 +34,7 @@ use std::{
|
||||||
cmp::{self, Ordering},
|
cmp::{self, Ordering},
|
||||||
collections::BTreeMap,
|
collections::BTreeMap,
|
||||||
ffi::OsStr,
|
ffi::OsStr,
|
||||||
|
fmt,
|
||||||
future::Future,
|
future::Future,
|
||||||
iter::{self, Iterator, Peekable},
|
iter::{self, Iterator, Peekable},
|
||||||
mem,
|
mem,
|
||||||
|
@ -461,7 +465,7 @@ pub struct BufferChunks<'a> {
|
||||||
|
|
||||||
/// A chunk of a buffer's text, along with its syntax highlight and
|
/// A chunk of a buffer's text, along with its syntax highlight and
|
||||||
/// diagnostic status.
|
/// diagnostic status.
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct Chunk<'a> {
|
pub struct Chunk<'a> {
|
||||||
/// The text of the chunk.
|
/// The text of the chunk.
|
||||||
pub text: &'a str,
|
pub text: &'a str,
|
||||||
|
@ -476,6 +480,25 @@ pub struct Chunk<'a> {
|
||||||
pub is_unnecessary: bool,
|
pub is_unnecessary: bool,
|
||||||
/// Whether this chunk of text was originally a tab character.
|
/// Whether this chunk of text was originally a tab character.
|
||||||
pub is_tab: bool,
|
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.
|
/// A set of edits to a given version of a buffer, computed asynchronously.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue