Rework diff rendering to allow putting the cursor into deleted text, soft-wrapping and scrolling deleted text correctly (#22994)
Closes #12553 * [x] Fix `diff_hunk_before` * [x] Fix failure to show deleted text when expanding hunk w/ cursor on second line of the hunk * [x] Failure to expand diff hunk below the cursor. * [x] Delete the whole file, and expand the diff. Backspace over the deleted hunk, panic! * [x] Go-to-line now counts the diff hunks, but it should not * [x] backspace at the beginning of a deleted hunk deletes too much text * [x] Indent guides are rendered incorrectly * [ ] Fix randomized multi buffer tests Maybe: * [ ] Buffer search should include deleted text (in vim mode it turns out I use `/x` all the time to jump to the next x I can see). * [ ] vim: should refuse to switch into insert mode if selection is fully within a diff. * [ ] vim `o` command when cursor is on last line of deleted hunk. * [ ] vim `shift-o` on first line of deleted hunk moves cursor but doesn't insert line * [x] `enter` at end of diff hunk inserts a new line but doesn't move cursor * [x] (`shift-enter` at start of diff hunk does nothing) * [ ] Inserting a line just before an expanded hunk collapses it Release Notes: - Improved diff rendering, allowing you to navigate with your cursor inside of deleted text in diff hunks. --------- Co-authored-by: Conrad <conrad@zed.dev> Co-authored-by: Cole <cole@zed.dev> Co-authored-by: Mikayla <mikayla@zed.dev> Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com> Co-authored-by: Michael <michael@zed.dev> Co-authored-by: Agus <agus@zed.dev> Co-authored-by: João <joao@zed.dev>
This commit is contained in:
parent
1fdae4bae0
commit
d2c55cbe3d
64 changed files with 7653 additions and 5495 deletions
|
@ -372,7 +372,7 @@ gpui::actions!(
|
|||
ToggleAutoSignatureHelp,
|
||||
ToggleGitBlame,
|
||||
ToggleGitBlameInline,
|
||||
ToggleHunkDiff,
|
||||
ToggleSelectedDiffHunks,
|
||||
ToggleIndentGuides,
|
||||
ToggleInlayHints,
|
||||
ToggleInlineCompletions,
|
||||
|
|
|
@ -30,8 +30,8 @@ use crate::{
|
|||
hover_links::InlayHighlight, movement::TextLayoutDetails, EditorStyle, InlayId, RowExt,
|
||||
};
|
||||
pub use block_map::{
|
||||
Block, BlockBufferRows, BlockChunks as DisplayChunks, BlockContext, BlockId, BlockMap,
|
||||
BlockPlacement, BlockPoint, BlockProperties, BlockStyle, CustomBlockId, RenderBlock,
|
||||
Block, BlockChunks as DisplayChunks, BlockContext, BlockId, BlockMap, BlockPlacement,
|
||||
BlockPoint, BlockProperties, BlockRows, BlockStyle, CustomBlockId, RenderBlock,
|
||||
StickyHeaderExcerpt,
|
||||
};
|
||||
use block_map::{BlockRow, BlockSnapshot};
|
||||
|
@ -54,7 +54,7 @@ use language::{
|
|||
use lsp::DiagnosticSeverity;
|
||||
use multi_buffer::{
|
||||
Anchor, AnchorRangeExt, MultiBuffer, MultiBufferPoint, MultiBufferRow, MultiBufferSnapshot,
|
||||
ToOffset, ToPoint,
|
||||
RowInfo, ToOffset, ToPoint,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use std::{
|
||||
|
@ -68,7 +68,7 @@ use std::{
|
|||
};
|
||||
use sum_tree::{Bias, TreeMap};
|
||||
use tab_map::{TabMap, TabSnapshot};
|
||||
use text::LineIndent;
|
||||
use text::{BufferId, LineIndent};
|
||||
use ui::{px, SharedString, WindowContext};
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use wrap_map::{WrapMap, WrapSnapshot};
|
||||
|
@ -367,10 +367,14 @@ impl DisplayMap {
|
|||
block_map.unfold_buffer(buffer_id, self.buffer.read(cx), cx)
|
||||
}
|
||||
|
||||
pub(crate) fn buffer_folded(&self, buffer_id: language::BufferId) -> bool {
|
||||
pub(crate) fn is_buffer_folded(&self, buffer_id: language::BufferId) -> bool {
|
||||
self.block_map.folded_buffers.contains(&buffer_id)
|
||||
}
|
||||
|
||||
pub(crate) fn folded_buffers(&self) -> &HashSet<BufferId> {
|
||||
&self.block_map.folded_buffers
|
||||
}
|
||||
|
||||
pub fn insert_creases(
|
||||
&mut self,
|
||||
creases: impl IntoIterator<Item = Crease<Anchor>>,
|
||||
|
@ -716,13 +720,8 @@ impl DisplaySnapshot {
|
|||
self.buffer_snapshot.len() == 0
|
||||
}
|
||||
|
||||
pub fn buffer_rows(
|
||||
&self,
|
||||
start_row: DisplayRow,
|
||||
) -> impl Iterator<Item = Option<MultiBufferRow>> + '_ {
|
||||
self.block_snapshot
|
||||
.buffer_rows(BlockRow(start_row.0))
|
||||
.map(|row| row.map(MultiBufferRow))
|
||||
pub fn row_infos(&self, start_row: DisplayRow) -> impl Iterator<Item = RowInfo> + '_ {
|
||||
self.block_snapshot.row_infos(BlockRow(start_row.0))
|
||||
}
|
||||
|
||||
pub fn widest_line_number(&self) -> u32 {
|
||||
|
|
|
@ -7,8 +7,8 @@ use collections::{Bound, HashMap, HashSet};
|
|||
use gpui::{AnyElement, AppContext, EntityId, Pixels, WindowContext};
|
||||
use language::{Chunk, Patch, Point};
|
||||
use multi_buffer::{
|
||||
Anchor, ExcerptId, ExcerptInfo, MultiBuffer, MultiBufferRow, MultiBufferSnapshot, ToOffset,
|
||||
ToPoint as _,
|
||||
Anchor, ExcerptId, ExcerptInfo, MultiBuffer, MultiBufferRow, MultiBufferSnapshot, RowInfo,
|
||||
ToOffset, ToPoint as _,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use std::{
|
||||
|
@ -399,9 +399,9 @@ pub struct BlockChunks<'a> {
|
|||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BlockBufferRows<'a> {
|
||||
pub struct BlockRows<'a> {
|
||||
transforms: sum_tree::Cursor<'a, Transform, (BlockRow, WrapRow)>,
|
||||
input_buffer_rows: wrap_map::WrapBufferRows<'a>,
|
||||
input_rows: wrap_map::WrapRows<'a>,
|
||||
output_row: BlockRow,
|
||||
started: bool,
|
||||
}
|
||||
|
@ -777,14 +777,12 @@ impl BlockMap {
|
|||
if let Some(new_buffer_id) = new_buffer_id {
|
||||
let first_excerpt = excerpt_boundary.next.clone().unwrap();
|
||||
if folded_buffers.contains(&new_buffer_id) {
|
||||
let mut buffer_end = Point::new(excerpt_boundary.row.0, 0)
|
||||
+ excerpt_boundary.next.as_ref().unwrap().text_summary.lines;
|
||||
let mut last_excerpt_end_row = first_excerpt.end_row;
|
||||
|
||||
while let Some(next_boundary) = boundaries.peek() {
|
||||
if let Some(next_excerpt_boundary) = &next_boundary.next {
|
||||
if next_excerpt_boundary.buffer_id == new_buffer_id {
|
||||
buffer_end = Point::new(next_boundary.row.0, 0)
|
||||
+ next_excerpt_boundary.text_summary.lines;
|
||||
last_excerpt_end_row = next_excerpt_boundary.end_row;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
@ -793,7 +791,15 @@ impl BlockMap {
|
|||
boundaries.next();
|
||||
}
|
||||
|
||||
let wrap_end_row = wrap_snapshot.make_wrap_point(buffer_end, Bias::Right).row();
|
||||
let wrap_end_row = wrap_snapshot
|
||||
.make_wrap_point(
|
||||
Point::new(
|
||||
last_excerpt_end_row.0,
|
||||
buffer.line_len(last_excerpt_end_row),
|
||||
),
|
||||
Bias::Right,
|
||||
)
|
||||
.row();
|
||||
|
||||
return Some((
|
||||
BlockPlacement::Replace(WrapRow(wrap_row)..=WrapRow(wrap_end_row)),
|
||||
|
@ -1360,7 +1366,7 @@ impl BlockSnapshot {
|
|||
}
|
||||
}
|
||||
|
||||
pub(super) fn buffer_rows(&self, start_row: BlockRow) -> BlockBufferRows {
|
||||
pub(super) fn row_infos(&self, start_row: BlockRow) -> BlockRows {
|
||||
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
|
||||
cursor.seek(&start_row, Bias::Right, &());
|
||||
let (output_start, input_start) = cursor.start();
|
||||
|
@ -1373,9 +1379,9 @@ impl BlockSnapshot {
|
|||
0
|
||||
};
|
||||
let input_start_row = input_start.0 + overshoot;
|
||||
BlockBufferRows {
|
||||
BlockRows {
|
||||
transforms: cursor,
|
||||
input_buffer_rows: self.wrap_snapshot.buffer_rows(input_start_row),
|
||||
input_rows: self.wrap_snapshot.row_infos(input_start_row),
|
||||
output_row: start_row,
|
||||
started: false,
|
||||
}
|
||||
|
@ -1480,7 +1486,7 @@ impl BlockSnapshot {
|
|||
}
|
||||
BlockId::ExcerptBoundary(next_excerpt_id) => {
|
||||
if let Some(next_excerpt_id) = next_excerpt_id {
|
||||
let excerpt_range = buffer.range_for_excerpt::<Point>(next_excerpt_id)?;
|
||||
let excerpt_range = buffer.range_for_excerpt(next_excerpt_id)?;
|
||||
self.wrap_snapshot
|
||||
.make_wrap_point(excerpt_range.start, Bias::Left)
|
||||
} else {
|
||||
|
@ -1488,10 +1494,9 @@ impl BlockSnapshot {
|
|||
.make_wrap_point(buffer.max_point(), Bias::Left)
|
||||
}
|
||||
}
|
||||
BlockId::FoldedBuffer(excerpt_id) => self.wrap_snapshot.make_wrap_point(
|
||||
buffer.range_for_excerpt::<Point>(excerpt_id)?.start,
|
||||
Bias::Left,
|
||||
),
|
||||
BlockId::FoldedBuffer(excerpt_id) => self
|
||||
.wrap_snapshot
|
||||
.make_wrap_point(buffer.range_for_excerpt(excerpt_id)?.start, Bias::Left),
|
||||
};
|
||||
let wrap_row = WrapRow(wrap_point.row());
|
||||
|
||||
|
@ -1832,8 +1837,8 @@ impl<'a> Iterator for BlockChunks<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for BlockBufferRows<'a> {
|
||||
type Item = Option<u32>;
|
||||
impl<'a> Iterator for BlockRows<'a> {
|
||||
type Item = RowInfo;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.started {
|
||||
|
@ -1862,7 +1867,7 @@ impl<'a> Iterator for BlockBufferRows<'a> {
|
|||
.as_ref()
|
||||
.map_or(true, |block| block.is_replacement())
|
||||
{
|
||||
self.input_buffer_rows.seek(self.transforms.start().1 .0);
|
||||
self.input_rows.seek(self.transforms.start().1 .0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1870,15 +1875,15 @@ impl<'a> Iterator for BlockBufferRows<'a> {
|
|||
if let Some(block) = transform.block.as_ref() {
|
||||
if block.is_replacement() && self.transforms.start().0 == self.output_row {
|
||||
if matches!(block, Block::FoldedBuffer { .. }) {
|
||||
Some(None)
|
||||
Some(RowInfo::default())
|
||||
} else {
|
||||
Some(self.input_buffer_rows.next().unwrap())
|
||||
Some(self.input_rows.next().unwrap())
|
||||
}
|
||||
} else {
|
||||
Some(None)
|
||||
Some(RowInfo::default())
|
||||
}
|
||||
} else {
|
||||
Some(self.input_buffer_rows.next().unwrap())
|
||||
Some(self.input_rows.next().unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2153,7 +2158,10 @@ mod tests {
|
|||
);
|
||||
|
||||
assert_eq!(
|
||||
snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
|
||||
snapshot
|
||||
.row_infos(BlockRow(0))
|
||||
.map(|row_info| row_info.buffer_row)
|
||||
.collect::<Vec<_>>(),
|
||||
&[
|
||||
Some(0),
|
||||
None,
|
||||
|
@ -2603,7 +2611,10 @@ mod tests {
|
|||
"\n\n\n111\n\n\n\n\n222\n\n\n333\n\n\n444\n\n\n\n\n555\n\n\n666\n"
|
||||
);
|
||||
assert_eq!(
|
||||
blocks_snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
|
||||
blocks_snapshot
|
||||
.row_infos(BlockRow(0))
|
||||
.map(|i| i.buffer_row)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
None,
|
||||
None,
|
||||
|
@ -2679,7 +2690,10 @@ mod tests {
|
|||
"\n\n\n111\n\n\n\n\n\n222\n\n\n\n333\n\n\n444\n\n\n\n\n\n\n555\n\n\n666\n\n"
|
||||
);
|
||||
assert_eq!(
|
||||
blocks_snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
|
||||
blocks_snapshot
|
||||
.row_infos(BlockRow(0))
|
||||
.map(|i| i.buffer_row)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
None,
|
||||
None,
|
||||
|
@ -2754,7 +2768,10 @@ mod tests {
|
|||
"\n\n\n\n\n\n222\n\n\n\n333\n\n\n444\n\n\n\n\n\n\n555\n\n\n666\n\n"
|
||||
);
|
||||
assert_eq!(
|
||||
blocks_snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
|
||||
blocks_snapshot
|
||||
.row_infos(BlockRow(0))
|
||||
.map(|i| i.buffer_row)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
None,
|
||||
None,
|
||||
|
@ -2819,7 +2836,10 @@ mod tests {
|
|||
);
|
||||
assert_eq!(blocks_snapshot.text(), "\n\n\n\n\n\n\n\n555\n\n\n666\n\n");
|
||||
assert_eq!(
|
||||
blocks_snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
|
||||
blocks_snapshot
|
||||
.row_infos(BlockRow(0))
|
||||
.map(|i| i.buffer_row)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
None,
|
||||
None,
|
||||
|
@ -2873,7 +2893,10 @@ mod tests {
|
|||
"Should have extra newline for 111 buffer, due to a new block added when it was folded"
|
||||
);
|
||||
assert_eq!(
|
||||
blocks_snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
|
||||
blocks_snapshot
|
||||
.row_infos(BlockRow(0))
|
||||
.map(|i| i.buffer_row)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
None,
|
||||
None,
|
||||
|
@ -2927,7 +2950,10 @@ mod tests {
|
|||
"Should have a single, first buffer left after folding"
|
||||
);
|
||||
assert_eq!(
|
||||
blocks_snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
|
||||
blocks_snapshot
|
||||
.row_infos(BlockRow(0))
|
||||
.map(|i| i.buffer_row)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
None,
|
||||
None,
|
||||
|
@ -2997,7 +3023,10 @@ mod tests {
|
|||
);
|
||||
assert_eq!(blocks_snapshot.text(), "\n");
|
||||
assert_eq!(
|
||||
blocks_snapshot.buffer_rows(BlockRow(0)).collect::<Vec<_>>(),
|
||||
blocks_snapshot
|
||||
.row_infos(BlockRow(0))
|
||||
.map(|i| i.buffer_row)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![None, None],
|
||||
"When fully folded, should be no buffer rows"
|
||||
);
|
||||
|
@ -3295,7 +3324,8 @@ mod tests {
|
|||
let mut sorted_blocks_iter = expected_blocks.into_iter().peekable();
|
||||
|
||||
let input_buffer_rows = buffer_snapshot
|
||||
.buffer_rows(MultiBufferRow(0))
|
||||
.row_infos(MultiBufferRow(0))
|
||||
.map(|row| row.buffer_row)
|
||||
.collect::<Vec<_>>();
|
||||
let mut expected_buffer_rows = Vec::new();
|
||||
let mut expected_text = String::new();
|
||||
|
@ -3450,7 +3480,8 @@ mod tests {
|
|||
);
|
||||
assert_eq!(
|
||||
blocks_snapshot
|
||||
.buffer_rows(BlockRow(start_row as u32))
|
||||
.row_infos(BlockRow(start_row as u32))
|
||||
.map(|row_info| row_info.buffer_row)
|
||||
.collect::<Vec<_>>(),
|
||||
&expected_buffer_rows[start_row..],
|
||||
"incorrect buffer_rows starting at row {:?}",
|
||||
|
|
|
@ -4,7 +4,9 @@ use super::{
|
|||
};
|
||||
use gpui::{AnyElement, ElementId, WindowContext};
|
||||
use language::{Chunk, ChunkRenderer, Edit, Point, TextSummary};
|
||||
use multi_buffer::{Anchor, AnchorRangeExt, MultiBufferRow, MultiBufferSnapshot, ToOffset};
|
||||
use multi_buffer::{
|
||||
Anchor, AnchorRangeExt, MultiBufferRow, MultiBufferSnapshot, RowInfo, ToOffset,
|
||||
};
|
||||
use std::{
|
||||
any::TypeId,
|
||||
cmp::{self, Ordering},
|
||||
|
@ -336,9 +338,7 @@ impl FoldMap {
|
|||
let mut folds = self.snapshot.folds.iter().peekable();
|
||||
while let Some(fold) = folds.next() {
|
||||
if let Some(next_fold) = folds.peek() {
|
||||
let comparison = fold
|
||||
.range
|
||||
.cmp(&next_fold.range, &self.snapshot.inlay_snapshot.buffer);
|
||||
let comparison = fold.range.cmp(&next_fold.range, self.snapshot.buffer());
|
||||
assert!(comparison.is_le());
|
||||
}
|
||||
}
|
||||
|
@ -578,6 +578,10 @@ pub struct FoldSnapshot {
|
|||
}
|
||||
|
||||
impl FoldSnapshot {
|
||||
pub fn buffer(&self) -> &MultiBufferSnapshot {
|
||||
&self.inlay_snapshot.buffer
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn text(&self) -> String {
|
||||
self.chunks(FoldOffset(0)..self.len(), false, Highlights::default())
|
||||
|
@ -673,7 +677,7 @@ impl FoldSnapshot {
|
|||
(line_end - line_start) as u32
|
||||
}
|
||||
|
||||
pub fn buffer_rows(&self, start_row: u32) -> FoldBufferRows {
|
||||
pub fn row_infos(&self, start_row: u32) -> FoldRows {
|
||||
if start_row > self.transforms.summary().output.lines.row {
|
||||
panic!("invalid display row {}", start_row);
|
||||
}
|
||||
|
@ -684,11 +688,11 @@ impl FoldSnapshot {
|
|||
|
||||
let overshoot = fold_point.0 - cursor.start().0 .0;
|
||||
let inlay_point = InlayPoint(cursor.start().1 .0 + overshoot);
|
||||
let input_buffer_rows = self.inlay_snapshot.buffer_rows(inlay_point.row());
|
||||
let input_rows = self.inlay_snapshot.row_infos(inlay_point.row());
|
||||
|
||||
FoldBufferRows {
|
||||
FoldRows {
|
||||
fold_point,
|
||||
input_buffer_rows,
|
||||
input_rows,
|
||||
cursor,
|
||||
}
|
||||
}
|
||||
|
@ -843,8 +847,8 @@ fn push_isomorphic(transforms: &mut SumTree<Transform>, summary: TextSummary) {
|
|||
transforms.update_last(
|
||||
|last| {
|
||||
if !last.is_fold() {
|
||||
last.summary.input += summary.clone();
|
||||
last.summary.output += summary.clone();
|
||||
last.summary.input += summary;
|
||||
last.summary.output += summary;
|
||||
did_merge = true;
|
||||
}
|
||||
},
|
||||
|
@ -854,7 +858,7 @@ fn push_isomorphic(transforms: &mut SumTree<Transform>, summary: TextSummary) {
|
|||
transforms.push(
|
||||
Transform {
|
||||
summary: TransformSummary {
|
||||
input: summary.clone(),
|
||||
input: summary,
|
||||
output: summary,
|
||||
},
|
||||
placeholder: None,
|
||||
|
@ -1134,25 +1138,25 @@ impl<'a> sum_tree::Dimension<'a, FoldSummary> for usize {
|
|||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FoldBufferRows<'a> {
|
||||
pub struct FoldRows<'a> {
|
||||
cursor: Cursor<'a, Transform, (FoldPoint, InlayPoint)>,
|
||||
input_buffer_rows: InlayBufferRows<'a>,
|
||||
input_rows: InlayBufferRows<'a>,
|
||||
fold_point: FoldPoint,
|
||||
}
|
||||
|
||||
impl<'a> FoldBufferRows<'a> {
|
||||
impl<'a> FoldRows<'a> {
|
||||
pub(crate) fn seek(&mut self, row: u32) {
|
||||
let fold_point = FoldPoint::new(row, 0);
|
||||
self.cursor.seek(&fold_point, Bias::Left, &());
|
||||
let overshoot = fold_point.0 - self.cursor.start().0 .0;
|
||||
let inlay_point = InlayPoint(self.cursor.start().1 .0 + overshoot);
|
||||
self.input_buffer_rows.seek(inlay_point.row());
|
||||
self.input_rows.seek(inlay_point.row());
|
||||
self.fold_point = fold_point;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for FoldBufferRows<'a> {
|
||||
type Item = Option<u32>;
|
||||
impl<'a> Iterator for FoldRows<'a> {
|
||||
type Item = RowInfo;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let mut traversed_fold = false;
|
||||
|
@ -1166,11 +1170,11 @@ impl<'a> Iterator for FoldBufferRows<'a> {
|
|||
|
||||
if self.cursor.item().is_some() {
|
||||
if traversed_fold {
|
||||
self.input_buffer_rows.seek(self.cursor.start().1.row());
|
||||
self.input_buffer_rows.next();
|
||||
self.input_rows.seek(self.cursor.start().1 .0.row);
|
||||
self.input_rows.next();
|
||||
}
|
||||
*self.fold_point.row_mut() += 1;
|
||||
self.input_buffer_rows.next()
|
||||
self.input_rows.next()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -1683,12 +1687,12 @@ mod tests {
|
|||
.row();
|
||||
expected_buffer_rows.extend(
|
||||
inlay_snapshot
|
||||
.buffer_rows(prev_row)
|
||||
.row_infos(prev_row)
|
||||
.take((1 + fold_start - prev_row) as usize),
|
||||
);
|
||||
prev_row = 1 + fold_end;
|
||||
}
|
||||
expected_buffer_rows.extend(inlay_snapshot.buffer_rows(prev_row));
|
||||
expected_buffer_rows.extend(inlay_snapshot.row_infos(prev_row));
|
||||
|
||||
assert_eq!(
|
||||
expected_buffer_rows.len(),
|
||||
|
@ -1777,7 +1781,7 @@ mod tests {
|
|||
let mut fold_row = 0;
|
||||
while fold_row < expected_buffer_rows.len() as u32 {
|
||||
assert_eq!(
|
||||
snapshot.buffer_rows(fold_row).collect::<Vec<_>>(),
|
||||
snapshot.row_infos(fold_row).collect::<Vec<_>>(),
|
||||
expected_buffer_rows[(fold_row as usize)..],
|
||||
"wrong buffer rows starting at fold row {}",
|
||||
fold_row,
|
||||
|
@ -1892,10 +1896,19 @@ mod tests {
|
|||
let (snapshot, _) = map.read(inlay_snapshot, vec![]);
|
||||
assert_eq!(snapshot.text(), "aa⋯cccc\nd⋯eeeee\nffffff\n");
|
||||
assert_eq!(
|
||||
snapshot.buffer_rows(0).collect::<Vec<_>>(),
|
||||
snapshot
|
||||
.row_infos(0)
|
||||
.map(|info| info.buffer_row)
|
||||
.collect::<Vec<_>>(),
|
||||
[Some(0), Some(3), Some(5), Some(6)]
|
||||
);
|
||||
assert_eq!(snapshot.buffer_rows(3).collect::<Vec<_>>(), [Some(6)]);
|
||||
assert_eq!(
|
||||
snapshot
|
||||
.row_infos(3)
|
||||
.map(|info| info.buffer_row)
|
||||
.collect::<Vec<_>>(),
|
||||
[Some(6)]
|
||||
);
|
||||
}
|
||||
|
||||
fn init_test(cx: &mut gpui::AppContext) {
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use crate::{HighlightStyles, InlayId};
|
||||
use collections::BTreeSet;
|
||||
use language::{Chunk, Edit, Point, TextSummary};
|
||||
use multi_buffer::{Anchor, MultiBufferRow, MultiBufferRows, MultiBufferSnapshot, ToOffset};
|
||||
use multi_buffer::{
|
||||
Anchor, MultiBufferRow, MultiBufferRows, MultiBufferSnapshot, RowInfo, ToOffset,
|
||||
};
|
||||
use std::{
|
||||
cmp,
|
||||
ops::{Add, AddAssign, Range, Sub, SubAssign},
|
||||
|
@ -67,11 +69,11 @@ impl Inlay {
|
|||
impl sum_tree::Item for Transform {
|
||||
type Summary = TransformSummary;
|
||||
|
||||
fn summary(&self, _cx: &()) -> Self::Summary {
|
||||
fn summary(&self, _: &()) -> Self::Summary {
|
||||
match self {
|
||||
Transform::Isomorphic(summary) => TransformSummary {
|
||||
input: summary.clone(),
|
||||
output: summary.clone(),
|
||||
input: *summary,
|
||||
output: *summary,
|
||||
},
|
||||
Transform::Inlay(inlay) => TransformSummary {
|
||||
input: TextSummary::default(),
|
||||
|
@ -362,14 +364,14 @@ impl<'a> InlayBufferRows<'a> {
|
|||
}
|
||||
|
||||
impl<'a> Iterator for InlayBufferRows<'a> {
|
||||
type Item = Option<u32>;
|
||||
type Item = RowInfo;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let buffer_row = if self.inlay_row == 0 {
|
||||
self.buffer_rows.next().unwrap()
|
||||
} else {
|
||||
match self.transforms.item()? {
|
||||
Transform::Inlay(_) => None,
|
||||
Transform::Inlay(_) => Default::default(),
|
||||
Transform::Isomorphic(_) => self.buffer_rows.next().unwrap(),
|
||||
}
|
||||
};
|
||||
|
@ -448,7 +450,7 @@ impl InlayMap {
|
|||
new_transforms.append(cursor.slice(&buffer_edit.old.start, Bias::Left, &()), &());
|
||||
if let Some(Transform::Isomorphic(transform)) = cursor.item() {
|
||||
if cursor.end(&()).0 == buffer_edit.old.start {
|
||||
push_isomorphic(&mut new_transforms, transform.clone());
|
||||
push_isomorphic(&mut new_transforms, *transform);
|
||||
cursor.next(&());
|
||||
}
|
||||
}
|
||||
|
@ -892,7 +894,7 @@ impl InlaySnapshot {
|
|||
}
|
||||
|
||||
pub fn text_summary(&self) -> TextSummary {
|
||||
self.transforms.summary().output.clone()
|
||||
self.transforms.summary().output
|
||||
}
|
||||
|
||||
pub fn text_summary_for_range(&self, range: Range<InlayOffset>) -> TextSummary {
|
||||
|
@ -945,7 +947,7 @@ impl InlaySnapshot {
|
|||
summary
|
||||
}
|
||||
|
||||
pub fn buffer_rows(&self, row: u32) -> InlayBufferRows<'_> {
|
||||
pub fn row_infos(&self, row: u32) -> InlayBufferRows<'_> {
|
||||
let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>(&());
|
||||
let inlay_point = InlayPoint::new(row, 0);
|
||||
cursor.seek(&inlay_point, Bias::Left, &());
|
||||
|
@ -967,7 +969,7 @@ impl InlaySnapshot {
|
|||
InlayBufferRows {
|
||||
transforms: cursor,
|
||||
inlay_row: inlay_point.row(),
|
||||
buffer_rows: self.buffer.buffer_rows(buffer_row),
|
||||
buffer_rows: self.buffer.row_infos(buffer_row),
|
||||
max_buffer_row,
|
||||
}
|
||||
}
|
||||
|
@ -1477,7 +1479,10 @@ mod tests {
|
|||
);
|
||||
assert_eq!(inlay_snapshot.text(), "|123|\nabc\n|456|def\n|567|\n\nghi");
|
||||
assert_eq!(
|
||||
inlay_snapshot.buffer_rows(0).collect::<Vec<_>>(),
|
||||
inlay_snapshot
|
||||
.row_infos(0)
|
||||
.map(|info| info.buffer_row)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![Some(0), None, Some(1), None, None, Some(2)]
|
||||
);
|
||||
}
|
||||
|
@ -1548,7 +1553,7 @@ mod tests {
|
|||
}
|
||||
assert_eq!(inlay_snapshot.text(), expected_text.to_string());
|
||||
|
||||
let expected_buffer_rows = inlay_snapshot.buffer_rows(0).collect::<Vec<_>>();
|
||||
let expected_buffer_rows = inlay_snapshot.row_infos(0).collect::<Vec<_>>();
|
||||
assert_eq!(
|
||||
expected_buffer_rows.len() as u32,
|
||||
expected_text.max_point().row + 1
|
||||
|
@ -1556,7 +1561,7 @@ mod tests {
|
|||
for row_start in 0..expected_buffer_rows.len() {
|
||||
assert_eq!(
|
||||
inlay_snapshot
|
||||
.buffer_rows(row_start as u32)
|
||||
.row_infos(row_start as u32)
|
||||
.collect::<Vec<_>>(),
|
||||
&expected_buffer_rows[row_start..],
|
||||
"incorrect buffer rows starting at {}",
|
||||
|
|
|
@ -272,8 +272,8 @@ impl TabSnapshot {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn buffer_rows(&self, row: u32) -> fold_map::FoldBufferRows<'_> {
|
||||
self.fold_snapshot.buffer_rows(row)
|
||||
pub fn rows(&self, row: u32) -> fold_map::FoldRows<'_> {
|
||||
self.fold_snapshot.row_infos(row)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
use super::{
|
||||
fold_map::FoldBufferRows,
|
||||
fold_map::FoldRows,
|
||||
tab_map::{self, TabEdit, TabPoint, TabSnapshot},
|
||||
Highlights,
|
||||
};
|
||||
use gpui::{AppContext, Context, Font, LineWrapper, Model, ModelContext, Pixels, Task};
|
||||
use language::{Chunk, Point};
|
||||
use multi_buffer::MultiBufferSnapshot;
|
||||
use multi_buffer::{MultiBufferSnapshot, RowInfo};
|
||||
use smol::future::yield_now;
|
||||
use std::sync::LazyLock;
|
||||
use std::{cmp, collections::VecDeque, mem, ops::Range, time::Duration};
|
||||
|
@ -60,16 +60,16 @@ pub struct WrapChunks<'a> {
|
|||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct WrapBufferRows<'a> {
|
||||
input_buffer_rows: FoldBufferRows<'a>,
|
||||
input_buffer_row: Option<u32>,
|
||||
pub struct WrapRows<'a> {
|
||||
input_buffer_rows: FoldRows<'a>,
|
||||
input_buffer_row: RowInfo,
|
||||
output_row: u32,
|
||||
soft_wrapped: bool,
|
||||
max_output_row: u32,
|
||||
transforms: Cursor<'a, Transform, (WrapPoint, TabPoint)>,
|
||||
}
|
||||
|
||||
impl<'a> WrapBufferRows<'a> {
|
||||
impl<'a> WrapRows<'a> {
|
||||
pub(crate) fn seek(&mut self, start_row: u32) {
|
||||
self.transforms
|
||||
.seek(&WrapPoint::new(start_row, 0), Bias::Left, &());
|
||||
|
@ -717,7 +717,7 @@ impl WrapSnapshot {
|
|||
self.transforms.summary().output.longest_row
|
||||
}
|
||||
|
||||
pub fn buffer_rows(&self, start_row: u32) -> WrapBufferRows {
|
||||
pub fn row_infos(&self, start_row: u32) -> WrapRows {
|
||||
let mut transforms = self.transforms.cursor::<(WrapPoint, TabPoint)>(&());
|
||||
transforms.seek(&WrapPoint::new(start_row, 0), Bias::Left, &());
|
||||
let mut input_row = transforms.start().1.row();
|
||||
|
@ -725,9 +725,9 @@ impl WrapSnapshot {
|
|||
input_row += start_row - transforms.start().0.row();
|
||||
}
|
||||
let soft_wrapped = transforms.item().map_or(false, |t| !t.is_isomorphic());
|
||||
let mut input_buffer_rows = self.tab_snapshot.buffer_rows(input_row);
|
||||
let mut input_buffer_rows = self.tab_snapshot.rows(input_row);
|
||||
let input_buffer_row = input_buffer_rows.next().unwrap();
|
||||
WrapBufferRows {
|
||||
WrapRows {
|
||||
transforms,
|
||||
input_buffer_row,
|
||||
input_buffer_rows,
|
||||
|
@ -847,7 +847,7 @@ impl WrapSnapshot {
|
|||
}
|
||||
|
||||
let text = language::Rope::from(self.text().as_str());
|
||||
let mut input_buffer_rows = self.tab_snapshot.buffer_rows(0);
|
||||
let mut input_buffer_rows = self.tab_snapshot.rows(0);
|
||||
let mut expected_buffer_rows = Vec::new();
|
||||
let mut prev_tab_row = 0;
|
||||
for display_row in 0..=self.max_point().row() {
|
||||
|
@ -855,7 +855,7 @@ impl WrapSnapshot {
|
|||
if tab_point.row() == prev_tab_row && display_row != 0 {
|
||||
expected_buffer_rows.push(None);
|
||||
} else {
|
||||
expected_buffer_rows.push(input_buffer_rows.next().unwrap());
|
||||
expected_buffer_rows.push(input_buffer_rows.next().unwrap().buffer_row);
|
||||
}
|
||||
|
||||
prev_tab_row = tab_point.row();
|
||||
|
@ -864,7 +864,8 @@ impl WrapSnapshot {
|
|||
|
||||
for start_display_row in 0..expected_buffer_rows.len() {
|
||||
assert_eq!(
|
||||
self.buffer_rows(start_display_row as u32)
|
||||
self.row_infos(start_display_row as u32)
|
||||
.map(|row_info| row_info.buffer_row)
|
||||
.collect::<Vec<_>>(),
|
||||
&expected_buffer_rows[start_display_row..],
|
||||
"invalid buffer_rows({}..)",
|
||||
|
@ -958,8 +959,8 @@ impl<'a> Iterator for WrapChunks<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for WrapBufferRows<'a> {
|
||||
type Item = Option<u32>;
|
||||
impl<'a> Iterator for WrapRows<'a> {
|
||||
type Item = RowInfo;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.output_row > self.max_output_row {
|
||||
|
@ -968,6 +969,7 @@ impl<'a> Iterator for WrapBufferRows<'a> {
|
|||
|
||||
let buffer_row = self.input_buffer_row;
|
||||
let soft_wrapped = self.soft_wrapped;
|
||||
let diff_status = self.input_buffer_row.diff_status;
|
||||
|
||||
self.output_row += 1;
|
||||
self.transforms
|
||||
|
@ -979,7 +981,15 @@ impl<'a> Iterator for WrapBufferRows<'a> {
|
|||
self.soft_wrapped = true;
|
||||
}
|
||||
|
||||
Some(if soft_wrapped { None } else { buffer_row })
|
||||
Some(if soft_wrapped {
|
||||
RowInfo {
|
||||
buffer_row: None,
|
||||
multibuffer_row: None,
|
||||
diff_status,
|
||||
}
|
||||
} else {
|
||||
buffer_row
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -19,11 +19,11 @@ use language::{
|
|||
},
|
||||
BracketPairConfig,
|
||||
Capability::ReadWrite,
|
||||
FakeLspAdapter, IndentGuide, LanguageConfig, LanguageConfigOverride, LanguageMatcher,
|
||||
LanguageName, Override, ParsedMarkdown, Point,
|
||||
FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher, LanguageName,
|
||||
Override, ParsedMarkdown, Point,
|
||||
};
|
||||
use language_settings::{Formatter, FormatterList, IndentGuideSettings};
|
||||
use multi_buffer::MultiBufferIndentGuide;
|
||||
use multi_buffer::IndentGuide;
|
||||
use parking_lot::Mutex;
|
||||
use pretty_assertions::{assert_eq, assert_ne};
|
||||
use project::{buffer_store::BufferChangeSet, FakeFs};
|
||||
|
@ -3363,8 +3363,8 @@ async fn test_custom_newlines_cause_no_false_positive_diffs(
|
|||
let snapshot = editor.snapshot(cx);
|
||||
assert_eq!(
|
||||
snapshot
|
||||
.diff_map
|
||||
.diff_hunks_in_range(0..snapshot.buffer_snapshot.len(), &snapshot.buffer_snapshot)
|
||||
.buffer_snapshot
|
||||
.diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
|
||||
.collect::<Vec<_>>(),
|
||||
Vec::new(),
|
||||
"Should not have any diffs for files with custom newlines"
|
||||
|
@ -5480,6 +5480,109 @@ async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
|
|||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_fold_function_bodies(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let base_text = r#"
|
||||
impl A {
|
||||
// this is an unstaged comment
|
||||
|
||||
fn b() {
|
||||
c();
|
||||
}
|
||||
|
||||
// this is another unstaged comment
|
||||
|
||||
fn d() {
|
||||
// e
|
||||
// f
|
||||
}
|
||||
}
|
||||
|
||||
fn g() {
|
||||
// h
|
||||
}
|
||||
"#
|
||||
.unindent();
|
||||
|
||||
let text = r#"
|
||||
ˇimpl A {
|
||||
|
||||
fn b() {
|
||||
c();
|
||||
}
|
||||
|
||||
fn d() {
|
||||
// e
|
||||
// f
|
||||
}
|
||||
}
|
||||
|
||||
fn g() {
|
||||
// h
|
||||
}
|
||||
"#
|
||||
.unindent();
|
||||
|
||||
let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
|
||||
cx.set_state(&text);
|
||||
cx.set_diff_base(&base_text);
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.expand_all_diff_hunks(&Default::default(), cx);
|
||||
});
|
||||
|
||||
cx.assert_state_with_diff(
|
||||
"
|
||||
ˇimpl A {
|
||||
- // this is an unstaged comment
|
||||
|
||||
fn b() {
|
||||
c();
|
||||
}
|
||||
|
||||
- // this is another unstaged comment
|
||||
-
|
||||
fn d() {
|
||||
// e
|
||||
// f
|
||||
}
|
||||
}
|
||||
|
||||
fn g() {
|
||||
// h
|
||||
}
|
||||
"
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
let expected_display_text = "
|
||||
impl A {
|
||||
// this is an unstaged comment
|
||||
|
||||
fn b() {
|
||||
⋯
|
||||
}
|
||||
|
||||
// this is another unstaged comment
|
||||
|
||||
fn d() {
|
||||
⋯
|
||||
}
|
||||
}
|
||||
|
||||
fn g() {
|
||||
⋯
|
||||
}
|
||||
"
|
||||
.unindent();
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.fold_function_bodies(&FoldFunctionBodies, cx);
|
||||
assert_eq!(editor.display_text(cx), expected_display_text);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_autoindent(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
@ -10319,7 +10422,7 @@ async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
|
|||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
|
||||
async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
|
@ -10420,7 +10523,26 @@ async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext)
|
|||
);
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
for _ in 0..3 {
|
||||
editor.go_to_prev_hunk(&GoToPrevHunk, cx);
|
||||
});
|
||||
|
||||
cx.assert_editor_state(
|
||||
&r#"
|
||||
ˇuse some::modified;
|
||||
|
||||
|
||||
fn main() {
|
||||
println!("hello there");
|
||||
|
||||
println!("around the");
|
||||
println!("world");
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
for _ in 0..2 {
|
||||
editor.go_to_prev_hunk(&GoToPrevHunk, cx);
|
||||
}
|
||||
});
|
||||
|
@ -10442,11 +10564,10 @@ async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext)
|
|||
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.fold(&Fold, cx);
|
||||
});
|
||||
|
||||
//Make sure that the fold only gets one hunk
|
||||
for _ in 0..4 {
|
||||
editor.go_to_next_hunk(&GoToHunk, cx);
|
||||
}
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.go_to_next_hunk(&GoToHunk, cx);
|
||||
});
|
||||
|
||||
cx.assert_editor_state(
|
||||
|
@ -11815,6 +11936,39 @@ async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
|
|||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_deleting_over_diff_hunk(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
|
||||
let base_text = indoc! {r#"
|
||||
one
|
||||
|
||||
two
|
||||
three
|
||||
"#};
|
||||
|
||||
cx.set_diff_base(base_text);
|
||||
cx.set_state("\nˇ\n");
|
||||
cx.executor().run_until_parked();
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.expand_selected_diff_hunks(cx);
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.backspace(&Default::default(), cx);
|
||||
});
|
||||
cx.run_until_parked();
|
||||
cx.assert_state_with_diff(
|
||||
indoc! {r#"
|
||||
|
||||
- two
|
||||
- threeˇ
|
||||
+
|
||||
"#}
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
@ -12019,13 +12173,11 @@ async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
|
|||
(buffer_3.clone(), base_text_3),
|
||||
] {
|
||||
let change_set = cx.new_model(|cx| {
|
||||
BufferChangeSet::new_with_base_text(
|
||||
diff_base.to_string(),
|
||||
buffer.read(cx).text_snapshot(),
|
||||
cx,
|
||||
)
|
||||
BufferChangeSet::new_with_base_text(diff_base.to_string(), &buffer, cx)
|
||||
});
|
||||
editor.diff_map.add_change_set(change_set, cx)
|
||||
editor
|
||||
.buffer
|
||||
.update(cx, |buffer, cx| buffer.add_change_set(change_set, cx));
|
||||
}
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
|
@ -12385,7 +12537,10 @@ async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
|
|||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
|
||||
async fn test_toggle_selected_diff_hunks(
|
||||
executor: BackgroundExecutor,
|
||||
cx: &mut gpui::TestAppContext,
|
||||
) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
|
@ -12423,7 +12578,7 @@ async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::Test
|
|||
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.go_to_next_hunk(&GoToHunk, cx);
|
||||
editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
|
||||
editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
cx.assert_state_with_diff(
|
||||
|
@ -12443,12 +12598,34 @@ async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::Test
|
|||
);
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
for _ in 0..3 {
|
||||
for _ in 0..2 {
|
||||
editor.go_to_next_hunk(&GoToHunk, cx);
|
||||
editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
|
||||
editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, cx);
|
||||
}
|
||||
});
|
||||
executor.run_until_parked();
|
||||
cx.assert_state_with_diff(
|
||||
r#"
|
||||
- use some::mod;
|
||||
+ ˇuse some::modified;
|
||||
|
||||
|
||||
fn main() {
|
||||
- println!("hello");
|
||||
+ println!("hello there");
|
||||
|
||||
+ println!("around the");
|
||||
println!("world");
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.go_to_next_hunk(&GoToHunk, cx);
|
||||
editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
cx.assert_state_with_diff(
|
||||
r#"
|
||||
- use some::mod;
|
||||
|
@ -12534,7 +12711,7 @@ async fn test_diff_base_change_with_expanded_diff_hunks(
|
|||
executor.run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
|
||||
editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
cx.assert_state_with_diff(
|
||||
|
@ -12579,7 +12756,7 @@ async fn test_diff_base_change_with_expanded_diff_hunks(
|
|||
);
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
|
||||
editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
cx.assert_state_with_diff(
|
||||
|
@ -12602,170 +12779,6 @@ async fn test_diff_base_change_with_expanded_diff_hunks(
|
|||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_fold_unfold_diff_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
|
||||
let diff_base = r#"
|
||||
use some::mod1;
|
||||
use some::mod2;
|
||||
|
||||
const A: u32 = 42;
|
||||
const B: u32 = 42;
|
||||
const C: u32 = 42;
|
||||
|
||||
fn main() {
|
||||
println!("hello");
|
||||
|
||||
println!("world");
|
||||
}
|
||||
|
||||
fn another() {
|
||||
println!("another");
|
||||
}
|
||||
|
||||
fn another2() {
|
||||
println!("another2");
|
||||
}
|
||||
"#
|
||||
.unindent();
|
||||
|
||||
cx.set_state(
|
||||
&r#"
|
||||
«use some::mod2;
|
||||
|
||||
const A: u32 = 42;
|
||||
const C: u32 = 42;
|
||||
|
||||
fn main() {
|
||||
//println!("hello");
|
||||
|
||||
println!("world");
|
||||
//
|
||||
//ˇ»
|
||||
}
|
||||
|
||||
fn another() {
|
||||
println!("another");
|
||||
println!("another");
|
||||
}
|
||||
|
||||
println!("another2");
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
cx.set_diff_base(&diff_base);
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.assert_state_with_diff(
|
||||
r#"
|
||||
- use some::mod1;
|
||||
«use some::mod2;
|
||||
|
||||
const A: u32 = 42;
|
||||
- const B: u32 = 42;
|
||||
const C: u32 = 42;
|
||||
|
||||
fn main() {
|
||||
- println!("hello");
|
||||
+ //println!("hello");
|
||||
|
||||
println!("world");
|
||||
+ //
|
||||
+ //ˇ»
|
||||
}
|
||||
|
||||
fn another() {
|
||||
println!("another");
|
||||
+ println!("another");
|
||||
}
|
||||
|
||||
- fn another2() {
|
||||
println!("another2");
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
// Fold across some of the diff hunks. They should no longer appear expanded.
|
||||
cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
// Hunks are not shown if their position is within a fold
|
||||
cx.assert_state_with_diff(
|
||||
r#"
|
||||
«use some::mod2;
|
||||
|
||||
const A: u32 = 42;
|
||||
const C: u32 = 42;
|
||||
|
||||
fn main() {
|
||||
//println!("hello");
|
||||
|
||||
println!("world");
|
||||
//
|
||||
//ˇ»
|
||||
}
|
||||
|
||||
fn another() {
|
||||
println!("another");
|
||||
+ println!("another");
|
||||
}
|
||||
|
||||
- fn another2() {
|
||||
println!("another2");
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.select_all(&SelectAll, cx);
|
||||
editor.unfold_lines(&UnfoldLines, cx);
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
// The deletions reappear when unfolding.
|
||||
cx.assert_state_with_diff(
|
||||
r#"
|
||||
- use some::mod1;
|
||||
«use some::mod2;
|
||||
|
||||
const A: u32 = 42;
|
||||
- const B: u32 = 42;
|
||||
const C: u32 = 42;
|
||||
|
||||
fn main() {
|
||||
- println!("hello");
|
||||
+ //println!("hello");
|
||||
|
||||
println!("world");
|
||||
+ //
|
||||
+ //
|
||||
}
|
||||
|
||||
fn another() {
|
||||
println!("another");
|
||||
+ println!("another");
|
||||
}
|
||||
|
||||
- fn another2() {
|
||||
println!("another2");
|
||||
}
|
||||
ˇ»"#
|
||||
.unindent(),
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
@ -12849,13 +12862,11 @@ async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext)
|
|||
(buffer_3.clone(), file_3_old),
|
||||
] {
|
||||
let change_set = cx.new_model(|cx| {
|
||||
BufferChangeSet::new_with_base_text(
|
||||
diff_base.to_string(),
|
||||
buffer.read(cx).text_snapshot(),
|
||||
cx,
|
||||
)
|
||||
BufferChangeSet::new_with_base_text(diff_base.to_string(), &buffer, cx)
|
||||
});
|
||||
editor.diff_map.add_change_set(change_set, cx)
|
||||
editor
|
||||
.buffer
|
||||
.update(cx, |buffer, cx| buffer.add_change_set(change_set, cx));
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
@ -12895,7 +12906,7 @@ async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext)
|
|||
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.select_all(&SelectAll, cx);
|
||||
editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
|
||||
editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, cx);
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
|
@ -12962,17 +12973,18 @@ async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut gpui::TestAppContext
|
|||
let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
|
||||
editor
|
||||
.update(cx, |editor, cx| {
|
||||
let buffer = buffer.read(cx).text_snapshot();
|
||||
let change_set = cx
|
||||
.new_model(|cx| BufferChangeSet::new_with_base_text(base.to_string(), buffer, cx));
|
||||
editor.diff_map.add_change_set(change_set, cx)
|
||||
.new_model(|cx| BufferChangeSet::new_with_base_text(base.to_string(), &buffer, cx));
|
||||
editor
|
||||
.buffer
|
||||
.update(cx, |buffer, cx| buffer.add_change_set(change_set, cx))
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let mut cx = EditorTestContext::for_editor(editor, cx).await;
|
||||
cx.run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, cx| editor.expand_all_hunk_diffs(&Default::default(), cx));
|
||||
cx.update_editor(|editor, cx| editor.expand_all_diff_hunks(&Default::default(), cx));
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
cx.assert_state_with_diff(
|
||||
|
@ -12981,8 +12993,6 @@ async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut gpui::TestAppContext
|
|||
- bbb
|
||||
+ BBB
|
||||
|
||||
- ddd
|
||||
- eee
|
||||
+ EEE
|
||||
fff
|
||||
"
|
||||
|
@ -13036,7 +13046,7 @@ async fn test_edits_around_expanded_insertion_hunks(
|
|||
executor.run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
|
||||
editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
||||
|
@ -13055,7 +13065,7 @@ async fn test_edits_around_expanded_insertion_hunks(
|
|||
|
||||
println!("world");
|
||||
}
|
||||
"#
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
|
@ -13078,7 +13088,7 @@ async fn test_edits_around_expanded_insertion_hunks(
|
|||
|
||||
println!("world");
|
||||
}
|
||||
"#
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
|
@ -13102,7 +13112,7 @@ async fn test_edits_around_expanded_insertion_hunks(
|
|||
|
||||
println!("world");
|
||||
}
|
||||
"#
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
|
@ -13127,7 +13137,7 @@ async fn test_edits_around_expanded_insertion_hunks(
|
|||
|
||||
println!("world");
|
||||
}
|
||||
"#
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
|
@ -13153,7 +13163,7 @@ async fn test_edits_around_expanded_insertion_hunks(
|
|||
|
||||
println!("world");
|
||||
}
|
||||
"#
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
|
@ -13164,21 +13174,63 @@ async fn test_edits_around_expanded_insertion_hunks(
|
|||
executor.run_until_parked();
|
||||
cx.assert_state_with_diff(
|
||||
r#"
|
||||
use some::mod1;
|
||||
- use some::mod2;
|
||||
-
|
||||
- const A: u32 = 42;
|
||||
ˇ
|
||||
fn main() {
|
||||
println!("hello");
|
||||
|
||||
println!("world");
|
||||
}
|
||||
"#
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
cx.set_diff_base(indoc! { "
|
||||
one
|
||||
two
|
||||
three
|
||||
four
|
||||
five
|
||||
"
|
||||
});
|
||||
cx.set_state(indoc! { "
|
||||
one
|
||||
ˇthree
|
||||
five
|
||||
"});
|
||||
cx.run_until_parked();
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.toggle_selected_diff_hunks(&Default::default(), cx);
|
||||
});
|
||||
cx.assert_state_with_diff(
|
||||
indoc! { "
|
||||
one
|
||||
- two
|
||||
ˇthree
|
||||
- four
|
||||
five
|
||||
"}
|
||||
.to_string(),
|
||||
);
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.toggle_selected_diff_hunks(&Default::default(), cx);
|
||||
});
|
||||
|
||||
cx.assert_state_with_diff(
|
||||
indoc! { "
|
||||
one
|
||||
ˇthree
|
||||
five
|
||||
"}
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_edits_around_expanded_deletion_hunks(
|
||||
executor: BackgroundExecutor,
|
||||
|
@ -13227,7 +13279,7 @@ async fn test_edits_around_expanded_deletion_hunks(
|
|||
executor.run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
|
||||
editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
||||
|
@ -13246,7 +13298,7 @@ async fn test_edits_around_expanded_deletion_hunks(
|
|||
|
||||
println!("world");
|
||||
}
|
||||
"#
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
|
@ -13269,7 +13321,7 @@ async fn test_edits_around_expanded_deletion_hunks(
|
|||
|
||||
println!("world");
|
||||
}
|
||||
"#
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
|
@ -13292,7 +13344,7 @@ async fn test_edits_around_expanded_deletion_hunks(
|
|||
|
||||
println!("world");
|
||||
}
|
||||
"#
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
|
@ -13316,6 +13368,71 @@ async fn test_edits_around_expanded_deletion_hunks(
|
|||
|
||||
println!("world");
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_backspace_after_deletion_hunk(
|
||||
executor: BackgroundExecutor,
|
||||
cx: &mut gpui::TestAppContext,
|
||||
) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
|
||||
let base_text = r#"
|
||||
one
|
||||
two
|
||||
three
|
||||
four
|
||||
five
|
||||
"#
|
||||
.unindent();
|
||||
executor.run_until_parked();
|
||||
cx.set_state(
|
||||
&r#"
|
||||
one
|
||||
two
|
||||
fˇour
|
||||
five
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
cx.set_diff_base(&base_text);
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.assert_state_with_diff(
|
||||
r#"
|
||||
one
|
||||
two
|
||||
- three
|
||||
fˇour
|
||||
five
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.backspace(&Backspace, cx);
|
||||
editor.backspace(&Backspace, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
cx.assert_state_with_diff(
|
||||
r#"
|
||||
one
|
||||
two
|
||||
- threeˇ
|
||||
- four
|
||||
+ our
|
||||
five
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
|
@ -13369,7 +13486,7 @@ async fn test_edit_after_expanded_modification_hunk(
|
|||
cx.set_diff_base(&diff_base);
|
||||
executor.run_until_parked();
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
|
||||
editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
||||
|
@ -13478,22 +13595,14 @@ fn assert_indent_guides(
|
|||
);
|
||||
}
|
||||
|
||||
let expected: Vec<_> = expected
|
||||
.into_iter()
|
||||
.map(|guide| MultiBufferIndentGuide {
|
||||
multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
|
||||
buffer: guide,
|
||||
})
|
||||
.collect();
|
||||
|
||||
assert_eq!(indent_guides, expected, "Indent guides do not match");
|
||||
}
|
||||
|
||||
fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
|
||||
IndentGuide {
|
||||
buffer_id,
|
||||
start_row,
|
||||
end_row,
|
||||
start_row: MultiBufferRow(start_row),
|
||||
end_row: MultiBufferRow(end_row),
|
||||
depth,
|
||||
tab_size: 4,
|
||||
settings: IndentGuideSettings {
|
||||
|
@ -13945,6 +14054,105 @@ async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppCont
|
|||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
let text = indoc! {
|
||||
"
|
||||
impl A {
|
||||
fn b() {
|
||||
0;
|
||||
3;
|
||||
5;
|
||||
6;
|
||||
7;
|
||||
}
|
||||
}
|
||||
"
|
||||
};
|
||||
let base_text = indoc! {
|
||||
"
|
||||
impl A {
|
||||
fn b() {
|
||||
0;
|
||||
1;
|
||||
2;
|
||||
3;
|
||||
4;
|
||||
}
|
||||
fn c() {
|
||||
5;
|
||||
6;
|
||||
7;
|
||||
}
|
||||
}
|
||||
"
|
||||
};
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.set_text(text, cx);
|
||||
|
||||
editor.buffer().update(cx, |multibuffer, cx| {
|
||||
let buffer = multibuffer.as_singleton().unwrap();
|
||||
let change_set = cx.new_model(|cx| {
|
||||
let mut change_set = BufferChangeSet::new(&buffer, cx);
|
||||
change_set.recalculate_diff_sync(
|
||||
base_text.into(),
|
||||
buffer.read(cx).text_snapshot(),
|
||||
true,
|
||||
cx,
|
||||
);
|
||||
change_set
|
||||
});
|
||||
|
||||
multibuffer.set_all_diff_hunks_expanded(cx);
|
||||
multibuffer.add_change_set(change_set, cx);
|
||||
|
||||
buffer.read(cx).remote_id()
|
||||
})
|
||||
});
|
||||
|
||||
cx.assert_state_with_diff(
|
||||
indoc! { "
|
||||
impl A {
|
||||
fn b() {
|
||||
0;
|
||||
- 1;
|
||||
- 2;
|
||||
3;
|
||||
- 4;
|
||||
- }
|
||||
- fn c() {
|
||||
5;
|
||||
6;
|
||||
7;
|
||||
}
|
||||
}
|
||||
ˇ"
|
||||
}
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
let mut actual_guides = cx.update_editor(|editor, cx| {
|
||||
editor
|
||||
.snapshot(cx)
|
||||
.buffer_snapshot
|
||||
.indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
|
||||
.map(|guide| (guide.start_row..=guide.end_row, guide.depth))
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
|
||||
assert_eq!(
|
||||
actual_guides,
|
||||
vec![
|
||||
(MultiBufferRow(1)..=MultiBufferRow(12), 0),
|
||||
(MultiBufferRow(2)..=MultiBufferRow(6), 1),
|
||||
(MultiBufferRow(9)..=MultiBufferRow(11), 1),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
@ -15229,7 +15437,7 @@ pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsC
|
|||
#[track_caller]
|
||||
fn assert_hunk_revert(
|
||||
not_reverted_text_with_selections: &str,
|
||||
expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
|
||||
expected_hunk_statuses_before: Vec<DiffHunkStatus>,
|
||||
expected_reverted_text_with_selections: &str,
|
||||
base_text: &str,
|
||||
cx: &mut EditorLspTestContext,
|
||||
|
@ -15238,12 +15446,12 @@ fn assert_hunk_revert(
|
|||
cx.set_diff_base(base_text);
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
|
||||
let actual_hunk_statuses_before = cx.update_editor(|editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
let reverted_hunk_statuses = snapshot
|
||||
.diff_map
|
||||
.diff_hunks_in_range(0..snapshot.buffer_snapshot.len(), &snapshot.buffer_snapshot)
|
||||
.map(|hunk| hunk_status(&hunk))
|
||||
.buffer_snapshot
|
||||
.diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
|
||||
.map(|hunk| hunk.status())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
editor.revert_selected_hunks(&RevertSelectedHunks, cx);
|
||||
|
@ -15251,5 +15459,5 @@ fn assert_hunk_revert(
|
|||
});
|
||||
cx.executor().run_until_parked();
|
||||
cx.assert_editor_state(expected_reverted_text_with_selections);
|
||||
assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
|
||||
assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,5 +1,3 @@
|
|||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
use anyhow::Result;
|
||||
use collections::HashMap;
|
||||
use git::{
|
||||
|
@ -9,9 +7,10 @@ use git::{
|
|||
use gpui::{AppContext, Model, ModelContext, Subscription, Task};
|
||||
use http_client::HttpClient;
|
||||
use language::{markdown, Bias, Buffer, BufferSnapshot, Edit, LanguageRegistry, ParsedMarkdown};
|
||||
use multi_buffer::MultiBufferRow;
|
||||
use multi_buffer::RowInfo;
|
||||
use project::{Project, ProjectItem};
|
||||
use smallvec::SmallVec;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use sum_tree::SumTree;
|
||||
use url::Url;
|
||||
|
||||
|
@ -194,15 +193,15 @@ impl GitBlame {
|
|||
|
||||
pub fn blame_for_rows<'a>(
|
||||
&'a mut self,
|
||||
rows: impl 'a + IntoIterator<Item = Option<MultiBufferRow>>,
|
||||
rows: &'a [RowInfo],
|
||||
cx: &AppContext,
|
||||
) -> impl 'a + Iterator<Item = Option<BlameEntry>> {
|
||||
self.sync(cx);
|
||||
|
||||
let mut cursor = self.entries.cursor::<u32>(&());
|
||||
rows.into_iter().map(move |row| {
|
||||
let row = row?;
|
||||
cursor.seek_forward(&row.0, Bias::Right, &());
|
||||
rows.into_iter().map(move |info| {
|
||||
let row = info.buffer_row?;
|
||||
cursor.seek_forward(&row, Bias::Right, &());
|
||||
cursor.item()?.blame.clone()
|
||||
})
|
||||
}
|
||||
|
@ -563,15 +562,38 @@ mod tests {
|
|||
use unindent::Unindent as _;
|
||||
use util::RandomCharIter;
|
||||
|
||||
macro_rules! assert_blame_rows {
|
||||
($blame:expr, $rows:expr, $expected:expr, $cx:expr) => {
|
||||
assert_eq!(
|
||||
$blame
|
||||
.blame_for_rows($rows.map(MultiBufferRow).map(Some), $cx)
|
||||
.collect::<Vec<_>>(),
|
||||
$expected
|
||||
);
|
||||
};
|
||||
// macro_rules! assert_blame_rows {
|
||||
// ($blame:expr, $rows:expr, $expected:expr, $cx:expr) => {
|
||||
// assert_eq!(
|
||||
// $blame
|
||||
// .blame_for_rows($rows.map(MultiBufferRow).map(Some), $cx)
|
||||
// .collect::<Vec<_>>(),
|
||||
// $expected
|
||||
// );
|
||||
// };
|
||||
// }
|
||||
|
||||
#[track_caller]
|
||||
fn assert_blame_rows(
|
||||
blame: &mut GitBlame,
|
||||
rows: Range<u32>,
|
||||
expected: Vec<Option<BlameEntry>>,
|
||||
cx: &mut ModelContext<GitBlame>,
|
||||
) {
|
||||
assert_eq!(
|
||||
blame
|
||||
.blame_for_rows(
|
||||
&rows
|
||||
.map(|row| RowInfo {
|
||||
buffer_row: Some(row),
|
||||
..Default::default()
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
cx
|
||||
)
|
||||
.collect::<Vec<_>>(),
|
||||
expected
|
||||
);
|
||||
}
|
||||
|
||||
fn init_test(cx: &mut gpui::TestAppContext) {
|
||||
|
@ -634,7 +656,15 @@ mod tests {
|
|||
blame.update(cx, |blame, cx| {
|
||||
assert_eq!(
|
||||
blame
|
||||
.blame_for_rows((0..1).map(MultiBufferRow).map(Some), cx)
|
||||
.blame_for_rows(
|
||||
&(0..1)
|
||||
.map(|row| RowInfo {
|
||||
buffer_row: Some(row),
|
||||
..Default::default()
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
cx
|
||||
)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![None]
|
||||
);
|
||||
|
@ -698,7 +728,15 @@ mod tests {
|
|||
// All lines
|
||||
assert_eq!(
|
||||
blame
|
||||
.blame_for_rows((0..8).map(MultiBufferRow).map(Some), cx)
|
||||
.blame_for_rows(
|
||||
&(0..8)
|
||||
.map(|buffer_row| RowInfo {
|
||||
buffer_row: Some(buffer_row),
|
||||
..Default::default()
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
cx
|
||||
)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
Some(blame_entry("1b1b1b", 0..1)),
|
||||
|
@ -714,7 +752,15 @@ mod tests {
|
|||
// Subset of lines
|
||||
assert_eq!(
|
||||
blame
|
||||
.blame_for_rows((1..4).map(MultiBufferRow).map(Some), cx)
|
||||
.blame_for_rows(
|
||||
&(1..4)
|
||||
.map(|buffer_row| RowInfo {
|
||||
buffer_row: Some(buffer_row),
|
||||
..Default::default()
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
cx
|
||||
)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
Some(blame_entry("0d0d0d", 1..2)),
|
||||
|
@ -725,7 +771,17 @@ mod tests {
|
|||
// Subset of lines, with some not displayed
|
||||
assert_eq!(
|
||||
blame
|
||||
.blame_for_rows(vec![Some(MultiBufferRow(1)), None, None], cx)
|
||||
.blame_for_rows(
|
||||
&[
|
||||
RowInfo {
|
||||
buffer_row: Some(1),
|
||||
..Default::default()
|
||||
},
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
],
|
||||
cx
|
||||
)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![Some(blame_entry("0d0d0d", 1..2)), None, None]
|
||||
);
|
||||
|
@ -777,16 +833,16 @@ mod tests {
|
|||
git_blame.update(cx, |blame, cx| {
|
||||
// Sanity check before edits: make sure that we get the same blame entry for all
|
||||
// lines.
|
||||
assert_blame_rows!(
|
||||
assert_blame_rows(
|
||||
blame,
|
||||
(0..4),
|
||||
0..4,
|
||||
vec![
|
||||
Some(blame_entry("1b1b1b", 0..4)),
|
||||
Some(blame_entry("1b1b1b", 0..4)),
|
||||
Some(blame_entry("1b1b1b", 0..4)),
|
||||
Some(blame_entry("1b1b1b", 0..4)),
|
||||
],
|
||||
cx
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -795,11 +851,11 @@ mod tests {
|
|||
buffer.edit([(Point::new(0, 0)..Point::new(0, 0), "X")], None, cx);
|
||||
});
|
||||
git_blame.update(cx, |blame, cx| {
|
||||
assert_blame_rows!(
|
||||
assert_blame_rows(
|
||||
blame,
|
||||
(0..2),
|
||||
0..2,
|
||||
vec![None, Some(blame_entry("1b1b1b", 0..4))],
|
||||
cx
|
||||
cx,
|
||||
);
|
||||
});
|
||||
// Modify a single line, in the middle of the line
|
||||
|
@ -807,21 +863,21 @@ mod tests {
|
|||
buffer.edit([(Point::new(1, 2)..Point::new(1, 2), "X")], None, cx);
|
||||
});
|
||||
git_blame.update(cx, |blame, cx| {
|
||||
assert_blame_rows!(
|
||||
assert_blame_rows(
|
||||
blame,
|
||||
(1..4),
|
||||
1..4,
|
||||
vec![
|
||||
None,
|
||||
Some(blame_entry("1b1b1b", 0..4)),
|
||||
Some(blame_entry("1b1b1b", 0..4))
|
||||
Some(blame_entry("1b1b1b", 0..4)),
|
||||
],
|
||||
cx
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
// Before we insert a newline at the end, sanity check:
|
||||
git_blame.update(cx, |blame, cx| {
|
||||
assert_blame_rows!(blame, (3..4), vec![Some(blame_entry("1b1b1b", 0..4))], cx);
|
||||
assert_blame_rows(blame, 3..4, vec![Some(blame_entry("1b1b1b", 0..4))], cx);
|
||||
});
|
||||
// Insert a newline at the end
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
|
@ -829,17 +885,17 @@ mod tests {
|
|||
});
|
||||
// Only the new line is marked as edited:
|
||||
git_blame.update(cx, |blame, cx| {
|
||||
assert_blame_rows!(
|
||||
assert_blame_rows(
|
||||
blame,
|
||||
(3..5),
|
||||
3..5,
|
||||
vec![Some(blame_entry("1b1b1b", 0..4)), None],
|
||||
cx
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
// Before we insert a newline at the start, sanity check:
|
||||
git_blame.update(cx, |blame, cx| {
|
||||
assert_blame_rows!(blame, (2..3), vec![Some(blame_entry("1b1b1b", 0..4)),], cx);
|
||||
assert_blame_rows(blame, 2..3, vec![Some(blame_entry("1b1b1b", 0..4))], cx);
|
||||
});
|
||||
|
||||
// Usage example
|
||||
|
@ -849,11 +905,11 @@ mod tests {
|
|||
});
|
||||
// Only the new line is marked as edited:
|
||||
git_blame.update(cx, |blame, cx| {
|
||||
assert_blame_rows!(
|
||||
assert_blame_rows(
|
||||
blame,
|
||||
(2..4),
|
||||
vec![None, Some(blame_entry("1b1b1b", 0..4)),],
|
||||
cx
|
||||
2..4,
|
||||
vec![None, Some(blame_entry("1b1b1b", 0..4))],
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -146,7 +146,7 @@ impl ProjectDiffEditor {
|
|||
let editor = cx.new_view(|cx| {
|
||||
let mut diff_display_editor =
|
||||
Editor::for_multibuffer(excerpts.clone(), Some(project.clone()), true, cx);
|
||||
diff_display_editor.set_expand_all_diff_hunks();
|
||||
diff_display_editor.set_expand_all_diff_hunks(cx);
|
||||
diff_display_editor
|
||||
});
|
||||
|
||||
|
@ -310,9 +310,11 @@ impl ProjectDiffEditor {
|
|||
.update(&mut cx, |project_diff_editor, cx| {
|
||||
project_diff_editor.update_excerpts(id, new_changes, new_entry_order, cx);
|
||||
project_diff_editor.editor.update(cx, |editor, cx| {
|
||||
for change_set in change_sets {
|
||||
editor.diff_map.add_change_set(change_set, cx)
|
||||
}
|
||||
editor.buffer.update(cx, |buffer, cx| {
|
||||
for change_set in change_sets {
|
||||
buffer.add_change_set(change_set, cx)
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
.ok();
|
||||
|
@ -1105,6 +1107,8 @@ mod tests {
|
|||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use crate::test::editor_test_context::assert_state_with_diff;
|
||||
|
||||
use super::*;
|
||||
|
||||
// TODO finish
|
||||
|
@ -1183,19 +1187,13 @@ mod tests {
|
|||
let change_set = cx.new_model(|cx| {
|
||||
BufferChangeSet::new_with_base_text(
|
||||
old_text.clone(),
|
||||
file_a_editor
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.as_singleton()
|
||||
.unwrap()
|
||||
.read(cx)
|
||||
.text_snapshot(),
|
||||
&file_a_editor.buffer().read(cx).as_singleton().unwrap(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
file_a_editor
|
||||
.diff_map
|
||||
.add_change_set(change_set.clone(), cx);
|
||||
file_a_editor.buffer.update(cx, |buffer, cx| {
|
||||
buffer.add_change_set(change_set.clone(), cx)
|
||||
});
|
||||
project.update(cx, |project, cx| {
|
||||
project.buffer_store().update(cx, |buffer_store, cx| {
|
||||
buffer_store.set_change_set(
|
||||
|
@ -1225,15 +1223,17 @@ mod tests {
|
|||
cx.executor()
|
||||
.advance_clock(UPDATE_DEBOUNCE + Duration::from_millis(100));
|
||||
cx.run_until_parked();
|
||||
let editor = project_diff_editor.update(cx, |view, _| view.editor.clone());
|
||||
|
||||
project_diff_editor.update(cx, |project_diff_editor, cx| {
|
||||
assert_eq!(
|
||||
// TODO assert it better: extract added text (based on the background changes) and deleted text (based on the deleted blocks added)
|
||||
project_diff_editor.editor.read(cx).text(cx),
|
||||
format!("{change}{old_text}"),
|
||||
"Should have a new change shown in the beginning, and the old text shown as deleted text afterwards"
|
||||
);
|
||||
});
|
||||
assert_state_with_diff(
|
||||
&editor,
|
||||
cx,
|
||||
indoc::indoc! {
|
||||
"
|
||||
- This is file_a
|
||||
+ an edit after git addThis is file_aˇ",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn init_test(cx: &mut gpui::TestAppContext) {
|
||||
|
|
|
@ -265,12 +265,9 @@ fn show_hover(
|
|||
|
||||
let local_diagnostic = snapshot
|
||||
.buffer_snapshot
|
||||
.diagnostics_in_range(anchor..anchor, false)
|
||||
.diagnostics_in_range::<_, usize>(anchor..anchor)
|
||||
// Find the entry with the most specific range
|
||||
.min_by_key(|entry| {
|
||||
let range = entry.range.to_offset(&snapshot.buffer_snapshot);
|
||||
range.end - range.start
|
||||
});
|
||||
.min_by_key(|entry| entry.range.len());
|
||||
|
||||
let diagnostic_popover = if let Some(local_diagnostic) = local_diagnostic {
|
||||
let text = match local_diagnostic.diagnostic.source {
|
||||
|
@ -279,6 +276,15 @@ fn show_hover(
|
|||
}
|
||||
None => local_diagnostic.diagnostic.message.clone(),
|
||||
};
|
||||
let local_diagnostic = DiagnosticEntry {
|
||||
diagnostic: local_diagnostic.diagnostic,
|
||||
range: snapshot
|
||||
.buffer_snapshot
|
||||
.anchor_before(local_diagnostic.range.start)
|
||||
..snapshot
|
||||
.buffer_snapshot
|
||||
.anchor_after(local_diagnostic.range.end),
|
||||
};
|
||||
|
||||
let mut border_color: Option<Hsla> = None;
|
||||
let mut background_color: Option<Hsla> = None;
|
||||
|
@ -770,7 +776,7 @@ impl InfoPopover {
|
|||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DiagnosticPopover {
|
||||
local_diagnostic: DiagnosticEntry<Anchor>,
|
||||
pub(crate) local_diagnostic: DiagnosticEntry<Anchor>,
|
||||
parsed_content: Option<View<Markdown>>,
|
||||
border_color: Option<Hsla>,
|
||||
background_color: Option<Hsla>,
|
||||
|
@ -823,10 +829,6 @@ impl DiagnosticPopover {
|
|||
|
||||
diagnostic_div.into_any_element()
|
||||
}
|
||||
|
||||
pub fn group_id(&self) -> usize {
|
||||
self.local_diagnostic.diagnostic.group_id
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -2,17 +2,16 @@ use std::{ops::Range, time::Duration};
|
|||
|
||||
use collections::HashSet;
|
||||
use gpui::{AppContext, Task};
|
||||
use language::{language_settings::language_settings, BufferRow};
|
||||
use multi_buffer::{MultiBufferIndentGuide, MultiBufferRow};
|
||||
use text::{BufferId, LineIndent, Point};
|
||||
use language::language_settings::language_settings;
|
||||
use multi_buffer::{IndentGuide, MultiBufferRow};
|
||||
use text::{LineIndent, Point};
|
||||
use ui::ViewContext;
|
||||
use util::ResultExt;
|
||||
|
||||
use crate::{DisplaySnapshot, Editor};
|
||||
|
||||
struct ActiveIndentedRange {
|
||||
buffer_id: BufferId,
|
||||
row_range: Range<BufferRow>,
|
||||
row_range: Range<MultiBufferRow>,
|
||||
indent: LineIndent,
|
||||
}
|
||||
|
||||
|
@ -36,7 +35,7 @@ impl Editor {
|
|||
visible_buffer_range: Range<MultiBufferRow>,
|
||||
snapshot: &DisplaySnapshot,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> Option<Vec<MultiBufferIndentGuide>> {
|
||||
) -> Option<Vec<IndentGuide>> {
|
||||
let show_indent_guides = self.should_show_indent_guides().unwrap_or_else(|| {
|
||||
if let Some(buffer) = self.buffer().read(cx).as_singleton() {
|
||||
language_settings(
|
||||
|
@ -66,7 +65,7 @@ impl Editor {
|
|||
|
||||
pub fn find_active_indent_guide_indices(
|
||||
&mut self,
|
||||
indent_guides: &[MultiBufferIndentGuide],
|
||||
indent_guides: &[IndentGuide],
|
||||
snapshot: &DisplaySnapshot,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> Option<HashSet<usize>> {
|
||||
|
@ -134,9 +133,7 @@ impl Editor {
|
|||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, indent_guide)| {
|
||||
indent_guide.buffer_id == active_indent_range.buffer_id
|
||||
&& indent_guide.indent_level()
|
||||
== active_indent_range.indent.len(indent_guide.tab_size)
|
||||
indent_guide.indent_level() == active_indent_range.indent.len(indent_guide.tab_size)
|
||||
});
|
||||
|
||||
let mut matches = HashSet::default();
|
||||
|
@ -158,7 +155,7 @@ pub fn indent_guides_in_range(
|
|||
ignore_disabled_for_language: bool,
|
||||
snapshot: &DisplaySnapshot,
|
||||
cx: &AppContext,
|
||||
) -> Vec<MultiBufferIndentGuide> {
|
||||
) -> Vec<IndentGuide> {
|
||||
let start_anchor = snapshot
|
||||
.buffer_snapshot
|
||||
.anchor_before(Point::new(visible_buffer_range.start.0, 0));
|
||||
|
@ -169,14 +166,12 @@ pub fn indent_guides_in_range(
|
|||
snapshot
|
||||
.buffer_snapshot
|
||||
.indent_guides_in_range(start_anchor..end_anchor, ignore_disabled_for_language, cx)
|
||||
.into_iter()
|
||||
.filter(|indent_guide| {
|
||||
if editor.buffer_folded(indent_guide.buffer_id, cx) {
|
||||
if editor.is_buffer_folded(indent_guide.buffer_id, cx) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let start =
|
||||
MultiBufferRow(indent_guide.multibuffer_row_range.start.0.saturating_sub(1));
|
||||
let start = MultiBufferRow(indent_guide.start_row.0.saturating_sub(1));
|
||||
// Filter out indent guides that are inside a fold
|
||||
// All indent guides that are starting "offscreen" have a start value of the first visible row minus one
|
||||
// Therefore checking if a line is folded at first visible row minus one causes the other indent guides that are not related to the fold to disappear as well
|
||||
|
@ -193,24 +188,11 @@ async fn resolve_indented_range(
|
|||
snapshot: DisplaySnapshot,
|
||||
buffer_row: MultiBufferRow,
|
||||
) -> Option<ActiveIndentedRange> {
|
||||
let (buffer_row, buffer_snapshot, buffer_id) =
|
||||
if let Some((_, buffer_id, snapshot)) = snapshot.buffer_snapshot.as_singleton() {
|
||||
(buffer_row.0, snapshot, buffer_id)
|
||||
} else {
|
||||
let (snapshot, point) = snapshot.buffer_snapshot.buffer_line_for_row(buffer_row)?;
|
||||
|
||||
let buffer_id = snapshot.remote_id();
|
||||
(point.start.row, snapshot, buffer_id)
|
||||
};
|
||||
|
||||
buffer_snapshot
|
||||
snapshot
|
||||
.buffer_snapshot
|
||||
.enclosing_indent(buffer_row)
|
||||
.await
|
||||
.map(|(row_range, indent)| ActiveIndentedRange {
|
||||
row_range,
|
||||
indent,
|
||||
buffer_id,
|
||||
})
|
||||
.map(|(row_range, indent)| ActiveIndentedRange { row_range, indent })
|
||||
}
|
||||
|
||||
fn should_recalculate_indented_range(
|
||||
|
@ -222,23 +204,23 @@ fn should_recalculate_indented_range(
|
|||
if prev_row.0 == new_row.0 {
|
||||
return false;
|
||||
}
|
||||
if let Some((_, _, snapshot)) = snapshot.buffer_snapshot.as_singleton() {
|
||||
if !current_indent_range.row_range.contains(&new_row.0) {
|
||||
if snapshot.buffer_snapshot.is_singleton() {
|
||||
if !current_indent_range.row_range.contains(&new_row) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let old_line_indent = snapshot.line_indent_for_row(prev_row.0);
|
||||
let new_line_indent = snapshot.line_indent_for_row(new_row.0);
|
||||
let old_line_indent = snapshot.buffer_snapshot.line_indent_for_row(prev_row);
|
||||
let new_line_indent = snapshot.buffer_snapshot.line_indent_for_row(new_row);
|
||||
|
||||
if old_line_indent.is_line_empty()
|
||||
|| new_line_indent.is_line_empty()
|
||||
|| old_line_indent != new_line_indent
|
||||
|| snapshot.max_point().row == new_row.0
|
||||
|| snapshot.buffer_snapshot.max_point().row == new_row.0
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
let next_line_indent = snapshot.line_indent_for_row(new_row.0 + 1);
|
||||
let next_line_indent = snapshot.buffer_snapshot.line_indent_for_row(new_row + 1);
|
||||
next_line_indent.is_line_empty() || next_line_indent != old_line_indent
|
||||
} else {
|
||||
true
|
||||
|
|
|
@ -20,7 +20,6 @@ use language::{
|
|||
SelectionGoal,
|
||||
};
|
||||
use lsp::DiagnosticSeverity;
|
||||
use multi_buffer::AnchorRangeExt;
|
||||
use project::{
|
||||
lsp_store::FormatTrigger, project_settings::ProjectSettings, search::SearchQuery, Project,
|
||||
ProjectItem as _, ProjectPath,
|
||||
|
@ -528,6 +527,7 @@ fn deserialize_anchor(buffer: &MultiBufferSnapshot, anchor: proto::EditorAnchor)
|
|||
excerpt_id,
|
||||
text_anchor: language::proto::deserialize_anchor(anchor.anchor?)?,
|
||||
buffer_id: buffer.buffer_id_for_excerpt(excerpt_id),
|
||||
diff_base_anchor: None,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1435,59 +1435,34 @@ impl SearchableItem for Editor {
|
|||
cx.background_executor().spawn(async move {
|
||||
let mut ranges = Vec::new();
|
||||
|
||||
if let Some((_, _, excerpt_buffer)) = buffer.as_singleton() {
|
||||
let search_within_ranges = if search_within_ranges.is_empty() {
|
||||
vec![None]
|
||||
} else {
|
||||
search_within_ranges
|
||||
.into_iter()
|
||||
.map(|range| Some(range.to_offset(&buffer)))
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
for range in search_within_ranges {
|
||||
let buffer = &buffer;
|
||||
ranges.extend(
|
||||
query
|
||||
.search(excerpt_buffer, range.clone())
|
||||
.await
|
||||
.into_iter()
|
||||
.map(|matched_range| {
|
||||
let offset = range.clone().map(|r| r.start).unwrap_or(0);
|
||||
buffer.anchor_after(matched_range.start + offset)
|
||||
..buffer.anchor_before(matched_range.end + offset)
|
||||
}),
|
||||
);
|
||||
}
|
||||
let search_within_ranges = if search_within_ranges.is_empty() {
|
||||
vec![buffer.anchor_before(0)..buffer.anchor_after(buffer.len())]
|
||||
} else {
|
||||
let search_within_ranges = if search_within_ranges.is_empty() {
|
||||
vec![buffer.anchor_before(0)..buffer.anchor_after(buffer.len())]
|
||||
} else {
|
||||
search_within_ranges
|
||||
};
|
||||
|
||||
for (excerpt_id, search_buffer, search_range) in
|
||||
buffer.excerpts_in_ranges(search_within_ranges)
|
||||
{
|
||||
if !search_range.is_empty() {
|
||||
ranges.extend(
|
||||
query
|
||||
.search(search_buffer, Some(search_range.clone()))
|
||||
.await
|
||||
.into_iter()
|
||||
.map(|match_range| {
|
||||
let start = search_buffer
|
||||
.anchor_after(search_range.start + match_range.start);
|
||||
let end = search_buffer
|
||||
.anchor_before(search_range.start + match_range.end);
|
||||
buffer.anchor_in_excerpt(excerpt_id, start).unwrap()
|
||||
..buffer.anchor_in_excerpt(excerpt_id, end).unwrap()
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
search_within_ranges
|
||||
};
|
||||
|
||||
for (search_buffer, search_range, excerpt_id) in
|
||||
buffer.ranges_to_buffer_ranges(search_within_ranges.into_iter())
|
||||
{
|
||||
ranges.extend(
|
||||
query
|
||||
.search(search_buffer, Some(search_range.clone()))
|
||||
.await
|
||||
.into_iter()
|
||||
.map(|match_range| {
|
||||
let start =
|
||||
search_buffer.anchor_after(search_range.start + match_range.start);
|
||||
let end =
|
||||
search_buffer.anchor_before(search_range.start + match_range.end);
|
||||
Anchor::range_in_buffer(
|
||||
excerpt_id,
|
||||
search_buffer.remote_id(),
|
||||
start..end,
|
||||
)
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
ranges
|
||||
})
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ impl ProposedChangesEditor {
|
|||
let mut this = Self {
|
||||
editor: cx.new_view(|cx| {
|
||||
let mut editor = Editor::for_multibuffer(multibuffer.clone(), project, true, cx);
|
||||
editor.set_expand_all_diff_hunks();
|
||||
editor.set_expand_all_diff_hunks(cx);
|
||||
editor.set_completion_provider(None);
|
||||
editor.clear_code_action_providers();
|
||||
editor.set_semantics_provider(
|
||||
|
@ -104,16 +104,10 @@ impl ProposedChangesEditor {
|
|||
let buffer = buffer.read(cx);
|
||||
let base_buffer = buffer.base_buffer()?;
|
||||
let buffer = buffer.text_snapshot();
|
||||
let change_set = this.editor.update(cx, |editor, _| {
|
||||
Some(
|
||||
editor
|
||||
.diff_map
|
||||
.diff_bases
|
||||
.get(&buffer.remote_id())?
|
||||
.change_set
|
||||
.clone(),
|
||||
)
|
||||
})?;
|
||||
let change_set = this
|
||||
.multibuffer
|
||||
.read(cx)
|
||||
.change_set_for(buffer.remote_id())?;
|
||||
Some(change_set.update(cx, |change_set, cx| {
|
||||
change_set.set_base_text(
|
||||
base_buffer.read(cx).text(),
|
||||
|
@ -193,7 +187,7 @@ impl ProposedChangesEditor {
|
|||
} else {
|
||||
branch_buffer = location.buffer.update(cx, |buffer, cx| buffer.branch(cx));
|
||||
new_change_sets.push(cx.new_model(|cx| {
|
||||
let mut change_set = BufferChangeSet::new(branch_buffer.read(cx));
|
||||
let mut change_set = BufferChangeSet::new(&branch_buffer, cx);
|
||||
let _ = change_set.set_base_text(
|
||||
location.buffer.read(cx).text(),
|
||||
branch_buffer.read(cx).text_snapshot(),
|
||||
|
@ -223,9 +217,11 @@ impl ProposedChangesEditor {
|
|||
self.buffer_entries = buffer_entries;
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(None, cx, |selections| selections.refresh());
|
||||
for change_set in new_change_sets {
|
||||
editor.diff_map.add_change_set(change_set, cx)
|
||||
}
|
||||
editor.buffer.update(cx, |buffer, cx| {
|
||||
for change_set in new_change_sets {
|
||||
buffer.add_change_set(change_set, cx)
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -323,8 +323,7 @@ impl SelectionsCollection {
|
|||
self.all(cx).last().unwrap().clone()
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn ranges<D: TextDimension + Ord + Sub<D, Output = D> + std::fmt::Debug>(
|
||||
pub fn ranges<D: TextDimension + Ord + Sub<D, Output = D>>(
|
||||
&self,
|
||||
cx: &mut AppContext,
|
||||
) -> Vec<Range<D>> {
|
||||
|
@ -332,9 +331,9 @@ impl SelectionsCollection {
|
|||
.iter()
|
||||
.map(|s| {
|
||||
if s.reversed {
|
||||
s.end.clone()..s.start.clone()
|
||||
s.end..s.start
|
||||
} else {
|
||||
s.start.clone()..s.end.clone()
|
||||
s.start..s.end
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
|
@ -921,7 +920,7 @@ pub(crate) fn resolve_selections<'a, D, I>(
|
|||
map: &'a DisplaySnapshot,
|
||||
) -> impl 'a + Iterator<Item = Selection<D>>
|
||||
where
|
||||
D: TextDimension + Clone + Ord + Sub<D, Output = D>,
|
||||
D: TextDimension + Ord + Sub<D, Output = D>,
|
||||
I: 'a + IntoIterator<Item = &'a Selection<Anchor>>,
|
||||
{
|
||||
let (to_convert, selections) = resolve_selections_display(selections, map).tee();
|
||||
|
|
|
@ -5,6 +5,7 @@ use crate::actions::ShowSignatureHelp;
|
|||
use crate::{Editor, EditorSettings, ToggleAutoSignatureHelp};
|
||||
use gpui::{AppContext, ViewContext};
|
||||
use language::markdown::parse_markdown;
|
||||
use language::BufferSnapshot;
|
||||
use multi_buffer::{Anchor, ToOffset};
|
||||
use settings::Settings;
|
||||
use std::ops::Range;
|
||||
|
@ -94,13 +95,14 @@ impl Editor {
|
|||
(a, b) if b <= buffer_snapshot.len() => a - 1..b,
|
||||
(a, b) => a - 1..b - 1,
|
||||
};
|
||||
let not_quote_like_brackets = |start: Range<usize>, end: Range<usize>| {
|
||||
let text = buffer_snapshot.text();
|
||||
let (text_start, text_end) = (text.get(start), text.get(end));
|
||||
QUOTE_PAIRS
|
||||
.into_iter()
|
||||
.all(|(start, end)| text_start != Some(start) && text_end != Some(end))
|
||||
};
|
||||
let not_quote_like_brackets =
|
||||
|buffer: &BufferSnapshot, start: Range<usize>, end: Range<usize>| {
|
||||
let text_start = buffer.text_for_range(start).collect::<String>();
|
||||
let text_end = buffer.text_for_range(end).collect::<String>();
|
||||
QUOTE_PAIRS
|
||||
.into_iter()
|
||||
.all(|(start, end)| text_start != start && text_end != end)
|
||||
};
|
||||
|
||||
let previous_position = old_cursor_position.to_offset(&buffer_snapshot);
|
||||
let previous_brackets_range = bracket_range(previous_position);
|
||||
|
|
|
@ -15,7 +15,7 @@ fn task_context_with_editor(
|
|||
};
|
||||
let (selection, buffer, editor_snapshot) = {
|
||||
let selection = editor.selections.newest_adjusted(cx);
|
||||
let Some((buffer, _, _)) = editor
|
||||
let Some((buffer, _)) = editor
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.point_to_buffer_offset(selection.start, cx)
|
||||
|
|
|
@ -67,6 +67,13 @@ pub(crate) fn rust_lang() -> Arc<Language> {
|
|||
("<" @open ">" @close)
|
||||
("\"" @open "\"" @close)
|
||||
(closure_parameters "|" @open "|" @close)"#})),
|
||||
text_objects: Some(Cow::from(indoc! {r#"
|
||||
(function_item
|
||||
body: (_
|
||||
"{"
|
||||
(_)* @function.inside
|
||||
"}" )) @function.around
|
||||
"#})),
|
||||
..Default::default()
|
||||
})
|
||||
.expect("Could not parse queries");
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
display_map::ToDisplayPoint, AnchorRangeExt, Autoscroll, DiffRowHighlight, DisplayPoint,
|
||||
Editor, MultiBuffer, RowExt,
|
||||
display_map::ToDisplayPoint, AnchorRangeExt, Autoscroll, DisplayPoint, Editor, MultiBuffer,
|
||||
RowExt,
|
||||
};
|
||||
use collections::BTreeMap;
|
||||
use futures::Future;
|
||||
|
@ -11,7 +11,7 @@ use gpui::{
|
|||
};
|
||||
use itertools::Itertools;
|
||||
use language::{Buffer, BufferSnapshot, LanguageRegistry};
|
||||
use multi_buffer::{ExcerptRange, ToPoint};
|
||||
use multi_buffer::{ExcerptRange, MultiBufferRow};
|
||||
use parking_lot::RwLock;
|
||||
use project::{FakeFs, Project};
|
||||
use std::{
|
||||
|
@ -333,85 +333,8 @@ impl EditorTestContext {
|
|||
///
|
||||
/// Diff hunks are indicated by lines starting with `+` and `-`.
|
||||
#[track_caller]
|
||||
pub fn assert_state_with_diff(&mut self, expected_diff: String) {
|
||||
let has_diff_markers = expected_diff
|
||||
.lines()
|
||||
.any(|line| line.starts_with("+") || line.starts_with("-"));
|
||||
let expected_diff_text = expected_diff
|
||||
.split('\n')
|
||||
.map(|line| {
|
||||
let trimmed = line.trim();
|
||||
if trimmed.is_empty() {
|
||||
String::new()
|
||||
} else if has_diff_markers {
|
||||
line.to_string()
|
||||
} else {
|
||||
format!(" {line}")
|
||||
}
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
let actual_selections = self.editor_selections();
|
||||
let actual_marked_text =
|
||||
generate_marked_text(&self.buffer_text(), &actual_selections, true);
|
||||
|
||||
// Read the actual diff from the editor's row highlights and block
|
||||
// decorations.
|
||||
let actual_diff = self.editor.update(&mut self.cx, |editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
let insertions = editor
|
||||
.highlighted_rows::<DiffRowHighlight>()
|
||||
.map(|(range, _)| {
|
||||
let start = range.start.to_point(&snapshot.buffer_snapshot);
|
||||
let end = range.end.to_point(&snapshot.buffer_snapshot);
|
||||
start.row..end.row
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let deletions = editor
|
||||
.diff_map
|
||||
.hunks
|
||||
.iter()
|
||||
.filter_map(|hunk| {
|
||||
if hunk.blocks.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let row = hunk
|
||||
.hunk_range
|
||||
.start
|
||||
.to_point(&snapshot.buffer_snapshot)
|
||||
.row;
|
||||
let (_, buffer, _) = editor
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.excerpt_containing(hunk.hunk_range.start, cx)
|
||||
.expect("no excerpt for expanded buffer's hunk start");
|
||||
let buffer_id = buffer.read(cx).remote_id();
|
||||
let change_set = &editor
|
||||
.diff_map
|
||||
.diff_bases
|
||||
.get(&buffer_id)
|
||||
.expect("should have a diff base for expanded hunk")
|
||||
.change_set;
|
||||
let deleted_text = change_set
|
||||
.read(cx)
|
||||
.base_text
|
||||
.as_ref()
|
||||
.expect("no base text for expanded hunk")
|
||||
.read(cx)
|
||||
.as_rope()
|
||||
.slice(hunk.diff_base_byte_range.clone())
|
||||
.to_string();
|
||||
if let DiffHunkStatus::Modified | DiffHunkStatus::Removed = hunk.status {
|
||||
Some((row, deleted_text))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
format_diff(actual_marked_text, deletions, insertions)
|
||||
});
|
||||
|
||||
pretty_assertions::assert_eq!(actual_diff, expected_diff_text, "unexpected diff state");
|
||||
pub fn assert_state_with_diff(&mut self, expected_diff_text: String) {
|
||||
assert_state_with_diff(&self.editor, &mut self.cx, &expected_diff_text);
|
||||
}
|
||||
|
||||
/// Make an assertion about the editor's text and the ranges and directions
|
||||
|
@ -504,44 +427,49 @@ impl EditorTestContext {
|
|||
}
|
||||
}
|
||||
|
||||
fn format_diff(
|
||||
text: String,
|
||||
actual_deletions: Vec<(u32, String)>,
|
||||
actual_insertions: Vec<Range<u32>>,
|
||||
) -> String {
|
||||
let mut diff = String::new();
|
||||
for (row, line) in text.split('\n').enumerate() {
|
||||
let row = row as u32;
|
||||
if row > 0 {
|
||||
diff.push('\n');
|
||||
}
|
||||
if let Some(text) = actual_deletions
|
||||
.iter()
|
||||
.find_map(|(deletion_row, deleted_text)| {
|
||||
if *deletion_row == row {
|
||||
Some(deleted_text)
|
||||
} else {
|
||||
None
|
||||
#[track_caller]
|
||||
pub fn assert_state_with_diff(
|
||||
editor: &View<Editor>,
|
||||
cx: &mut VisualTestContext,
|
||||
expected_diff_text: &str,
|
||||
) {
|
||||
let (snapshot, selections) = editor.update(cx, |editor, cx| {
|
||||
(
|
||||
editor.snapshot(cx).buffer_snapshot.clone(),
|
||||
editor.selections.ranges::<usize>(cx),
|
||||
)
|
||||
});
|
||||
|
||||
let actual_marked_text = generate_marked_text(&snapshot.text(), &selections, true);
|
||||
|
||||
// Read the actual diff.
|
||||
let line_infos = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
|
||||
let has_diff = line_infos.iter().any(|info| info.diff_status.is_some());
|
||||
let actual_diff = actual_marked_text
|
||||
.split('\n')
|
||||
.zip(line_infos)
|
||||
.map(|(line, info)| {
|
||||
let mut marker = match info.diff_status {
|
||||
Some(DiffHunkStatus::Added) => "+ ",
|
||||
Some(DiffHunkStatus::Removed) => "- ",
|
||||
Some(DiffHunkStatus::Modified) => unreachable!(),
|
||||
None => {
|
||||
if has_diff {
|
||||
" "
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
})
|
||||
{
|
||||
for line in text.lines() {
|
||||
diff.push('-');
|
||||
if !line.is_empty() {
|
||||
diff.push(' ');
|
||||
diff.push_str(line);
|
||||
}
|
||||
diff.push('\n');
|
||||
};
|
||||
if line.is_empty() {
|
||||
marker = marker.trim();
|
||||
}
|
||||
}
|
||||
let marker = if actual_insertions.iter().any(|range| range.contains(&row)) {
|
||||
"+ "
|
||||
} else {
|
||||
" "
|
||||
};
|
||||
diff.push_str(format!("{marker}{line}").trim_end());
|
||||
}
|
||||
diff
|
||||
format!("{marker}{line}")
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
|
||||
pretty_assertions::assert_eq!(actual_diff, expected_diff_text, "unexpected diff state");
|
||||
}
|
||||
|
||||
impl Deref for EditorTestContext {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue