Account for inlay biases when clipping a point

This commit is contained in:
Antonio Scandurra 2023-06-23 17:35:18 +03:00 committed by Kirill Bulatov
parent 976edfedf7
commit f77b680db9
3 changed files with 415 additions and 95 deletions

View file

@ -301,7 +301,7 @@ impl InlayMap {
let version = 0; let version = 0;
let snapshot = InlaySnapshot { let snapshot = InlaySnapshot {
buffer: buffer.clone(), buffer: buffer.clone(),
transforms: SumTree::from_item(Transform::Isomorphic(buffer.text_summary()), &()), transforms: SumTree::from_iter(Some(Transform::Isomorphic(buffer.text_summary())), &()),
version, version,
}; };
@ -357,7 +357,7 @@ impl InlayMap {
new_transforms.append(cursor.slice(&buffer_edit.old.start, Bias::Left, &()), &()); new_transforms.append(cursor.slice(&buffer_edit.old.start, Bias::Left, &()), &());
if let Some(Transform::Isomorphic(transform)) = cursor.item() { if let Some(Transform::Isomorphic(transform)) = cursor.item() {
if cursor.end(&()).0 == buffer_edit.old.start { if cursor.end(&()).0 == buffer_edit.old.start {
new_transforms.push(Transform::Isomorphic(transform.clone()), &()); push_isomorphic(&mut new_transforms, transform.clone());
cursor.next(&()); cursor.next(&());
} }
} }
@ -436,7 +436,7 @@ impl InlayMap {
} }
new_transforms.append(cursor.suffix(&()), &()); new_transforms.append(cursor.suffix(&()), &());
if new_transforms.first().is_none() { if new_transforms.is_empty() {
new_transforms.push(Transform::Isomorphic(Default::default()), &()); new_transforms.push(Transform::Isomorphic(Default::default()), &());
} }
@ -654,55 +654,124 @@ impl InlaySnapshot {
pub fn to_inlay_point(&self, point: Point) -> InlayPoint { pub fn to_inlay_point(&self, point: Point) -> InlayPoint {
let mut cursor = self.transforms.cursor::<(Point, InlayPoint)>(); let mut cursor = self.transforms.cursor::<(Point, InlayPoint)>();
cursor.seek(&point, Bias::Left, &()); cursor.seek(&point, Bias::Left, &());
match cursor.item() { loop {
Some(Transform::Isomorphic(_)) => { match cursor.item() {
let overshoot = point - cursor.start().0; Some(Transform::Isomorphic(_)) => {
InlayPoint(cursor.start().1 .0 + overshoot) if point == cursor.end(&()).0 {
while let Some(Transform::Inlay(inlay)) = cursor.next_item() {
if inlay.position.bias() == Bias::Right {
break;
} else {
cursor.next(&());
}
}
return cursor.end(&()).1;
} else {
let overshoot = point - cursor.start().0;
return InlayPoint(cursor.start().1 .0 + overshoot);
}
}
Some(Transform::Inlay(inlay)) => {
if inlay.position.bias() == Bias::Left {
cursor.next(&());
} else {
return cursor.start().1;
}
}
None => {
return self.max_point();
}
} }
Some(Transform::Inlay(_)) => cursor.start().1,
None => self.max_point(),
} }
} }
pub fn clip_point(&self, point: InlayPoint, bias: Bias) -> InlayPoint { pub fn clip_point(&self, mut point: InlayPoint, mut bias: Bias) -> InlayPoint {
let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>(); let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>();
cursor.seek(&point, Bias::Left, &()); cursor.seek(&point, Bias::Left, &());
let mut bias = bias;
let mut skipped_inlay = false;
loop { loop {
match cursor.item() { match cursor.item() {
Some(Transform::Isomorphic(transform)) => { Some(Transform::Isomorphic(transform)) => {
let overshoot = if skipped_inlay { if cursor.start().0 == point {
match bias { if let Some(Transform::Inlay(inlay)) = cursor.prev_item() {
Bias::Left => transform.lines, if inlay.position.bias() == Bias::Left {
Bias::Right => { return point;
if transform.first_line_chars == 0 { } else if bias == Bias::Left {
Point::new(1, 0) cursor.prev(&());
} else { } else if transform.first_line_chars == 0 {
Point::new(0, 1) point.0 += Point::new(1, 0);
} } else {
point.0 += Point::new(0, 1);
} }
} else {
return point;
}
} else if cursor.end(&()).0 == point {
if let Some(Transform::Inlay(inlay)) = cursor.next_item() {
if inlay.position.bias() == Bias::Right {
return point;
} else if bias == Bias::Right {
cursor.next(&());
} else if point.0.column == 0 {
point.0.row -= 1;
point.0.column = self.line_len(point.0.row);
} else {
point.0.column -= 1;
}
} else {
return point;
} }
} else { } else {
point.0 - cursor.start().0 .0 let overshoot = point.0 - cursor.start().0 .0;
}; let buffer_point = cursor.start().1 + overshoot;
let buffer_point = cursor.start().1 + overshoot; let clipped_buffer_point = self.buffer.clip_point(buffer_point, bias);
let clipped_buffer_point = self.buffer.clip_point(buffer_point, bias); let clipped_overshoot = clipped_buffer_point - cursor.start().1;
let clipped_overshoot = clipped_buffer_point - cursor.start().1; let clipped_point = InlayPoint(cursor.start().0 .0 + clipped_overshoot);
return InlayPoint(cursor.start().0 .0 + clipped_overshoot); if clipped_point == point {
return clipped_point;
} else {
point = clipped_point;
}
}
} }
Some(Transform::Inlay(_)) => skipped_inlay = true, Some(Transform::Inlay(inlay)) => {
None => match bias { if point == cursor.start().0 && inlay.position.bias() == Bias::Right {
Bias::Left => return Default::default(), match cursor.prev_item() {
Bias::Right => bias = Bias::Left, Some(Transform::Inlay(inlay)) => {
}, if inlay.position.bias() == Bias::Left {
} return point;
}
}
_ => return point,
}
} else if point == cursor.end(&()).0 && inlay.position.bias() == Bias::Left {
match cursor.next_item() {
Some(Transform::Inlay(inlay)) => {
if inlay.position.bias() == Bias::Right {
return point;
}
}
_ => return point,
}
}
if bias == Bias::Left { if bias == Bias::Left {
cursor.prev(&()); point = cursor.start().0;
} else { cursor.prev(&());
cursor.next(&()); } else {
cursor.next(&());
point = cursor.start().0;
}
}
None => {
bias = bias.invert();
if bias == Bias::Left {
point = cursor.start().0;
cursor.prev(&());
} else {
cursor.next(&());
point = cursor.start().0;
}
}
} }
} }
} }
@ -833,6 +902,18 @@ impl InlaySnapshot {
#[cfg(any(debug_assertions, feature = "test-support"))] #[cfg(any(debug_assertions, feature = "test-support"))]
{ {
assert_eq!(self.transforms.summary().input, self.buffer.text_summary()); assert_eq!(self.transforms.summary().input, self.buffer.text_summary());
let mut transforms = self.transforms.iter().peekable();
while let Some(transform) = transforms.next() {
let transform_is_isomorphic = matches!(transform, Transform::Isomorphic(_));
if let Some(next_transform) = transforms.peek() {
let next_transform_is_isomorphic =
matches!(next_transform, Transform::Isomorphic(_));
assert!(
!transform_is_isomorphic || !next_transform_is_isomorphic,
"two adjacent isomorphic transforms"
);
}
}
} }
} }
} }
@ -983,6 +1064,177 @@ mod tests {
); );
assert_eq!(inlay_snapshot.text(), "abx|123|JKL|456|yDzefghi"); assert_eq!(inlay_snapshot.text(), "abx|123|JKL|456|yDzefghi");
assert_eq!(
inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Left),
InlayPoint::new(0, 0)
);
assert_eq!(
inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Right),
InlayPoint::new(0, 0)
);
assert_eq!(
inlay_snapshot.clip_point(InlayPoint::new(0, 1), Bias::Left),
InlayPoint::new(0, 1)
);
assert_eq!(
inlay_snapshot.clip_point(InlayPoint::new(0, 1), Bias::Right),
InlayPoint::new(0, 1)
);
assert_eq!(
inlay_snapshot.clip_point(InlayPoint::new(0, 2), Bias::Left),
InlayPoint::new(0, 2)
);
assert_eq!(
inlay_snapshot.clip_point(InlayPoint::new(0, 2), Bias::Right),
InlayPoint::new(0, 2)
);
assert_eq!(
inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Left),
InlayPoint::new(0, 2)
);
assert_eq!(
inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Right),
InlayPoint::new(0, 8)
);
assert_eq!(
inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Left),
InlayPoint::new(0, 2)
);
assert_eq!(
inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Right),
InlayPoint::new(0, 8)
);
assert_eq!(
inlay_snapshot.clip_point(InlayPoint::new(0, 5), Bias::Left),
InlayPoint::new(0, 2)
);
assert_eq!(
inlay_snapshot.clip_point(InlayPoint::new(0, 5), Bias::Right),
InlayPoint::new(0, 8)
);
assert_eq!(
inlay_snapshot.clip_point(InlayPoint::new(0, 6), Bias::Left),
InlayPoint::new(0, 2)
);
assert_eq!(
inlay_snapshot.clip_point(InlayPoint::new(0, 6), Bias::Right),
InlayPoint::new(0, 8)
);
assert_eq!(
inlay_snapshot.clip_point(InlayPoint::new(0, 7), Bias::Left),
InlayPoint::new(0, 2)
);
assert_eq!(
inlay_snapshot.clip_point(InlayPoint::new(0, 7), Bias::Right),
InlayPoint::new(0, 8)
);
assert_eq!(
inlay_snapshot.clip_point(InlayPoint::new(0, 8), Bias::Left),
InlayPoint::new(0, 8)
);
assert_eq!(
inlay_snapshot.clip_point(InlayPoint::new(0, 8), Bias::Right),
InlayPoint::new(0, 8)
);
assert_eq!(
inlay_snapshot.clip_point(InlayPoint::new(0, 9), Bias::Left),
InlayPoint::new(0, 9)
);
assert_eq!(
inlay_snapshot.clip_point(InlayPoint::new(0, 9), Bias::Right),
InlayPoint::new(0, 9)
);
assert_eq!(
inlay_snapshot.clip_point(InlayPoint::new(0, 10), Bias::Left),
InlayPoint::new(0, 10)
);
assert_eq!(
inlay_snapshot.clip_point(InlayPoint::new(0, 10), Bias::Right),
InlayPoint::new(0, 10)
);
assert_eq!(
inlay_snapshot.clip_point(InlayPoint::new(0, 11), Bias::Left),
InlayPoint::new(0, 11)
);
assert_eq!(
inlay_snapshot.clip_point(InlayPoint::new(0, 11), Bias::Right),
InlayPoint::new(0, 11)
);
assert_eq!(
inlay_snapshot.clip_point(InlayPoint::new(0, 12), Bias::Left),
InlayPoint::new(0, 11)
);
assert_eq!(
inlay_snapshot.clip_point(InlayPoint::new(0, 12), Bias::Right),
InlayPoint::new(0, 17)
);
assert_eq!(
inlay_snapshot.clip_point(InlayPoint::new(0, 13), Bias::Left),
InlayPoint::new(0, 11)
);
assert_eq!(
inlay_snapshot.clip_point(InlayPoint::new(0, 13), Bias::Right),
InlayPoint::new(0, 17)
);
assert_eq!(
inlay_snapshot.clip_point(InlayPoint::new(0, 14), Bias::Left),
InlayPoint::new(0, 11)
);
assert_eq!(
inlay_snapshot.clip_point(InlayPoint::new(0, 14), Bias::Right),
InlayPoint::new(0, 17)
);
assert_eq!(
inlay_snapshot.clip_point(InlayPoint::new(0, 15), Bias::Left),
InlayPoint::new(0, 11)
);
assert_eq!(
inlay_snapshot.clip_point(InlayPoint::new(0, 15), Bias::Right),
InlayPoint::new(0, 17)
);
assert_eq!(
inlay_snapshot.clip_point(InlayPoint::new(0, 16), Bias::Left),
InlayPoint::new(0, 11)
);
assert_eq!(
inlay_snapshot.clip_point(InlayPoint::new(0, 16), Bias::Right),
InlayPoint::new(0, 17)
);
assert_eq!(
inlay_snapshot.clip_point(InlayPoint::new(0, 17), Bias::Left),
InlayPoint::new(0, 17)
);
assert_eq!(
inlay_snapshot.clip_point(InlayPoint::new(0, 17), Bias::Right),
InlayPoint::new(0, 17)
);
assert_eq!(
inlay_snapshot.clip_point(InlayPoint::new(0, 18), Bias::Left),
InlayPoint::new(0, 18)
);
assert_eq!(
inlay_snapshot.clip_point(InlayPoint::new(0, 18), Bias::Right),
InlayPoint::new(0, 18)
);
// The inlays can be manually removed. // The inlays can be manually removed.
let (inlay_snapshot, _) = inlay_map let (inlay_snapshot, _) = inlay_map
.splice::<String>(inlay_map.inlays_by_id.keys().copied().collect(), Vec::new()); .splice::<String>(inlay_map.inlays_by_id.keys().copied().collect(), Vec::new());
@ -1146,6 +1398,41 @@ mod tests {
assert_eq!(expected_text.max_point(), inlay_snapshot.max_point().0); assert_eq!(expected_text.max_point(), inlay_snapshot.max_point().0);
assert_eq!(expected_text.len(), inlay_snapshot.len().0); assert_eq!(expected_text.len(), inlay_snapshot.len().0);
let mut buffer_point = Point::default();
let mut inlay_point = inlay_snapshot.to_inlay_point(buffer_point);
let mut buffer_chars = buffer_snapshot.chars_at(0);
loop {
// No matter which bias we clip an inlay point with, it doesn't move
// because it was constructed from a buffer point.
assert_eq!(
inlay_snapshot.clip_point(inlay_point, Bias::Left),
inlay_point,
"invalid inlay point for buffer point {:?} when clipped left",
buffer_point
);
assert_eq!(
inlay_snapshot.clip_point(inlay_point, Bias::Right),
inlay_point,
"invalid inlay point for buffer point {:?} when clipped right",
buffer_point
);
if let Some(ch) = buffer_chars.next() {
if ch == '\n' {
buffer_point += Point::new(1, 0);
} else {
buffer_point += Point::new(0, ch.len_utf8() as u32);
}
// Ensure that moving forward in the buffer always moves the inlay point forward as well.
let new_inlay_point = inlay_snapshot.to_inlay_point(buffer_point);
assert!(new_inlay_point > inlay_point);
inlay_point = new_inlay_point;
} else {
break;
}
}
let mut inlay_point = InlayPoint::default(); let mut inlay_point = InlayPoint::default();
let mut inlay_offset = InlayOffset::default(); let mut inlay_offset = InlayOffset::default();
for ch in expected_text.chars() { for ch in expected_text.chars() {
@ -1161,13 +1448,6 @@ mod tests {
"invalid to_point({:?})", "invalid to_point({:?})",
inlay_offset inlay_offset
); );
assert_eq!(
inlay_snapshot.to_inlay_point(inlay_snapshot.to_buffer_point(inlay_point)),
inlay_snapshot.clip_point(inlay_point, Bias::Left),
"to_buffer_point({:?}) = {:?}",
inlay_point,
inlay_snapshot.to_buffer_point(inlay_point),
);
let mut bytes = [0; 4]; let mut bytes = [0; 4];
for byte in ch.encode_utf8(&mut bytes).as_bytes() { for byte in ch.encode_utf8(&mut bytes).as_bytes() {
@ -1182,7 +1462,8 @@ mod tests {
let clipped_right_point = inlay_snapshot.clip_point(inlay_point, Bias::Right); let clipped_right_point = inlay_snapshot.clip_point(inlay_point, Bias::Right);
assert!( assert!(
clipped_left_point <= clipped_right_point, clipped_left_point <= clipped_right_point,
"clipped left point {:?} is greater than clipped right point {:?}", "inlay point {:?} when clipped left is greater than when clipped right ({:?} > {:?})",
inlay_point,
clipped_left_point, clipped_left_point,
clipped_right_point clipped_right_point
); );
@ -1200,6 +1481,24 @@ mod tests {
// Ensure the clipped points never overshoot the end of the map. // Ensure the clipped points never overshoot the end of the map.
assert!(clipped_left_point <= inlay_snapshot.max_point()); assert!(clipped_left_point <= inlay_snapshot.max_point());
assert!(clipped_right_point <= inlay_snapshot.max_point()); assert!(clipped_right_point <= inlay_snapshot.max_point());
// Ensure the clipped points are at valid buffer locations.
assert_eq!(
inlay_snapshot
.to_inlay_point(inlay_snapshot.to_buffer_point(clipped_left_point)),
clipped_left_point,
"to_buffer_point({:?}) = {:?}",
clipped_left_point,
inlay_snapshot.to_buffer_point(clipped_left_point),
);
assert_eq!(
inlay_snapshot
.to_inlay_point(inlay_snapshot.to_buffer_point(clipped_right_point)),
clipped_right_point,
"to_buffer_point({:?}) = {:?}",
clipped_right_point,
inlay_snapshot.to_buffer_point(clipped_right_point),
);
} }
} }
} }

