Enable project search on type by default (#62506)
Closes https://github.com/zed-industries/zed/issues/9318 Most of the diff is tests. Notable behavior changes during this mode, compared to current mode: * unsaved changes in multi buffer won't spawn any "save or not" pop-ups during typing — submitting the query still does it, as before. Unsaved files will stay so after being sorted out on typing: we cannot save them by default as file-less buffers need a save modal; Zed should already be able to persist and restore such changes on restart. * gutter may jump during search, if new excerpts with large line numbers appear during query input — yet it would not shrink back to avoid excessive flickering and rewraps Release Notes: - Enabled project search on type by default (use `{ "search": { "search_on_type": false } }` to restore the previous behavior)
This commit is contained in:
1 parent
4ccbcabfee
commit
1475887faf
21 files changed
+4459
-212
No files matched your search
@@ -3131,7 +3131,7 @@ impl MultiBuffer {
|
||||
}
|
||||
}
|
||||
|
||||
fn build_excerpt_ranges(
|
||||
pub fn build_excerpt_ranges(
|
||||
ranges: impl IntoIterator<Item = Range<Point>>,
|
||||
context_line_count: u32,
|
||||
buffer_snapshot: &BufferSnapshot,
|
||||
|
||||
@@ -856,6 +856,315 @@ fn test_excerpt_events(cx: &mut App) {
|
||||
assert_eq!(*follower_edit_event_count.read(), 5);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_set_excerpts_for_path_reuses_excerpts_when_only_primary_changes(cx: &mut App) {
|
||||
let buffer = cx.new(|cx| Buffer::local(sample_text(10, 6, 'a'), cx));
|
||||
let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
|
||||
let path = PathKey::for_buffer(&buffer, cx);
|
||||
|
||||
let edited_event_count = Arc::new(RwLock::new(0));
|
||||
let ranges_updated_event_count = Arc::new(RwLock::new(0));
|
||||
multibuffer.update(cx, |_, cx| {
|
||||
cx.subscribe(&multibuffer, {
|
||||
let edited_event_count = edited_event_count.clone();
|
||||
let ranges_updated_event_count = ranges_updated_event_count.clone();
|
||||
move |_, _, event, _| match event {
|
||||
Event::Edited { .. } => *edited_event_count.write() += 1,
|
||||
Event::BufferRangesUpdated { .. } => *ranges_updated_event_count.write() += 1,
|
||||
_ => {}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
});
|
||||
|
||||
multibuffer.update(cx, |multibuffer, cx| {
|
||||
multibuffer.set_excerpts_for_path(
|
||||
path.clone(),
|
||||
buffer.clone(),
|
||||
vec![Point::new(3, 0)..Point::new(3, 1)],
|
||||
2,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
let text = multibuffer.read(cx).snapshot(cx).text();
|
||||
assert_eq!(*edited_event_count.read(), 1);
|
||||
assert_eq!(*ranges_updated_event_count.read(), 1);
|
||||
|
||||
multibuffer.update(cx, |multibuffer, cx| {
|
||||
multibuffer.set_excerpts_for_path(
|
||||
path.clone(),
|
||||
buffer.clone(),
|
||||
vec![Point::new(3, 0)..Point::new(3, 2)],
|
||||
2,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
assert_eq!(multibuffer.read(cx).snapshot(cx).text(), text);
|
||||
assert_eq!(*edited_event_count.read(), 1);
|
||||
assert_eq!(*ranges_updated_event_count.read(), 2);
|
||||
|
||||
let buffer_snapshot = buffer.read(cx).snapshot();
|
||||
let primary_ranges = multibuffer
|
||||
.read(cx)
|
||||
.snapshot(cx)
|
||||
.excerpts()
|
||||
.map(|range| range.primary.to_point(&buffer_snapshot))
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(primary_ranges, vec![Point::new(3, 0)..Point::new(3, 2)]);
|
||||
|
||||
multibuffer.update(cx, |multibuffer, cx| {
|
||||
multibuffer.set_excerpts_for_path(
|
||||
path.clone(),
|
||||
buffer.clone(),
|
||||
vec![Point::new(3, 0)..Point::new(3, 2)],
|
||||
2,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
assert_eq!(*edited_event_count.read(), 1);
|
||||
assert_eq!(*ranges_updated_event_count.read(), 2);
|
||||
|
||||
multibuffer.update(cx, |multibuffer, cx| {
|
||||
multibuffer.set_excerpts_for_path(
|
||||
path,
|
||||
buffer,
|
||||
vec![Point::new(7, 0)..Point::new(7, 1)],
|
||||
2,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
assert_ne!(multibuffer.read(cx).snapshot(cx).text(), text);
|
||||
assert_eq!(*edited_event_count.read(), 2);
|
||||
assert_eq!(*ranges_updated_event_count.read(), 3);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_set_excerpts_for_path_reuses_excerpts_after_edits_shift_anchors(cx: &mut App) {
|
||||
let buffer = cx.new(|cx| Buffer::local(sample_text(20, 6, 'a'), cx));
|
||||
let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
|
||||
let path = PathKey::for_buffer(&buffer, cx);
|
||||
|
||||
multibuffer.update(cx, |multibuffer, cx| {
|
||||
multibuffer.set_excerpts_for_path(
|
||||
path.clone(),
|
||||
buffer.clone(),
|
||||
vec![Point::new(10, 0)..Point::new(10, 1)],
|
||||
2,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
let text = multibuffer.read(cx).snapshot(cx).text();
|
||||
|
||||
let excerpt_edit_count = Arc::new(RwLock::new(0));
|
||||
let ranges_updated_event_count = Arc::new(RwLock::new(0));
|
||||
multibuffer.update(cx, |_, cx| {
|
||||
cx.subscribe(&multibuffer, {
|
||||
let excerpt_edit_count = excerpt_edit_count.clone();
|
||||
let ranges_updated_event_count = ranges_updated_event_count.clone();
|
||||
move |_, _, event, _| match event {
|
||||
Event::Edited {
|
||||
edited_buffer: None,
|
||||
..
|
||||
} => *excerpt_edit_count.write() += 1,
|
||||
Event::BufferRangesUpdated { .. } => *ranges_updated_event_count.write() += 1,
|
||||
_ => {}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
});
|
||||
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit([(Point::new(0, 0)..Point::new(0, 0), "zzzzzz\n")], None, cx)
|
||||
});
|
||||
multibuffer.update(cx, |multibuffer, cx| {
|
||||
multibuffer.set_excerpts_for_path(
|
||||
path,
|
||||
buffer,
|
||||
vec![Point::new(11, 0)..Point::new(11, 1)],
|
||||
2,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
assert_eq!(
|
||||
multibuffer.read(cx).snapshot(cx).text(),
|
||||
text,
|
||||
"an edit above the excerpt shifts the match but not its content, so the shown text \
|
||||
must stay the same"
|
||||
);
|
||||
assert_eq!(
|
||||
*excerpt_edit_count.read(),
|
||||
0,
|
||||
"the shifted excerpt covers the same buffer content, so it must be reused as is \
|
||||
instead of being replaced"
|
||||
);
|
||||
assert_eq!(
|
||||
*ranges_updated_event_count.read(),
|
||||
0,
|
||||
"the shifted primary range covers the same buffer content, so no range update \
|
||||
must be reported"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_ranges_grouped_by_excerpt_path_skips_stale_ranges(cx: &mut App) {
|
||||
let buffer_a = cx.new(|cx| Buffer::local(sample_text(6, 6, 'a'), cx));
|
||||
let buffer_b = cx.new(|cx| Buffer::local(sample_text(6, 6, 'g'), cx));
|
||||
let buffer_c = cx.new(|cx| Buffer::local(sample_text(6, 6, 'm'), cx));
|
||||
let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
|
||||
let path_a = PathKey::sorted(0);
|
||||
let path_b = PathKey::sorted(1);
|
||||
let path_c = PathKey::sorted(2);
|
||||
|
||||
multibuffer.update(cx, |multibuffer, cx| {
|
||||
for (path, buffer) in [
|
||||
(path_a.clone(), &buffer_a),
|
||||
(path_b.clone(), &buffer_b),
|
||||
(path_c.clone(), &buffer_c),
|
||||
] {
|
||||
multibuffer.set_excerpts_for_path(
|
||||
path,
|
||||
buffer.clone(),
|
||||
vec![Point::new(2, 0)..Point::new(2, 3)],
|
||||
1,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
let snapshot = multibuffer.read(cx).snapshot(cx);
|
||||
let ranges = [&buffer_a, &buffer_b, &buffer_c]
|
||||
.into_iter()
|
||||
.map(|buffer| {
|
||||
let buffer_snapshot = buffer.read(cx).snapshot();
|
||||
let start = buffer_snapshot.anchor_before(Point::new(2, 0));
|
||||
let end = buffer_snapshot.anchor_after(Point::new(2, 3));
|
||||
snapshot
|
||||
.anchor_range_in_buffer(start..end)
|
||||
.expect("the buffer has excerpts")
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(
|
||||
multibuffer.read(cx).ranges_grouped_by_excerpt_path(&ranges),
|
||||
vec![
|
||||
(path_a.clone(), 0..1),
|
||||
(path_b.clone(), 1..2),
|
||||
(path_c.clone(), 2..3),
|
||||
]
|
||||
);
|
||||
|
||||
multibuffer.update(cx, |multibuffer, cx| {
|
||||
multibuffer.remove_excerpts(path_b, cx);
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
multibuffer.read(cx).ranges_grouped_by_excerpt_path(&ranges),
|
||||
vec![(path_a, 0..1), (path_c, 2..3)],
|
||||
"a range whose path lost its excerpts must be skipped instead of blocking the \
|
||||
consumption of every range that follows it"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_remove_excerpts_for_paths(cx: &mut App) {
|
||||
let buffers = (0u64..4)
|
||||
.map(|index| {
|
||||
cx.new(|cx| Buffer::local(sample_text(4, 4, (b'a' + index as u8 * 4) as char), cx))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for paths_to_remove in [
|
||||
vec![1, 2, 9],
|
||||
vec![2, 3],
|
||||
vec![0, 1, 2, 3],
|
||||
vec![0, 2],
|
||||
vec![3],
|
||||
vec![9],
|
||||
] {
|
||||
let batched = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
|
||||
let sequential = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
|
||||
for multibuffer in [&batched, &sequential] {
|
||||
multibuffer.update(cx, |multibuffer, cx| {
|
||||
for (index, buffer) in buffers.iter().enumerate() {
|
||||
multibuffer.set_excerpts_for_path(
|
||||
PathKey::sorted(index as u64),
|
||||
buffer.clone(),
|
||||
vec![Point::new(1, 0)..Point::new(1, 3)],
|
||||
1,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let edited_event_count = Arc::new(RwLock::new(0));
|
||||
let removed_buffer_events = Arc::new(RwLock::new(Vec::new()));
|
||||
batched.update(cx, |_, cx| {
|
||||
cx.subscribe(&batched, {
|
||||
let edited_event_count = edited_event_count.clone();
|
||||
let removed_buffer_events = removed_buffer_events.clone();
|
||||
move |_, _, event, _| match event {
|
||||
Event::Edited { .. } => *edited_event_count.write() += 1,
|
||||
Event::BuffersRemoved { removed_buffer_ids } => removed_buffer_events
|
||||
.write()
|
||||
.push(removed_buffer_ids.clone()),
|
||||
_ => {}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
});
|
||||
|
||||
let old_snapshot = batched.read(cx).snapshot(cx);
|
||||
let subscription = batched.update(cx, |multibuffer, _| multibuffer.subscribe());
|
||||
batched.update(cx, |multibuffer, cx| {
|
||||
multibuffer.remove_excerpts_for_paths(
|
||||
paths_to_remove
|
||||
.iter()
|
||||
.map(|index| PathKey::sorted(*index))
|
||||
.collect(),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
sequential.update(cx, |multibuffer, cx| {
|
||||
for index in &paths_to_remove {
|
||||
multibuffer.remove_excerpts(PathKey::sorted(*index), cx);
|
||||
}
|
||||
});
|
||||
|
||||
let batched_snapshot = batched.read(cx).snapshot(cx);
|
||||
assert_eq!(
|
||||
batched_snapshot.text(),
|
||||
sequential.read(cx).snapshot(cx).text(),
|
||||
"removing {paths_to_remove:?} in one batch must match sequential removals"
|
||||
);
|
||||
check_multibuffer_edits(&batched_snapshot, &old_snapshot, subscription);
|
||||
assert_eq!(
|
||||
batched_snapshot.trailing_excerpt_update_count()
|
||||
- old_snapshot.trailing_excerpt_update_count(),
|
||||
usize::from(paths_to_remove.contains(&3)),
|
||||
"removing {paths_to_remove:?} must bump the trailing excerpt update count only \
|
||||
when the trailing excerpt was actually removed"
|
||||
);
|
||||
|
||||
let expected_removed_ids = paths_to_remove
|
||||
.iter()
|
||||
.filter_map(|index| buffers.get(*index as usize))
|
||||
.map(|buffer| buffer.read(cx).remote_id())
|
||||
.collect::<Vec<_>>();
|
||||
if expected_removed_ids.is_empty() {
|
||||
assert_eq!(*edited_event_count.read(), 0);
|
||||
assert_eq!(*removed_buffer_events.read(), Vec::<Vec<BufferId>>::new());
|
||||
} else {
|
||||
assert_eq!(
|
||||
*edited_event_count.read(),
|
||||
1,
|
||||
"removing {paths_to_remove:?} must edit the multibuffer exactly once"
|
||||
);
|
||||
assert_eq!(*removed_buffer_events.read(), vec![expected_removed_ids]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_expand_excerpts(cx: &mut App) {
|
||||
let buffer = cx.new(|cx| Buffer::local(sample_text(20, 3, 'a'), cx));
|
||||
|
||||
@@ -44,12 +44,13 @@ impl PathKey {
|
||||
}
|
||||
|
||||
pub fn for_buffer(buffer: &Entity<Buffer>, cx: &App) -> Self {
|
||||
if let Some(file) = buffer.read(cx).file() {
|
||||
let buffer = buffer.read(cx);
|
||||
if let Some(file) = buffer.file() {
|
||||
Self::with_sort_prefix(file.worktree_id(cx).to_proto(), file.path().clone())
|
||||
} else {
|
||||
Self {
|
||||
sort_prefix: None,
|
||||
path: RelPath::from_unix_str(&buffer.entity_id().to_string())
|
||||
path: RelPath::from_unix_str(&buffer.remote_id().to_string())
|
||||
.unwrap()
|
||||
.into_arc(),
|
||||
}
|
||||
@@ -421,6 +422,7 @@ impl MultiBuffer {
|
||||
});
|
||||
}
|
||||
|
||||
let mut primary_ranges_changed = false;
|
||||
while let Some(excerpt) = cursor.item()
|
||||
&& excerpt.path_key == path_key
|
||||
{
|
||||
@@ -428,7 +430,11 @@ impl MultiBuffer {
|
||||
let Some(next_excerpt) = to_insert.peek() else {
|
||||
break;
|
||||
};
|
||||
if &excerpt.range == *next_excerpt {
|
||||
let context_anchors_match = excerpt.range.context == next_excerpt.context;
|
||||
if context_anchors_match
|
||||
|| excerpt.range.context.to_offset(buffer_snapshot)
|
||||
== next_excerpt.context.to_offset(buffer_snapshot)
|
||||
{
|
||||
let before = new_excerpts.summary().len();
|
||||
new_excerpts.update_last(
|
||||
|prev_excerpt| {
|
||||
@@ -442,7 +448,26 @@ impl MultiBuffer {
|
||||
},
|
||||
(),
|
||||
);
|
||||
new_excerpts.push(excerpt.clone(), ());
|
||||
let primary_offsets_changed = excerpt.range.primary != next_excerpt.primary
|
||||
&& excerpt.range.primary.to_offset(buffer_snapshot)
|
||||
!= next_excerpt.primary.to_offset(buffer_snapshot);
|
||||
primary_ranges_changed |= primary_offsets_changed;
|
||||
let reused_excerpt = if context_anchors_match {
|
||||
let mut reused_excerpt = excerpt.clone();
|
||||
if reused_excerpt.range.primary != next_excerpt.primary {
|
||||
reused_excerpt.range.primary = next_excerpt.primary.clone();
|
||||
}
|
||||
reused_excerpt
|
||||
} else {
|
||||
Excerpt::new(
|
||||
path_key.clone(),
|
||||
path_key_index,
|
||||
buffer_snapshot,
|
||||
(*next_excerpt).clone(),
|
||||
excerpt.has_trailing_newline,
|
||||
)
|
||||
};
|
||||
new_excerpts.push(reused_excerpt, ());
|
||||
to_insert.next();
|
||||
cursor.next();
|
||||
continue;
|
||||
@@ -609,11 +634,70 @@ impl MultiBuffer {
|
||||
ranges: new_ranges,
|
||||
});
|
||||
cx.notify();
|
||||
} else if primary_ranges_changed {
|
||||
cx.emit(Event::BufferRangesUpdated {
|
||||
buffer,
|
||||
path_key: path_key.clone(),
|
||||
ranges: new_ranges,
|
||||
});
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
added_new_excerpt
|
||||
}
|
||||
|
||||
pub fn ranges_grouped_by_excerpt_path(
|
||||
&self,
|
||||
ranges: &[Range<Anchor>],
|
||||
) -> Vec<(PathKey, Range<usize>)> {
|
||||
debug_assert!(
|
||||
ranges
|
||||
.iter()
|
||||
.all(|range| matches!(range.start, Anchor::Excerpt(_))),
|
||||
"ranges must start with excerpt anchors, Min/Max starts stall the grouping"
|
||||
);
|
||||
let snapshot = self.snapshot.borrow();
|
||||
let mut grouped: Vec<(PathKey, Range<usize>)> = Vec::new();
|
||||
let mut next_range_index = 0;
|
||||
for excerpt in snapshot.excerpts.iter() {
|
||||
while ranges
|
||||
.get(next_range_index)
|
||||
.is_some_and(|range| match &range.start {
|
||||
Anchor::Excerpt(anchor) => {
|
||||
anchor.path != excerpt.path_key_index
|
||||
&& snapshot
|
||||
.path_keys
|
||||
.get_index(anchor.path.0 as usize)
|
||||
.is_some_and(|path| path < &excerpt.path_key)
|
||||
}
|
||||
Anchor::Min | Anchor::Max => false,
|
||||
})
|
||||
{
|
||||
next_range_index += 1;
|
||||
}
|
||||
let span_start = match grouped.last() {
|
||||
Some((last_path, span)) if *last_path == excerpt.path_key => span.start,
|
||||
_ => next_range_index,
|
||||
};
|
||||
while ranges
|
||||
.get(next_range_index)
|
||||
.is_some_and(|range| match &range.start {
|
||||
Anchor::Excerpt(anchor) => anchor.path == excerpt.path_key_index,
|
||||
Anchor::Min | Anchor::Max => false,
|
||||
})
|
||||
{
|
||||
next_range_index += 1;
|
||||
}
|
||||
match grouped.last_mut() {
|
||||
Some((last_path, span)) if *last_path == excerpt.path_key => {
|
||||
span.end = next_range_index;
|
||||
}
|
||||
_ => grouped.push((excerpt.path_key.clone(), span_start..next_range_index)),
|
||||
}
|
||||
}
|
||||
grouped
|
||||
}
|
||||
|
||||
pub fn remove_excerpts_for_buffer(&mut self, buffer: BufferId, cx: &mut Context<Self>) {
|
||||
let snapshot = self.sync_mut(cx);
|
||||
let Some(path) = snapshot.path_for_buffer(buffer).cloned() else {
|
||||
@@ -622,6 +706,108 @@ impl MultiBuffer {
|
||||
self.remove_excerpts(path, cx);
|
||||
}
|
||||
|
||||
pub fn remove_excerpts_for_paths(&mut self, paths: Vec<PathKey>, cx: &mut Context<Self>) {
|
||||
debug_assert!(paths.is_sorted(), "paths must be in ascending order");
|
||||
assert_eq!(self.history.transaction_depth(), 0);
|
||||
self.sync_mut(cx);
|
||||
|
||||
let mut snapshot = self.snapshot.get_mut();
|
||||
let old_len = snapshot.excerpts.summary().len();
|
||||
let mut cursor = snapshot
|
||||
.excerpts
|
||||
.cursor::<Dimensions<PathKey, ExcerptOffset>>(());
|
||||
let mut new_excerpts = SumTree::new(());
|
||||
let mut excerpt_edits: Vec<Edit<ExcerptOffset>> = Vec::new();
|
||||
let mut removed_buffer_ids = Vec::new();
|
||||
|
||||
for path in &paths {
|
||||
new_excerpts.append(cursor.slice(path, Bias::Left), ());
|
||||
let Some(excerpt) = cursor.item() else {
|
||||
break;
|
||||
};
|
||||
if excerpt.path_key != *path {
|
||||
continue;
|
||||
}
|
||||
removed_buffer_ids.push(excerpt.buffer_id);
|
||||
let edit_start = cursor.position.1;
|
||||
cursor.seek_forward(path, Bias::Right);
|
||||
let edit_end = cursor.position.1;
|
||||
match excerpt_edits.last_mut() {
|
||||
Some(last_edit) if last_edit.old.end == edit_start => {
|
||||
last_edit.old.end = edit_end;
|
||||
}
|
||||
_ => {
|
||||
let new_position = new_excerpts.summary().len();
|
||||
excerpt_edits.push(Edit {
|
||||
old: edit_start..edit_end,
|
||||
new: new_position..new_position,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
let suffix = cursor.suffix();
|
||||
let changed_trailing_excerpt = suffix.is_empty()
|
||||
&& excerpt_edits
|
||||
.last()
|
||||
.is_some_and(|last_edit| last_edit.old.end == old_len);
|
||||
new_excerpts.append(suffix, ());
|
||||
drop(cursor);
|
||||
|
||||
if removed_buffer_ids.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
for buffer_id in &removed_buffer_ids {
|
||||
snapshot.buffers.remove(buffer_id);
|
||||
remove_diff_state(&mut snapshot.diffs, *buffer_id);
|
||||
self.buffers.remove(buffer_id);
|
||||
self.diffs.remove(buffer_id);
|
||||
}
|
||||
cx.emit(Event::BuffersRemoved { removed_buffer_ids });
|
||||
|
||||
if changed_trailing_excerpt {
|
||||
snapshot.trailing_excerpt_update_count += 1;
|
||||
new_excerpts.update_last(
|
||||
|excerpt| {
|
||||
if excerpt.has_trailing_newline {
|
||||
excerpt.has_trailing_newline = false;
|
||||
if let Some(last_edit) = excerpt_edits.last_mut() {
|
||||
last_edit.old.start.0.0 = last_edit
|
||||
.old
|
||||
.start
|
||||
.0
|
||||
.0
|
||||
.checked_sub(1)
|
||||
.expect("should have at least one excerpt");
|
||||
last_edit.new.start.0.0 = last_edit
|
||||
.new
|
||||
.start
|
||||
.0
|
||||
.0
|
||||
.checked_sub(1)
|
||||
.expect("should have at least one excerpt");
|
||||
last_edit.new.end = last_edit.new.start;
|
||||
}
|
||||
}
|
||||
},
|
||||
(),
|
||||
);
|
||||
}
|
||||
|
||||
snapshot.excerpts = new_excerpts;
|
||||
|
||||
let edits =
|
||||
Self::sync_diff_transforms(&mut snapshot, excerpt_edits, DiffChangeKind::BufferEdited);
|
||||
if !edits.is_empty() {
|
||||
self.subscriptions.publish(edits);
|
||||
}
|
||||
cx.emit(Event::Edited {
|
||||
edited_buffer: None,
|
||||
source: BufferEditSource::User,
|
||||
});
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn remove_excerpts(&mut self, path: PathKey, cx: &mut Context<Self>) {
|
||||
assert_eq!(self.history.transaction_depth(), 0);
|
||||
self.sync_mut(cx);
|
||||
|
||||
Reference in new issue
Block a user