View file

@ -1832,22 +1832,34 @@ impl LspCommand for InlayHints {
Ok(message Ok(message
.unwrap_or_default() .unwrap_or_default()
.into_iter() .into_iter()
.map(|lsp_hint| InlayHint { .map(|lsp_hint| {
buffer_id: origin_buffer.remote_id(), let kind = lsp_hint.kind.and_then(|kind| match kind {
position: origin_buffer.anchor_after( lsp::InlayHintKind::TYPE => Some(InlayHintKind::Type),
origin_buffer lsp::InlayHintKind::PARAMETER => Some(InlayHintKind::Parameter),
.clip_point_utf16(point_from_lsp(lsp_hint.position), Bias::Left), _ => None,
), });
padding_left: lsp_hint.padding_left.unwrap_or(false), let position = origin_buffer
padding_right: lsp_hint.padding_right.unwrap_or(false), .clip_point_utf16(point_from_lsp(lsp_hint.position), Bias::Left);
label: match lsp_hint.label { InlayHint {
lsp::InlayHintLabel::String(s) => InlayHintLabel::String(s), buffer_id: origin_buffer.remote_id(),
lsp::InlayHintLabel::LabelParts(lsp_parts) => InlayHintLabel::LabelParts( position: if kind == Some(InlayHintKind::Parameter) {
lsp_parts origin_buffer.anchor_before(position)
.into_iter() } else {
.map(|label_part| InlayHintLabelPart { origin_buffer.anchor_after(position)
value: label_part.value, },
tooltip: label_part.tooltip.map(|tooltip| match tooltip { padding_left: lsp_hint.padding_left.unwrap_or(false),
padding_right: lsp_hint.padding_right.unwrap_or(false),
label: match lsp_hint.label {
lsp::InlayHintLabel::String(s) => InlayHintLabel::String(s),
lsp::InlayHintLabel::LabelParts(lsp_parts) => {
InlayHintLabel::LabelParts(
lsp_parts
.into_iter()
.map(|label_part| InlayHintLabelPart {
value: label_part.value,
tooltip: label_part.tooltip.map(
|tooltip| {
match tooltip {
lsp::InlayHintLabelPartTooltip::String(s) => { lsp::InlayHintLabelPartTooltip::String(s) => {
InlayHintLabelPartTooltip::String(s) InlayHintLabelPartTooltip::String(s)
} }
@ -1859,40 +1871,40 @@ impl LspCommand for InlayHints {
value: markup_content.value, value: markup_content.value,
}, },
), ),
}), }
location: label_part.location.map(|lsp_location| { },
let target_start = origin_buffer.clip_point_utf16( ),
point_from_lsp(lsp_location.range.start), location: label_part.location.map(|lsp_location| {
Bias::Left, let target_start = origin_buffer.clip_point_utf16(
); point_from_lsp(lsp_location.range.start),
let target_end = origin_buffer.clip_point_utf16( Bias::Left,
point_from_lsp(lsp_location.range.end), );
Bias::Left, let target_end = origin_buffer.clip_point_utf16(
); point_from_lsp(lsp_location.range.end),
Location { Bias::Left,
buffer: buffer.clone(), );
range: origin_buffer.anchor_after(target_start) Location {
..origin_buffer.anchor_before(target_end), buffer: buffer.clone(),
} range: origin_buffer.anchor_after(target_start)
}), ..origin_buffer.anchor_before(target_end),
}
}),
})
.collect(),
)
}
},
kind,
tooltip: lsp_hint.tooltip.map(|tooltip| match tooltip {
lsp::InlayHintTooltip::String(s) => InlayHintTooltip::String(s),
lsp::InlayHintTooltip::MarkupContent(markup_content) => {
InlayHintTooltip::MarkupContent(MarkupContent {
kind: format!("{:?}", markup_content.kind),
value: markup_content.value,
}) })
.collect(), }
), }),
}, }
kind: lsp_hint.kind.and_then(|kind| match kind {
lsp::InlayHintKind::TYPE => Some(InlayHintKind::Type),
lsp::InlayHintKind::PARAMETER => Some(InlayHintKind::Parameter),
_ => None,
}),
tooltip: lsp_hint.tooltip.map(|tooltip| match tooltip {
lsp::InlayHintTooltip::String(s) => InlayHintTooltip::String(s),
lsp::InlayHintTooltip::MarkupContent(markup_content) => {
InlayHintTooltip::MarkupContent(MarkupContent {
kind: format!("{:?}", markup_content.kind),
value: markup_content.value,
})
}
}),
}) })
.collect()) .collect())
}) })

View file

@ -102,6 +102,15 @@ pub enum Bias {
Right, Right,
} }
impl Bias {
pub fn invert(self) -> Self {
match self {
Self::Left => Self::Right,
Self::Right => Self::Left,
}
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct SumTree<T: Item>(Arc<Node<T>>); pub struct SumTree<T: Item>(Arc<Node<T>>);