Detailed changes
@@ -2616,7 +2616,7 @@ impl AcpThread {
text_diff(old_text.as_str(), &content)
.into_iter()
.map(|(range, replacement)| {
- (snapshot.anchor_range_around(range), replacement)
+ (snapshot.anchor_range_inside(range), replacement)
})
.collect::<Vec<_>>()
})
@@ -191,7 +191,7 @@ impl Diff {
}
pub fn has_revealed_range(&self, cx: &App) -> bool {
- self.multibuffer().read(cx).paths().next().is_some()
+ !self.multibuffer().read(cx).is_empty()
}
pub fn needs_update(&self, old_text: &str, new_text: &str, cx: &App) -> bool {
@@ -738,6 +738,7 @@ impl ActionLog {
let task = if let Some(existing_file_content) = existing_file_content {
// Capture the agent's content before restoring existing file content
let agent_content = buffer.read(cx).text();
+ let buffer_id = buffer.read(cx).remote_id();
buffer.update(cx, |buffer, cx| {
buffer.start_transaction();
@@ -750,7 +751,10 @@ impl ActionLog {
undo_info = Some(PerBufferUndo {
buffer: buffer.downgrade(),
- edits_to_restore: vec![(Anchor::MIN..Anchor::MAX, agent_content)],
+ edits_to_restore: vec![(
+ Anchor::min_for_buffer(buffer_id)..Anchor::max_for_buffer(buffer_id),
+ agent_content,
+ )],
status: UndoBufferStatus::Created {
had_existing_content: true,
},
@@ -990,8 +994,8 @@ impl ActionLog {
let mut valid_edits = Vec::new();
for (anchor_range, text_to_restore) in per_buffer_undo.edits_to_restore {
- if anchor_range.start.buffer_id == Some(buffer.remote_id())
- && anchor_range.end.buffer_id == Some(buffer.remote_id())
+ if anchor_range.start.buffer_id == buffer.remote_id()
+ && anchor_range.end.buffer_id == buffer.remote_id()
{
valid_edits.push((anchor_range, text_to_restore));
}
@@ -374,13 +374,13 @@ impl EditAgent {
buffer.edit(edits.iter().cloned(), None, cx);
let max_edit_end = buffer
.summaries_for_anchors::<Point, _>(
- edits.iter().map(|(range, _)| &range.end),
+ edits.iter().map(|(range, _)| range.end),
)
.max()
.unwrap();
let min_edit_start = buffer
.summaries_for_anchors::<Point, _>(
- edits.iter().map(|(range, _)| &range.start),
+ edits.iter().map(|(range, _)| range.start),
)
.min()
.unwrap();
@@ -760,7 +760,7 @@ impl EditSession {
{
if let Some(match_range) = matcher.push(chunk, None) {
let anchor_range = self.buffer.read_with(cx, |buffer, _cx| {
- buffer.anchor_range_between(match_range.clone())
+ buffer.anchor_range_outside(match_range.clone())
});
self.diff
.update(cx, |diff, cx| diff.reveal_range(anchor_range, cx));
@@ -795,7 +795,7 @@ impl EditSession {
let anchor_range = self
.buffer
- .read_with(cx, |buffer, _cx| buffer.anchor_range_between(range.clone()));
+ .read_with(cx, |buffer, _cx| buffer.anchor_range_outside(range.clone()));
self.diff
.update(cx, |diff, cx| diff.reveal_range(anchor_range, cx));
@@ -953,7 +953,7 @@ fn apply_char_operations(
}
CharOperation::Delete { bytes } => {
let delete_end = *edit_cursor + bytes;
- let anchor_range = snapshot.anchor_range_around(*edit_cursor..delete_end);
+ let anchor_range = snapshot.anchor_range_inside(*edit_cursor..delete_end);
agent_edit_buffer(&buffer, [(anchor_range, "")], action_log, cx);
*edit_cursor = delete_end;
}
@@ -138,11 +138,12 @@ impl AgentDiffPane {
path_a.cmp(&path_b)
});
- let mut paths_to_delete = self
+ let mut buffers_to_delete = self
.multibuffer
.read(cx)
- .paths()
- .cloned()
+ .snapshot(cx)
+ .excerpts()
+ .map(|excerpt| excerpt.context.start.buffer_id)
.collect::<HashSet<_>>();
for (buffer, diff_handle) in sorted_buffers {
@@ -151,7 +152,7 @@ impl AgentDiffPane {
}
let path_key = PathKey::for_buffer(&buffer, cx);
- paths_to_delete.remove(&path_key);
+ buffers_to_delete.remove(&buffer.read(cx).remote_id());
let snapshot = buffer.read(cx).snapshot();
@@ -168,7 +169,7 @@ impl AgentDiffPane {
let (was_empty, is_excerpt_newly_added) =
self.multibuffer.update(cx, |multibuffer, cx| {
let was_empty = multibuffer.is_empty();
- let (_, is_excerpt_newly_added) = multibuffer.set_excerpts_for_path(
+ let is_excerpt_newly_added = multibuffer.update_excerpts_for_path(
path_key.clone(),
buffer.clone(),
diff_hunk_ranges,
@@ -183,13 +184,13 @@ impl AgentDiffPane {
if was_empty {
let first_hunk = editor
.diff_hunks_in_ranges(
- &[editor::Anchor::min()..editor::Anchor::max()],
+ &[editor::Anchor::Min..editor::Anchor::Max],
&self.multibuffer.read(cx).read(cx),
)
.next();
if let Some(first_hunk) = first_hunk {
- let first_hunk_start = first_hunk.multi_buffer_range().start;
+ let first_hunk_start = first_hunk.multi_buffer_range.start;
editor.change_selections(Default::default(), window, cx, |selections| {
selections.select_anchor_ranges([first_hunk_start..first_hunk_start]);
})
@@ -208,8 +209,8 @@ impl AgentDiffPane {
}
self.multibuffer.update(cx, |multibuffer, cx| {
- for path in paths_to_delete {
- multibuffer.remove_excerpts_for_path(path, cx);
+ for buffer_id in buffers_to_delete {
+ multibuffer.remove_excerpts_for_buffer(buffer_id, cx);
}
});
@@ -239,13 +240,13 @@ impl AgentDiffPane {
self.editor.update(cx, |editor, cx| {
let first_hunk = editor
.diff_hunks_in_ranges(
- &[position..editor::Anchor::max()],
+ &[position..editor::Anchor::Max],
&self.multibuffer.read(cx).read(cx),
)
.next();
if let Some(first_hunk) = first_hunk {
- let first_hunk_start = first_hunk.multi_buffer_range().start;
+ let first_hunk_start = first_hunk.multi_buffer_range.start;
editor.change_selections(Default::default(), window, cx, |selections| {
selections.select_anchor_ranges([first_hunk_start..first_hunk_start]);
})
@@ -282,7 +283,7 @@ impl AgentDiffPane {
editor,
&snapshot,
&self.thread,
- vec![editor::Anchor::min()..editor::Anchor::max()],
+ vec![editor::Anchor::Min..editor::Anchor::Max],
self.workspace.clone(),
window,
cx,
@@ -451,20 +452,20 @@ fn update_editor_selection(
diff_hunks
.last()
.and_then(|last_kept_hunk| {
- let last_kept_hunk_end = last_kept_hunk.multi_buffer_range().end;
+ let last_kept_hunk_end = last_kept_hunk.multi_buffer_range.end;
editor
.diff_hunks_in_ranges(
- &[last_kept_hunk_end..editor::Anchor::max()],
+ &[last_kept_hunk_end..editor::Anchor::Max],
buffer_snapshot,
)
.nth(1)
})
.or_else(|| {
let first_kept_hunk = diff_hunks.first()?;
- let first_kept_hunk_start = first_kept_hunk.multi_buffer_range().start;
+ let first_kept_hunk_start = first_kept_hunk.multi_buffer_range.start;
editor
.diff_hunks_in_ranges(
- &[editor::Anchor::min()..first_kept_hunk_start],
+ &[editor::Anchor::Min..first_kept_hunk_start],
buffer_snapshot,
)
.next()
@@ -473,7 +474,7 @@ fn update_editor_selection(
if let Some(target_hunk) = target_hunk {
editor.change_selections(Default::default(), window, cx, |selections| {
- let next_hunk_start = target_hunk.multi_buffer_range().start;
+ let next_hunk_start = target_hunk.multi_buffer_range.start;
selections.select_anchor_ranges([next_hunk_start..next_hunk_start]);
})
}
@@ -1567,7 +1568,7 @@ impl AgentDiff {
editor.update(cx, |editor, cx| {
let snapshot = multibuffer.read(cx).snapshot(cx);
if let Some(first_hunk) = snapshot.diff_hunks().next() {
- let first_hunk_start = first_hunk.multi_buffer_range().start;
+ let first_hunk_start = first_hunk.multi_buffer_range.start;
editor.change_selections(
SelectionEffects::scroll(Autoscroll::center()),
@@ -1648,7 +1649,7 @@ impl AgentDiff {
editor,
&snapshot,
thread,
- vec![editor::Anchor::min()..editor::Anchor::max()],
+ vec![editor::Anchor::Min..editor::Anchor::Max],
window,
cx,
);
@@ -1669,7 +1670,7 @@ impl AgentDiff {
editor,
&snapshot,
thread,
- vec![editor::Anchor::min()..editor::Anchor::max()],
+ vec![editor::Anchor::Min..editor::Anchor::Max],
workspace.clone(),
window,
cx,
@@ -303,7 +303,7 @@ impl CodegenAlternative {
let snapshot = buffer.read(cx).snapshot(cx);
let (old_buffer, _, _) = snapshot
- .range_to_buffer_ranges(range.start..=range.end)
+ .range_to_buffer_ranges(range.start..range.end)
.pop()
.unwrap();
let old_buffer = cx.new(|cx| {
@@ -684,7 +684,7 @@ impl CodegenAlternative {
let language_name = {
let multibuffer = self.buffer.read(cx);
let snapshot = multibuffer.snapshot(cx);
- let ranges = snapshot.range_to_buffer_ranges(self.range.start..=self.range.end);
+ let ranges = snapshot.range_to_buffer_ranges(self.range.start..self.range.end);
ranges
.first()
.and_then(|(buffer, _, _)| buffer.language())
@@ -9,9 +9,7 @@ use crate::ThreadHistory;
use acp_thread::MentionUri;
use agent_client_protocol as acp;
use anyhow::Result;
-use editor::{
- CompletionProvider, Editor, ExcerptId, code_context_menus::COMPLETION_MENU_MAX_WIDTH,
-};
+use editor::{CompletionProvider, Editor, code_context_menus::COMPLETION_MENU_MAX_WIDTH};
use futures::FutureExt as _;
use fuzzy::{PathMatch, StringMatch, StringMatchCandidate};
use gpui::{App, BackgroundExecutor, Entity, SharedString, Task, WeakEntity};
@@ -621,7 +619,7 @@ impl<T: PromptCompletionProviderDelegate> PromptCompletionProvider<T> {
for (terminal_text, terminal_range) in terminal_ranges {
let snapshot = editor.read(cx).buffer().read(cx).snapshot(cx);
let Some(start) =
- snapshot.as_singleton_anchor(source_range.start)
+ snapshot.anchor_in_excerpt(source_range.start)
else {
return;
};
@@ -1235,7 +1233,6 @@ impl<T: PromptCompletionProviderDelegate> PromptCompletionProvider<T> {
impl<T: PromptCompletionProviderDelegate> CompletionProvider for PromptCompletionProvider<T> {
fn completions(
&self,
- _excerpt_id: ExcerptId,
buffer: &Entity<Buffer>,
buffer_position: Anchor,
_trigger: CompletionContext,
@@ -7126,17 +7126,10 @@ impl ThreadView {
};
active_editor.update_in(cx, |editor, window, cx| {
- let singleton = editor
- .buffer()
- .read(cx)
- .read(cx)
- .as_singleton()
- .map(|(a, b, _)| (a, b));
- if let Some((excerpt_id, buffer_id)) = singleton
- && let Some(agent_buffer) = agent_location.buffer.upgrade()
- && agent_buffer.read(cx).remote_id() == buffer_id
+ let snapshot = editor.buffer().read(cx).snapshot(cx);
+ if snapshot.as_singleton().is_some()
+ && let Some(anchor) = snapshot.anchor_in_excerpt(agent_location.position)
{
- let anchor = editor::Anchor::in_buffer(excerpt_id, agent_location.position);
editor.change_selections(Default::default(), window, cx, |selections| {
selections.select_anchor_ranges([anchor..anchor]);
})
@@ -27,8 +27,8 @@ use editor::RowExt;
use editor::SelectionEffects;
use editor::scroll::ScrollOffset;
use editor::{
- Anchor, AnchorRangeExt, CodeActionProvider, Editor, EditorEvent, ExcerptId, HighlightKey,
- MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint,
+ Anchor, AnchorRangeExt, CodeActionProvider, Editor, EditorEvent, HighlightKey, MultiBuffer,
+ MultiBufferSnapshot, ToOffset as _, ToPoint,
actions::SelectAll,
display_map::{
BlockContext, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, EditorMargins,
@@ -443,15 +443,17 @@ impl InlineAssistant {
let newest_selection = newest_selection.unwrap();
let mut codegen_ranges = Vec::new();
- for (buffer, buffer_range, excerpt_id) in
- snapshot.ranges_to_buffer_ranges(selections.iter().map(|selection| {
- snapshot.anchor_before(selection.start)..snapshot.anchor_after(selection.end)
- }))
+ for (buffer, buffer_range, _) in selections
+ .iter()
+ .flat_map(|selection| snapshot.range_to_buffer_ranges(selection.start..selection.end))
{
- let anchor_range = Anchor::range_in_buffer(
- excerpt_id,
- buffer.anchor_before(buffer_range.start)..buffer.anchor_after(buffer_range.end),
- );
+ let (Some(start), Some(end)) = (
+ snapshot.anchor_in_buffer(buffer.anchor_before(buffer_range.start)),
+ snapshot.anchor_in_buffer(buffer.anchor_after(buffer_range.end)),
+ ) else {
+ continue;
+ };
+ let anchor_range = start..end;
codegen_ranges.push(anchor_range);
@@ -982,8 +984,7 @@ impl InlineAssistant {
match event {
EditorEvent::Edited { transaction_id } => {
let buffer = editor.read(cx).buffer().read(cx);
- let edited_ranges =
- buffer.edited_ranges_for_transaction::<MultiBufferOffset>(*transaction_id, cx);
+ let edited_ranges = buffer.edited_ranges_for_transaction(*transaction_id, cx);
let snapshot = buffer.snapshot(cx);
for assist_id in editor_assists.assist_ids.clone() {
@@ -1089,7 +1090,7 @@ impl InlineAssistant {
let multibuffer = editor.read(cx).buffer().read(cx);
let snapshot = multibuffer.snapshot(cx);
let ranges =
- snapshot.range_to_buffer_ranges(assist.range.start..=assist.range.end);
+ snapshot.range_to_buffer_ranges(assist.range.start..assist.range.end);
ranges
.first()
.and_then(|(buffer, _, _)| buffer.language())
@@ -1496,10 +1497,10 @@ impl InlineAssistant {
let mut new_blocks = Vec::new();
for (new_row, old_row_range) in deleted_row_ranges {
- let (_, start, _) = old_snapshot
+ let (_, start) = old_snapshot
.point_to_buffer_point(Point::new(*old_row_range.start(), 0))
.unwrap();
- let (_, end, _) = old_snapshot
+ let (_, end) = old_snapshot
.point_to_buffer_point(Point::new(
*old_row_range.end(),
old_snapshot.line_len(MultiBufferRow(*old_row_range.end())),
@@ -1530,7 +1531,7 @@ impl InlineAssistant {
editor.set_read_only(true);
editor.set_show_edit_predictions(Some(false), window, cx);
editor.highlight_rows::<DeletedLines>(
- Anchor::min()..Anchor::max(),
+ Anchor::Min..Anchor::Max,
cx.theme().status().deleted_background,
Default::default(),
cx,
@@ -1938,9 +1939,8 @@ impl CodeActionProvider for AssistantCodeActionProvider {
fn apply_code_action(
&self,
- buffer: Entity<Buffer>,
+ _buffer: Entity<Buffer>,
action: CodeAction,
- excerpt_id: ExcerptId,
_push_to_history: bool,
window: &mut Window,
cx: &mut App,
@@ -1970,31 +1970,8 @@ impl CodeActionProvider for AssistantCodeActionProvider {
let range = editor
.update(cx, |editor, cx| {
editor.buffer().update(cx, |multibuffer, cx| {
- let buffer = buffer.read(cx);
- let multibuffer_snapshot = multibuffer.read(cx);
-
- let old_context_range =
- multibuffer_snapshot.context_range_for_excerpt(excerpt_id)?;
- let mut new_context_range = old_context_range.clone();
- if action
- .range
- .start
- .cmp(&old_context_range.start, buffer)
- .is_lt()
- {
- new_context_range.start = action.range.start;
- }
- if action.range.end.cmp(&old_context_range.end, buffer).is_gt() {
- new_context_range.end = action.range.end;
- }
- drop(multibuffer_snapshot);
-
- if new_context_range != old_context_range {
- multibuffer.resize_excerpt(excerpt_id, new_context_range, cx);
- }
-
let multibuffer_snapshot = multibuffer.read(cx);
- multibuffer_snapshot.anchor_range_in_excerpt(excerpt_id, action.range)
+ multibuffer_snapshot.buffer_anchor_range_to_anchor_range(action.range)
})
})
.context("invalid range")?;
@@ -6,7 +6,7 @@ use agent_servers::{AgentServer, AgentServerDelegate};
use anyhow::{Context as _, Result, anyhow};
use collections::{HashMap, HashSet};
use editor::{
- Anchor, Editor, EditorSnapshot, ExcerptId, FoldPlaceholder, ToOffset,
+ Anchor, Editor, EditorSnapshot, FoldPlaceholder, ToOffset,
display_map::{Crease, CreaseId, CreaseMetadata, FoldId},
scroll::Autoscroll,
};
@@ -204,10 +204,9 @@ impl MentionSet {
};
let snapshot = editor.update(cx, |editor, cx| editor.snapshot(window, cx));
- let Some(start_anchor) = snapshot.buffer_snapshot().as_singleton_anchor(start) else {
+ let Some(start_anchor) = snapshot.buffer_snapshot().anchor_in_excerpt(start) else {
return Task::ready(());
};
- let excerpt_id = start_anchor.excerpt_id;
let end_anchor = snapshot.buffer_snapshot().anchor_before(
start_anchor.to_offset(&snapshot.buffer_snapshot()) + content_len + 1usize,
);
@@ -234,7 +233,6 @@ impl MentionSet {
})
.shared();
insert_crease_for_mention(
- excerpt_id,
start,
content_len,
mention_uri.name().into(),
@@ -249,7 +247,6 @@ impl MentionSet {
)
} else {
insert_crease_for_mention(
- excerpt_id,
start,
content_len,
crease_text,
@@ -468,7 +465,7 @@ impl MentionSet {
};
let snapshot = editor.read(cx).buffer().read(cx).snapshot(cx);
- let Some(start) = snapshot.as_singleton_anchor(source_range.start) else {
+ let Some(start) = snapshot.anchor_in_excerpt(source_range.start) else {
return;
};
@@ -745,19 +742,17 @@ pub(crate) async fn insert_images_as_context(
let replacement_text = MentionUri::PastedImage.as_link().to_string();
for (image, name) in images {
- let Some((excerpt_id, text_anchor, multibuffer_anchor)) = editor
+ let Some((text_anchor, multibuffer_anchor)) = editor
.update_in(cx, |editor, window, cx| {
let snapshot = editor.snapshot(window, cx);
- let (excerpt_id, _, buffer_snapshot) =
- snapshot.buffer_snapshot().as_singleton().unwrap();
-
- let cursor_anchor = editor.selections.newest_anchor().start.text_anchor;
- let text_anchor = cursor_anchor.bias_left(&buffer_snapshot);
- let multibuffer_anchor = snapshot
+ let (cursor_anchor, buffer_snapshot) = snapshot
.buffer_snapshot()
- .anchor_in_excerpt(excerpt_id, text_anchor);
+ .anchor_to_buffer_anchor(editor.selections.newest_anchor().start)
+ .unwrap();
+ let text_anchor = cursor_anchor.bias_left(buffer_snapshot);
+ let multibuffer_anchor = snapshot.buffer_snapshot().anchor_in_excerpt(text_anchor);
editor.insert(&format!("{replacement_text} "), window, cx);
- (excerpt_id, text_anchor, multibuffer_anchor)
+ (text_anchor, multibuffer_anchor)
})
.ok()
else {
@@ -775,7 +770,6 @@ pub(crate) async fn insert_images_as_context(
let image = Arc::new(image);
let Ok(Some((crease_id, tx))) = cx.update(|window, cx| {
insert_crease_for_mention(
- excerpt_id,
text_anchor,
content_len,
name.clone(),
@@ -909,7 +903,6 @@ pub(crate) fn paste_images_as_context(
}
pub(crate) fn insert_crease_for_mention(
- excerpt_id: ExcerptId,
anchor: text::Anchor,
content_len: usize,
crease_label: SharedString,
@@ -927,7 +920,7 @@ pub(crate) fn insert_crease_for_mention(
let crease_id = editor.update(cx, |editor, cx| {
let snapshot = editor.buffer().read(cx).snapshot(cx);
- let start = snapshot.anchor_in_excerpt(excerpt_id, anchor)?;
+ let start = snapshot.anchor_in_excerpt(anchor)?;
let start = start.bias_right(&snapshot);
let end = snapshot.anchor_before(start.to_offset(&snapshot) + content_len);
@@ -203,12 +203,10 @@ fn insert_mention_for_project_path(
MentionInsertPosition::AtCursor => editor.update(cx, |editor, cx| {
let buffer = editor.buffer().read(cx);
let snapshot = buffer.snapshot(cx);
- let (_, _, buffer_snapshot) = snapshot.as_singleton()?;
- let text_anchor = editor
- .selections
- .newest_anchor()
- .start
- .text_anchor
+ let buffer_snapshot = snapshot.as_singleton()?;
+ let text_anchor = snapshot
+ .anchor_to_buffer_anchor(editor.selections.newest_anchor().start)?
+ .0
.bias_left(&buffer_snapshot);
editor.insert(&mention_text, window, cx);
@@ -224,7 +222,7 @@ fn insert_mention_for_project_path(
editor.update(cx, |editor, cx| {
editor.edit(
[(
- multi_buffer::Anchor::max()..multi_buffer::Anchor::max(),
+ multi_buffer::Anchor::Max..multi_buffer::Anchor::Max,
new_text,
)],
cx,
@@ -603,7 +601,7 @@ impl MessageEditor {
COMMAND_HINT_INLAY_ID,
hint_pos,
&InlayHint {
- position: hint_pos.text_anchor,
+ position: snapshot.anchor_to_buffer_anchor(hint_pos)?.0,
label: InlayHintLabel::String(hint),
kind: Some(InlayHintKind::Parameter),
padding_left: false,
@@ -640,12 +638,11 @@ impl MessageEditor {
let start = self.editor.update(cx, |editor, cx| {
editor.set_text(content, window, cx);
- editor
- .buffer()
- .read(cx)
- .snapshot(cx)
- .anchor_before(Point::zero())
- .text_anchor
+ let snapshot = editor.buffer().read(cx).snapshot(cx);
+ snapshot
+ .anchor_to_buffer_anchor(snapshot.anchor_before(Point::zero()))
+ .unwrap()
+ .0
});
let supports_images = self.session_capabilities.read().supports_images();
@@ -999,13 +996,10 @@ impl MessageEditor {
if should_insert_creases && let Some(selections) = editor_clipboard_selections {
cx.stop_propagation();
- let insertion_target = self
- .editor
- .read(cx)
- .selections
- .newest_anchor()
- .start
- .text_anchor;
+ let snapshot = self.editor.read(cx).buffer().read(cx).snapshot(cx);
+ let (insertion_target, _) = snapshot
+ .anchor_to_buffer_anchor(self.editor.read(cx).selections.newest_anchor().start)
+ .unwrap();
let project = workspace.read(cx).project().clone();
for selection in selections {
@@ -1021,21 +1015,19 @@ impl MessageEditor {
};
let mention_text = mention_uri.as_link().to_string();
- let (excerpt_id, text_anchor, content_len) =
- self.editor.update(cx, |editor, cx| {
- let buffer = editor.buffer().read(cx);
- let snapshot = buffer.snapshot(cx);
- let (excerpt_id, _, buffer_snapshot) = snapshot.as_singleton().unwrap();
- let text_anchor = insertion_target.bias_left(&buffer_snapshot);
+ let (text_anchor, content_len) = self.editor.update(cx, |editor, cx| {
+ let buffer = editor.buffer().read(cx);
+ let snapshot = buffer.snapshot(cx);
+ let buffer_snapshot = snapshot.as_singleton().unwrap();
+ let text_anchor = insertion_target.bias_left(&buffer_snapshot);
- editor.insert(&mention_text, window, cx);
- editor.insert(" ", window, cx);
+ editor.insert(&mention_text, window, cx);
+ editor.insert(" ", window, cx);
- (excerpt_id, text_anchor, mention_text.len())
- });
+ (text_anchor, mention_text.len())
+ });
let Some((crease_id, tx)) = insert_crease_for_mention(
- excerpt_id,
text_anchor,
content_len,
crease_text.into(),
@@ -1145,8 +1137,7 @@ impl MessageEditor {
for (anchor, content_len, mention_uri) in all_mentions {
let Some((crease_id, tx)) = insert_crease_for_mention(
- anchor.excerpt_id,
- anchor.text_anchor,
+ snapshot.anchor_to_buffer_anchor(anchor).unwrap().0,
content_len,
mention_uri.name().into(),
mention_uri.icon_path(cx),
@@ -1339,25 +1330,23 @@ impl MessageEditor {
};
let mention_text = mention_uri.as_link().to_string();
- let (excerpt_id, text_anchor, content_len) = editor.update(cx, |editor, cx| {
+ let (text_anchor, content_len) = editor.update(cx, |editor, cx| {
let buffer = editor.buffer().read(cx);
let snapshot = buffer.snapshot(cx);
- let (excerpt_id, _, buffer_snapshot) = snapshot.as_singleton().unwrap();
- let text_anchor = editor
- .selections
- .newest_anchor()
- .start
- .text_anchor
+ let buffer_snapshot = snapshot.as_singleton().unwrap();
+ let text_anchor = snapshot
+ .anchor_to_buffer_anchor(editor.selections.newest_anchor().start)
+ .unwrap()
+ .0
.bias_left(&buffer_snapshot);
editor.insert(&mention_text, window, cx);
editor.insert(" ", window, cx);
- (excerpt_id, text_anchor, mention_text.len())
+ (text_anchor, mention_text.len())
});
let Some((crease_id, tx)) = insert_crease_for_mention(
- excerpt_id,
text_anchor,
content_len,
mention_uri.name().into(),
@@ -1700,8 +1689,7 @@ impl MessageEditor {
let adjusted_start = insertion_start + range.start;
let anchor = snapshot.anchor_before(MultiBufferOffset(adjusted_start));
let Some((crease_id, tx)) = insert_crease_for_mention(
- anchor.excerpt_id,
- anchor.text_anchor,
+ snapshot.anchor_to_buffer_anchor(anchor).unwrap().0,
range.end - range.start,
mention_uri.name().into(),
mention_uri.icon_path(cx),
@@ -2077,23 +2065,13 @@ mod tests {
cx.run_until_parked();
- let excerpt_id = editor.update(cx, |editor, cx| {
- editor
- .buffer()
- .read(cx)
- .excerpt_ids()
- .into_iter()
- .next()
- .unwrap()
- });
let completions = editor.update_in(cx, |editor, window, cx| {
editor.set_text("Hello @file ", window, cx);
let buffer = editor.buffer().read(cx).as_singleton().unwrap();
let completion_provider = editor.completion_provider().unwrap();
completion_provider.completions(
- excerpt_id,
&buffer,
- text::Anchor::MAX,
+ text::Anchor::max_for_buffer(buffer.read(cx).remote_id()),
CompletionContext {
trigger_kind: CompletionTriggerKind::TRIGGER_CHARACTER,
trigger_character: Some("@".into()),
@@ -2114,7 +2092,7 @@ mod tests {
editor.update_in(cx, |editor, window, cx| {
let snapshot = editor.buffer().read(cx).snapshot(cx);
let range = snapshot
- .anchor_range_in_excerpt(excerpt_id, completion.replace_range)
+ .buffer_anchor_range_to_anchor_range(completion.replace_range)
.unwrap();
editor.edit([(range, completion.new_text)], cx);
(completion.confirm.unwrap())(CompletionIntent::Complete, window, cx);
@@ -171,9 +171,9 @@ impl sum_tree::Item for PendingHunk {
impl sum_tree::Summary for DiffHunkSummary {
type Context<'a> = &'a text::BufferSnapshot;
- fn zero(_cx: Self::Context<'_>) -> Self {
+ fn zero(buffer: &text::BufferSnapshot) -> Self {
DiffHunkSummary {
- buffer_range: Anchor::MIN..Anchor::MIN,
+ buffer_range: Anchor::min_min_range_for_buffer(buffer.remote_id()),
diff_base_byte_range: 0..0,
added_rows: 0,
removed_rows: 0,
@@ -248,6 +248,10 @@ impl BufferDiffSnapshot {
buffer_diff.update(cx, |buffer_diff, cx| buffer_diff.snapshot(cx))
}
+ pub fn buffer_id(&self) -> BufferId {
+ self.inner.buffer_snapshot.remote_id()
+ }
+
pub fn is_empty(&self) -> bool {
self.inner.hunks.is_empty()
}
@@ -953,7 +957,7 @@ impl BufferDiffInner<language::BufferSnapshot> {
.flat_map(move |hunk| {
[
(
- &hunk.buffer_range.start,
+ hunk.buffer_range.start,
(
hunk.buffer_range.start,
hunk.diff_base_byte_range.start,
@@ -961,7 +965,7 @@ impl BufferDiffInner<language::BufferSnapshot> {
),
),
(
- &hunk.buffer_range.end,
+ hunk.buffer_range.end,
(hunk.buffer_range.end, hunk.diff_base_byte_range.end, hunk),
),
]
@@ -1653,7 +1657,7 @@ impl BufferDiff {
) {
let hunks = self
.snapshot(cx)
- .hunks_intersecting_range(Anchor::MIN..Anchor::MAX, buffer)
+ .hunks_intersecting_range(Anchor::min_max_range_for_buffer(buffer.remote_id()), buffer)
.collect::<Vec<_>>();
let Some(secondary) = self.secondary_diff.clone() else {
return;
@@ -21,7 +21,7 @@ use language::LanguageRegistry;
use livekit::{LocalTrackPublication, ParticipantIdentity, RoomEvent};
use livekit_client::{self as livekit, AudioStream, TrackSid};
use postage::{sink::Sink, stream::Stream, watch};
-use project::Project;
+use project::{CURRENT_PROJECT_FEATURES, Project};
use settings::Settings as _;
use std::sync::atomic::AtomicU64;
use std::{future::Future, mem, rc::Rc, sync::Arc, time::Duration, time::Instant};
@@ -1237,6 +1237,10 @@ impl Room {
worktrees: project.read(cx).worktree_metadata_protos(cx),
is_ssh_project: project.read(cx).is_via_remote_server(),
windows_paths: Some(project.read(cx).path_style(cx) == PathStyle::Windows),
+ features: CURRENT_PROJECT_FEATURES
+ .iter()
+ .map(|s| s.to_string())
+ .collect(),
});
cx.spawn(async move |this, cx| {
@@ -2141,11 +2141,13 @@ mod tests {
project_id: 1,
committer_name: None,
committer_email: None,
+ features: Vec::new(),
});
server.send(proto::JoinProject {
project_id: 2,
committer_name: None,
committer_email: None,
+ features: Vec::new(),
});
done_rx1.recv().await.unwrap();
done_rx2.recv().await.unwrap();
@@ -48,7 +48,8 @@ CREATE TABLE "projects" (
"host_connection_id" INTEGER,
"host_connection_server_id" INTEGER REFERENCES servers (id) ON DELETE CASCADE,
"unregistered" BOOLEAN NOT NULL DEFAULT FALSE,
- "windows_paths" BOOLEAN NOT NULL DEFAULT FALSE
+ "windows_paths" BOOLEAN NOT NULL DEFAULT FALSE,
+ "features" TEXT NOT NULL DEFAULT ''
);
CREATE INDEX "index_projects_on_host_connection_server_id" ON "projects" ("host_connection_server_id");
@@ -332,7 +332,8 @@ CREATE TABLE public.projects (
room_id integer,
host_connection_id integer,
host_connection_server_id integer,
- windows_paths boolean DEFAULT false
+ windows_paths boolean DEFAULT false,
+ features text NOT NULL DEFAULT ''
);
CREATE SEQUENCE public.projects_id_seq
@@ -589,6 +589,7 @@ pub struct Project {
pub repositories: Vec<proto::UpdateRepository>,
pub language_servers: Vec<LanguageServer>,
pub path_style: PathStyle,
+ pub features: Vec<String>,
}
pub struct ProjectCollaborator {
@@ -34,6 +34,7 @@ impl Database {
worktrees: &[proto::WorktreeMetadata],
is_ssh_project: bool,
windows_paths: bool,
+ features: &[String],
) -> Result<TransactionGuard<(ProjectId, proto::Room)>> {
self.room_transaction(room_id, |tx| async move {
let participant = room_participant::Entity::find()
@@ -71,6 +72,7 @@ impl Database {
))),
id: ActiveValue::NotSet,
windows_paths: ActiveValue::set(windows_paths),
+ features: ActiveValue::set(serde_json::to_string(features).unwrap()),
}
.insert(&*tx)
.await?;
@@ -948,6 +950,7 @@ impl Database {
} else {
PathStyle::Posix
};
+ let features: Vec<String> = serde_json::from_str(&project.features).unwrap_or_default();
let project = Project {
id: project.id,
@@ -977,6 +980,7 @@ impl Database {
})
.collect(),
path_style,
+ features,
};
Ok((project, replica_id as ReplicaId))
}
@@ -13,6 +13,7 @@ pub struct Model {
pub host_connection_id: Option<i32>,
pub host_connection_server_id: Option<ServerId>,
pub windows_paths: bool,
+ pub features: String,
}
impl Model {
@@ -1775,6 +1775,7 @@ async fn share_project(
&request.worktrees,
request.is_ssh_project,
request.windows_paths.unwrap_or(false),
+ &request.features,
)
.await?;
response.send(proto::ShareProjectResponse {
@@ -1840,6 +1841,28 @@ async fn join_project(
tracing::info!(%project_id, "join project");
let db = session.db().await;
+ let project_model = db.get_project(project_id).await?;
+ let host_features: Vec<String> =
+ serde_json::from_str(&project_model.features).unwrap_or_default();
+ let guest_features: HashSet<_> = request.features.iter().collect();
+ let host_features_set: HashSet<_> = host_features.iter().collect();
+ if guest_features != host_features_set {
+ let host_connection_id = project_model.host_connection()?;
+ let mut pool = session.connection_pool().await;
+ let host_version = pool
+ .connection(host_connection_id)
+ .map(|c| c.zed_version.to_string());
+ let guest_version = pool
+ .connection(session.connection_id)
+ .map(|c| c.zed_version.to_string());
+ drop(pool);
+ Err(anyhow!(
+ "The host (v{}) and guest (v{}) are using incompatible versions of Zed. The peer with the older version must update to collaborate.",
+ host_version.as_deref().unwrap_or("unknown"),
+ guest_version.as_deref().unwrap_or("unknown"),
+ ))?;
+ }
+
let (project, replica_id) = &mut *db
.join_project(
project_id,
@@ -1850,6 +1873,7 @@ async fn join_project(
)
.await?;
drop(db);
+
tracing::info!(%project_id, "join remote project");
let collaborators = project
.collaborators
@@ -1909,6 +1933,7 @@ async fn join_project(
language_server_capabilities,
role: project.role.into(),
windows_paths: project.path_style == PathStyle::Windows,
+ features: project.features.clone(),
})?;
for (worktree_id, worktree) in mem::take(&mut project.worktrees) {
@@ -313,7 +313,7 @@ fn assert_remote_selections(
let snapshot = editor.snapshot(window, cx);
let hub = editor.collaboration_hub().unwrap();
let collaborators = hub.collaborators(cx);
- let range = Anchor::min()..Anchor::max();
+ let range = Anchor::Min..Anchor::Max;
let remote_selections = snapshot
.remote_selections_in_range(&range, hub, cx)
.map(|s| {
@@ -350,20 +350,41 @@ async fn test_project_count(db: &Arc<Database>) {
.unwrap();
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0);
- db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[], false, false)
- .await
- .unwrap();
+ db.share_project(
+ room_id,
+ ConnectionId { owner_id, id: 1 },
+ &[],
+ false,
+ false,
+ &[],
+ )
+ .await
+ .unwrap();
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 1);
- db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[], false, false)
- .await
- .unwrap();
+ db.share_project(
+ room_id,
+ ConnectionId { owner_id, id: 1 },
+ &[],
+ false,
+ false,
+ &[],
+ )
+ .await
+ .unwrap();
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
// Projects shared by admins aren't counted.
- db.share_project(room_id, ConnectionId { owner_id, id: 0 }, &[], false, false)
- .await
- .unwrap();
+ db.share_project(
+ room_id,
+ ConnectionId { owner_id, id: 0 },
+ &[],
+ false,
+ false,
+ &[],
+ )
+ .await
+ .unwrap();
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
db.leave_room(ConnectionId { owner_id, id: 1 })
@@ -2184,6 +2184,7 @@ async fn test_following_after_replacement(cx_a: &mut TestAppContext, cx_b: &mut
);
mb
});
+ let multibuffer_snapshot = multibuffer.update(cx_a, |mb, cx| mb.snapshot(cx));
let snapshot = buffer.update(cx_a, |buffer, _| buffer.snapshot());
let editor: Entity<Editor> = cx_a.new_window_entity(|window, cx| {
Editor::for_multibuffer(
@@ -2205,7 +2206,13 @@ async fn test_following_after_replacement(cx_a: &mut TestAppContext, cx_b: &mut
editor
.selections
.disjoint_anchor_ranges()
- .map(|range| range.start.text_anchor.to_point(&snapshot))
+ .map(|range| {
+ multibuffer_snapshot
+ .anchor_to_buffer_anchor(range.start)
+ .unwrap()
+ .0
+ .to_point(&snapshot)
+ })
.collect::<Vec<_>>()
});
multibuffer.update(cx_a, |multibuffer, cx| {
@@ -2232,7 +2239,13 @@ async fn test_following_after_replacement(cx_a: &mut TestAppContext, cx_b: &mut
editor
.selections
.disjoint_anchor_ranges()
- .map(|range| range.start.text_anchor.to_point(&snapshot))
+ .map(|range| {
+ multibuffer_snapshot
+ .anchor_to_buffer_anchor(range.start)
+ .unwrap()
+ .0
+ .to_point(&snapshot)
+ })
.collect::<Vec<_>>()
});
assert_eq!(positions, new_positions);
@@ -1166,7 +1166,7 @@ impl CollabPanel {
"Failed to join project",
window,
cx,
- |_, _, _| None,
+ |error, _, _| Some(format!("{error:#}")),
);
})
.ok();
@@ -1729,7 +1729,7 @@ impl CollabPanel {
"Failed to join project",
window,
cx,
- |_, _, _| None,
+ |error, _, _| Some(format!("{error:#}")),
);
}
}
@@ -161,9 +161,7 @@ impl CsvPreviewView {
editor,
|this: &mut CsvPreviewView, _editor, event: &EditorEvent, cx| {
match event {
- EditorEvent::Edited { .. }
- | EditorEvent::DirtyChanged
- | EditorEvent::ExcerptsEdited { .. } => {
+ EditorEvent::Edited { .. } | EditorEvent::DirtyChanged => {
this.parse_csv_from_active_editor(true, cx);
}
_ => {}
@@ -299,7 +299,7 @@ pub fn init(cx: &mut App) {
return;
}
maybe!({
- let (buffer, position, _) = editor
+ let (buffer, position) = editor
.update(cx, |editor, cx| {
let cursor_point: language::Point = editor
.selections
@@ -7,8 +7,8 @@ use anyhow::Result;
use collections::HashMap;
use dap::{CompletionItem, CompletionItemType, OutputEvent};
use editor::{
- Bias, CompletionProvider, Editor, EditorElement, EditorMode, EditorStyle, ExcerptId,
- HighlightKey, MultiBufferOffset, SizingBehavior,
+ Bias, CompletionProvider, Editor, EditorElement, EditorMode, EditorStyle, HighlightKey,
+ MultiBufferOffset, SizingBehavior,
};
use fuzzy::StringMatchCandidate;
use gpui::{
@@ -528,7 +528,6 @@ struct ConsoleQueryBarCompletionProvider(WeakEntity<Console>);
impl CompletionProvider for ConsoleQueryBarCompletionProvider {
fn completions(
&self,
- _excerpt_id: ExcerptId,
buffer: &Entity<Buffer>,
buffer_position: language::Anchor,
_trigger: editor::CompletionContext,
@@ -24,6 +24,7 @@ use settings::Settings;
use std::{
any::{Any, TypeId},
cmp::{self, Ordering},
+ ops::Range,
sync::Arc,
};
use text::{Anchor, BufferSnapshot, OffsetRangeExt};
@@ -480,25 +481,35 @@ impl BufferDiagnosticsEditor {
})
});
- let (anchor_ranges, _) =
- buffer_diagnostics_editor
- .multibuffer
- .update(cx, |multibuffer, cx| {
- let excerpt_ranges = excerpt_ranges
- .into_iter()
- .map(|range| ExcerptRange {
- context: range.context.to_point(&buffer_snapshot),
- primary: range.primary.to_point(&buffer_snapshot),
- })
- .collect();
- multibuffer.set_excerpt_ranges_for_path(
- PathKey::for_buffer(&buffer, cx),
- buffer.clone(),
- &buffer_snapshot,
- excerpt_ranges,
- cx,
- )
- });
+ let excerpt_ranges: Vec<_> = excerpt_ranges
+ .into_iter()
+ .map(|range| ExcerptRange {
+ context: range.context.to_point(&buffer_snapshot),
+ primary: range.primary.to_point(&buffer_snapshot),
+ })
+ .collect();
+ buffer_diagnostics_editor
+ .multibuffer
+ .update(cx, |multibuffer, cx| {
+ multibuffer.set_excerpt_ranges_for_path(
+ PathKey::for_buffer(&buffer, cx),
+ buffer.clone(),
+ &buffer_snapshot,
+ excerpt_ranges.clone(),
+ cx,
+ )
+ });
+ let multibuffer_snapshot =
+ buffer_diagnostics_editor.multibuffer.read(cx).snapshot(cx);
+ let anchor_ranges: Vec<Range<editor::Anchor>> = excerpt_ranges
+ .into_iter()
+ .filter_map(|range| {
+ let text_range = buffer_snapshot.anchor_range_inside(range.primary);
+ let start = multibuffer_snapshot.anchor_in_buffer(text_range.start)?;
+ let end = multibuffer_snapshot.anchor_in_buffer(text_range.end)?;
+ Some(start..end)
+ })
+ .collect();
if was_empty {
if let Some(anchor_range) = anchor_ranges.first() {
@@ -10,7 +10,7 @@ use language::{BufferId, Diagnostic, DiagnosticEntryRef, LanguageRegistry};
use lsp::DiagnosticSeverity;
use markdown::{CopyButtonVisibility, Markdown, MarkdownElement};
use settings::Settings;
-use text::{AnchorRangeExt, Point};
+use text::Point;
use theme_settings::ThemeSettings;
use ui::{CopyButton, prelude::*};
use util::maybe;
@@ -289,23 +289,12 @@ impl DiagnosticBlock {
.nth(ix)
{
let multibuffer = editor.buffer().read(cx);
- let Some(snapshot) = multibuffer
- .buffer(buffer_id)
- .map(|entity| entity.read(cx).snapshot())
- else {
+ if let Some(anchor_range) = multibuffer
+ .snapshot(cx)
+ .buffer_anchor_range_to_anchor_range(diagnostic.range)
+ {
+ Self::jump_to(editor, anchor_range, window, cx);
return;
- };
-
- for (excerpt_id, _, range) in multibuffer.excerpts_for_buffer(buffer_id, cx) {
- if range.context.overlaps(&diagnostic.range, &snapshot) {
- Self::jump_to(
- editor,
- Anchor::range_in_buffer(excerpt_id, diagnostic.range),
- window,
- cx,
- );
- return;
- }
}
}
} else if let Some(diagnostic) = editor
@@ -12,7 +12,7 @@ use buffer_diagnostics::BufferDiagnosticsEditor;
use collections::{BTreeSet, HashMap, HashSet};
use diagnostic_renderer::DiagnosticBlock;
use editor::{
- Editor, EditorEvent, ExcerptRange, MultiBuffer, PathKey,
+ Anchor, Editor, EditorEvent, ExcerptRange, MultiBuffer, PathKey,
display_map::{BlockPlacement, BlockProperties, BlockStyle, CustomBlockId},
multibuffer_context_lines,
};
@@ -301,17 +301,21 @@ impl ProjectDiagnosticsEditor {
let snapshot = self
.editor
.update(cx, |editor, cx| editor.display_snapshot(cx));
- let buffer = self.multibuffer.read(cx);
- let buffer_ids = buffer.all_buffer_ids();
let selected_buffers = self.editor.update(cx, |editor, _| {
editor
.selections
.all_anchors(&snapshot)
.iter()
- .filter_map(|anchor| anchor.start.text_anchor.buffer_id)
+ .filter_map(|anchor| {
+ Some(snapshot.anchor_to_buffer_anchor(anchor.start)?.0.buffer_id)
+ })
.collect::<HashSet<_>>()
});
- for buffer_id in buffer_ids {
+ for buffer_id in snapshot
+ .excerpts()
+ .map(|excerpt| excerpt.context.start.buffer_id)
+ .dedup()
+ {
if retain_selections && selected_buffers.contains(&buffer_id) {
continue;
}
@@ -329,7 +333,7 @@ impl ProjectDiagnosticsEditor {
continue;
}
self.multibuffer.update(cx, |b, cx| {
- b.remove_excerpts_for_path(PathKey::for_buffer(&buffer, cx), cx);
+ b.remove_excerpts(PathKey::for_buffer(&buffer, cx), cx);
});
}
}
@@ -581,9 +585,8 @@ impl ProjectDiagnosticsEditor {
match retain_excerpts {
RetainExcerpts::Dirty if !is_dirty => Vec::new(),
RetainExcerpts::All | RetainExcerpts::Dirty => multi_buffer
- .excerpts_for_buffer(buffer_id, cx)
- .into_iter()
- .map(|(_, _, range)| range)
+ .snapshot(cx)
+ .excerpts_for_buffer(buffer_id)
.sorted_by(|a, b| cmp_excerpts(&buffer_snapshot, a, b))
.collect(),
}
@@ -621,22 +624,33 @@ impl ProjectDiagnosticsEditor {
});
})
}
- let (anchor_ranges, _) = this.multibuffer.update(cx, |multi_buffer, cx| {
- let excerpt_ranges = excerpt_ranges
- .into_iter()
- .map(|range| ExcerptRange {
- context: range.context.to_point(&buffer_snapshot),
- primary: range.primary.to_point(&buffer_snapshot),
- })
- .collect();
+ let excerpt_ranges: Vec<_> = excerpt_ranges
+ .into_iter()
+ .map(|range| ExcerptRange {
+ context: range.context.to_point(&buffer_snapshot),
+ primary: range.primary.to_point(&buffer_snapshot),
+ })
+ .collect();
+ // TODO(cole): maybe should use the nonshrinking API?
+ this.multibuffer.update(cx, |multi_buffer, cx| {
multi_buffer.set_excerpt_ranges_for_path(
PathKey::for_buffer(&buffer, cx),
buffer.clone(),
&buffer_snapshot,
- excerpt_ranges,
+ excerpt_ranges.clone(),
cx,
)
});
+ let multibuffer_snapshot = this.multibuffer.read(cx).snapshot(cx);
+ let anchor_ranges: Vec<Range<Anchor>> = excerpt_ranges
+ .into_iter()
+ .filter_map(|range| {
+ let text_range = buffer_snapshot.anchor_range_inside(range.primary);
+ let start = multibuffer_snapshot.anchor_in_buffer(text_range.start)?;
+ let end = multibuffer_snapshot.anchor_in_buffer(text_range.end)?;
+ Some(start..end)
+ })
+ .collect();
#[cfg(test)]
let cloned_blocks = result_blocks.clone();
@@ -414,7 +414,7 @@ mod tests {
capture_example(
project.clone(),
buffer.clone(),
- Anchor::MIN,
+ Anchor::min_for_buffer(buffer.read(cx).remote_id()),
events,
true,
cx,
@@ -1676,7 +1676,7 @@ impl EditPredictionStore {
buffer.pending_predictions.push(PendingSettledPrediction {
request_id: request_id,
editable_anchor_range: edited_buffer_snapshot
- .anchor_range_around(editable_offset_range),
+ .anchor_range_inside(editable_offset_range),
example,
e2e_latency,
enqueued_at: now,
@@ -2351,7 +2351,10 @@ impl EditPredictionStore {
cx: &mut AsyncApp,
) -> Result<Option<(Entity<Buffer>, language::Anchor)>> {
let collaborator_cursor_rows: Vec<u32> = active_buffer_snapshot
- .selections_in_range(Anchor::MIN..Anchor::MAX, false)
+ .selections_in_range(
+ Anchor::min_max_range_for_buffer(active_buffer_snapshot.remote_id()),
+ false,
+ )
.flat_map(|(_, _, _, selections)| {
selections.map(|s| s.head().to_point(active_buffer_snapshot).row)
})
@@ -2427,7 +2430,10 @@ impl EditPredictionStore {
candidate_buffer.read_with(cx, |buffer, _cx| {
let snapshot = buffer.snapshot();
let has_collaborators = snapshot
- .selections_in_range(Anchor::MIN..Anchor::MAX, false)
+ .selections_in_range(
+ Anchor::min_max_range_for_buffer(snapshot.remote_id()),
+ false,
+ )
.next()
.is_some();
let position = buffer
@@ -2761,7 +2767,7 @@ fn collaborator_edit_overlaps_locality_region(
(position..position).to_point(snapshot),
COLLABORATOR_EDIT_LOCALITY_CONTEXT_TOKENS,
);
- let locality_anchor_range = snapshot.anchor_range_around(locality_point_range);
+ let locality_anchor_range = snapshot.anchor_range_inside(locality_point_range);
edit_range.overlaps(&locality_anchor_range, snapshot)
}
@@ -54,7 +54,6 @@ pub async fn apply_diff(
let mut included_files: HashMap<String, Entity<Buffer>> = HashMap::default();
- let ranges = [Anchor::MIN..Anchor::MAX];
let mut diff = DiffParser::new(diff_str);
let mut current_file = None;
let mut edits: Vec<(std::ops::Range<Anchor>, Arc<str>)> = vec![];
@@ -115,7 +114,7 @@ pub async fn apply_diff(
edits.extend(resolve_hunk_edits_in_buffer(
hunk,
buffer,
- ranges.as_slice(),
+ &[Anchor::min_max_range_for_buffer(buffer.remote_id())],
status,
)?);
anyhow::Ok(())
@@ -201,10 +201,14 @@ impl EditPredictionContextView {
multibuffer.clear(cx);
for (path, buffer, ranges, orders, _) in paths {
- let (anchor_ranges, _) =
- multibuffer.set_excerpts_for_path(path, buffer, ranges, 0, cx);
- for (anchor_range, order) in anchor_ranges.into_iter().zip(orders) {
- excerpt_anchors_with_orders.push((anchor_range.start, order));
+ multibuffer.set_excerpts_for_path(path, buffer.clone(), ranges.clone(), 0, cx);
+ let snapshot = multibuffer.snapshot(cx);
+ let buffer_snapshot = buffer.read(cx).snapshot();
+ for (range, order) in ranges.into_iter().zip(orders) {
+ let text_anchor = buffer_snapshot.anchor_range_inside(range);
+ if let Some(start) = snapshot.anchor_in_buffer(text_anchor.start) {
+ excerpt_anchors_with_orders.push((start, order));
+ }
}
}
});
@@ -357,35 +357,26 @@ impl RatePredictionsModal {
});
editor.disable_header_for_buffer(new_buffer_id, cx);
- let excerpt_id = editor.buffer().update(cx, |multibuffer, cx| {
+ editor.buffer().update(cx, |multibuffer, cx| {
multibuffer.clear(cx);
- multibuffer.set_excerpts_for_buffer(new_buffer, [start..end], 0, cx);
+ multibuffer.set_excerpts_for_buffer(new_buffer.clone(), [start..end], 0, cx);
multibuffer.add_diff(diff, cx);
- multibuffer.excerpt_ids().into_iter().next()
});
- if let Some((excerpt_id, cursor_position)) =
- excerpt_id.zip(prediction.cursor_position.as_ref())
- {
+ if let Some(cursor_position) = prediction.cursor_position.as_ref() {
let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
- if let Some(buffer_snapshot) =
- multibuffer_snapshot.buffer_for_excerpt(excerpt_id)
- {
- let cursor_offset = prediction
- .edit_preview
- .anchor_to_offset_in_result(cursor_position.anchor)
- + cursor_position.offset;
- let cursor_anchor = buffer_snapshot.anchor_after(cursor_offset);
-
- if let Some(anchor) =
- multibuffer_snapshot.anchor_in_excerpt(excerpt_id, cursor_anchor)
- {
- editor.splice_inlays(
- &[InlayId::EditPrediction(0)],
- vec![Inlay::edit_prediction(0, anchor, "▏")],
- cx,
- );
- }
+ let cursor_offset = prediction
+ .edit_preview
+ .anchor_to_offset_in_result(cursor_position.anchor)
+ + cursor_position.offset;
+ let cursor_anchor = new_buffer.read(cx).snapshot().anchor_after(cursor_offset);
+
+ if let Some(anchor) = multibuffer_snapshot.anchor_in_excerpt(cursor_anchor) {
+ editor.splice_inlays(
+ &[InlayId::EditPrediction(0)],
+ vec![Inlay::edit_prediction(0, anchor, "▏")],
+ cx,
+ );
}
}
});
@@ -991,7 +982,6 @@ impl FeedbackCompletionProvider {
impl editor::CompletionProvider for FeedbackCompletionProvider {
fn completions(
&self,
- _excerpt_id: editor::ExcerptId,
buffer: &Entity<Buffer>,
buffer_position: language::Anchor,
_trigger: editor::CompletionContext,
@@ -7,9 +7,9 @@ use std::ops::Range;
use crate::{Editor, HighlightKey};
use collections::{HashMap, HashSet};
use gpui::{AppContext as _, Context, HighlightStyle};
-use itertools::Itertools;
use language::{BufferRow, BufferSnapshot, language_settings::LanguageSettings};
-use multi_buffer::{Anchor, ExcerptId};
+use multi_buffer::{Anchor, BufferOffset, ExcerptRange, MultiBufferSnapshot};
+use text::OffsetRangeExt as _;
use ui::{ActiveTheme, utils::ensure_minimum_contrast};
impl Editor {
@@ -25,55 +25,49 @@ impl Editor {
let accents_count = cx.theme().accents().0.len();
let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
- let visible_excerpts = self.visible_excerpts(false, cx);
- let excerpt_data: Vec<(ExcerptId, BufferSnapshot, Range<usize>)> = visible_excerpts
+ let visible_excerpts = self.visible_buffer_ranges(cx);
+ let excerpt_data: Vec<(
+ BufferSnapshot,
+ Range<BufferOffset>,
+ ExcerptRange<text::Anchor>,
+ )> = visible_excerpts
.into_iter()
- .filter_map(|(excerpt_id, (buffer, _, buffer_range))| {
- let buffer = buffer.read(cx);
- let buffer_snapshot = buffer.snapshot();
- if LanguageSettings::for_buffer(&buffer, cx).colorize_brackets {
- Some((excerpt_id, buffer_snapshot, buffer_range))
- } else {
- None
- }
+ .filter(|(buffer_snapshot, _, _)| {
+ let Some(buffer) = self.buffer().read(cx).buffer(buffer_snapshot.remote_id())
+ else {
+ return false;
+ };
+ LanguageSettings::for_buffer(buffer.read(cx), cx).colorize_brackets
})
.collect();
let mut fetched_tree_sitter_chunks = excerpt_data
.iter()
- .filter_map(|(excerpt_id, ..)| {
+ .filter_map(|(_, _, excerpt_range)| {
+ let key = excerpt_range.context.clone();
Some((
- *excerpt_id,
- self.bracket_fetched_tree_sitter_chunks
- .get(excerpt_id)
- .cloned()?,
+ key.clone(),
+ self.bracket_fetched_tree_sitter_chunks.get(&key).cloned()?,
))
})
- .collect::<HashMap<ExcerptId, HashSet<Range<BufferRow>>>>();
+ .collect::<HashMap<Range<text::Anchor>, HashSet<Range<BufferRow>>>>();
let bracket_matches_by_accent = cx.background_spawn(async move {
- let anchors_in_multi_buffer = |current_excerpt: ExcerptId,
- text_anchors: [text::Anchor; 4]|
- -> Option<[Option<_>; 4]> {
- multi_buffer_snapshot
- .anchors_in_excerpt(current_excerpt, text_anchors)?
- .collect_array()
- };
-
let bracket_matches_by_accent: HashMap<usize, Vec<Range<Anchor>>> =
excerpt_data.into_iter().fold(
HashMap::default(),
- |mut acc, (excerpt_id, buffer_snapshot, buffer_range)| {
- let fetched_chunks =
- fetched_tree_sitter_chunks.entry(excerpt_id).or_default();
+ |mut acc, (buffer_snapshot, buffer_range, excerpt_range)| {
+ let fetched_chunks = fetched_tree_sitter_chunks
+ .entry(excerpt_range.context.clone())
+ .or_default();
let brackets_by_accent = compute_bracket_ranges(
+ &multi_buffer_snapshot,
&buffer_snapshot,
buffer_range,
+ excerpt_range,
fetched_chunks,
- excerpt_id,
accents_count,
- &anchors_in_multi_buffer,
);
for (accent_number, new_ranges) in brackets_by_accent {
@@ -144,15 +138,20 @@ impl Editor {
}
fn compute_bracket_ranges(
+ multi_buffer_snapshot: &MultiBufferSnapshot,
buffer_snapshot: &BufferSnapshot,
- buffer_range: Range<usize>,
+ buffer_range: Range<BufferOffset>,
+ excerpt_range: ExcerptRange<text::Anchor>,
fetched_chunks: &mut HashSet<Range<BufferRow>>,
- excerpt_id: ExcerptId,
accents_count: usize,
- anchors_in_multi_buffer: &impl Fn(ExcerptId, [text::Anchor; 4]) -> Option<[Option<Anchor>; 4]>,
) -> Vec<(usize, Vec<Range<Anchor>>)> {
+ let context = excerpt_range.context.to_offset(buffer_snapshot);
+
buffer_snapshot
- .fetch_bracket_ranges(buffer_range.start..buffer_range.end, Some(fetched_chunks))
+ .fetch_bracket_ranges(
+ buffer_range.start.0..buffer_range.end.0,
+ Some(fetched_chunks),
+ )
.into_iter()
.flat_map(|(chunk_range, pairs)| {
if fetched_chunks.insert(chunk_range) {
@@ -164,37 +163,25 @@ fn compute_bracket_ranges(
.filter_map(|pair| {
let color_index = pair.color_index?;
- let buffer_open_range = buffer_snapshot.anchor_range_around(pair.open_range);
- let buffer_close_range = buffer_snapshot.anchor_range_around(pair.close_range);
- let [
- buffer_open_range_start,
- buffer_open_range_end,
- buffer_close_range_start,
- buffer_close_range_end,
- ] = anchors_in_multi_buffer(
- excerpt_id,
- [
- buffer_open_range.start,
- buffer_open_range.end,
- buffer_close_range.start,
- buffer_close_range.end,
- ],
- )?;
- let multi_buffer_open_range = buffer_open_range_start.zip(buffer_open_range_end);
- let multi_buffer_close_range = buffer_close_range_start.zip(buffer_close_range_end);
+ let mut ranges = Vec::new();
- let mut ranges = Vec::with_capacity(2);
- if let Some((open_start, open_end)) = multi_buffer_open_range {
- ranges.push(open_start..open_end);
- }
- if let Some((close_start, close_end)) = multi_buffer_close_range {
- ranges.push(close_start..close_end);
- }
- if ranges.is_empty() {
- None
- } else {
- Some((color_index % accents_count, ranges))
- }
+ if context.start <= pair.open_range.start && pair.open_range.end <= context.end {
+ let anchors = buffer_snapshot.anchor_range_inside(pair.open_range);
+ ranges.push(
+ multi_buffer_snapshot.anchor_in_buffer(anchors.start)?
+ ..multi_buffer_snapshot.anchor_in_buffer(anchors.end)?,
+ );
+ };
+
+ if context.start <= pair.close_range.start && pair.close_range.end <= context.end {
+ let anchors = buffer_snapshot.anchor_range_inside(pair.close_range);
+ ranges.push(
+ multi_buffer_snapshot.anchor_in_buffer(anchors.start)?
+ ..multi_buffer_snapshot.anchor_in_buffer(anchors.end)?,
+ );
+ };
+
+ Some((color_index % accents_count, ranges))
})
.collect()
}
@@ -1197,7 +1184,7 @@ mod foo «1{
);
}
- let buffer_snapshot = snapshot.buffer().as_singleton().unwrap().2;
+ let buffer_snapshot = snapshot.buffer().as_singleton().unwrap();
for bracket_match in buffer_snapshot
.fetch_bracket_ranges(
snapshot
@@ -1464,6 +1451,101 @@ mod foo «1{
);
}
+ #[gpui::test]
+ async fn test_multi_buffer_close_excerpts(cx: &mut gpui::TestAppContext) {
+ let comment_lines = 5;
+
+ init_test(cx, |language_settings| {
+ language_settings.defaults.colorize_brackets = Some(true);
+ });
+ let fs = FakeFs::new(cx.background_executor.clone());
+ fs.insert_tree(
+ path!("/a"),
+ json!({
+ "lib.rs": separate_with_comment_lines(
+ indoc! {r#"
+ fn process_data_1() {
+ let map: Option<Vec<()>> = None;
+ }
+ "#},
+ indoc! {r#"
+ fn process_data_2() {
+ let other_map: Option<Vec<()>> = None;
+ }
+ "#},
+ comment_lines,
+ )
+ }),
+ )
+ .await;
+
+ let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
+ let language_registry = project.read_with(cx, |project, _| project.languages().clone());
+ language_registry.add(rust_lang());
+
+ let buffer_1 = project
+ .update(cx, |project, cx| {
+ project.open_local_buffer(path!("/a/lib.rs"), cx)
+ })
+ .await
+ .unwrap();
+
+ let second_excerpt_start = buffer_1.read_with(cx, |buffer, _| {
+ let text = buffer.text();
+ text.lines()
+ .enumerate()
+ .find(|(_, line)| line.contains("process_data_2"))
+ .map(|(row, _)| row as u32)
+ .unwrap()
+ });
+
+ let multi_buffer = cx.new(|cx| {
+ let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
+ multi_buffer.set_excerpts_for_path(
+ PathKey::sorted(0),
+ buffer_1.clone(),
+ [
+ Point::new(0, 0)..Point::new(3, 0),
+ Point::new(second_excerpt_start, 0)..Point::new(second_excerpt_start + 3, 0),
+ ],
+ 0,
+ cx,
+ );
+ multi_buffer
+ });
+
+ let editor = cx.add_window(|window, cx| {
+ Editor::for_multibuffer(multi_buffer, Some(project.clone()), window, cx)
+ });
+ cx.executor().advance_clock(Duration::from_millis(100));
+ cx.executor().run_until_parked();
+
+ let editor_snapshot = editor
+ .update(cx, |editor, window, cx| editor.snapshot(window, cx))
+ .unwrap();
+ assert_eq!(
+ concat!(
+ "\n",
+ "\n",
+ "fn process_data_1\u{00ab}1()1\u{00bb} \u{00ab}1{\n",
+ " let map: Option\u{00ab}2<Vec\u{00ab}3<\u{00ab}4()4\u{00bb}>3\u{00bb}>2\u{00bb} = None;\n",
+ "}1\u{00bb}\n",
+ "\n",
+ "\n",
+ "fn process_data_2\u{00ab}1()1\u{00bb} \u{00ab}1{\n",
+ " let other_map: Option\u{00ab}2<Vec\u{00ab}3<\u{00ab}4()4\u{00bb}>3\u{00bb}>2\u{00bb} = None;\n",
+ "}1\u{00bb}\n",
+ "\n",
+ "1 hsla(207.80, 16.20%, 69.19%, 1.00)\n",
+ "2 hsla(29.00, 54.00%, 65.88%, 1.00)\n",
+ "3 hsla(286.00, 51.00%, 75.25%, 1.00)\n",
+ "4 hsla(187.00, 47.00%, 59.22%, 1.00)\n",
+ ),
+ &editor_bracket_colors_markup(&editor_snapshot),
+ "Two close excerpts from the same buffer (within same tree-sitter chunk) should both have bracket colors"
+ );
+ }
+
#[gpui::test]
// reproduction of #47846
async fn test_bracket_colorization_with_folds(cx: &mut gpui::TestAppContext) {
@@ -7,7 +7,7 @@ use project::{Completion, CompletionSource};
use settings::SnippetSortOrder;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
-use text::Anchor;
+use text::{Anchor, BufferId};
#[gpui::test]
async fn test_sort_kind(cx: &mut TestAppContext) {
@@ -393,7 +393,7 @@ impl CompletionBuilder {
kind: Option<CompletionItemKind>,
) -> Completion {
Completion {
- replace_range: Anchor::MIN..Anchor::MAX,
+ replace_range: Anchor::min_max_range_for_buffer(BufferId::new(1).unwrap()),
new_text: label.to_string(),
label: CodeLabel::plain(label.to_string(), filter_text),
documentation: None,
@@ -10,7 +10,7 @@ use language::CodeLabel;
use language::{Buffer, LanguageName, LanguageRegistry};
use lsp::CompletionItemTag;
use markdown::{CopyButtonVisibility, Markdown, MarkdownElement};
-use multi_buffer::{Anchor, ExcerptId};
+use multi_buffer::Anchor;
use ordered_float::OrderedFloat;
use project::lsp_store::CompletionDocumentation;
use project::{CodeAction, Completion, TaskSourceKind};
@@ -357,7 +357,8 @@ impl CompletionsMenu {
id: CompletionId,
sort_completions: bool,
choices: &Vec<String>,
- selection: Range<Anchor>,
+ initial_position: Anchor,
+ selection: Range<text::Anchor>,
buffer: Entity<Buffer>,
scroll_handle: Option<UniformListScrollHandle>,
snippet_sort_order: SnippetSortOrder,
@@ -365,7 +366,7 @@ impl CompletionsMenu {
let completions = choices
.iter()
.map(|choice| Completion {
- replace_range: selection.start.text_anchor..selection.end.text_anchor,
+ replace_range: selection.clone(),
new_text: choice.to_string(),
label: CodeLabel::plain(choice.to_string(), None),
match_start: None,
@@ -400,7 +401,7 @@ impl CompletionsMenu {
id,
source: CompletionsMenuSource::SnippetChoices,
sort_completions,
- initial_position: selection.start,
+ initial_position,
initial_query: None,
is_incomplete: false,
buffer,
@@ -1380,7 +1381,6 @@ impl CompletionsMenu {
#[derive(Clone)]
pub struct AvailableCodeAction {
- pub excerpt_id: ExcerptId,
pub action: CodeAction,
pub provider: Rc<dyn CodeActionProvider>,
}
@@ -1433,7 +1433,6 @@ impl CodeActionContents {
})
.chain(self.actions.iter().flat_map(|actions| {
actions.iter().map(|available| CodeActionsItem::CodeAction {
- excerpt_id: available.excerpt_id,
action: available.action.clone(),
provider: available.provider.clone(),
})
@@ -1457,7 +1456,6 @@ impl CodeActionContents {
if let Some(actions) = &self.actions {
if let Some(available) = actions.get(index) {
return Some(CodeActionsItem::CodeAction {
- excerpt_id: available.excerpt_id,
action: available.action.clone(),
provider: available.provider.clone(),
});
@@ -1477,7 +1475,6 @@ impl CodeActionContents {
pub enum CodeActionsItem {
Task(TaskSourceKind, ResolvedTask),
CodeAction {
- excerpt_id: ExcerptId,
action: CodeAction,
provider: Rc<dyn CodeActionProvider>,
},
@@ -103,7 +103,7 @@ use language::{
};
use multi_buffer::{
- Anchor, AnchorRangeExt, ExcerptId, MultiBuffer, MultiBufferOffset, MultiBufferOffsetUtf16,
+ Anchor, AnchorRangeExt, MultiBuffer, MultiBufferOffset, MultiBufferOffsetUtf16,
MultiBufferPoint, MultiBufferRow, MultiBufferSnapshot, RowInfo, ToOffset, ToPoint,
};
use project::project_settings::DiagnosticSeverity;
@@ -125,7 +125,7 @@ use std::{
fmt::Debug,
iter,
num::NonZeroU32,
- ops::{self, Add, Bound, Range, Sub},
+ ops::{self, Add, Range, Sub},
sync::Arc,
};
@@ -195,10 +195,9 @@ pub struct CompanionExcerptPatch {
}
pub type ConvertMultiBufferRows = fn(
- &HashMap<ExcerptId, ExcerptId>,
&MultiBufferSnapshot,
&MultiBufferSnapshot,
- (Bound<MultiBufferPoint>, Bound<MultiBufferPoint>),
+ Range<MultiBufferPoint>,
) -> Vec<CompanionExcerptPatch>;
/// Decides how text in a [`MultiBuffer`] should be displayed in a buffer, handling inlay hints,
@@ -240,8 +239,6 @@ pub(crate) struct Companion {
rhs_display_map_id: EntityId,
rhs_buffer_to_lhs_buffer: HashMap<BufferId, BufferId>,
lhs_buffer_to_rhs_buffer: HashMap<BufferId, BufferId>,
- rhs_excerpt_to_lhs_excerpt: HashMap<ExcerptId, ExcerptId>,
- lhs_excerpt_to_rhs_excerpt: HashMap<ExcerptId, ExcerptId>,
rhs_rows_to_lhs_rows: ConvertMultiBufferRows,
lhs_rows_to_rhs_rows: ConvertMultiBufferRows,
rhs_custom_block_to_balancing_block: RefCell<HashMap<CustomBlockId, CustomBlockId>>,
@@ -258,8 +255,6 @@ impl Companion {
rhs_display_map_id,
rhs_buffer_to_lhs_buffer: Default::default(),
lhs_buffer_to_rhs_buffer: Default::default(),
- rhs_excerpt_to_lhs_excerpt: Default::default(),
- lhs_excerpt_to_rhs_excerpt: Default::default(),
rhs_rows_to_lhs_rows,
lhs_rows_to_rhs_rows,
rhs_custom_block_to_balancing_block: Default::default(),
@@ -287,14 +282,14 @@ impl Companion {
display_map_id: EntityId,
companion_snapshot: &MultiBufferSnapshot,
our_snapshot: &MultiBufferSnapshot,
- bounds: (Bound<MultiBufferPoint>, Bound<MultiBufferPoint>),
+ bounds: Range<MultiBufferPoint>,
) -> Vec<CompanionExcerptPatch> {
- let (excerpt_map, convert_fn) = if self.is_rhs(display_map_id) {
- (&self.rhs_excerpt_to_lhs_excerpt, self.rhs_rows_to_lhs_rows)
+ let convert_fn = if self.is_rhs(display_map_id) {
+ self.rhs_rows_to_lhs_rows
} else {
- (&self.lhs_excerpt_to_rhs_excerpt, self.lhs_rows_to_rhs_rows)
+ self.lhs_rows_to_rhs_rows
};
- convert_fn(excerpt_map, companion_snapshot, our_snapshot, bounds)
+ convert_fn(companion_snapshot, our_snapshot, bounds)
}
pub(crate) fn convert_point_from_companion(
@@ -304,20 +299,15 @@ impl Companion {
companion_snapshot: &MultiBufferSnapshot,
point: MultiBufferPoint,
) -> Range<MultiBufferPoint> {
- let (excerpt_map, convert_fn) = if self.is_rhs(display_map_id) {
- (&self.lhs_excerpt_to_rhs_excerpt, self.lhs_rows_to_rhs_rows)
+ let convert_fn = if self.is_rhs(display_map_id) {
+ self.lhs_rows_to_rhs_rows
} else {
- (&self.rhs_excerpt_to_lhs_excerpt, self.rhs_rows_to_lhs_rows)
+ self.rhs_rows_to_lhs_rows
};
- let excerpt = convert_fn(
- excerpt_map,
- our_snapshot,
- companion_snapshot,
- (Bound::Included(point), Bound::Included(point)),
- )
- .into_iter()
- .next();
+ let excerpt = convert_fn(our_snapshot, companion_snapshot, point..point)
+ .into_iter()
+ .next();
let Some(excerpt) = excerpt else {
return Point::zero()..our_snapshot.max_point();
@@ -332,20 +322,15 @@ impl Companion {
companion_snapshot: &MultiBufferSnapshot,
point: MultiBufferPoint,
) -> Range<MultiBufferPoint> {
- let (excerpt_map, convert_fn) = if self.is_rhs(display_map_id) {
- (&self.rhs_excerpt_to_lhs_excerpt, self.rhs_rows_to_lhs_rows)
+ let convert_fn = if self.is_rhs(display_map_id) {
+ self.rhs_rows_to_lhs_rows
} else {
- (&self.lhs_excerpt_to_rhs_excerpt, self.lhs_rows_to_rhs_rows)
+ self.lhs_rows_to_rhs_rows
};
- let excerpt = convert_fn(
- excerpt_map,
- companion_snapshot,
- our_snapshot,
- (Bound::Included(point), Bound::Included(point)),
- )
- .into_iter()
- .next();
+ let excerpt = convert_fn(companion_snapshot, our_snapshot, point..point)
+ .into_iter()
+ .next();
let Some(excerpt) = excerpt else {
return Point::zero()..companion_snapshot.max_point();
@@ -353,30 +338,6 @@ impl Companion {
excerpt.patch.edit_for_old_position(point).new
}
- pub(crate) fn companion_excerpt_to_excerpt(
- &self,
- display_map_id: EntityId,
- ) -> &HashMap<ExcerptId, ExcerptId> {
- if self.is_rhs(display_map_id) {
- &self.lhs_excerpt_to_rhs_excerpt
- } else {
- &self.rhs_excerpt_to_lhs_excerpt
- }
- }
-
- #[cfg(test)]
- pub(crate) fn excerpt_mappings(
- &self,
- ) -> (
- &HashMap<ExcerptId, ExcerptId>,
- &HashMap<ExcerptId, ExcerptId>,
- ) {
- (
- &self.lhs_excerpt_to_rhs_excerpt,
- &self.rhs_excerpt_to_lhs_excerpt,
- )
- }
-
fn buffer_to_companion_buffer(&self, display_map_id: EntityId) -> &HashMap<BufferId, BufferId> {
if self.is_rhs(display_map_id) {
&self.rhs_buffer_to_lhs_buffer
@@ -385,24 +346,6 @@ impl Companion {
}
}
- pub(crate) fn add_excerpt_mapping(&mut self, lhs_id: ExcerptId, rhs_id: ExcerptId) {
- self.lhs_excerpt_to_rhs_excerpt.insert(lhs_id, rhs_id);
- self.rhs_excerpt_to_lhs_excerpt.insert(rhs_id, lhs_id);
- }
-
- pub(crate) fn remove_excerpt_mappings(
- &mut self,
- lhs_ids: impl IntoIterator<Item = ExcerptId>,
- rhs_ids: impl IntoIterator<Item = ExcerptId>,
- ) {
- for id in lhs_ids {
- self.lhs_excerpt_to_rhs_excerpt.remove(&id);
- }
- for id in rhs_ids {
- self.rhs_excerpt_to_lhs_excerpt.remove(&id);
- }
- }
-
pub(crate) fn lhs_to_rhs_buffer(&self, lhs_buffer_id: BufferId) -> Option<BufferId> {
self.lhs_buffer_to_rhs_buffer.get(&lhs_buffer_id).copied()
}
@@ -540,8 +483,7 @@ impl DisplayMap {
.wrap_map
.update(cx, |wrap_map, cx| wrap_map.sync(snapshot, edits, cx));
- let (snapshot, edits) =
- writer.unfold_intersecting([Anchor::min()..Anchor::max()], true);
+ let (snapshot, edits) = writer.unfold_intersecting([Anchor::Min..Anchor::Max], true);
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
let (snapshot, _edits) = self
.wrap_map
@@ -632,18 +574,6 @@ impl DisplayMap {
self.companion.as_ref().map(|(_, c)| c)
}
- pub(crate) fn companion_excerpt_to_my_excerpt(
- &self,
- their_id: ExcerptId,
- cx: &App,
- ) -> Option<ExcerptId> {
- let (_, companion) = self.companion.as_ref()?;
- let c = companion.read(cx);
- c.companion_excerpt_to_excerpt(self.entity_id)
- .get(&their_id)
- .copied()
- }
-
fn sync_through_wrap(&mut self, cx: &mut App) -> (WrapSnapshot, WrapPatch) {
let tab_size = Self::tab_size(&self.buffer, cx);
let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
@@ -1054,17 +984,10 @@ impl DisplayMap {
return;
}
- let excerpt_ids = snapshot
- .excerpts()
- .filter(|(_, buf, _)| buf.remote_id() == buffer_id)
- .map(|(id, _, _)| id)
- .collect::<Vec<_>>();
-
let base_placeholder = self.fold_placeholder.clone();
let creases = ranges.into_iter().filter_map(|folding_range| {
- let mb_range = excerpt_ids.iter().find_map(|&id| {
- snapshot.anchor_range_in_excerpt(id, folding_range.range.clone())
- })?;
+ let mb_range =
+ snapshot.buffer_anchor_range_to_anchor_range(folding_range.range.clone())?;
let placeholder = if let Some(collapsed_text) = folding_range.collapsed_text {
FoldPlaceholder {
render: Arc::new({
@@ -11,8 +11,8 @@ use collections::{Bound, HashMap, HashSet};
use gpui::{AnyElement, App, EntityId, Pixels, Window};
use language::{Patch, Point};
use multi_buffer::{
- Anchor, ExcerptId, ExcerptInfo, MultiBuffer, MultiBufferOffset, MultiBufferPoint,
- MultiBufferRow, MultiBufferSnapshot, RowInfo, ToOffset, ToPoint as _,
+ Anchor, ExcerptBoundaryInfo, MultiBuffer, MultiBufferOffset, MultiBufferPoint, MultiBufferRow,
+ MultiBufferSnapshot, RowInfo, ToOffset, ToPoint as _,
};
use parking_lot::Mutex;
use std::{
@@ -298,10 +298,10 @@ pub struct BlockContext<'a, 'b> {
pub indent_guide_padding: Pixels,
}
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BlockId {
- ExcerptBoundary(ExcerptId),
- FoldedBuffer(ExcerptId),
+ ExcerptBoundary(Anchor),
+ FoldedBuffer(BufferId),
Custom(CustomBlockId),
Spacer(SpacerId),
}
@@ -310,10 +310,8 @@ impl From<BlockId> for ElementId {
fn from(value: BlockId) -> Self {
match value {
BlockId::Custom(CustomBlockId(id)) => ("Block", id).into(),
- BlockId::ExcerptBoundary(excerpt_id) => {
- ("ExcerptBoundary", EntityId::from(excerpt_id)).into()
- }
- BlockId::FoldedBuffer(id) => ("FoldedBuffer", EntityId::from(id)).into(),
+ BlockId::ExcerptBoundary(anchor) => anchor.opaque_id().unwrap().into(),
+ BlockId::FoldedBuffer(id) => ("FoldedBuffer", EntityId::from(id.to_proto())).into(),
BlockId::Spacer(SpacerId(id)) => ("Spacer", id).into(),
}
}
@@ -323,7 +321,7 @@ impl std::fmt::Display for BlockId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Custom(id) => write!(f, "Block({id:?})"),
- Self::ExcerptBoundary(id) => write!(f, "ExcerptHeader({id:?})"),
+ Self::ExcerptBoundary(id) => write!(f, "ExcerptBoundary({id:?})"),
Self::FoldedBuffer(id) => write!(f, "FoldedBuffer({id:?})"),
Self::Spacer(id) => write!(f, "Spacer({id:?})"),
}
@@ -340,15 +338,15 @@ struct Transform {
pub enum Block {
Custom(Arc<CustomBlock>),
FoldedBuffer {
- first_excerpt: ExcerptInfo,
+ first_excerpt: ExcerptBoundaryInfo,
height: u32,
},
ExcerptBoundary {
- excerpt: ExcerptInfo,
+ excerpt: ExcerptBoundaryInfo,
height: u32,
},
BufferHeader {
- excerpt: ExcerptInfo,
+ excerpt: ExcerptBoundaryInfo,
height: u32,
},
Spacer {
@@ -365,12 +363,14 @@ impl Block {
Block::ExcerptBoundary {
excerpt: next_excerpt,
..
- } => BlockId::ExcerptBoundary(next_excerpt.id),
- Block::FoldedBuffer { first_excerpt, .. } => BlockId::FoldedBuffer(first_excerpt.id),
+ } => BlockId::ExcerptBoundary(next_excerpt.start_anchor),
+ Block::FoldedBuffer { first_excerpt, .. } => {
+ BlockId::FoldedBuffer(first_excerpt.buffer_id())
+ }
Block::BufferHeader {
excerpt: next_excerpt,
..
- } => BlockId::ExcerptBoundary(next_excerpt.id),
+ } => BlockId::ExcerptBoundary(next_excerpt.start_anchor),
Block::Spacer { id, .. } => BlockId::Spacer(*id),
}
}
@@ -1174,10 +1174,10 @@ impl BlockMap {
let wrap_row = wrap_row_for(Point::new(excerpt_boundary.row.0, 0), Bias::Left);
let new_buffer_id = match (&excerpt_boundary.prev, &excerpt_boundary.next) {
- (None, next) => Some(next.buffer_id),
+ (None, next) => Some(next.buffer_id()),
(Some(prev), next) => {
- if prev.buffer_id != next.buffer_id {
- Some(next.buffer_id)
+ if prev.buffer_id() != next.buffer_id() {
+ Some(next.buffer_id())
} else {
None
}
@@ -1195,7 +1195,7 @@ impl BlockMap {
let mut last_excerpt_end_row = first_excerpt.end_row;
while let Some(next_boundary) = boundaries.peek() {
- if next_boundary.next.buffer_id == new_buffer_id {
+ if next_boundary.next.buffer_id() == new_buffer_id {
last_excerpt_end_row = next_boundary.next.end_row;
} else {
break;
@@ -1254,12 +1254,24 @@ impl BlockMap {
let our_buffer = wrap_snapshot.buffer_snapshot();
let companion_buffer = companion_snapshot.buffer_snapshot();
- let patches = companion.convert_rows_to_companion(
+ let range = match bounds {
+ (Bound::Included(start), Bound::Excluded(end)) => start..end,
+ (Bound::Included(start), Bound::Unbounded) => start..wrap_snapshot.buffer().max_point(),
+ _ => unreachable!(),
+ };
+ let mut patches = companion.convert_rows_to_companion(
display_map_id,
companion_buffer,
our_buffer,
- bounds,
+ range,
);
+ if let Some(patch) = patches.last()
+ && let Bound::Excluded(end) = bounds.1
+ && end == wrap_snapshot.buffer().max_point()
+ && patch.source_excerpt_range.is_empty()
+ {
+ patches.pop();
+ }
let mut our_inlay_point_cursor = wrap_snapshot.inlay_point_cursor();
let mut our_fold_point_cursor = wrap_snapshot.fold_point_cursor();
@@ -1391,18 +1403,15 @@ impl BlockMap {
}
}
- // Main loop: process one hunk/group at a time, possibly inserting spacers before and after.
while let Some(source_point) = source_points.next() {
let mut current_boundary = source_point;
let current_range = excerpt.patch.edit_for_old_position(current_boundary).new;
- // This can only occur at the end of an excerpt.
if current_boundary.column > 0 {
debug_assert_eq!(current_boundary, excerpt.source_excerpt_range.end);
break;
}
- // Align the two sides at the start of this group.
let (delta_at_start, mut spacer_at_start) = determine_spacer(
&mut our_wrapper,
&mut companion_wrapper,
@@ -1434,7 +1443,6 @@ impl BlockMap {
source_points.next();
}
- // This can only occur at the end of an excerpt.
if current_boundary.column > 0 {
debug_assert_eq!(current_boundary, excerpt.source_excerpt_range.end);
break;
@@ -1538,7 +1546,8 @@ impl BlockMap {
| Block::BufferHeader {
excerpt: excerpt_b, ..
},
- ) => Some(excerpt_a.id).cmp(&Some(excerpt_b.id)),
+ ) => Some(excerpt_a.start_text_anchor().opaque_id())
+ .cmp(&Some(excerpt_b.start_text_anchor().opaque_id())),
(
Block::ExcerptBoundary { .. } | Block::BufferHeader { .. },
Block::Spacer { .. } | Block::Custom(_),
@@ -2042,7 +2051,7 @@ impl BlockMapWriter<'_> {
} else {
self.block_map.folded_buffers.remove(&buffer_id);
}
- ranges.extend(multi_buffer.excerpt_ranges_for_buffer(buffer_id, cx));
+ ranges.extend(multi_buffer.range_for_buffer(buffer_id, cx));
if let Some(companion) = &self.companion
&& companion.inverse.is_some()
{
@@ -2268,14 +2277,16 @@ impl BlockSnapshot {
let custom_block = self.custom_blocks_by_id.get(&custom_block_id)?;
return Some(Block::Custom(custom_block.clone()));
}
- BlockId::ExcerptBoundary(next_excerpt_id) => {
- let excerpt_range = buffer.range_for_excerpt(next_excerpt_id)?;
- self.wrap_snapshot
- .make_wrap_point(excerpt_range.start, Bias::Left)
+ BlockId::ExcerptBoundary(start_anchor) => {
+ let start_point = start_anchor.to_point(&buffer);
+ self.wrap_snapshot.make_wrap_point(start_point, Bias::Left)
}
- BlockId::FoldedBuffer(excerpt_id) => self
- .wrap_snapshot
- .make_wrap_point(buffer.range_for_excerpt(excerpt_id)?.start, Bias::Left),
+ BlockId::FoldedBuffer(buffer_id) => self.wrap_snapshot.make_wrap_point(
+ buffer
+ .anchor_in_excerpt(buffer.excerpts_for_buffer(buffer_id).next()?.context.start)?
+ .to_point(buffer),
+ Bias::Left,
+ ),
BlockId::Spacer(_) => return None,
};
let wrap_row = wrap_point.row();
@@ -2571,7 +2582,7 @@ impl BlockChunks<'_> {
}
pub struct StickyHeaderExcerpt<'a> {
- pub excerpt: &'a ExcerptInfo,
+ pub excerpt: &'a ExcerptBoundaryInfo,
}
impl<'a> Iterator for BlockChunks<'a> {
@@ -3096,7 +3107,13 @@ mod tests {
);
multi_buffer
});
- let excerpt_ids = multi_buffer.read_with(cx, |mb, _| mb.excerpt_ids());
+ let excerpt_start_anchors = multi_buffer.read_with(cx, |mb, _| {
+ let snapshot = mb.snapshot(cx);
+ snapshot
+ .excerpts()
+ .map(|e| snapshot.anchor_in_excerpt(e.context.start).unwrap())
+ .collect::<Vec<_>>()
+ });
let font = test_font();
let font_size = px(14.);
@@ -3129,9 +3146,9 @@ mod tests {
assert_eq!(
blocks,
vec![
- (0..1, BlockId::ExcerptBoundary(excerpt_ids[0])), // path, header
- (3..4, BlockId::ExcerptBoundary(excerpt_ids[1])), // path, header
- (6..7, BlockId::ExcerptBoundary(excerpt_ids[2])), // path, header
+ (0..1, BlockId::ExcerptBoundary(excerpt_start_anchors[0])), // path, header
+ (3..4, BlockId::ExcerptBoundary(excerpt_start_anchors[1])), // path, header
+ (6..7, BlockId::ExcerptBoundary(excerpt_start_anchors[2])), // path, header
]
);
}
@@ -3447,13 +3464,13 @@ mod tests {
],
cx,
);
- assert_eq!(multibuffer.read(cx).excerpt_ids().len(), 6);
+ assert_eq!(multibuffer.read(cx).snapshot(cx).excerpts().count(), 6);
multibuffer
});
let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
let buffer_ids = buffer_snapshot
.excerpts()
- .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
+ .map(|excerpt| excerpt.context.start.buffer_id)
.dedup()
.collect::<Vec<_>>();
assert_eq!(buffer_ids.len(), 3);
@@ -3800,7 +3817,7 @@ mod tests {
let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
let buffer_ids = buffer_snapshot
.excerpts()
- .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
+ .map(|excerpt| excerpt.context.start.buffer_id)
.dedup()
.collect::<Vec<_>>();
assert_eq!(buffer_ids.len(), 1);
@@ -4008,17 +4025,16 @@ mod tests {
wrap_map.sync(tab_snapshot, tab_edits, cx)
});
let mut block_map = block_map.write(wraps_snapshot, wrap_edits, None);
- let (unfolded_buffers, folded_buffers) = buffer.read_with(cx, |buffer, _| {
- let folded_buffers: Vec<_> =
- block_map.block_map.folded_buffers.iter().cloned().collect();
- let mut unfolded_buffers = buffer.excerpt_buffer_ids();
- unfolded_buffers.dedup();
- log::debug!("All buffers {unfolded_buffers:?}");
- log::debug!("Folded buffers {folded_buffers:?}");
- unfolded_buffers.retain(|buffer_id| {
- !block_map.block_map.folded_buffers.contains(buffer_id)
- });
- (unfolded_buffers, folded_buffers)
+ let folded_buffers: Vec<_> =
+ block_map.block_map.folded_buffers.iter().cloned().collect();
+ let mut unfolded_buffers = buffer_snapshot
+ .buffer_ids_for_range(Anchor::Min..Anchor::Max)
+ .collect::<Vec<_>>();
+ unfolded_buffers.dedup();
+ log::debug!("All buffers {unfolded_buffers:?}");
+ log::debug!("Folded buffers {folded_buffers:?}");
+ unfolded_buffers.retain(|buffer_id| {
+ !block_map.block_map.folded_buffers.contains(buffer_id)
});
let mut folded_count = folded_buffers.len();
let mut unfolded_count = unfolded_buffers.len();
@@ -4039,12 +4055,14 @@ mod tests {
log::info!("Folding {buffer_to_fold:?}");
let related_excerpts = buffer_snapshot
.excerpts()
- .filter_map(|(excerpt_id, buffer, range)| {
- if buffer.remote_id() == buffer_to_fold {
+ .filter_map(|excerpt| {
+ if excerpt.context.start.buffer_id == buffer_to_fold {
Some((
- excerpt_id,
- buffer
- .text_for_range(range.context)
+ excerpt.context.start,
+ buffer_snapshot
+ .buffer_for_id(buffer_to_fold)
+ .unwrap()
+ .text_for_range(excerpt.context)
.collect::<String>(),
))
} else {
@@ -4518,7 +4536,7 @@ mod tests {
let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
let buffer_ids = buffer_snapshot
.excerpts()
- .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
+ .map(|excerpt| excerpt.context.start.buffer_id)
.dedup()
.collect::<Vec<_>>();
assert_eq!(buffer_ids.len(), 1);
@@ -4563,7 +4581,7 @@ mod tests {
let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
let buffer_ids = buffer_snapshot
.excerpts()
- .map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
+ .map(|excerpt| excerpt.context.start.buffer_id)
.dedup()
.collect::<Vec<_>>();
assert_eq!(buffer_ids.len(), 1);
@@ -4635,11 +4653,6 @@ mod tests {
let subscription =
rhs_multibuffer.update(cx, |rhs_multibuffer, _| rhs_multibuffer.subscribe());
- let lhs_excerpt_id =
- lhs_multibuffer.read_with(cx, |mb, cx| mb.snapshot(cx).excerpts().next().unwrap().0);
- let rhs_excerpt_id =
- rhs_multibuffer.read_with(cx, |mb, cx| mb.snapshot(cx).excerpts().next().unwrap().0);
-
let lhs_buffer_snapshot = cx.update(|cx| lhs_multibuffer.read(cx).snapshot(cx));
let (mut _lhs_inlay_map, lhs_inlay_snapshot) = InlayMap::new(lhs_buffer_snapshot);
let (mut _lhs_fold_map, lhs_fold_snapshot) = FoldMap::new(lhs_inlay_snapshot);
@@ -4661,13 +4674,11 @@ mod tests {
let rhs_entity_id = rhs_multibuffer.entity_id();
let companion = cx.new(|_| {
- let mut c = Companion::new(
+ Companion::new(
rhs_entity_id,
convert_rhs_rows_to_lhs,
convert_lhs_rows_to_rhs,
- );
- c.add_excerpt_mapping(lhs_excerpt_id, rhs_excerpt_id);
- c
+ )
});
let rhs_edits = Patch::new(vec![text::Edit {
@@ -363,7 +363,7 @@ pub struct ItemSummary {
impl Default for ItemSummary {
fn default() -> Self {
Self {
- range: Anchor::min()..Anchor::min(),
+ range: Anchor::Min..Anchor::Min,
}
}
}
@@ -185,16 +185,18 @@ impl FoldMapWriter<'_> {
continue;
}
+ let fold_range = buffer.anchor_after(range.start)..buffer.anchor_before(range.end);
// For now, ignore any ranges that span an excerpt boundary.
- let fold_range =
- FoldRange(buffer.anchor_after(range.start)..buffer.anchor_before(range.end));
- if fold_range.0.start.excerpt_id != fold_range.0.end.excerpt_id {
+ if buffer
+ .anchor_range_to_buffer_anchor_range(fold_range.clone())
+ .is_none()
+ {
continue;
}
folds.push(Fold {
id: FoldId(post_inc(&mut self.0.next_fold_id.0)),
- range: fold_range,
+ range: FoldRange(fold_range),
placeholder: fold_text,
});
@@ -510,7 +512,7 @@ impl FoldMap {
.snapshot
.folds
.cursor::<FoldRange>(&inlay_snapshot.buffer);
- folds_cursor.seek(&FoldRange(anchor..Anchor::max()), Bias::Left);
+ folds_cursor.seek(&FoldRange(anchor..Anchor::Max), Bias::Left);
let mut folds = iter::from_fn({
let inlay_snapshot = &inlay_snapshot;
@@ -1226,7 +1228,7 @@ impl DerefMut for FoldRange {
impl Default for FoldRange {
fn default() -> Self {
- Self(Anchor::min()..Anchor::max())
+ Self(Anchor::Min..Anchor::Max)
}
}
@@ -1262,10 +1264,10 @@ pub struct FoldSummary {
impl Default for FoldSummary {
fn default() -> Self {
Self {
- start: Anchor::min(),
- end: Anchor::max(),
- min_start: Anchor::max(),
- max_end: Anchor::min(),
+ start: Anchor::Min,
+ end: Anchor::Max,
+ min_start: Anchor::Max,
+ max_end: Anchor::Min,
count: 0,
}
}
@@ -1342,7 +1342,7 @@ mod tests {
use settings::SettingsStore;
use std::{cmp::Reverse, env, sync::Arc};
use sum_tree::TreeMap;
- use text::{Patch, Rope};
+ use text::{BufferId, Patch, Rope};
use util::RandomCharIter;
use util::post_inc;
@@ -1351,10 +1351,10 @@ mod tests {
assert_eq!(
Inlay::hint(
InlayId::Hint(0),
- Anchor::min(),
+ Anchor::Min,
&InlayHint {
label: InlayHintLabel::String("a".to_string()),
- position: text::Anchor::MIN,
+ position: text::Anchor::min_for_buffer(BufferId::new(1).unwrap()),
padding_left: false,
padding_right: false,
tooltip: None,
@@ -1371,10 +1371,10 @@ mod tests {
assert_eq!(
Inlay::hint(
InlayId::Hint(0),
- Anchor::min(),
+ Anchor::Min,
&InlayHint {
label: InlayHintLabel::String("a".to_string()),
- position: text::Anchor::MIN,
+ position: text::Anchor::min_for_buffer(BufferId::new(1).unwrap()),
padding_left: true,
padding_right: true,
tooltip: None,
@@ -1391,10 +1391,10 @@ mod tests {
assert_eq!(
Inlay::hint(
InlayId::Hint(0),
- Anchor::min(),
+ Anchor::Min,
&InlayHint {
label: InlayHintLabel::String(" a ".to_string()),
- position: text::Anchor::MIN,
+ position: text::Anchor::min_for_buffer(BufferId::new(1).unwrap()),
padding_left: false,
padding_right: false,
tooltip: None,
@@ -1411,10 +1411,10 @@ mod tests {
assert_eq!(
Inlay::hint(
InlayId::Hint(0),
- Anchor::min(),
+ Anchor::Min,
&InlayHint {
label: InlayHintLabel::String(" a ".to_string()),
- position: text::Anchor::MIN,
+ position: text::Anchor::min_for_buffer(BufferId::new(1).unwrap()),
padding_left: true,
padding_right: true,
tooltip: None,
@@ -1434,10 +1434,10 @@ mod tests {
assert_eq!(
Inlay::hint(
InlayId::Hint(0),
- Anchor::min(),
+ Anchor::Min,
&InlayHint {
label: InlayHintLabel::String("🎨".to_string()),
- position: text::Anchor::MIN,
+ position: text::Anchor::min_for_buffer(BufferId::new(1).unwrap()),
padding_left: true,
padding_right: true,
tooltip: None,
@@ -8,7 +8,7 @@ use language::point_from_lsp;
use multi_buffer::Anchor;
use project::{DocumentColor, InlayId};
use settings::Settings as _;
-use text::{Bias, BufferId, OffsetRangeExt as _};
+use text::{Bias, BufferId};
use ui::{App, Context, Window};
use util::post_inc;
@@ -160,9 +160,9 @@ impl Editor {
}
let buffers_to_query = self
- .visible_excerpts(true, cx)
- .into_values()
- .map(|(buffer, ..)| buffer)
+ .visible_buffers(cx)
+ .into_iter()
+ .filter(|buffer| self.is_lsp_relevant(buffer.read(cx).file(), cx))
.chain(buffer_id.and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id)))
.filter(|editor_buffer| {
let editor_buffer_id = editor_buffer.read(cx).remote_id();
@@ -184,9 +184,9 @@ impl Editor {
buffers_to_query
.into_iter()
.filter_map(|buffer| {
- let buffer_id = buffer.read(cx).remote_id();
+ let buffer_snapshot = buffer.read(cx).snapshot();
let colors_task = lsp_store.document_colors(buffer, cx)?;
- Some(async move { (buffer_id, colors_task.await) })
+ Some(async move { (buffer_snapshot, colors_task.await) })
})
.collect::<Vec<_>>()
})
@@ -200,40 +200,21 @@ impl Editor {
if all_colors.is_empty() {
return;
}
- let Ok((multi_buffer_snapshot, editor_excerpts)) = editor.update(cx, |editor, cx| {
- let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
- let editor_excerpts = multi_buffer_snapshot.excerpts().fold(
- HashMap::default(),
- |mut acc, (excerpt_id, buffer_snapshot, excerpt_range)| {
- let excerpt_data = acc
- .entry(buffer_snapshot.remote_id())
- .or_insert_with(Vec::new);
- let excerpt_point_range =
- excerpt_range.context.to_point_utf16(buffer_snapshot);
- excerpt_data.push((
- excerpt_id,
- buffer_snapshot.clone(),
- excerpt_point_range,
- ));
- acc
- },
- );
- (multi_buffer_snapshot, editor_excerpts)
- }) else {
+ let Some(multi_buffer_snapshot) = editor
+ .update(cx, |editor, cx| editor.buffer.read(cx).snapshot(cx))
+ .ok()
+ else {
return;
};
let mut new_editor_colors: HashMap<BufferId, Vec<(Range<Anchor>, DocumentColor)>> =
HashMap::default();
- for (buffer_id, colors) in all_colors {
- let Some(excerpts) = editor_excerpts.get(&buffer_id) else {
- continue;
- };
+ for (buffer_snapshot, colors) in all_colors {
match colors {
Ok(colors) => {
if colors.colors.is_empty() {
new_editor_colors
- .entry(buffer_id)
+ .entry(buffer_snapshot.remote_id())
.or_insert_with(Vec::new)
.clear();
} else {
@@ -241,41 +222,33 @@ impl Editor {
let color_start = point_from_lsp(color.lsp_range.start);
let color_end = point_from_lsp(color.lsp_range.end);
- for (excerpt_id, buffer_snapshot, excerpt_range) in excerpts {
- if !excerpt_range.contains(&color_start.0)
- || !excerpt_range.contains(&color_end.0)
- {
- continue;
- }
- let start = buffer_snapshot.anchor_before(
- buffer_snapshot.clip_point_utf16(color_start, Bias::Left),
- );
- let end = buffer_snapshot.anchor_after(
- buffer_snapshot.clip_point_utf16(color_end, Bias::Right),
- );
- let Some(range) = multi_buffer_snapshot
- .anchor_range_in_excerpt(*excerpt_id, start..end)
- else {
- continue;
- };
-
- let new_buffer_colors =
- new_editor_colors.entry(buffer_id).or_insert_with(Vec::new);
-
- let (Ok(i) | Err(i)) =
- new_buffer_colors.binary_search_by(|(probe, _)| {
- probe
- .start
- .cmp(&range.start, &multi_buffer_snapshot)
- .then_with(|| {
- probe
- .end
- .cmp(&range.end, &multi_buffer_snapshot)
- })
- });
- new_buffer_colors.insert(i, (range, color));
- break;
- }
+ let Some(range) = multi_buffer_snapshot
+ .buffer_anchor_range_to_anchor_range(
+ buffer_snapshot.anchor_range_outside(
+ buffer_snapshot
+ .clip_point_utf16(color_start, Bias::Left)
+ ..buffer_snapshot
+ .clip_point_utf16(color_end, Bias::Right),
+ ),
+ )
+ else {
+ continue;
+ };
+
+ let new_buffer_colors = new_editor_colors
+ .entry(buffer_snapshot.remote_id())
+ .or_insert_with(Vec::new);
+
+ let (Ok(i) | Err(i)) =
+ new_buffer_colors.binary_search_by(|(probe, _)| {
+ probe
+ .start
+ .cmp(&range.start, &multi_buffer_snapshot)
+ .then_with(|| {
+ probe.end.cmp(&range.end, &multi_buffer_snapshot)
+ })
+ });
+ new_buffer_colors.insert(i, (range, color));
}
}
}
@@ -62,10 +62,10 @@ impl Editor {
multi_buffer_snapshot: &MultiBufferSnapshot,
cx: &Context<Self>,
) -> bool {
- let Some(excerpt) = multi_buffer_snapshot.excerpt_containing(cursor..cursor) else {
+ let Some((anchor, _)) = multi_buffer_snapshot.anchor_to_buffer_anchor(cursor) else {
return false;
};
- let Some(buffer) = self.buffer.read(cx).buffer(excerpt.buffer_id()) else {
+ let Some(buffer) = self.buffer.read(cx).buffer(anchor.buffer_id) else {
return false;
};
lsp_symbols_enabled(buffer.read(cx), cx)
@@ -77,19 +77,12 @@ impl Editor {
&self,
cursor: Anchor,
multi_buffer_snapshot: &MultiBufferSnapshot,
- cx: &Context<Self>,
+ _cx: &Context<Self>,
) -> Option<(BufferId, Vec<OutlineItem<Anchor>>)> {
- let excerpt = multi_buffer_snapshot.excerpt_containing(cursor..cursor)?;
- let excerpt_id = excerpt.id();
- let buffer_id = excerpt.buffer_id();
- if Some(buffer_id) != cursor.text_anchor.buffer_id {
- return None;
- }
- let buffer = self.buffer.read(cx).buffer(buffer_id)?;
- let buffer_snapshot = buffer.read(cx).snapshot();
- let cursor_text_anchor = cursor.text_anchor;
-
- let all_items = self.lsp_document_symbols.get(&buffer_id)?;
+ let (cursor_text_anchor, buffer) = multi_buffer_snapshot.anchor_to_buffer_anchor(cursor)?;
+ let all_items = self
+ .lsp_document_symbols
+ .get(&cursor_text_anchor.buffer_id)?;
if all_items.is_empty() {
return None;
}
@@ -97,34 +90,36 @@ impl Editor {
let mut symbols = all_items
.iter()
.filter(|item| {
- item.range
- .start
- .cmp(&cursor_text_anchor, &buffer_snapshot)
- .is_le()
- && item
- .range
- .end
- .cmp(&cursor_text_anchor, &buffer_snapshot)
- .is_ge()
+ item.range.start.cmp(&cursor_text_anchor, buffer).is_le()
+ && item.range.end.cmp(&cursor_text_anchor, buffer).is_ge()
})
- .map(|item| OutlineItem {
- depth: item.depth,
- range: Anchor::range_in_buffer(excerpt_id, item.range.clone()),
- source_range_for_text: Anchor::range_in_buffer(
- excerpt_id,
- item.source_range_for_text.clone(),
- ),
- text: item.text.clone(),
- highlight_ranges: item.highlight_ranges.clone(),
- name_ranges: item.name_ranges.clone(),
- body_range: item
- .body_range
- .as_ref()
- .map(|r| Anchor::range_in_buffer(excerpt_id, r.clone())),
- annotation_range: item
- .annotation_range
- .as_ref()
- .map(|r| Anchor::range_in_buffer(excerpt_id, r.clone())),
+ .filter_map(|item| {
+ let range_start = multi_buffer_snapshot.anchor_in_buffer(item.range.start)?;
+ let range_end = multi_buffer_snapshot.anchor_in_buffer(item.range.end)?;
+ let source_range_for_text_start =
+ multi_buffer_snapshot.anchor_in_buffer(item.source_range_for_text.start)?;
+ let source_range_for_text_end =
+ multi_buffer_snapshot.anchor_in_buffer(item.source_range_for_text.end)?;
+ Some(OutlineItem {
+ depth: item.depth,
+ range: range_start..range_end,
+ source_range_for_text: source_range_for_text_start..source_range_for_text_end,
+ text: item.text.clone(),
+ highlight_ranges: item.highlight_ranges.clone(),
+ name_ranges: item.name_ranges.clone(),
+ body_range: item.body_range.as_ref().and_then(|r| {
+ Some(
+ multi_buffer_snapshot.anchor_in_buffer(r.start)?
+ ..multi_buffer_snapshot.anchor_in_buffer(r.end)?,
+ )
+ }),
+ annotation_range: item.annotation_range.as_ref().and_then(|r| {
+ Some(
+ multi_buffer_snapshot.anchor_in_buffer(r.start)?
+ ..multi_buffer_snapshot.anchor_in_buffer(r.end)?,
+ )
+ }),
+ })
})
.collect::<Vec<_>>();
@@ -135,7 +130,7 @@ impl Editor {
retain
});
- Some((buffer_id, symbols))
+ Some((buffer.remote_id(), symbols))
}
/// Fetches document symbols from the LSP for buffers that have the setting
@@ -155,9 +150,10 @@ impl Editor {
};
let buffers_to_query = self
- .visible_excerpts(true, cx)
+ .visible_buffers(cx)
.into_iter()
- .filter_map(|(_, (buffer, _, _))| {
+ .filter(|buffer| self.is_lsp_relevant(buffer.read(cx).file(), cx))
+ .filter_map(|buffer| {
let id = buffer.read(cx).remote_id();
if for_buffer.is_none_or(|target| target == id)
&& lsp_symbols_enabled(buffer.read(cx), cx)
@@ -7,7 +7,7 @@ use gpui::{
use indoc::indoc;
use language::EditPredictionsMode;
use language::{Buffer, CodeLabel};
-use multi_buffer::{Anchor, ExcerptId, MultiBufferSnapshot, ToPoint};
+use multi_buffer::{Anchor, MultiBufferSnapshot, ToPoint};
use project::{Completion, CompletionResponse, CompletionSource};
use std::{
ops::Range,
@@ -1242,15 +1242,14 @@ struct FakeCompletionMenuProvider;
impl CompletionProvider for FakeCompletionMenuProvider {
fn completions(
&self,
- _excerpt_id: ExcerptId,
- _buffer: &Entity<Buffer>,
+ buffer: &Entity<Buffer>,
_buffer_position: text::Anchor,
_trigger: CompletionContext,
_window: &mut Window,
- _cx: &mut Context<crate::Editor>,
+ cx: &mut Context<crate::Editor>,
) -> Task<anyhow::Result<Vec<CompletionResponse>>> {
let completion = Completion {
- replace_range: text::Anchor::MIN..text::Anchor::MAX,
+ replace_range: text::Anchor::min_max_range_for_buffer(buffer.read(cx).remote_id()),
new_text: "fake_completion".to_string(),
label: CodeLabel::plain("fake_completion".to_string(), None),
documentation: None,
@@ -76,7 +76,7 @@ pub use linked_editing_ranges::LinkedEdits;
pub use lsp::CompletionContext;
pub use lsp_ext::lsp_tasks;
pub use multi_buffer::{
- Anchor, AnchorRangeExt, BufferOffset, ExcerptId, ExcerptRange, MBTextSummary, MultiBuffer,
+ Anchor, AnchorRangeExt, BufferOffset, ExcerptRange, MBTextSummary, MultiBuffer,
MultiBufferOffset, MultiBufferOffsetUtf16, MultiBufferSnapshot, PathKey, RowInfo, ToOffset,
ToPoint,
};
@@ -150,7 +150,8 @@ use markdown::Markdown;
use mouse_context_menu::MouseContextMenu;
use movement::TextLayoutDetails;
use multi_buffer::{
- ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
+ ExcerptBoundaryInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint,
+ MultiBufferRow,
};
use parking_lot::Mutex;
use persistence::EditorDb;
@@ -640,6 +641,7 @@ pub(crate) enum EditDisplayMode {
enum EditPrediction {
Edit {
+ // TODO could be a language::Anchor?
edits: Vec<(Range<Anchor>, Arc<str>)>,
/// Predicted cursor position as (anchor, offset_from_anchor).
/// The anchor is in multibuffer coordinates; after applying edits,
@@ -887,7 +889,8 @@ pub trait Addon: 'static {
fn render_buffer_header_controls(
&self,
- _: &ExcerptInfo,
+ _: &ExcerptBoundaryInfo,
+ _: &language::BufferSnapshot,
_: &Window,
_: &App,
) -> Option<AnyElement> {
@@ -1340,7 +1343,7 @@ pub struct Editor {
suppress_selection_callback: bool,
applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
accent_data: Option<AccentData>,
- bracket_fetched_tree_sitter_chunks: HashMap<ExcerptId, HashSet<Range<BufferRow>>>,
+ bracket_fetched_tree_sitter_chunks: HashMap<Range<text::Anchor>, HashSet<Range<BufferRow>>>,
semantic_token_state: SemanticTokenState,
pub(crate) refresh_matching_bracket_highlights_task: Task<()>,
refresh_document_symbols_task: Shared<Task<()>>,
@@ -1763,15 +1766,13 @@ impl ClipboardSelection {
project.absolute_path(&project_path, cx)
});
- let line_range = file_path.as_ref().and_then(|_| {
- let (_, start_point, start_excerpt_id) = buffer.point_to_buffer_point(range.start)?;
- let (_, end_point, end_excerpt_id) = buffer.point_to_buffer_point(range.end)?;
- if start_excerpt_id == end_excerpt_id {
- Some(start_point.row..=end_point.row)
- } else {
- None
- }
- });
+ let line_range = if file_path.is_some() {
+ buffer
+ .range_to_buffer_range(range)
+ .map(|(_, buffer_range)| buffer_range.start.row..=buffer_range.end.row)
+ } else {
+ None
+ };
Self {
len,
@@ -1852,9 +1853,8 @@ pub enum JumpData {
line_offset_from_top: u32,
},
MultiBufferPoint {
- excerpt_id: ExcerptId,
+ anchor: language::Anchor,
position: Point,
- anchor: text::Anchor,
line_offset_from_top: u32,
},
}
@@ -1990,17 +1990,21 @@ impl Editor {
if !self.mode.is_full() {
return;
}
- let multi_buffer = display_snapshot.buffer_snapshot();
+ let multi_buffer = display_snapshot.buffer_snapshot().clone();
let scroll_anchor = self
.scroll_manager
.native_anchor(display_snapshot, cx)
.anchor;
- let Some((excerpt_id, _, buffer)) = multi_buffer.as_singleton() else {
+ let Some(buffer_snapshot) = multi_buffer.as_singleton() else {
return;
};
- let buffer = buffer.clone();
- let buffer_visible_start = scroll_anchor.text_anchor.to_point(&buffer);
+ let buffer = buffer_snapshot.clone();
+ let Some((buffer_visible_start, _)) = multi_buffer.anchor_to_buffer_anchor(scroll_anchor)
+ else {
+ return;
+ };
+ let buffer_visible_start = buffer_visible_start.to_point(&buffer);
let max_row = buffer.max_point().row;
let start_row = buffer_visible_start.row.min(max_row);
let end_row = (buffer_visible_start.row + 10).min(max_row);
@@ -2014,22 +2018,24 @@ impl Editor {
Some(syntax.as_ref()),
)
.into_iter()
- .map(|outline_item| OutlineItem {
- depth: outline_item.depth,
- range: Anchor::range_in_buffer(excerpt_id, outline_item.range),
- source_range_for_text: Anchor::range_in_buffer(
- excerpt_id,
- outline_item.source_range_for_text,
- ),
- text: outline_item.text,
- highlight_ranges: outline_item.highlight_ranges,
- name_ranges: outline_item.name_ranges,
- body_range: outline_item
- .body_range
- .map(|range| Anchor::range_in_buffer(excerpt_id, range)),
- annotation_range: outline_item
- .annotation_range
- .map(|range| Anchor::range_in_buffer(excerpt_id, range)),
+ .filter_map(|outline_item| {
+ Some(OutlineItem {
+ depth: outline_item.depth,
+ range: multi_buffer
+ .buffer_anchor_range_to_anchor_range(outline_item.range)?,
+ source_range_for_text: multi_buffer.buffer_anchor_range_to_anchor_range(
+ outline_item.source_range_for_text,
+ )?,
+ text: outline_item.text,
+ highlight_ranges: outline_item.highlight_ranges,
+ name_ranges: outline_item.name_ranges,
+ body_range: outline_item.body_range.and_then(|range| {
+ multi_buffer.buffer_anchor_range_to_anchor_range(range)
+ }),
+ annotation_range: outline_item.annotation_range.and_then(|range| {
+ multi_buffer.buffer_anchor_range_to_anchor_range(range)
+ }),
+ })
})
.collect()
});
@@ -3024,7 +3030,10 @@ impl Editor {
fn edit_prediction_cursor_popover_prefers_preview(
&self,
completion: &EditPredictionState,
+ cx: &App,
) -> bool {
+ let multibuffer_snapshot = self.buffer.read(cx).snapshot(cx);
+
match &completion.completion {
EditPrediction::Edit {
edits, snapshot, ..
@@ -3033,8 +3042,13 @@ impl Editor {
let mut end_row: Option<u32> = None;
for (range, text) in edits {
- let edit_start_row = range.start.text_anchor.to_point(snapshot).row;
- let old_end_row = range.end.text_anchor.to_point(snapshot).row;
+ let Some((_, range)) =
+ multibuffer_snapshot.anchor_range_to_buffer_anchor_range(range.clone())
+ else {
+ continue;
+ };
+ let edit_start_row = range.start.to_point(snapshot).row;
+ let old_end_row = range.end.to_point(snapshot).row;
let inserted_newline_count = text
.as_ref()
.chars()
@@ -3083,7 +3097,7 @@ impl Editor {
.active_edit_prediction
.as_ref()
.filter(|completion| {
- self.edit_prediction_cursor_popover_prefers_preview(completion)
+ self.edit_prediction_cursor_popover_prefers_preview(completion, cx)
})
.map_or(EditPredictionKeybindAction::Accept, |_| {
EditPredictionKeybindAction::Preview
@@ -3320,13 +3334,12 @@ impl Editor {
self.buffer.read(cx).read(cx).file_at(point).cloned()
}
- pub fn active_excerpt(
- &self,
- cx: &App,
- ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
- self.buffer
- .read(cx)
- .excerpt_containing(self.selections.newest_anchor().head(), cx)
+ pub fn active_buffer(&self, cx: &App) -> Option<Entity<Buffer>> {
+ let multibuffer = self.buffer.read(cx);
+ let snapshot = multibuffer.snapshot(cx);
+ let (anchor, _) =
+ snapshot.anchor_to_buffer_anchor(self.selections.newest_anchor().head())?;
+ multibuffer.buffer(anchor.buffer_id)
}
pub fn mode(&self) -> &EditorMode {
@@ -3695,8 +3708,8 @@ impl Editor {
}
if local {
- if let Some(buffer_id) = new_cursor_position.text_anchor.buffer_id {
- self.register_buffer(buffer_id, cx);
+ if let Some((anchor, _)) = buffer.anchor_to_buffer_anchor(new_cursor_position) {
+ self.register_buffer(anchor.buffer_id, cx);
}
let mut context_menu = self.context_menu.borrow_mut();
@@ -3778,12 +3791,13 @@ impl Editor {
if selections.len() == 1 {
cx.emit(SearchEvent::ActiveMatchChanged)
}
- if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
+ if local && let Some(buffer_snapshot) = buffer.as_singleton() {
let inmemory_selections = selections
.iter()
.map(|s| {
- text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
- ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
+ let start = s.range().start.text_anchor_in(buffer_snapshot);
+ let end = s.range().end.text_anchor_in(buffer_snapshot);
+ (start..end).to_point(buffer_snapshot)
})
.collect();
self.update_restoration_data(cx, |data| {
@@ -3829,7 +3843,6 @@ impl Editor {
fn folds_did_change(&mut self, cx: &mut Context<Self>) {
use text::ToOffset as _;
- use text::ToPoint as _;
if self.mode.is_minimap()
|| WorkspaceSettings::get(None, cx).restore_on_startup
@@ -3838,21 +3851,18 @@ impl Editor {
return;
}
- if !self.buffer().read(cx).is_singleton() {
- return;
- }
-
let display_snapshot = self
.display_map
.update(cx, |display_map, cx| display_map.snapshot(cx));
- let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
+ let Some(buffer_snapshot) = display_snapshot.buffer_snapshot().as_singleton() else {
return;
};
let inmemory_folds = display_snapshot
.folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
.map(|fold| {
- fold.range.start.text_anchor.to_point(&snapshot)
- ..fold.range.end.text_anchor.to_point(&snapshot)
+ let start = fold.range.start.text_anchor_in(buffer_snapshot);
+ let end = fold.range.end.text_anchor_in(buffer_snapshot);
+ (start..end).to_point(buffer_snapshot)
})
.collect();
self.update_restoration_data(cx, |data| {
@@ -3876,8 +3886,16 @@ impl Editor {
let db_folds = display_snapshot
.folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
.map(|fold| {
- let start = fold.range.start.text_anchor.to_offset(&snapshot);
- let end = fold.range.end.text_anchor.to_offset(&snapshot);
+ let start = fold
+ .range
+ .start
+ .text_anchor_in(buffer_snapshot)
+ .to_offset(buffer_snapshot);
+ let end = fold
+ .range
+ .end
+ .text_anchor_in(buffer_snapshot)
+ .to_offset(buffer_snapshot);
// Extract fingerprints - content at fold boundaries for validation on restore
// Both fingerprints must be INSIDE the fold to avoid capturing surrounding
@@ -3886,12 +3904,14 @@ impl Editor {
// end_fp: last min(32, fold_len) bytes of fold content
// Clip to character boundaries to handle multibyte UTF-8 characters.
let fold_len = end - start;
- let start_fp_end = snapshot
+ let start_fp_end = buffer_snapshot
.clip_offset(start + std::cmp::min(FINGERPRINT_LEN, fold_len), Bias::Left);
- let start_fp: String = snapshot.text_for_range(start..start_fp_end).collect();
- let end_fp_start = snapshot
+ let start_fp: String = buffer_snapshot
+ .text_for_range(start..start_fp_end)
+ .collect();
+ let end_fp_start = buffer_snapshot
.clip_offset(end.saturating_sub(FINGERPRINT_LEN).max(start), Bias::Right);
- let end_fp: String = snapshot.text_for_range(end_fp_start..end).collect();
+ let end_fp: String = buffer_snapshot.text_for_range(end_fp_start..end).collect();
(start, end, start_fp, end_fp)
})
@@ -4654,30 +4674,31 @@ impl Editor {
fn linked_editing_ranges_for(
&self,
- selection: Range<text::Anchor>,
+ query_range: Range<text::Anchor>,
cx: &App,
) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
+ use text::ToOffset as TO;
+
if self.linked_edit_ranges.is_empty() {
return None;
}
- let ((base_range, linked_ranges), buffer_snapshot, buffer) =
- selection.end.buffer_id.and_then(|end_buffer_id| {
- if selection.start.buffer_id != Some(end_buffer_id) {
- return None;
- }
- let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
- let snapshot = buffer.read(cx).snapshot();
- self.linked_edit_ranges
- .get(end_buffer_id, selection.start..selection.end, &snapshot)
- .map(|ranges| (ranges, snapshot, buffer))
- })?;
- use text::ToOffset as TO;
+ if query_range.start.buffer_id != query_range.end.buffer_id {
+ return None;
+ };
+ let multibuffer_snapshot = self.buffer.read(cx).snapshot(cx);
+ let buffer = self.buffer.read(cx).buffer(query_range.end.buffer_id)?;
+ let buffer_snapshot = buffer.read(cx).snapshot();
+ let (base_range, linked_ranges) = self.linked_edit_ranges.get(
+ buffer_snapshot.remote_id(),
+ query_range.clone(),
+ &buffer_snapshot,
+ )?;
// find offset from the start of current range to current cursor position
let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
- let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
+ let start_offset = TO::to_offset(&query_range.start, &buffer_snapshot);
let start_difference = start_offset - start_byte_offset;
- let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
+ let end_offset = TO::to_offset(&query_range.end, &buffer_snapshot);
let end_difference = end_offset - start_byte_offset;
// Current range has associated linked ranges.
@@ -4690,13 +4711,22 @@ impl Editor {
continue;
}
if self.selections.disjoint_anchor_ranges().any(|s| {
- if s.start.text_anchor.buffer_id != selection.start.buffer_id
- || s.end.text_anchor.buffer_id != selection.end.buffer_id
+ let Some((selection_start, _)) =
+ multibuffer_snapshot.anchor_to_buffer_anchor(s.start)
+ else {
+ return false;
+ };
+ let Some((selection_end, _)) = multibuffer_snapshot.anchor_to_buffer_anchor(s.end)
+ else {
+ return false;
+ };
+ if selection_start.buffer_id != query_range.start.buffer_id
+ || selection_end.buffer_id != query_range.end.buffer_id
{
return false;
}
- TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
- && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
+ TO::to_offset(&selection_start, &buffer_snapshot) <= end_offset
+ && TO::to_offset(&selection_end, &buffer_snapshot) >= start_offset
}) {
continue;
}
@@ -5015,21 +5045,26 @@ impl Editor {
if !self.linked_edit_ranges.is_empty() {
let start_anchor = snapshot.anchor_before(selection.start);
+ let classifier = snapshot
+ .char_classifier_at(start_anchor)
+ .scope_context(Some(CharScopeContext::LinkedEdit));
- let is_word_char = text.chars().next().is_none_or(|char| {
- let classifier = snapshot
- .char_classifier_at(start_anchor.to_offset(&snapshot))
- .scope_context(Some(CharScopeContext::LinkedEdit));
- classifier.is_word(char)
- });
- let is_dot = text.as_ref() == ".";
- let should_apply_linked_edit = is_word_char || is_dot;
+ if let Some((_, anchor_range)) =
+ snapshot.anchor_range_to_buffer_anchor_range(start_anchor..anchor)
+ {
+ let is_word_char = text
+ .chars()
+ .next()
+ .is_none_or(|char| classifier.is_word(char));
- if should_apply_linked_edit {
- let anchor_range = start_anchor.text_anchor..anchor.text_anchor;
- linked_edits.push(&self, anchor_range, text.clone(), cx);
- } else {
- clear_linked_edit_ranges = true;
+ let is_dot = text.as_ref() == ".";
+ let should_apply_linked_edit = is_word_char || is_dot;
+
+ if should_apply_linked_edit {
+ linked_edits.push(&self, anchor_range, text.clone(), cx);
+ } else {
+ clear_linked_edit_ranges = true;
+ }
}
}
@@ -5522,7 +5557,7 @@ impl Editor {
let row = cursor.row;
let point = Point::new(row, 0);
- let Some((buffer_handle, buffer_point, _)) =
+ let Some((buffer_handle, buffer_point)) =
self.buffer.read(cx).point_to_buffer_point(point, cx)
else {
continue;
@@ -5662,12 +5697,16 @@ impl Editor {
/// Collects linked edits for the current selections, pairing each linked
/// range with `text`.
pub fn linked_edits_for_selections(&self, text: Arc<str>, cx: &App) -> LinkedEdits {
+ let multibuffer_snapshot = self.buffer().read(cx).snapshot(cx);
let mut linked_edits = LinkedEdits::new();
if !self.linked_edit_ranges.is_empty() {
for selection in self.selections.disjoint_anchors() {
- let start = selection.start.text_anchor;
- let end = selection.end.text_anchor;
- linked_edits.push(self, start..end, text.clone(), cx);
+ let Some((_, range)) =
+ multibuffer_snapshot.anchor_range_to_buffer_anchor_range(selection.range())
+ else {
+ continue;
+ };
+ linked_edits.push(self, range, text.clone(), cx);
}
}
linked_edits
@@ -5898,53 +5937,54 @@ impl Editor {
}
}
- pub fn visible_excerpts(
- &self,
- lsp_related_only: bool,
- cx: &mut Context<Editor>,
- ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
- let project = self.project().cloned();
- let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
+ pub fn is_lsp_relevant(&self, file: Option<&Arc<dyn language::File>>, cx: &App) -> bool {
+ let Some(project) = self.project() else {
+ return false;
+ };
+ let Some(buffer_file) = project::File::from_dyn(file) else {
+ return false;
+ };
+ let Some(entry_id) = buffer_file.project_entry_id() else {
+ return false;
+ };
+ let project = project.read(cx);
+ let Some(buffer_worktree) = project.worktree_for_id(buffer_file.worktree_id(cx), cx) else {
+ return false;
+ };
+ let Some(worktree_entry) = buffer_worktree.read(cx).entry_for_id(entry_id) else {
+ return false;
+ };
+ !worktree_entry.is_ignored
+ }
+
+ pub fn visible_buffers(&self, cx: &mut Context<Editor>) -> Vec<Entity<Buffer>> {
+ let display_snapshot = self.display_snapshot(cx);
+ let visible_range = self.multi_buffer_visible_range(&display_snapshot, cx);
let multi_buffer = self.buffer().read(cx);
- let multi_buffer_snapshot = multi_buffer.snapshot(cx);
- multi_buffer_snapshot
- .range_to_buffer_ranges(
- self.multi_buffer_visible_range(&display_snapshot, cx)
- .to_inclusive(),
- )
+ display_snapshot
+ .buffer_snapshot()
+ .range_to_buffer_ranges(visible_range)
.into_iter()
.filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
- .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
- if !lsp_related_only {
- return Some((
- excerpt_id,
- (
- multi_buffer.buffer(buffer.remote_id()).unwrap(),
- buffer.version().clone(),
- excerpt_visible_range.start.0..excerpt_visible_range.end.0,
- ),
- ));
- }
+ .filter_map(|(buffer_snapshot, _, _)| multi_buffer.buffer(buffer_snapshot.remote_id()))
+ .collect()
+ }
- let project = project.as_ref()?.read(cx);
- let buffer_file = project::File::from_dyn(buffer.file())?;
- let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
- let worktree_entry = buffer_worktree
- .read(cx)
- .entry_for_id(buffer_file.project_entry_id()?)?;
- if worktree_entry.is_ignored {
- None
- } else {
- Some((
- excerpt_id,
- (
- multi_buffer.buffer(buffer.remote_id()).unwrap(),
- buffer.version().clone(),
- excerpt_visible_range.start.0..excerpt_visible_range.end.0,
- ),
- ))
- }
- })
+ pub fn visible_buffer_ranges(
+ &self,
+ cx: &mut Context<Editor>,
+ ) -> Vec<(
+ BufferSnapshot,
+ Range<BufferOffset>,
+ ExcerptRange<text::Anchor>,
+ )> {
+ let display_snapshot = self.display_snapshot(cx);
+ let visible_range = self.multi_buffer_visible_range(&display_snapshot, cx);
+ display_snapshot
+ .buffer_snapshot()
+ .range_to_buffer_ranges(visible_range)
+ .into_iter()
+ .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
.collect()
}
@@ -6069,17 +6109,19 @@ impl Editor {
.newest_anchor()
.start
.bias_right(&multibuffer_snapshot);
- if position.diff_base_anchor.is_some() {
+
+ if position.diff_base_anchor().is_some() {
return;
}
- let buffer_position = multibuffer_snapshot.anchor_before(position);
- let Some(buffer) = buffer_position
- .text_anchor
- .buffer_id
- .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
+ let multibuffer_position = multibuffer_snapshot.anchor_before(position);
+ let Some((buffer_position, _)) =
+ multibuffer_snapshot.anchor_to_buffer_anchor(multibuffer_position)
else {
return;
};
+ let Some(buffer) = self.buffer.read(cx).buffer(buffer_position.buffer_id) else {
+ return;
+ };
let buffer_snapshot = buffer.read(cx).snapshot();
let menu_is_open = matches!(
@@ -6088,9 +6130,9 @@ impl Editor {
);
let language = buffer_snapshot
- .language_at(buffer_position.text_anchor)
+ .language_at(buffer_position)
.map(|language| language.name());
- let language_settings = multibuffer_snapshot.language_settings_at(buffer_position, cx);
+ let language_settings = multibuffer_snapshot.language_settings_at(multibuffer_position, cx);
let completion_settings = language_settings.completions.clone();
let show_completions_on_input = self
@@ -6101,7 +6143,7 @@ impl Editor {
}
let query: Option<Arc<String>> =
- Self::completion_query(&multibuffer_snapshot, buffer_position)
+ Self::completion_query(&multibuffer_snapshot, multibuffer_position)
.map(|query| query.into());
drop(multibuffer_snapshot);
@@ -6143,7 +6185,7 @@ impl Editor {
if filter_completions {
menu.filter(
query.clone().unwrap_or_default(),
- buffer_position.text_anchor,
+ buffer_position,
&buffer,
provider.clone(),
window,
@@ -6177,12 +6219,6 @@ impl Editor {
}
};
- let Anchor {
- excerpt_id: buffer_excerpt_id,
- text_anchor: buffer_position,
- ..
- } = buffer_position;
-
let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
buffer_snapshot.surrounding_word(buffer_position, None)
{
@@ -6225,7 +6261,7 @@ impl Editor {
trigger.as_ref().is_none_or(|trigger| {
provider.is_completion_trigger(
&buffer,
- position.text_anchor,
+ buffer_position,
trigger,
trigger_in_words,
cx,
@@ -6246,14 +6282,7 @@ impl Editor {
trigger_character,
};
- provider.completions(
- buffer_excerpt_id,
- &buffer,
- buffer_position,
- completion_context,
- window,
- cx,
- )
+ provider.completions(&buffer, buffer_position, completion_context, window, cx)
} else {
Task::ready(Ok(Vec::new()))
};
@@ -6593,42 +6622,42 @@ impl Editor {
cx.stop_propagation();
let buffer_handle = completions_menu.buffer.clone();
+ let multibuffer_snapshot = self.buffer.read(cx).snapshot(cx);
+ let (initial_position, _) =
+ multibuffer_snapshot.anchor_to_buffer_anchor(completions_menu.initial_position)?;
let CompletionEdit {
new_text,
snippet,
replace_range,
- } = process_completion_for_edit(
- &completion,
- intent,
- &buffer_handle,
- &completions_menu.initial_position.text_anchor,
- cx,
- );
+ } = process_completion_for_edit(&completion, intent, &buffer_handle, &initial_position, cx);
- let buffer = buffer_handle.read(cx);
- let snapshot = self.buffer.read(cx).snapshot(cx);
- let newest_anchor = self.selections.newest_anchor();
- let replace_range_multibuffer = {
- let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
- excerpt.map_range_from_buffer(replace_range.clone())
+ let buffer = buffer_handle.read(cx).snapshot();
+ let newest_selection = self.selections.newest_anchor();
+
+ let Some(replace_range_multibuffer) =
+ multibuffer_snapshot.buffer_anchor_range_to_anchor_range(replace_range.clone())
+ else {
+ return None;
};
- if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
+
+ let Some((buffer_snapshot, newest_range_buffer)) =
+ multibuffer_snapshot.anchor_range_to_buffer_anchor_range(newest_selection.range())
+ else {
return None;
- }
+ };
let old_text = buffer
.text_for_range(replace_range.clone())
.collect::<String>();
- let lookbehind = newest_anchor
+ let lookbehind = newest_range_buffer
.start
- .text_anchor
- .to_offset(buffer)
- .saturating_sub(replace_range.start.0);
+ .to_offset(buffer_snapshot)
+ .saturating_sub(replace_range.start.to_offset(&buffer_snapshot));
let lookahead = replace_range
.end
- .0
- .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
+ .to_offset(&buffer_snapshot)
+ .saturating_sub(newest_range_buffer.end.to_offset(&buffer));
let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
let suffix = &old_text[lookbehind.min(old_text.len())..];
@@ -6641,34 +6670,40 @@ impl Editor {
let text: Arc<str> = new_text.clone().into();
for selection in &selections {
- let range = if selection.id == newest_anchor.id {
+ let range = if selection.id == newest_selection.id {
replace_range_multibuffer.clone()
} else {
let mut range = selection.range();
// if prefix is present, don't duplicate it
- if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
+ if multibuffer_snapshot
+ .contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix)
+ {
range.start = range.start.saturating_sub_usize(lookbehind);
// if suffix is also present, mimic the newest cursor and replace it
- if selection.id != newest_anchor.id
- && snapshot.contains_str_at(range.end, suffix)
+ if selection.id != newest_selection.id
+ && multibuffer_snapshot.contains_str_at(range.end, suffix)
{
range.end += lookahead;
}
}
- range
+ range.to_anchors(&multibuffer_snapshot)
};
ranges.push(range.clone());
- let start_anchor = snapshot.anchor_before(range.start);
- let end_anchor = snapshot.anchor_after(range.end);
- let anchor_range = start_anchor.text_anchor..end_anchor.text_anchor;
- all_commit_ranges.push(anchor_range.clone());
+ let start_anchor = multibuffer_snapshot.anchor_before(range.start);
+ let end_anchor = multibuffer_snapshot.anchor_after(range.end);
- if !self.linked_edit_ranges.is_empty() {
- linked_edits.push(&self, anchor_range, text.clone(), cx);
+ if let Some((buffer_snapshot_2, anchor_range)) =
+ multibuffer_snapshot.anchor_range_to_buffer_anchor_range(start_anchor..end_anchor)
+ && buffer_snapshot_2.remote_id() == buffer_snapshot.remote_id()
+ {
+ all_commit_ranges.push(anchor_range.clone());
+ if !self.linked_edit_ranges.is_empty() {
+ linked_edits.push(&self, anchor_range, text.clone(), cx);
+ }
}
}
@@ -6687,8 +6722,12 @@ impl Editor {
let tx_id = self.transact(window, cx, |editor, window, cx| {
if let Some(mut snippet) = snippet {
snippet.text = new_text.to_string();
+ let offset_ranges = ranges
+ .iter()
+ .map(|range| range.to_offset(&multibuffer_snapshot))
+ .collect::<Vec<_>>();
editor
- .insert_snippet(&ranges, snippet, window, cx)
+ .insert_snippet(&offset_ranges, snippet, window, cx)
.log_err();
} else {
editor.buffer.update(cx, |multi_buffer, cx| {
@@ -6703,7 +6742,10 @@ impl Editor {
linked_edits.apply(cx);
editor.refresh_edit_prediction(true, false, window, cx);
});
- self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
+ self.invalidate_autoclose_regions(
+ &self.selections.disjoint_anchors_arc(),
+ &multibuffer_snapshot,
+ );
let show_new_completions_on_confirm = completion
.confirm
@@ -6739,7 +6781,7 @@ impl Editor {
if available_commands.contains(&lsp_command.command) {
Some(CodeAction {
server_id: *server_id,
- range: language::Anchor::MIN..language::Anchor::MIN,
+ range: language::Anchor::min_min_range_for_buffer(buffer.remote_id()),
lsp_action: LspAction::Command(lsp_command.clone()),
resolved: false,
})
@@ -7069,13 +7111,9 @@ impl Editor {
Some(Task::ready(Ok(())))
})
}
- CodeActionsItem::CodeAction {
- excerpt_id,
- action,
- provider,
- } => {
+ CodeActionsItem::CodeAction { action, provider } => {
let apply_code_action =
- provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
+ provider.apply_code_action(buffer, action, true, window, cx);
let workspace = workspace.downgrade();
Some(cx.spawn_in(window, async move |editor, cx| {
let project_transaction = apply_code_action.await?;
@@ -7175,17 +7213,19 @@ impl Editor {
// avoid opening a new editor to display them.
if let [(buffer, transaction)] = &*entries {
- let excerpt = editor.update(cx, |editor, cx| {
- editor
- .buffer()
- .read(cx)
- .excerpt_containing(editor.selections.newest_anchor().head(), cx)
+ let cursor_excerpt = editor.update(cx, |editor, cx| {
+ let snapshot = editor.buffer().read(cx).snapshot(cx);
+ let head = editor.selections.newest_anchor().head();
+ let (buffer_snapshot, excerpt_range) = snapshot.excerpt_containing(head..head)?;
+ if buffer_snapshot.remote_id() != buffer.read(cx).remote_id() {
+ return None;
+ }
+ Some(excerpt_range)
})?;
- if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
- && excerpted_buffer == *buffer
- {
+
+ if let Some(excerpt_range) = cursor_excerpt {
let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
- let excerpt_range = excerpt_range.to_offset(buffer);
+ let excerpt_range = excerpt_range.context.to_offset(buffer);
buffer
.edited_ranges_for_transaction::<usize>(transaction)
.all(|range| {
@@ -7207,15 +7247,21 @@ impl Editor {
.read(cx)
.edited_ranges_for_transaction::<Point>(transaction)
.collect::<Vec<_>>();
- let (ranges, _) = multibuffer.set_excerpts_for_path(
+ multibuffer.set_excerpts_for_path(
PathKey::for_buffer(buffer_handle, cx),
buffer_handle.clone(),
- edited_ranges,
+ edited_ranges.clone(),
multibuffer_context_lines(cx),
cx,
);
-
- ranges_to_highlight.extend(ranges);
+ let snapshot = multibuffer.snapshot(cx);
+ let buffer_snapshot = buffer_handle.read(cx).snapshot();
+ ranges_to_highlight.extend(edited_ranges.into_iter().filter_map(|range| {
+ let text_range = buffer_snapshot.anchor_range_inside(range);
+ let start = snapshot.anchor_in_buffer(text_range.start)?;
+ let end = snapshot.anchor_in_buffer(text_range.end)?;
+ Some(start..end)
+ }));
}
multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
multibuffer
@@ -7339,10 +7385,10 @@ impl Editor {
.timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
.await;
- let (start_buffer, start, _, end, newest_selection) = this
+ let (start_buffer, start, _, end, _newest_selection) = this
.update(cx, |this, cx| {
let newest_selection = this.selections.newest_anchor().clone();
- if newest_selection.head().diff_base_anchor.is_some() {
+ if newest_selection.head().diff_base_anchor().is_some() {
return None;
}
let display_snapshot = this.display_snapshot(cx);
@@ -7378,7 +7424,6 @@ impl Editor {
if let Some(provider_actions) = provider_actions.log_err() {
actions.extend(provider_actions.into_iter().map(|action| {
AvailableCodeAction {
- excerpt_id: newest_selection.start.excerpt_id,
action,
provider: provider.clone(),
}
@@ -7426,8 +7471,7 @@ impl Editor {
.selections
.newest::<Point>(&snapshot.display_snapshot)
.head();
- let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
- else {
+ let Some((buffer, point)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor) else {
return;
};
@@ -7612,27 +7656,13 @@ impl Editor {
return;
}
- let cursor_buffer_snapshot = cursor_buffer.read(cx);
let mut write_ranges = Vec::new();
let mut read_ranges = Vec::new();
+ let multibuffer_snapshot = buffer.snapshot(cx);
for highlight in highlights {
- let buffer_id = cursor_buffer.read(cx).remote_id();
- for (excerpt_id, _, excerpt_range) in
- buffer.excerpts_for_buffer(buffer_id, cx)
+ for range in
+ multibuffer_snapshot.buffer_range_to_excerpt_ranges(highlight.range)
{
- let start = highlight
- .range
- .start
- .max(&excerpt_range.context.start, cursor_buffer_snapshot);
- let end = highlight
- .range
- .end
- .min(&excerpt_range.context.end, cursor_buffer_snapshot);
- if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
- continue;
- }
-
- let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
if highlight.kind == lsp::DocumentHighlightKind::WRITE {
write_ranges.push(range);
} else {
@@ -7713,7 +7743,7 @@ impl Editor {
let match_task = cx.background_spawn(async move {
let buffer_ranges = multi_buffer_snapshot
.range_to_buffer_ranges(
- multi_buffer_range_to_query.start..=multi_buffer_range_to_query.end,
+ multi_buffer_range_to_query.start..multi_buffer_range_to_query.end,
)
.into_iter()
.filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
@@ -7731,11 +7761,11 @@ impl Editor {
return Vec::default();
};
let query_range = query_range.to_anchors(&multi_buffer_snapshot);
- for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
+ for (buffer_snapshot, search_range, _) in buffer_ranges {
match_ranges.extend(
regex
.search(
- buffer_snapshot,
+ &buffer_snapshot,
Some(search_range.start.0..search_range.end.0),
)
.await
@@ -7745,9 +7775,14 @@ impl Editor {
.anchor_after(search_range.start + match_range.start);
let match_end = buffer_snapshot
.anchor_before(search_range.start + match_range.end);
- let match_anchor_range =
- Anchor::range_in_buffer(excerpt_id, match_start..match_end);
- (match_anchor_range != query_range).then_some(match_anchor_range)
+ {
+ let range = multi_buffer_snapshot
+ .anchor_in_buffer(match_start)?
+ ..multi_buffer_snapshot.anchor_in_buffer(match_end)?;
+ Some(range).filter(|match_anchor_range| {
+ match_anchor_range != &query_range
+ })
+ }
}),
);
}
@@ -8434,13 +8469,15 @@ impl Editor {
return;
};
- let Some((_, buffer, _)) = self
- .buffer
- .read(cx)
- .excerpt_containing(self.selections.newest_anchor().head(), cx)
+ let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
+ let Some((position, _)) =
+ buffer_snapshot.anchor_to_buffer_anchor(self.selections.newest_anchor().head())
else {
return;
};
+ let Some(buffer) = self.buffer.read(cx).buffer(position.buffer_id) else {
+ return;
+ };
let extension = buffer
.read(cx)
@@ -8687,17 +8724,16 @@ impl Editor {
}
let selection = self.selections.newest_anchor();
- let cursor = selection.head();
let multibuffer = self.buffer.read(cx).snapshot(cx);
+ let cursor = selection.head();
+ let (cursor_text_anchor, _) = multibuffer.anchor_to_buffer_anchor(cursor)?;
+ let buffer = self.buffer.read(cx).buffer(cursor_text_anchor.buffer_id)?;
// Check project-level disable_ai setting for the current buffer
- if let Some((buffer, _)) = self.buffer.read(cx).text_anchor_for_position(cursor, cx) {
- if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
- return None;
- }
+ if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
+ return None;
}
let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
- let excerpt_id = cursor.excerpt_id;
let show_in_menu = self.show_edit_predictions_in_menu();
let completions_menu_has_precedence = !show_in_menu
@@ -59,7 +59,6 @@ use std::{
sync::atomic::{self, AtomicUsize},
};
use test::build_editor_with_project;
-use text::ToPoint as _;
use unindent::Unindent;
use util::{
assert_set_eq, path,
@@ -1030,12 +1029,13 @@ async fn test_navigation_history(cx: &mut TestAppContext) {
original_scroll_position
);
+ let other_buffer =
+ cx.new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local("test", cx)), cx));
+
// Ensure we don't panic when navigation data contains invalid anchors *and* points.
- let mut invalid_anchor = editor
- .scroll_manager
- .native_anchor(&editor.display_snapshot(cx), cx)
- .anchor;
- invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
+ let invalid_anchor = other_buffer.update(cx, |buffer, cx| {
+ buffer.snapshot(cx).anchor_after(MultiBufferOffset(3))
+ });
let invalid_point = Point::new(9999, 0);
editor.navigate(
Arc::new(NavigationData {
@@ -13836,7 +13836,7 @@ async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
0,
cx,
);
- assert_eq!(multi_buffer.excerpt_ids().len(), 9);
+ assert_eq!(multi_buffer.read(cx).excerpts().count(), 9);
multi_buffer
});
let multi_buffer_editor = cx.new_window_entity(|window, cx| {
@@ -18946,157 +18946,6 @@ fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
});
}
-#[gpui::test]
-fn test_refresh_selections(cx: &mut TestAppContext) {
- init_test(cx, |_| {});
-
- let buffer = cx.new(|cx| Buffer::local(sample_text(5, 4, 'a'), cx));
- let multibuffer = cx.new(|cx| {
- let mut multibuffer = MultiBuffer::new(ReadWrite);
- multibuffer.set_excerpts_for_path(
- PathKey::sorted(0),
- buffer.clone(),
- [
- Point::new(0, 0)..Point::new(1, 4),
- Point::new(3, 0)..Point::new(4, 4),
- ],
- 0,
- cx,
- );
- multibuffer
- });
-
- let editor = cx.add_window(|window, cx| {
- let mut editor = build_editor(multibuffer.clone(), window, cx);
- let snapshot = editor.snapshot(window, cx);
- editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
- s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
- });
- editor.begin_selection(
- Point::new(2, 1).to_display_point(&snapshot),
- true,
- 1,
- window,
- cx,
- );
- assert_eq!(
- editor.selections.ranges(&editor.display_snapshot(cx)),
- [
- Point::new(1, 3)..Point::new(1, 3),
- Point::new(2, 1)..Point::new(2, 1),
- ]
- );
- editor
- });
-
- // Refreshing selections is a no-op when excerpts haven't changed.
- _ = editor.update(cx, |editor, window, cx| {
- editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
- assert_eq!(
- editor.selections.ranges(&editor.display_snapshot(cx)),
- [
- Point::new(1, 3)..Point::new(1, 3),
- Point::new(2, 1)..Point::new(2, 1),
- ]
- );
- });
-
- multibuffer.update(cx, |multibuffer, cx| {
- multibuffer.set_excerpts_for_path(
- PathKey::sorted(0),
- buffer.clone(),
- [Point::new(3, 0)..Point::new(4, 4)],
- 0,
- cx,
- );
- });
- _ = editor.update(cx, |editor, window, cx| {
- // Removing an excerpt causes the first selection to become degenerate.
- assert_eq!(
- editor.selections.ranges(&editor.display_snapshot(cx)),
- [
- Point::new(0, 0)..Point::new(0, 0),
- Point::new(0, 1)..Point::new(0, 1)
- ]
- );
-
- // Refreshing selections will relocate the first selection to the original buffer
- // location.
- editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
- assert_eq!(
- editor.selections.ranges(&editor.display_snapshot(cx)),
- [
- Point::new(0, 0)..Point::new(0, 0),
- Point::new(0, 1)..Point::new(0, 1),
- ]
- );
- assert!(editor.selections.pending_anchor().is_some());
- });
-}
-
-#[gpui::test]
-fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
- init_test(cx, |_| {});
-
- let buffer = cx.new(|cx| Buffer::local(sample_text(5, 4, 'a'), cx));
- let multibuffer = cx.new(|cx| {
- let mut multibuffer = MultiBuffer::new(ReadWrite);
- multibuffer.set_excerpts_for_path(
- PathKey::sorted(0),
- buffer.clone(),
- [
- Point::new(0, 0)..Point::new(1, 4),
- Point::new(3, 0)..Point::new(4, 4),
- ],
- 0,
- cx,
- );
- assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\ndddd\neeee");
- multibuffer
- });
-
- let editor = cx.add_window(|window, cx| {
- let mut editor = build_editor(multibuffer.clone(), window, cx);
- let snapshot = editor.snapshot(window, cx);
- editor.begin_selection(
- Point::new(1, 3).to_display_point(&snapshot),
- false,
- 1,
- window,
- cx,
- );
- assert_eq!(
- editor.selections.ranges(&editor.display_snapshot(cx)),
- [Point::new(1, 3)..Point::new(1, 3)]
- );
- editor
- });
-
- multibuffer.update(cx, |multibuffer, cx| {
- multibuffer.set_excerpts_for_path(
- PathKey::sorted(0),
- buffer.clone(),
- [Point::new(3, 0)..Point::new(4, 4)],
- 0,
- cx,
- );
- });
- _ = editor.update(cx, |editor, window, cx| {
- assert_eq!(
- editor.selections.ranges(&editor.display_snapshot(cx)),
- [Point::new(0, 0)..Point::new(0, 0)]
- );
-
- // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
- editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
- assert_eq!(
- editor.selections.ranges(&editor.display_snapshot(cx)),
- [Point::new(0, 0)..Point::new(0, 0)]
- );
- assert!(editor.selections.pending_anchor().is_some());
- });
-}
-
#[gpui::test]
async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
init_test(cx, |_| {});
@@ -19738,8 +19587,8 @@ async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
(
- project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
- project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
+ project.create_local_buffer("abc\ndef\nghi\njkl\nmno\npqr\nstu\nvwx\nyza\nbcd\nefg\nhij\nklm\nnop\nqrs\ntuv\nwxy\nzab\ncde\nfgh\n", None, false, cx),
+ project.create_local_buffer("aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\n", None, false, cx),
)
});
@@ -19814,7 +19663,7 @@ async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
// Remove some excerpts.
leader.update(cx, |leader, cx| {
leader.buffer.update(cx, |multibuffer, cx| {
- multibuffer.remove_excerpts_for_path(
+ multibuffer.remove_excerpts(
PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
cx,
);
@@ -23318,7 +23167,7 @@ async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
0,
cx,
);
- assert_eq!(multibuffer.excerpt_ids().len(), 9);
+ assert_eq!(multibuffer.read(cx).excerpts().count(), 9);
multibuffer
});
@@ -23422,7 +23271,7 @@ async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
0,
cx,
);
- assert_eq!(multibuffer.excerpt_ids().len(), 3);
+ assert_eq!(multibuffer.read(cx).excerpts().count(), 3);
multibuffer
});
@@ -24191,9 +24040,13 @@ async fn setup_indent_guides_editor(
let buffer_id = cx.update_editor(|editor, window, cx| {
editor.set_text(text, window, cx);
- let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
-
- buffer_ids[0]
+ editor
+ .buffer()
+ .read(cx)
+ .as_singleton()
+ .unwrap()
+ .read(cx)
+ .remote_id()
});
(buffer_id, cx)
@@ -24902,7 +24755,7 @@ async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
editor
.snapshot(window, cx)
.buffer_snapshot()
- .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
+ .indent_guides_in_range(Anchor::Min..Anchor::Max, false, cx)
.map(|guide| (guide.start_row..=guide.end_row, guide.depth))
.collect::<Vec<_>>()
});
@@ -24957,12 +24810,19 @@ async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestApp
let hunk_ranges = cx.update_editor(|editor, window, cx| {
let snapshot = editor.snapshot(window, cx);
let hunks = editor
- .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
+ .diff_hunks_in_ranges(&[Anchor::Min..Anchor::Max], &snapshot.buffer_snapshot())
.collect::<Vec<_>>();
- let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
+ let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
hunks
.into_iter()
- .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
+ .map(|hunk| {
+ multibuffer_snapshot
+ .anchor_in_excerpt(hunk.buffer_range.start)
+ .unwrap()
+ ..multibuffer_snapshot
+ .anchor_in_excerpt(hunk.buffer_range.end)
+ .unwrap()
+ })
.collect::<Vec<_>>()
});
assert_eq!(hunk_ranges.len(), 2);
@@ -25047,12 +24907,19 @@ async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestApp
let hunk_ranges = cx.update_editor(|editor, window, cx| {
let snapshot = editor.snapshot(window, cx);
let hunks = editor
- .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
+ .diff_hunks_in_ranges(&[Anchor::Min..Anchor::Max], &snapshot.buffer_snapshot())
.collect::<Vec<_>>();
- let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
+ let multibuffer_snapshot = snapshot.buffer_snapshot();
hunks
.into_iter()
- .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
+ .map(|hunk| {
+ multibuffer_snapshot
+ .anchor_in_excerpt(hunk.buffer_range.start)
+ .unwrap()
+ ..multibuffer_snapshot
+ .anchor_in_excerpt(hunk.buffer_range.end)
+ .unwrap()
+ })
.collect::<Vec<_>>()
});
assert_eq!(hunk_ranges.len(), 2);
@@ -25112,12 +24979,19 @@ async fn test_toggle_deletion_hunk_at_start_of_file(
let hunk_ranges = cx.update_editor(|editor, window, cx| {
let snapshot = editor.snapshot(window, cx);
let hunks = editor
- .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
+ .diff_hunks_in_ranges(&[Anchor::Min..Anchor::Max], &snapshot.buffer_snapshot())
.collect::<Vec<_>>();
- let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
+ let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
hunks
.into_iter()
- .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
+ .map(|hunk| {
+ multibuffer_snapshot
+ .anchor_in_excerpt(hunk.buffer_range.start)
+ .unwrap()
+ ..multibuffer_snapshot
+ .anchor_in_excerpt(hunk.buffer_range.end)
+ .unwrap()
+ })
.collect::<Vec<_>>()
});
assert_eq!(hunk_ranges.len(), 1);
@@ -25217,12 +25091,17 @@ async fn test_expand_first_line_diff_hunk_keeps_deleted_lines_visible(
// Expanding a diff hunk at the first line inserts deleted lines above the first buffer line.
cx.update_editor(|editor, window, cx| {
let snapshot = editor.snapshot(window, cx);
- let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
+ let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
let hunks = editor
- .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
+ .diff_hunks_in_ranges(&[Anchor::Min..Anchor::Max], &snapshot.buffer_snapshot())
.collect::<Vec<_>>();
assert_eq!(hunks.len(), 1);
- let hunk_range = Anchor::range_in_buffer(excerpt_id, hunks[0].buffer_range.clone());
+ let hunk_range = multibuffer_snapshot
+ .anchor_in_excerpt(hunks[0].buffer_range.start)
+ .unwrap()
+ ..multibuffer_snapshot
+ .anchor_in_excerpt(hunks[0].buffer_range.end)
+ .unwrap();
editor.toggle_single_diff_hunk(hunk_range, cx)
});
executor.run_until_parked();
@@ -25279,7 +25158,7 @@ async fn test_display_diff_hunks(cx: &mut TestAppContext) {
multibuffer.set_excerpts_for_path(
PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
buffer.clone(),
- vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
+ vec![Point::zero()..snapshot.max_point()],
2,
cx,
);
@@ -25365,7 +25244,7 @@ async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
cx.update_editor(|editor, window, cx| {
let snapshot = editor.snapshot(window, cx);
let hunks = editor
- .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
+ .diff_hunks_in_ranges(&[Anchor::Min..Anchor::Max], &snapshot.buffer_snapshot())
.collect::<Vec<_>>();
assert_eq!(hunks.len(), 1);
assert_eq!(
@@ -26450,7 +26329,7 @@ async fn test_folded_buffers_cleared_on_excerpts_removed(cx: &mut TestAppContext
// `multi_buffer::Event::ExcerptsRemoved` event is emitted, which should be
// picked up by the editor and update the display map accordingly.
multi_buffer.update(cx, |multi_buffer, cx| {
- multi_buffer.remove_excerpts_for_path(PathKey::sorted(0), cx)
+ multi_buffer.remove_excerpts(PathKey::sorted(0), cx)
});
assert!(!editor.update(cx, |editor, cx| editor.has_any_buffer_folded(cx)));
}
@@ -26702,7 +26581,12 @@ async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContex
);
let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
- let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
+ let buffer_ids = multi_buffer
+ .read(cx)
+ .snapshot(cx)
+ .excerpts()
+ .map(|excerpt| excerpt.context.start.buffer_id)
+ .collect::<Vec<_>>();
// fold all but the second buffer, so that we test navigating between two
// adjacent folded buffers, as well as folded buffers at the start and
// end the multibuffer
@@ -27038,7 +26922,12 @@ async fn assert_highlighted_edits(
let text_anchor_edits = edits
.clone()
.into_iter()
- .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit.into()))
+ .map(|(range, edit)| {
+ (
+ range.start.expect_text_anchor()..range.end.expect_text_anchor(),
+ edit.into(),
+ )
+ })
.collect::<Vec<_>>();
let edit_preview = window
@@ -27055,10 +26944,11 @@ async fn assert_highlighted_edits(
cx.update(|_window, cx| {
let highlighted_edits = edit_prediction_edit_text(
- snapshot.as_singleton().unwrap().2,
+ snapshot.as_singleton().unwrap(),
&edits,
&edit_preview,
include_deletions,
+ &snapshot,
cx,
);
assertion_fn(highlighted_edits, cx)
@@ -31479,12 +31369,8 @@ async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_mult
Point::new(1, 21)..Point::new(1, 25),
])
});
- let first_buffer_id = multi_buffer
- .read(cx)
- .excerpt_buffer_ids()
- .into_iter()
- .next()
- .unwrap();
+ let snapshot = multi_buffer.read(cx).snapshot(cx);
+ let first_buffer_id = snapshot.all_buffer_ids().next().unwrap();
let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
first_buffer.update(cx, |buffer, cx| {
buffer.set_language(Some(markdown_language.clone()), cx);
@@ -32530,7 +32416,12 @@ async fn test_multibuffer_selections_with_folding(cx: &mut TestAppContext) {
});
let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
- let buffer_ids = cx.multibuffer(|mb, _| mb.excerpt_buffer_ids());
+ let buffer_ids = cx.multibuffer(|mb, cx| {
+ mb.snapshot(cx)
+ .excerpts()
+ .map(|excerpt| excerpt.context.start.buffer_id)
+ .collect::<Vec<_>>()
+ });
cx.assert_excerpts_with_selections(indoc! {"
[EXCERPT]
@@ -33770,7 +33661,7 @@ async fn test_diff_review_button_shown_when_ai_enabled(cx: &mut TestAppContext)
}
/// Helper function to create a DiffHunkKey for testing.
-/// Uses Anchor::min() as a placeholder anchor since these tests don't need
+/// Uses Anchor::Min as a placeholder anchor since these tests don't need
/// real buffer positioning.
fn test_hunk_key(file_path: &str) -> DiffHunkKey {
DiffHunkKey {
@@ -33779,7 +33670,7 @@ fn test_hunk_key(file_path: &str) -> DiffHunkKey {
} else {
Arc::from(util::rel_path::RelPath::unix(file_path).unwrap())
},
- hunk_start_anchor: Anchor::min(),
+ hunk_start_anchor: Anchor::Min,
}
}
@@ -33802,7 +33693,7 @@ fn add_test_comment(
comment: &str,
cx: &mut Context<Editor>,
) -> usize {
- editor.add_review_comment(key, comment.to_string(), Anchor::min()..Anchor::max(), cx)
+ editor.add_review_comment(key, comment.to_string(), Anchor::Min..Anchor::Max, cx)
}
#[gpui::test]
@@ -54,7 +54,7 @@ use itertools::Itertools;
use language::{HighlightedText, IndentGuideSettings, language_settings::ShowWhitespaceSetting};
use markdown::Markdown;
use multi_buffer::{
- Anchor, ExcerptId, ExcerptInfo, ExpandExcerptDirection, ExpandInfo, MultiBufferPoint,
+ Anchor, ExcerptBoundaryInfo, ExpandExcerptDirection, ExpandInfo, MultiBufferPoint,
MultiBufferRow, RowInfo,
};
@@ -1390,13 +1390,13 @@ impl EditorElement {
.snapshot
.display_point_to_anchor(valid_point, Bias::Left);
- if let Some((buffer_snapshot, file)) = position_map
+ if let Some((buffer_anchor, buffer_snapshot)) = position_map
.snapshot
.buffer_snapshot()
- .buffer_for_excerpt(buffer_anchor.excerpt_id)
- .and_then(|buffer| buffer.file().map(|file| (buffer, file)))
+ .anchor_to_buffer_anchor(buffer_anchor)
+ && let Some(file) = buffer_snapshot.file()
{
- let as_point = text::ToPoint::to_point(&buffer_anchor.text_anchor, buffer_snapshot);
+ let as_point = text::ToPoint::to_point(&buffer_anchor, buffer_snapshot);
let is_visible = editor
.gutter_breakpoint_indicator
@@ -1752,7 +1752,7 @@ impl EditorElement {
// Remote cursors
if let Some(collaboration_hub) = &editor.collaboration_hub {
for remote_selection in snapshot.remote_selections_in_range(
- &(Anchor::min()..Anchor::max()),
+ &(Anchor::Min..Anchor::Max),
collaboration_hub.deref(),
cx,
) {
@@ -2589,12 +2589,6 @@ impl EditorElement {
const INLINE_SLOT_CHAR_LIMIT: u32 = 4;
const MAX_ALTERNATE_DISTANCE: u32 = 8;
- let excerpt_id = snapshot
- .display_snapshot
- .buffer_snapshot()
- .excerpt_containing(buffer_point..buffer_point)
- .map(|excerpt| excerpt.id());
-
let is_valid_row = |row_candidate: u32| -> bool {
// move to other row if folded row
if snapshot.is_line_folded(MultiBufferRow(row_candidate)) {
@@ -2610,13 +2604,18 @@ impl EditorElement {
row: row_candidate,
column: 0,
};
- let candidate_excerpt_id = snapshot
+ // move to other row if different excerpt
+ let range = if candidate_point < buffer_point {
+ candidate_point..buffer_point
+ } else {
+ buffer_point..candidate_point
+ };
+ if snapshot
.display_snapshot
.buffer_snapshot()
- .excerpt_containing(candidate_point..candidate_point)
- .map(|excerpt| excerpt.id());
- // move to other row if different excerpt
- if excerpt_id != candidate_excerpt_id {
+ .excerpt_containing(range)
+ .is_none()
+ {
return false;
}
}
@@ -2796,7 +2795,7 @@ impl EditorElement {
.newest::<language::Point>(&editor_snapshot.display_snapshot)
.head();
- let Some((buffer, buffer_point, _)) = editor_snapshot
+ let Some((buffer, buffer_point)) = editor_snapshot
.buffer_snapshot()
.point_to_buffer_point(cursor_point)
else {
@@ -3389,8 +3388,8 @@ impl EditorElement {
.enumerate()
.map(|(ix, row_info)| {
let ExpandInfo {
- excerpt_id,
direction,
+ start_anchor,
} = row_info.expand_info?;
let icon_name = match direction {
@@ -3419,7 +3418,7 @@ impl EditorElement {
.width(width)
.on_click(move |_, window, cx| {
editor.update(cx, |editor, cx| {
- editor.expand_excerpt(excerpt_id, direction, window, cx);
+ editor.expand_excerpt(start_anchor, direction, window, cx);
});
})
.tooltip(Tooltip::for_action_title(
@@ -3886,7 +3885,7 @@ impl EditorElement {
selected_buffer_ids: &Vec<BufferId>,
latest_selection_anchors: &HashMap<BufferId, Anchor>,
is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
- sticky_header_excerpt_id: Option<ExcerptId>,
+ sticky_header_excerpt_id: Option<BufferId>,
indent_guides: &Option<Vec<IndentGuideLayout>>,
block_resize_offset: &mut i32,
window: &mut Window,
@@ -3974,7 +3973,7 @@ impl EditorElement {
let mut result = v_flex().id(block_id).w_full().pr(editor_margins.right);
if self.should_show_buffer_headers() {
- let selected = selected_buffer_ids.contains(&first_excerpt.buffer_id);
+ let selected = selected_buffer_ids.contains(&first_excerpt.buffer_id());
let jump_data = header_jump_data(
snapshot,
block_row_start,
@@ -4029,8 +4028,8 @@ impl EditorElement {
latest_selection_anchors,
);
- if sticky_header_excerpt_id != Some(excerpt.id) {
- let selected = selected_buffer_ids.contains(&excerpt.buffer_id);
+ if sticky_header_excerpt_id != Some(excerpt.buffer_id()) {
+ let selected = selected_buffer_ids.contains(&excerpt.buffer_id());
result = result.child(div().pr(editor_margins.right).child(
self.render_buffer_header(
@@ -4190,7 +4189,7 @@ impl EditorElement {
fn render_buffer_header(
&self,
- for_excerpt: &ExcerptInfo,
+ for_excerpt: &ExcerptBoundaryInfo,
is_folded: bool,
is_selected: bool,
is_sticky: bool,
@@ -4227,7 +4226,7 @@ impl EditorElement {
selected_buffer_ids: &Vec<BufferId>,
latest_selection_anchors: &HashMap<BufferId, Anchor>,
is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
- sticky_header_excerpt_id: Option<ExcerptId>,
+ sticky_header_excerpt_id: Option<BufferId>,
indent_guides: &Option<Vec<IndentGuideLayout>>,
window: &mut Window,
cx: &mut App,
@@ -4520,7 +4519,7 @@ impl EditorElement {
let editor_bg_color = cx.theme().colors().editor_background;
- let selected = selected_buffer_ids.contains(&excerpt.buffer_id);
+ let selected = selected_buffer_ids.contains(&excerpt.buffer_id());
let available_width = hitbox.bounds.size.width - right_margin;
@@ -7894,23 +7893,26 @@ impl EditorElement {
return;
}
let buffer_snapshot = &display_snapshot.buffer_snapshot();
- for (buffer, buffer_range, excerpt_id) in
- buffer_snapshot.range_to_buffer_ranges(anchor_range.start..=anchor_range.end)
+ for (excerpt_buffer_snapshot, buffer_range, _) in
+ buffer_snapshot.range_to_buffer_ranges(anchor_range.start..anchor_range.end)
{
- let buffer_range =
- buffer.anchor_after(buffer_range.start)..buffer.anchor_before(buffer_range.end);
+ let buffer_range = excerpt_buffer_snapshot.anchor_after(buffer_range.start)
+ ..excerpt_buffer_snapshot.anchor_before(buffer_range.end);
selections.extend(debug_ranges.ranges.iter().flat_map(|debug_range| {
- let player_color = theme
- .players()
- .color_for_participant(debug_range.occurrence_index as u32 + 1);
- debug_range.ranges.iter().filter_map(move |range| {
- if range.start.buffer_id != Some(buffer.remote_id()) {
+ debug_range.ranges.iter().filter_map(|range| {
+ let player_color = theme
+ .players()
+ .color_for_participant(debug_range.occurrence_index as u32 + 1);
+ if range.start.buffer_id != excerpt_buffer_snapshot.remote_id() {
return None;
}
- let clipped_start = range.start.max(&buffer_range.start, buffer);
- let clipped_end = range.end.min(&buffer_range.end, buffer);
+ let clipped_start = range
+ .start
+ .max(&buffer_range.start, &excerpt_buffer_snapshot);
+ let clipped_end =
+ range.end.min(&buffer_range.end, &excerpt_buffer_snapshot);
let range = buffer_snapshot
- .anchor_range_in_excerpt(excerpt_id, *clipped_start..*clipped_end)?;
+ .buffer_anchor_range_to_anchor_range(*clipped_start..*clipped_end)?;
let start = range.start.to_display_point(display_snapshot);
let end = range.end.to_display_point(display_snapshot);
let selection_layout = SelectionLayout {
@@ -8150,49 +8152,23 @@ pub(crate) fn header_jump_data(
editor_snapshot: &EditorSnapshot,
block_row_start: DisplayRow,
height: u32,
- first_excerpt: &ExcerptInfo,
+ first_excerpt: &ExcerptBoundaryInfo,
latest_selection_anchors: &HashMap<BufferId, Anchor>,
) -> JumpData {
- let jump_target = if let Some(anchor) = latest_selection_anchors.get(&first_excerpt.buffer_id)
- && let Some(range) = editor_snapshot.context_range_for_excerpt(anchor.excerpt_id)
- && let Some(buffer) = editor_snapshot
- .buffer_snapshot()
- .buffer_for_excerpt(anchor.excerpt_id)
+ let multibuffer_snapshot = editor_snapshot.buffer_snapshot();
+ let buffer = first_excerpt.buffer(multibuffer_snapshot);
+ let (jump_anchor, jump_buffer) = if let Some(anchor) =
+ latest_selection_anchors.get(&first_excerpt.buffer_id())
+ && let Some((jump_anchor, selection_buffer)) =
+ multibuffer_snapshot.anchor_to_buffer_anchor(*anchor)
{
- JumpTargetInExcerptInput {
- id: anchor.excerpt_id,
- buffer,
- excerpt_start_anchor: range.start,
- jump_anchor: anchor.text_anchor,
- }
+ (jump_anchor, selection_buffer)
} else {
- JumpTargetInExcerptInput {
- id: first_excerpt.id,
- buffer: &first_excerpt.buffer,
- excerpt_start_anchor: first_excerpt.range.context.start,
- jump_anchor: first_excerpt.range.primary.start,
- }
+ (first_excerpt.range.primary.start, buffer)
};
- header_jump_data_inner(editor_snapshot, block_row_start, height, &jump_target)
-}
-
-struct JumpTargetInExcerptInput<'a> {
- id: ExcerptId,
- buffer: &'a language::BufferSnapshot,
- excerpt_start_anchor: text::Anchor,
- jump_anchor: text::Anchor,
-}
-
-fn header_jump_data_inner(
- snapshot: &EditorSnapshot,
- block_row_start: DisplayRow,
- height: u32,
- for_excerpt: &JumpTargetInExcerptInput,
-) -> JumpData {
- let buffer = &for_excerpt.buffer;
- let jump_position = language::ToPoint::to_point(&for_excerpt.jump_anchor, buffer);
- let excerpt_start = for_excerpt.excerpt_start_anchor;
- let rows_from_excerpt_start = if for_excerpt.jump_anchor == excerpt_start {
+ let excerpt_start = first_excerpt.range.context.start;
+ let jump_position = language::ToPoint::to_point(&jump_anchor, jump_buffer);
+ let rows_from_excerpt_start = if jump_anchor == excerpt_start {
0
} else {
let excerpt_start_point = language::ToPoint::to_point(&excerpt_start, buffer);
@@ -8201,15 +8177,14 @@ fn header_jump_data_inner(
let line_offset_from_top = (block_row_start.0 + height + rows_from_excerpt_start)
.saturating_sub(
- snapshot
+ editor_snapshot
.scroll_anchor
- .scroll_position(&snapshot.display_snapshot)
+ .scroll_position(&editor_snapshot.display_snapshot)
.y as u32,
);
JumpData::MultiBufferPoint {
- excerpt_id: for_excerpt.id,
- anchor: for_excerpt.jump_anchor,
+ anchor: jump_anchor,
position: jump_position,
line_offset_from_top,
}
@@ -8217,7 +8192,7 @@ fn header_jump_data_inner(
pub(crate) fn render_buffer_header(
editor: &Entity<Editor>,
- for_excerpt: &ExcerptInfo,
+ for_excerpt: &ExcerptBoundaryInfo,
is_folded: bool,
is_selected: bool,
is_sticky: bool,
@@ -8229,6 +8204,8 @@ pub(crate) fn render_buffer_header(
let multi_buffer = editor_read.buffer.read(cx);
let is_read_only = editor_read.read_only(cx);
let editor_handle: &dyn ItemHandle = editor;
+ let multibuffer_snapshot = multi_buffer.snapshot(cx);
+ let buffer = for_excerpt.buffer(&multibuffer_snapshot);
let breadcrumbs = if is_selected {
editor_read.breadcrumbs_inner(cx)
@@ -8236,31 +8213,30 @@ pub(crate) fn render_buffer_header(
None
};
+ let buffer_id = for_excerpt.buffer_id();
let file_status = multi_buffer
.all_diff_hunks_expanded()
- .then(|| editor_read.status_for_buffer_id(for_excerpt.buffer_id, cx))
+ .then(|| editor_read.status_for_buffer_id(buffer_id, cx))
.flatten();
- let indicator = multi_buffer
- .buffer(for_excerpt.buffer_id)
- .and_then(|buffer| {
- let buffer = buffer.read(cx);
- let indicator_color = match (buffer.has_conflict(), buffer.is_dirty()) {
- (true, _) => Some(Color::Warning),
- (_, true) => Some(Color::Accent),
- (false, false) => None,
- };
- indicator_color.map(|indicator_color| Indicator::dot().color(indicator_color))
- });
+ let indicator = multi_buffer.buffer(buffer_id).and_then(|buffer| {
+ let buffer = buffer.read(cx);
+ let indicator_color = match (buffer.has_conflict(), buffer.is_dirty()) {
+ (true, _) => Some(Color::Warning),
+ (_, true) => Some(Color::Accent),
+ (false, false) => None,
+ };
+ indicator_color.map(|indicator_color| Indicator::dot().color(indicator_color))
+ });
let include_root = editor_read
.project
.as_ref()
.map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
.unwrap_or_default();
- let file = for_excerpt.buffer.file();
+ let file = buffer.file();
let can_open_excerpts = file.is_none_or(|file| file.can_open());
let path_style = file.map(|file| file.path_style(cx));
- let relative_path = for_excerpt.buffer.resolve_file_path(include_root, cx);
+ let relative_path = buffer.resolve_file_path(include_root, cx);
let (parent_path, filename) = if let Some(path) = &relative_path {
if let Some(path_style) = path_style {
let (dir, file_name) = path_style.split(path);
@@ -8275,7 +8251,7 @@ pub(crate) fn render_buffer_header(
let colors = cx.theme().colors();
let header = div()
- .id(("buffer-header", for_excerpt.buffer_id.to_proto()))
+ .id(("buffer-header", buffer_id.to_proto()))
.p(BUFFER_HEADER_PADDING)
.w_full()
.h(FILE_HEADER_HEIGHT as f32 * window.line_height())
@@ -8303,7 +8279,7 @@ pub(crate) fn render_buffer_header(
.hover(|style| style.bg(colors.element_hover))
.map(|header| {
let editor = editor.clone();
- let buffer_id = for_excerpt.buffer_id;
+ let buffer_id = for_excerpt.buffer_id();
let toggle_chevron_icon =
FileIcons::get_chevron_icon(!is_folded, cx).map(Icon::from_path);
let button_size = rems_from_px(28.);
@@ -8367,7 +8343,7 @@ pub(crate) fn render_buffer_header(
.addons
.values()
.filter_map(|addon| {
- addon.render_buffer_header_controls(for_excerpt, window, cx)
+ addon.render_buffer_header_controls(for_excerpt, buffer, window, cx)
})
.take(1),
)
@@ -8460,7 +8436,7 @@ pub(crate) fn render_buffer_header(
),
)
})
- .when(!for_excerpt.buffer.capability.editable(), |el| {
+ .when(!buffer.capability.editable(), |el| {
el.child(Icon::new(IconName::FileLock).color(Color::Muted))
})
.when_some(breadcrumbs, |then, breadcrumbs| {
@@ -8511,7 +8487,7 @@ pub(crate) fn render_buffer_header(
})
.on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
.on_click(window.listener_for(editor, {
- let buffer_id = for_excerpt.buffer_id;
+ let buffer_id = for_excerpt.buffer_id();
move |editor, e: &ClickEvent, window, cx| {
if e.modifiers().alt {
editor.open_excerpts_common(
@@ -8533,7 +8509,7 @@ pub(crate) fn render_buffer_header(
),
);
- let file = for_excerpt.buffer.file().cloned();
+ let file = buffer.file().cloned();
let editor = editor.clone();
right_click_menu("buffer-header-context-menu")
@@ -9855,14 +9831,14 @@ impl Element for EditorElement {
};
let start_anchor = if start_row == Default::default() {
- Anchor::min()
+ Anchor::Min
} else {
snapshot.buffer_snapshot().anchor_before(
DisplayPoint::new(start_row, 0).to_offset(&snapshot, Bias::Left),
)
};
let end_anchor = if end_row > max_row {
- Anchor::max()
+ Anchor::Max
} else {
snapshot.buffer_snapshot().anchor_before(
DisplayPoint::new(end_row, 0).to_offset(&snapshot, Bias::Right),
@@ -9888,7 +9864,7 @@ impl Element for EditorElement {
editor.update(cx, |editor, cx| {
let snapshot = editor.snapshot(window, cx);
let start_anchor = if start_row == Default::default() {
- Anchor::min()
+ Anchor::Min
} else {
snapshot.buffer_snapshot().anchor_before(
DisplayPoint::new(start_row, 0)
@@ -9896,7 +9872,7 @@ impl Element for EditorElement {
)
};
let end_anchor = if end_row > max_row {
- Anchor::max()
+ Anchor::Max
} else {
snapshot.buffer_snapshot().anchor_before(
DisplayPoint::new(end_row, 0)
@@ -10052,9 +10028,11 @@ impl Element for EditorElement {
HashMap::default();
for selection in all_anchor_selections.iter() {
let head = selection.head();
- if let Some(buffer_id) = head.text_anchor.buffer_id {
+ if let Some((text_anchor, _)) =
+ snapshot.buffer_snapshot().anchor_to_buffer_anchor(head)
+ {
anchors_by_buffer
- .entry(buffer_id)
+ .entry(text_anchor.buffer_id)
.and_modify(|(latest_id, latest_anchor)| {
if selection.id > *latest_id {
*latest_id = selection.id;
@@ -10322,8 +10300,9 @@ impl Element for EditorElement {
} else {
None
};
- let sticky_header_excerpt_id =
- sticky_header_excerpt.as_ref().map(|top| top.excerpt.id);
+ let sticky_header_excerpt_id = sticky_header_excerpt
+ .as_ref()
+ .map(|top| top.excerpt.buffer_id());
let buffer = snapshot.buffer_snapshot();
let start_buffer_row = MultiBufferRow(start_anchor.to_point(&buffer).row);
@@ -12968,7 +12947,7 @@ mod tests {
editor.insert_blocks(
[BlockProperties {
style: BlockStyle::Fixed,
- placement: BlockPlacement::Above(Anchor::min()),
+ placement: BlockPlacement::Above(Anchor::Min),
height: Some(3),
render: Arc::new(|cx| div().h(3. * cx.window.line_height()).into_any()),
priority: 0,
@@ -21,9 +21,9 @@ impl Editor {
};
let buffers_to_query = self
- .visible_excerpts(true, cx)
- .into_values()
- .map(|(buffer, ..)| buffer)
+ .visible_buffers(cx)
+ .into_iter()
+ .filter(|buffer| self.is_lsp_relevant(buffer.read(cx).file(), cx))
.chain(for_buffer.and_then(|id| self.buffer.read(cx).buffer(id)))
.filter(|buffer| {
let id = buffer.read(cx).remote_id();
@@ -204,8 +204,8 @@ impl GitBlame {
git_blame.generate(cx);
}
}
- multi_buffer::Event::ExcerptsAdded { .. }
- | multi_buffer::Event::ExcerptsEdited { .. } => git_blame.regenerate_on_edit(cx),
+ multi_buffer::Event::BufferRangesUpdated { .. }
+ | multi_buffer::Event::BuffersEdited { .. } => git_blame.regenerate_on_edit(cx),
_ => {}
},
);
@@ -346,11 +346,10 @@ impl GitBlame {
let Some(multi_buffer) = self.multi_buffer.upgrade() else {
return;
};
- multi_buffer
- .read(cx)
- .excerpt_buffer_ids()
- .into_iter()
- .for_each(|id| self.sync(cx, id));
+ let snapshot = multi_buffer.read(cx).snapshot(cx);
+ for id in snapshot.all_buffer_ids() {
+ self.sync(cx, id)
+ }
}
fn sync(&mut self, cx: &mut App, buffer_id: BufferId) {
@@ -497,10 +496,10 @@ impl GitBlame {
}
let buffers_to_blame = self
.multi_buffer
- .update(cx, |multi_buffer, _| {
- multi_buffer
+ .update(cx, |multi_buffer, cx| {
+ let snapshot = multi_buffer.snapshot(cx);
+ snapshot
.all_buffer_ids()
- .into_iter()
.filter_map(|id| Some(multi_buffer.buffer(id)?.downgrade()))
.collect::<Vec<_>>()
})
@@ -237,7 +237,8 @@ impl Editor {
let Some(mb_anchor) = self
.buffer()
.read(cx)
- .buffer_anchor_to_anchor(&buffer, anchor, cx)
+ .snapshot(cx)
+ .anchor_in_excerpt(anchor)
else {
return Task::ready(Ok(Navigated::No));
};
@@ -324,16 +325,13 @@ pub fn show_link_definition(
return;
}
- let trigger_anchor = trigger_point.anchor();
- let anchor = snapshot.buffer_snapshot().anchor_before(*trigger_anchor);
- let Some(buffer) = editor.buffer().read(cx).buffer_for_anchor(anchor, cx) else {
+ let anchor = trigger_point.anchor().bias_left(snapshot.buffer_snapshot());
+ let Some((anchor, _)) = snapshot.buffer_snapshot().anchor_to_buffer_anchor(anchor) else {
+ return;
+ };
+ let Some(buffer) = editor.buffer.read(cx).buffer(anchor.buffer_id) else {
return;
};
- let Anchor {
- excerpt_id,
- text_anchor,
- ..
- } = anchor;
let same_kind = hovered_link_state.preferred_kind == preferred_kind
|| hovered_link_state
.links
@@ -363,39 +361,39 @@ pub fn show_link_definition(
async move {
let result = match &trigger_point {
TriggerPoint::Text(_) => {
- if let Some((url_range, url)) = find_url(&buffer, text_anchor, cx.clone()) {
+ if let Some((url_range, url)) = find_url(&buffer, anchor, cx.clone()) {
this.read_with(cx, |_, _| {
let range = maybe!({
let range =
- snapshot.anchor_range_in_excerpt(excerpt_id, url_range)?;
+ snapshot.buffer_anchor_range_to_anchor_range(url_range)?;
Some(RangeInEditor::Text(range))
});
(range, vec![HoverLink::Url(url)])
})
.ok()
} else if let Some((filename_range, filename)) =
- find_file(&buffer, project.clone(), text_anchor, cx).await
+ find_file(&buffer, project.clone(), anchor, cx).await
{
let range = maybe!({
let range =
- snapshot.anchor_range_in_excerpt(excerpt_id, filename_range)?;
+ snapshot.buffer_anchor_range_to_anchor_range(filename_range)?;
Some(RangeInEditor::Text(range))
});
Some((range, vec![HoverLink::File(filename)]))
} else if let Some(provider) = provider {
let task = cx.update(|_, cx| {
- provider.definitions(&buffer, text_anchor, preferred_kind, cx)
+ provider.definitions(&buffer, anchor, preferred_kind, cx)
})?;
if let Some(task) = task {
task.await.ok().flatten().map(|definition_result| {
(
definition_result.iter().find_map(|link| {
link.origin.as_ref().and_then(|origin| {
- let range = snapshot.anchor_range_in_excerpt(
- excerpt_id,
- origin.range.clone(),
- )?;
+ let range = snapshot
+ .buffer_anchor_range_to_anchor_range(
+ origin.range.clone(),
+ )?;
Some(RangeInEditor::Text(range))
})
}),
@@ -1602,7 +1600,11 @@ mod tests {
cx.set_state(input);
let (position, snapshot) = cx.editor(|editor, _, cx| {
- let positions = editor.selections.newest_anchor().head().text_anchor;
+ let positions = editor
+ .selections
+ .newest_anchor()
+ .head()
+ .expect_text_anchor();
let snapshot = editor
.buffer()
.clone()
@@ -275,12 +275,12 @@ fn show_hover(
let snapshot = editor.snapshot(window, cx);
- let (buffer, buffer_position) = editor
+ let (buffer_position, _) = editor
.buffer
.read(cx)
- .text_anchor_for_position(anchor, cx)?;
-
- let (excerpt_id, _, _) = editor.buffer().read(cx).excerpt_containing(anchor, cx)?;
+ .snapshot(cx)
+ .anchor_to_buffer_anchor(anchor)?;
+ let buffer = editor.buffer.read(cx).buffer(buffer_position.buffer_id)?;
let language_registry = editor
.project()
@@ -515,7 +515,7 @@ fn show_hover(
.and_then(|range| {
let range = snapshot
.buffer_snapshot()
- .anchor_range_in_excerpt(excerpt_id, range)?;
+ .buffer_anchor_range_to_anchor_range(range)?;
Some(range)
})
.or_else(|| {
@@ -45,6 +45,7 @@ impl InlaySplice {
#[derive(Debug, Clone)]
pub struct Inlay {
pub id: InlayId,
+ // TODO this could be an ExcerptAnchor
pub position: Anchor,
pub content: InlayContent,
}
@@ -14,7 +14,7 @@ use language::{
language_settings::{InlayHintKind, InlayHintSettings},
};
use lsp::LanguageServerId;
-use multi_buffer::{Anchor, ExcerptId, MultiBufferSnapshot};
+use multi_buffer::{Anchor, MultiBufferSnapshot};
use project::{
HoverBlock, HoverBlockKind, InlayHintLabel, InlayHintLabelPartTooltip, InlayHintTooltip,
InvalidationStrategy, ResolveState,
@@ -110,14 +110,15 @@ impl LspInlayHintData {
&mut self,
buffer_ids: &HashSet<BufferId>,
current_hints: impl IntoIterator<Item = Inlay>,
+ snapshot: &MultiBufferSnapshot,
) {
for buffer_id in buffer_ids {
self.hint_refresh_tasks.remove(buffer_id);
self.hint_chunk_fetching.remove(buffer_id);
}
for hint in current_hints {
- if let Some(buffer_id) = hint.position.text_anchor.buffer_id {
- if buffer_ids.contains(&buffer_id) {
+ if let Some((text_anchor, _)) = snapshot.anchor_to_buffer_anchor(hint.position) {
+ if buffer_ids.contains(&text_anchor.buffer_id) {
self.added_hints.remove(&hint.id);
}
}
@@ -237,7 +238,7 @@ pub enum InlayHintRefreshReason {
server_id: LanguageServerId,
request_id: Option<usize>,
},
- ExcerptsRemoved(Vec<ExcerptId>),
+ BuffersRemoved(Vec<BufferId>),
}
impl Editor {
@@ -303,7 +304,7 @@ impl Editor {
let debounce = match &reason {
InlayHintRefreshReason::SettingsChange(_)
| InlayHintRefreshReason::Toggle(_)
- | InlayHintRefreshReason::ExcerptsRemoved(_)
+ | InlayHintRefreshReason::BuffersRemoved(_)
| InlayHintRefreshReason::ModifiersChanged(_) => None,
_may_need_lsp_call => self.inlay_hints.as_ref().and_then(|inlay_hints| {
if invalidate_cache.should_invalidate() {
@@ -314,7 +315,8 @@ impl Editor {
}),
};
- let mut visible_excerpts = self.visible_excerpts(true, cx);
+ let mut visible_excerpts = self.visible_buffer_ranges(cx);
+ visible_excerpts.retain(|(snapshot, _, _)| self.is_lsp_relevant(snapshot.file(), cx));
let mut invalidate_hints_for_buffers = HashSet::default();
let ignore_previous_fetches = match reason {
@@ -324,7 +326,7 @@ impl Editor {
| InlayHintRefreshReason::ServerRemoved => true,
InlayHintRefreshReason::NewLinesShown
| InlayHintRefreshReason::RefreshRequested { .. }
- | InlayHintRefreshReason::ExcerptsRemoved(_) => false,
+ | InlayHintRefreshReason::BuffersRemoved(_) => false,
InlayHintRefreshReason::BufferEdited(buffer_id) => {
let Some(affected_language) = self
.buffer()
@@ -351,8 +353,8 @@ impl Editor {
);
semantics_provider.invalidate_inlay_hints(&invalidate_hints_for_buffers, cx);
- visible_excerpts.retain(|_, (visible_buffer, _, _)| {
- visible_buffer.read(cx).language() == Some(&affected_language)
+ visible_excerpts.retain(|(buffer_snapshot, _, _)| {
+ buffer_snapshot.language() == Some(&affected_language)
});
false
}
@@ -371,6 +373,7 @@ impl Editor {
inlay_hints.clear_for_buffers(
&invalidate_hints_for_buffers,
Self::visible_inlay_hints(self.display_map.read(cx)),
+ &multi_buffer.read(cx).snapshot(cx),
);
}
}
@@ -379,14 +382,18 @@ impl Editor {
.extend(invalidate_hints_for_buffers);
let mut buffers_to_query = HashMap::default();
- for (_, (buffer, buffer_version, visible_range)) in visible_excerpts {
- let buffer_id = buffer.read(cx).remote_id();
+ for (buffer_snapshot, visible_range, _) in visible_excerpts {
+ let buffer_id = buffer_snapshot.remote_id();
if !self.registered_buffers.contains_key(&buffer_id) {
continue;
}
- let buffer_snapshot = buffer.read(cx).snapshot();
+ let Some(buffer) = multi_buffer.read(cx).buffer(buffer_id) else {
+ continue;
+ };
+
+ let buffer_version = buffer_snapshot.version().clone();
let buffer_anchor_range = buffer_snapshot.anchor_before(visible_range.start)
..buffer_snapshot.anchor_after(visible_range.end);
@@ -514,13 +521,14 @@ impl Editor {
}
}
}
- InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
+ InlayHintRefreshReason::BuffersRemoved(buffers_removed) => {
let to_remove = self
.display_map
.read(cx)
.current_inlays()
.filter_map(|inlay| {
- if excerpts_removed.contains(&inlay.position.excerpt_id) {
+ let anchor = inlay.position.raw_text_anchor()?;
+ if buffers_removed.contains(&anchor.buffer_id) {
Some(inlay.id)
} else {
None
@@ -610,13 +618,11 @@ impl Editor {
})
.max_by_key(|hint| hint.id)
{
- if let Some(ResolvedHint::Resolved(cached_hint)) = hovered_hint
- .position
- .text_anchor
- .buffer_id
- .and_then(|buffer_id| {
+ if let Some(ResolvedHint::Resolved(cached_hint)) = buffer_snapshot
+ .anchor_to_buffer_anchor(hovered_hint.position)
+ .and_then(|(anchor, _)| {
lsp_store.update(cx, |lsp_store, cx| {
- lsp_store.resolved_hint(buffer_id, hovered_hint.id, cx)
+ lsp_store.resolved_hint(anchor.buffer_id, hovered_hint.id, cx)
})
})
{
@@ -787,15 +793,19 @@ impl Editor {
new_hints: Vec<(Range<BufferRow>, anyhow::Result<CacheInlayHints>)>,
cx: &mut Context<Self>,
) {
+ let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
let visible_inlay_hint_ids = Self::visible_inlay_hints(self.display_map.read(cx))
- .filter(|inlay| inlay.position.text_anchor.buffer_id == Some(buffer_id))
+ .filter(|inlay| {
+ multi_buffer_snapshot
+ .anchor_to_buffer_anchor(inlay.position)
+ .map(|(anchor, _)| anchor.buffer_id)
+ == Some(buffer_id)
+ })
.map(|inlay| inlay.id)
.collect::<Vec<_>>();
let Some(inlay_hints) = &mut self.inlay_hints else {
return;
};
-
- let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
let Some(buffer_snapshot) = self
.buffer
.read(cx)
@@ -910,12 +920,10 @@ impl Editor {
hints_to_remove.extend(
Self::visible_inlay_hints(self.display_map.read(cx))
.filter(|inlay| {
- inlay
- .position
- .text_anchor
- .buffer_id
- .is_none_or(|buffer_id| {
- invalidate_hints_for_buffers.contains(&buffer_id)
+ multi_buffer_snapshot
+ .anchor_to_buffer_anchor(inlay.position)
+ .is_none_or(|(anchor, _)| {
+ invalidate_hints_for_buffers.contains(&anchor.buffer_id)
})
})
.map(|inlay| inlay.id),
@@ -2285,17 +2293,15 @@ pub mod tests {
cx: &mut gpui::TestAppContext,
) -> Range<Point> {
let ranges = editor
- .update(cx, |editor, _window, cx| editor.visible_excerpts(true, cx))
+ .update(cx, |editor, _window, cx| editor.visible_buffer_ranges(cx))
.unwrap();
assert_eq!(
ranges.len(),
1,
"Single buffer should produce a single excerpt with visible range"
);
- let (_, (excerpt_buffer, _, excerpt_visible_range)) = ranges.into_iter().next().unwrap();
- excerpt_buffer.read_with(cx, |buffer, _| {
- excerpt_visible_range.to_point(&buffer.snapshot())
- })
+ let (buffer_snapshot, visible_range, _) = ranges.into_iter().next().unwrap();
+ visible_range.to_point(&buffer_snapshot)
}
#[gpui::test]
@@ -2968,7 +2974,7 @@ let c = 3;"#
.await
.unwrap();
let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
- let (buffer_1_excerpts, buffer_2_excerpts) = multibuffer.update(cx, |multibuffer, cx| {
+ multibuffer.update(cx, |multibuffer, cx| {
multibuffer.set_excerpts_for_path(
PathKey::sorted(0),
buffer_1.clone(),
@@ -2983,15 +2989,8 @@ let c = 3;"#
0,
cx,
);
- let excerpt_ids = multibuffer.excerpt_ids();
- let buffer_1_excerpts = vec![excerpt_ids[0]];
- let buffer_2_excerpts = vec![excerpt_ids[1]];
- (buffer_1_excerpts, buffer_2_excerpts)
});
- assert!(!buffer_1_excerpts.is_empty());
- assert!(!buffer_2_excerpts.is_empty());
-
cx.executor().run_until_parked();
let editor = cx.add_window(|window, cx| {
Editor::for_multibuffer(multibuffer, Some(project.clone()), window, cx)
@@ -3092,7 +3091,7 @@ let c = 3;"#
editor
.update(cx, |editor, _, cx| {
editor.buffer().update(cx, |multibuffer, cx| {
- multibuffer.remove_excerpts_for_path(PathKey::sorted(1), cx);
+ multibuffer.remove_excerpts(PathKey::sorted(1), cx);
})
})
.unwrap();
@@ -1,7 +1,7 @@
use crate::{
ActiveDebugLine, Anchor, Autoscroll, BufferSerialization, Capability, Editor, EditorEvent,
- EditorSettings, ExcerptId, ExcerptRange, FormatTarget, MultiBuffer, MultiBufferSnapshot,
- NavigationData, ReportEditorEvent, SelectionEffects, ToPoint as _,
+ EditorSettings, ExcerptRange, FormatTarget, MultiBuffer, MultiBufferSnapshot, NavigationData,
+ ReportEditorEvent, SelectionEffects, ToPoint as _,
display_map::HighlightKey,
editor_settings::SeedQuerySetting,
persistence::{EditorDb, SerializedEditor},
@@ -22,7 +22,7 @@ use language::{
SelectionGoal, proto::serialize_anchor as serialize_text_anchor,
};
use lsp::DiagnosticSeverity;
-use multi_buffer::MultiBufferOffset;
+use multi_buffer::{MultiBufferOffset, PathKey};
use project::{
File, Project, ProjectItem as _, ProjectPath, lsp_store::FormatTrigger,
project_settings::ProjectSettings, search::SearchQuery,
@@ -33,14 +33,13 @@ use std::{
any::{Any, TypeId},
borrow::Cow,
cmp::{self, Ordering},
- iter,
ops::Range,
path::{Path, PathBuf},
sync::Arc,
};
use text::{BufferId, BufferSnapshot, Selection};
use ui::{IconDecorationKind, prelude::*};
-use util::{ResultExt, TryFutureExt, paths::PathExt};
+use util::{ResultExt, TryFutureExt, paths::PathExt, rel_path::RelPath};
use workspace::item::{Dedup, ItemSettings, SerializableItem, TabContentParams};
use workspace::{
CollaboratorId, ItemId, ItemNavHistory, ToolbarItemLocation, ViewId, Workspace, WorkspaceId,
@@ -83,10 +82,11 @@ impl FollowableItem for Editor {
};
let buffer_ids = state
- .excerpts
+ .path_excerpts
.iter()
.map(|excerpt| excerpt.buffer_id)
.collect::<HashSet<_>>();
+
let buffers = project.update(cx, |project, cx| {
buffer_ids
.iter()
@@ -106,38 +106,32 @@ impl FollowableItem for Editor {
multibuffer = MultiBuffer::singleton(buffers.pop().unwrap(), cx)
} else {
multibuffer = MultiBuffer::new(project.read(cx).capability());
- let mut sorted_excerpts = state.excerpts.clone();
- sorted_excerpts.sort_by_key(|e| e.id);
- let sorted_excerpts = sorted_excerpts.into_iter().peekable();
-
- for excerpt in sorted_excerpts {
- let Ok(buffer_id) = BufferId::new(excerpt.buffer_id) else {
+ for path_with_ranges in state.path_excerpts {
+ let Some(path_key) =
+ path_with_ranges.path_key.and_then(deserialize_path_key)
+ else {
continue;
};
-
- let mut insert_position = ExcerptId::min();
- for e in &state.excerpts {
- if e.id == excerpt.id {
- break;
- }
- if e.id < excerpt.id {
- insert_position = ExcerptId::from_proto(e.id);
- }
- }
-
- let buffer =
- buffers.iter().find(|b| b.read(cx).remote_id() == buffer_id);
-
- let Some(excerpt) = deserialize_excerpt_range(excerpt) else {
+ let Some(buffer_id) = BufferId::new(path_with_ranges.buffer_id).ok()
+ else {
continue;
};
-
- let Some(buffer) = buffer else { continue };
-
- multibuffer.insert_excerpts_with_ids_after(
- insert_position,
+ let Some(buffer) =
+ buffers.iter().find(|b| b.read(cx).remote_id() == buffer_id)
+ else {
+ continue;
+ };
+ let buffer_snapshot = buffer.read(cx).snapshot();
+ let ranges = path_with_ranges
+ .ranges
+ .into_iter()
+ .filter_map(deserialize_excerpt_range)
+ .collect::<Vec<_>>();
+ multibuffer.update_path_excerpts(
+ path_key,
buffer.clone(),
- [excerpt],
+ &buffer_snapshot,
+ &ranges,
cx,
);
}
@@ -158,6 +152,7 @@ impl FollowableItem for Editor {
})
})?;
+ editor.update(cx, |editor, cx| editor.text(cx));
update_editor_from_message(
editor.downgrade(),
project,
@@ -215,38 +210,43 @@ impl FollowableItem for Editor {
let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let scroll_anchor = self.scroll_manager.native_anchor(&display_snapshot, cx);
let buffer = self.buffer.read(cx);
- let excerpts = buffer
- .read(cx)
- .excerpts()
- .map(|(id, buffer, range)| proto::Excerpt {
- id: id.to_proto(),
- buffer_id: buffer.remote_id().into(),
- context_start: Some(serialize_text_anchor(&range.context.start)),
- context_end: Some(serialize_text_anchor(&range.context.end)),
- primary_start: Some(serialize_text_anchor(&range.primary.start)),
- primary_end: Some(serialize_text_anchor(&range.primary.end)),
- })
- .collect();
let snapshot = buffer.snapshot(cx);
+ let mut path_excerpts: Vec<proto::PathExcerpts> = Vec::new();
+ for excerpt in snapshot.excerpts() {
+ if let Some(prev_entry) = path_excerpts.last_mut()
+ && prev_entry.buffer_id == excerpt.context.start.buffer_id.to_proto()
+ {
+ prev_entry.ranges.push(serialize_excerpt_range(excerpt));
+ } else if let Some(path_key) = snapshot.path_for_buffer(excerpt.context.start.buffer_id)
+ {
+ path_excerpts.push(proto::PathExcerpts {
+ path_key: Some(serialize_path_key(path_key)),
+ buffer_id: excerpt.context.start.buffer_id.to_proto(),
+ ranges: vec![serialize_excerpt_range(excerpt)],
+ });
+ }
+ }
Some(proto::view::Variant::Editor(proto::view::Editor {
singleton: buffer.is_singleton(),
title: buffer.explicit_title().map(ToOwned::to_owned),
- excerpts,
- scroll_top_anchor: Some(serialize_anchor(&scroll_anchor.anchor, &snapshot)),
+ excerpts: Vec::new(),
+ scroll_top_anchor: Some(serialize_anchor(&scroll_anchor.anchor)),
scroll_x: scroll_anchor.offset.x,
scroll_y: scroll_anchor.offset.y,
selections: self
.selections
.disjoint_anchors_arc()
.iter()
- .map(|s| serialize_selection(s, &snapshot))
+ .map(serialize_selection)
.collect(),
pending_selection: self
.selections
.pending_anchor()
.as_ref()
- .map(|s| serialize_selection(s, &snapshot)),
+ .copied()
+ .map(serialize_selection),
+ path_excerpts,
}))
}
@@ -277,56 +277,52 @@ impl FollowableItem for Editor {
match update {
proto::update_view::Variant::Editor(update) => match event {
- EditorEvent::ExcerptsAdded {
+ EditorEvent::BufferRangesUpdated {
buffer,
- predecessor,
- excerpts,
+ path_key,
+ ranges,
} => {
- let buffer_id = buffer.read(cx).remote_id();
- let mut excerpts = excerpts.iter();
- if let Some((id, range)) = excerpts.next() {
- update.inserted_excerpts.push(proto::ExcerptInsertion {
- previous_excerpt_id: Some(predecessor.to_proto()),
- excerpt: serialize_excerpt(buffer_id, id, range),
- });
- update.inserted_excerpts.extend(excerpts.map(|(id, range)| {
- proto::ExcerptInsertion {
- previous_excerpt_id: None,
- excerpt: serialize_excerpt(buffer_id, id, range),
- }
- }))
- }
+ let buffer_id = buffer.read(cx).remote_id().to_proto();
+ let path_key = serialize_path_key(path_key);
+ let ranges = ranges
+ .iter()
+ .cloned()
+ .map(serialize_excerpt_range)
+ .collect::<Vec<_>>();
+ update.updated_paths.push(proto::PathExcerpts {
+ path_key: Some(path_key),
+ buffer_id,
+ ranges,
+ });
true
}
- EditorEvent::ExcerptsRemoved { ids, .. } => {
+ EditorEvent::BuffersRemoved { removed_buffer_ids } => {
update
- .deleted_excerpts
- .extend(ids.iter().copied().map(ExcerptId::to_proto));
+ .deleted_buffers
+ .extend(removed_buffer_ids.iter().copied().map(BufferId::to_proto));
true
}
EditorEvent::ScrollPositionChanged { autoscroll, .. } if !autoscroll => {
let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
- let snapshot = self.buffer.read(cx).snapshot(cx);
let scroll_anchor = self.scroll_manager.native_anchor(&display_snapshot, cx);
- update.scroll_top_anchor =
- Some(serialize_anchor(&scroll_anchor.anchor, &snapshot));
+ update.scroll_top_anchor = Some(serialize_anchor(&scroll_anchor.anchor));
update.scroll_x = scroll_anchor.offset.x;
update.scroll_y = scroll_anchor.offset.y;
true
}
EditorEvent::SelectionsChanged { .. } => {
- let snapshot = self.buffer.read(cx).snapshot(cx);
update.selections = self
.selections
.disjoint_anchors_arc()
.iter()
- .map(|s| serialize_selection(s, &snapshot))
+ .map(serialize_selection)
.collect();
update.pending_selection = self
.selections
.pending_anchor()
.as_ref()
- .map(|s| serialize_selection(s, &snapshot));
+ .copied()
+ .map(serialize_selection);
true
}
_ => false,
@@ -370,7 +366,7 @@ impl FollowableItem for Editor {
) {
let buffer = self.buffer.read(cx);
let buffer = buffer.read(cx);
- let Some(position) = buffer.as_singleton_anchor(location) else {
+ let Some(position) = buffer.anchor_in_excerpt(location) else {
return;
};
let selection = Selection {
@@ -394,9 +390,9 @@ async fn update_editor_from_message(
) -> Result<()> {
// Open all of the buffers of which excerpts were added to the editor.
let inserted_excerpt_buffer_ids = message
- .inserted_excerpts
+ .updated_paths
.iter()
- .filter_map(|insertion| Some(insertion.excerpt.as_ref()?.buffer_id))
+ .map(|insertion| insertion.buffer_id)
.collect::<HashSet<_>>();
let inserted_excerpt_buffers = project.update(cx, |project, cx| {
inserted_excerpt_buffer_ids
@@ -407,66 +403,53 @@ async fn update_editor_from_message(
let _inserted_excerpt_buffers = try_join_all(inserted_excerpt_buffers).await?;
// Update the editor's excerpts.
- this.update(cx, |editor, cx| {
+ let buffer_snapshot = this.update(cx, |editor, cx| {
editor.buffer.update(cx, |multibuffer, cx| {
- let mut removed_excerpt_ids = message
- .deleted_excerpts
- .into_iter()
- .map(ExcerptId::from_proto)
- .collect::<Vec<_>>();
- removed_excerpt_ids.sort_by({
- let multibuffer = multibuffer.read(cx);
- move |a, b| a.cmp(b, &multibuffer)
- });
-
- let mut insertions = message.inserted_excerpts.into_iter().peekable();
- while let Some(insertion) = insertions.next() {
- let Some(excerpt) = insertion.excerpt else {
+ for path_with_excerpts in message.updated_paths {
+ let Some(path_key) = path_with_excerpts.path_key.and_then(deserialize_path_key)
+ else {
continue;
};
- let Some(previous_excerpt_id) = insertion.previous_excerpt_id else {
- continue;
- };
- let buffer_id = BufferId::new(excerpt.buffer_id)?;
- let Some(buffer) = project.read(cx).buffer_for_id(buffer_id, cx) else {
+ let ranges = path_with_excerpts
+ .ranges
+ .into_iter()
+ .filter_map(deserialize_excerpt_range)
+ .collect::<Vec<_>>();
+ let Some(buffer) = BufferId::new(path_with_excerpts.buffer_id)
+ .ok()
+ .and_then(|buffer_id| project.read(cx).buffer_for_id(buffer_id, cx))
+ else {
continue;
};
- let adjacent_excerpts = iter::from_fn(|| {
- let insertion = insertions.peek()?;
- if insertion.previous_excerpt_id.is_none()
- && insertion.excerpt.as_ref()?.buffer_id == u64::from(buffer_id)
- {
- insertions.next()?.excerpt
- } else {
- None
- }
- });
+ let buffer_snapshot = buffer.read(cx).snapshot();
+ multibuffer.update_path_excerpts(path_key, buffer, &buffer_snapshot, &ranges, cx);
+ }
- multibuffer.insert_excerpts_with_ids_after(
- ExcerptId::from_proto(previous_excerpt_id),
- buffer,
- [excerpt]
- .into_iter()
- .chain(adjacent_excerpts)
- .filter_map(deserialize_excerpt_range),
- cx,
- );
+ for buffer_id in message
+ .deleted_buffers
+ .into_iter()
+ .filter_map(|buffer_id| BufferId::new(buffer_id).ok())
+ {
+ multibuffer.remove_excerpts_for_buffer(buffer_id, cx);
}
- multibuffer.remove_excerpts(removed_excerpt_ids, cx);
- anyhow::Ok(())
+ multibuffer.snapshot(cx)
})
- })??;
+ })?;
// Deserialize the editor state.
let selections = message
.selections
.into_iter()
- .filter_map(deserialize_selection)
+ .filter_map(|selection| deserialize_selection(selection, &buffer_snapshot))
.collect::<Vec<_>>();
- let pending_selection = message.pending_selection.and_then(deserialize_selection);
- let scroll_top_anchor = message.scroll_top_anchor.and_then(deserialize_anchor);
+ let pending_selection = message
+ .pending_selection
+ .and_then(|selection| deserialize_selection(selection, &buffer_snapshot));
+ let scroll_top_anchor = message
+ .scroll_top_anchor
+ .and_then(|selection| deserialize_anchor(selection, &buffer_snapshot));
// Wait until the buffer has received all of the operations referenced by
// the editor's new state.
@@ -503,79 +486,103 @@ async fn update_editor_from_message(
Ok(())
}
-fn serialize_excerpt(
- buffer_id: BufferId,
- id: &ExcerptId,
- range: &ExcerptRange<language::Anchor>,
-) -> Option<proto::Excerpt> {
- Some(proto::Excerpt {
- id: id.to_proto(),
- buffer_id: buffer_id.into(),
- context_start: Some(serialize_text_anchor(&range.context.start)),
- context_end: Some(serialize_text_anchor(&range.context.end)),
- primary_start: Some(serialize_text_anchor(&range.primary.start)),
- primary_end: Some(serialize_text_anchor(&range.primary.end)),
- })
-}
-
-fn serialize_selection(
- selection: &Selection<Anchor>,
- buffer: &MultiBufferSnapshot,
-) -> proto::Selection {
+fn serialize_selection(selection: &Selection<Anchor>) -> proto::Selection {
proto::Selection {
id: selection.id as u64,
- start: Some(serialize_anchor(&selection.start, buffer)),
- end: Some(serialize_anchor(&selection.end, buffer)),
+ start: Some(serialize_anchor(&selection.start)),
+ end: Some(serialize_anchor(&selection.end)),
reversed: selection.reversed,
}
}
-fn serialize_anchor(anchor: &Anchor, buffer: &MultiBufferSnapshot) -> proto::EditorAnchor {
- proto::EditorAnchor {
- excerpt_id: buffer.latest_excerpt_id(anchor.excerpt_id).to_proto(),
- anchor: Some(serialize_text_anchor(&anchor.text_anchor)),
+fn serialize_anchor(anchor: &Anchor) -> proto::EditorAnchor {
+ match anchor {
+ Anchor::Min => proto::EditorAnchor {
+ excerpt_id: None,
+ anchor: Some(proto::Anchor {
+ replica_id: 0,
+ timestamp: 0,
+ offset: 0,
+ bias: proto::Bias::Left as i32,
+ buffer_id: None,
+ }),
+ },
+ Anchor::Excerpt(_) => proto::EditorAnchor {
+ excerpt_id: None,
+ anchor: anchor.raw_text_anchor().map(|a| serialize_text_anchor(&a)),
+ },
+ Anchor::Max => proto::EditorAnchor {
+ excerpt_id: None,
+ anchor: Some(proto::Anchor {
+ replica_id: u32::MAX,
+ timestamp: u32::MAX,
+ offset: u64::MAX,
+ bias: proto::Bias::Right as i32,
+ buffer_id: None,
+ }),
+ },
+ }
+}
+
+fn serialize_excerpt_range(range: ExcerptRange<language::Anchor>) -> proto::ExcerptRange {
+ let context_start = language::proto::serialize_anchor(&range.context.start);
+ let context_end = language::proto::serialize_anchor(&range.context.end);
+ let primary_start = language::proto::serialize_anchor(&range.primary.start);
+ let primary_end = language::proto::serialize_anchor(&range.primary.end);
+ proto::ExcerptRange {
+ context_start: Some(context_start),
+ context_end: Some(context_end),
+ primary_start: Some(primary_start),
+ primary_end: Some(primary_end),
}
}
fn deserialize_excerpt_range(
- excerpt: proto::Excerpt,
-) -> Option<(ExcerptId, ExcerptRange<language::Anchor>)> {
+ excerpt_range: proto::ExcerptRange,
+) -> Option<ExcerptRange<language::Anchor>> {
let context = {
- let start = language::proto::deserialize_anchor(excerpt.context_start?)?;
- let end = language::proto::deserialize_anchor(excerpt.context_end?)?;
+ let start = language::proto::deserialize_anchor(excerpt_range.context_start?)?;
+ let end = language::proto::deserialize_anchor(excerpt_range.context_end?)?;
start..end
};
- let primary = excerpt
+ let primary = excerpt_range
.primary_start
- .zip(excerpt.primary_end)
+ .zip(excerpt_range.primary_end)
.and_then(|(start, end)| {
let start = language::proto::deserialize_anchor(start)?;
let end = language::proto::deserialize_anchor(end)?;
Some(start..end)
})
.unwrap_or_else(|| context.clone());
- Some((
- ExcerptId::from_proto(excerpt.id),
- ExcerptRange { context, primary },
- ))
+ Some(ExcerptRange { context, primary })
}
-fn deserialize_selection(selection: proto::Selection) -> Option<Selection<Anchor>> {
+fn deserialize_selection(
+ selection: proto::Selection,
+ buffer: &MultiBufferSnapshot,
+) -> Option<Selection<Anchor>> {
Some(Selection {
id: selection.id as usize,
- start: deserialize_anchor(selection.start?)?,
- end: deserialize_anchor(selection.end?)?,
+ start: deserialize_anchor(selection.start?, buffer)?,
+ end: deserialize_anchor(selection.end?, buffer)?,
reversed: selection.reversed,
goal: SelectionGoal::None,
})
}
-fn deserialize_anchor(anchor: proto::EditorAnchor) -> Option<Anchor> {
- let excerpt_id = ExcerptId::from_proto(anchor.excerpt_id);
- Some(Anchor::in_buffer(
- excerpt_id,
- language::proto::deserialize_anchor(anchor.anchor?)?,
- ))
+fn deserialize_anchor(anchor: proto::EditorAnchor, buffer: &MultiBufferSnapshot) -> Option<Anchor> {
+ let anchor = anchor.anchor?;
+ if let Some(buffer_id) = anchor.buffer_id
+ && BufferId::new(buffer_id).is_ok()
+ {
+ let text_anchor = language::proto::deserialize_anchor(anchor)?;
+ buffer.anchor_in_buffer(text_anchor)
+ } else {
+ match proto::Bias::from_i32(anchor.bias)? {
+ proto::Bias::Left => Some(Anchor::Min),
+ proto::Bias::Right => Some(Anchor::Max),
+ }
+ }
}
impl Item for Editor {
@@ -1071,7 +1078,7 @@ impl Item for Editor {
f(ItemEvent::UpdateBreadcrumbs);
}
- EditorEvent::ExcerptsAdded { .. } | EditorEvent::ExcerptsRemoved { .. } => {
+ EditorEvent::BufferRangesUpdated { .. } | EditorEvent::BuffersRemoved { .. } => {
f(ItemEvent::Edit);
}
@@ -1434,9 +1441,9 @@ impl ProjectItem for Editor {
cx: &mut Context<Self>,
) -> Self {
let mut editor = Self::for_buffer(buffer.clone(), Some(project), window, cx);
+ let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
- if let Some((excerpt_id, _, snapshot)) =
- editor.buffer().read(cx).snapshot(cx).as_singleton()
+ if let Some(buffer_snapshot) = editor.buffer().read(cx).snapshot(cx).as_singleton()
&& WorkspaceSettings::get(None, cx).restore_on_file_reopen
&& let Some(restoration_data) = Self::project_item_kind()
.and_then(|kind| pane.as_ref()?.project_item_restoration_data.get(&kind))
@@ -1448,7 +1455,7 @@ impl ProjectItem for Editor {
{
if !restoration_data.folds.is_empty() {
editor.fold_ranges(
- clip_ranges(&restoration_data.folds, snapshot),
+ clip_ranges(&restoration_data.folds, buffer_snapshot),
false,
window,
cx,
@@ -1456,12 +1463,11 @@ impl ProjectItem for Editor {
}
if !restoration_data.selections.is_empty() {
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
- s.select_ranges(clip_ranges(&restoration_data.selections, snapshot));
+ s.select_ranges(clip_ranges(&restoration_data.selections, buffer_snapshot));
});
}
let (top_row, offset) = restoration_data.scroll_position;
- let anchor =
- Anchor::in_buffer(excerpt_id, snapshot.anchor_before(Point::new(top_row, 0)));
+ let anchor = multibuffer_snapshot.anchor_before(Point::new(top_row, 0));
editor.set_scroll_anchor(ScrollAnchor { anchor, offset }, window, cx);
}
@@ -1838,7 +1844,7 @@ impl SearchableItem for Editor {
};
for range in search_within_ranges {
- for (search_buffer, search_range, excerpt_id, deleted_hunk_anchor) in
+ for (search_buffer, search_range, deleted_hunk_anchor) in
buffer.range_to_buffer_ranges_with_deleted_hunks(range)
{
ranges.extend(
@@ -1849,20 +1855,22 @@ impl SearchableItem for Editor {
)
.await
.into_iter()
- .map(|match_range| {
+ .filter_map(|match_range| {
if let Some(deleted_hunk_anchor) = deleted_hunk_anchor {
let start = search_buffer
.anchor_after(search_range.start + match_range.start);
let end = search_buffer
.anchor_before(search_range.start + match_range.end);
- deleted_hunk_anchor.with_diff_base_anchor(start)
- ..deleted_hunk_anchor.with_diff_base_anchor(end)
+ Some(
+ deleted_hunk_anchor.with_diff_base_anchor(start)
+ ..deleted_hunk_anchor.with_diff_base_anchor(end),
+ )
} else {
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, start..end)
+ buffer.buffer_anchor_range_to_anchor_range(start..end)
}
}),
);
@@ -2050,6 +2058,20 @@ fn restore_serialized_buffer_contents(
}
}
+fn serialize_path_key(path_key: &PathKey) -> proto::PathKey {
+ proto::PathKey {
+ sort_prefix: path_key.sort_prefix,
+ path: path_key.path.to_proto(),
+ }
+}
+
+fn deserialize_path_key(path_key: proto::PathKey) -> Option<PathKey> {
+ Some(PathKey {
+ sort_prefix: path_key.sort_prefix,
+ path: RelPath::from_proto(&path_key.path).ok()?,
+ })
+}
+
#[cfg(test)]
mod tests {
use crate::editor_tests::init_test;
@@ -352,11 +352,12 @@ pub(crate) fn construct_initial_buffer_versions_map<
}
for (edit_range, _) in edits {
- let edit_range_buffer = editor
- .buffer()
- .read(cx)
- .excerpt_containing(edit_range.end, cx)
- .map(|e| e.1);
+ let multibuffer = editor.buffer.read(cx);
+ let snapshot = multibuffer.snapshot(cx);
+ let anchor = snapshot.anchor_before(edit_range.end);
+ let edit_range_buffer = snapshot
+ .anchor_to_buffer_anchor(anchor)
+ .and_then(|(text_anchor, _)| multibuffer.buffer(text_anchor.buffer_id));
if let Some(buffer) = edit_range_buffer {
let (buffer_id, buffer_version) =
buffer.read_with(cx, |buffer, _| (buffer.remote_id(), buffer.version.clone()));
@@ -2,7 +2,6 @@ use collections::HashMap;
use gpui::{AppContext, Context, Entity, Window};
use itertools::Itertools;
use language::Buffer;
-use multi_buffer::MultiBufferOffset;
use std::{ops::Range, sync::Arc, time::Duration};
use text::{Anchor, AnchorRangeExt, Bias, BufferId, ToOffset, ToPoint};
use util::ResultExt;
@@ -62,27 +61,15 @@ pub(super) fn refresh_linked_ranges(
editor
.update(cx, |editor, cx| {
let display_snapshot = editor.display_snapshot(cx);
- let selections = editor
- .selections
- .all::<MultiBufferOffset>(&display_snapshot);
+ let selections = editor.selections.all_anchors(&display_snapshot);
let snapshot = display_snapshot.buffer_snapshot();
let buffer = editor.buffer.read(cx);
- for selection in selections {
- let cursor_position = selection.head();
- let start_position = snapshot.anchor_before(cursor_position);
- let end_position = snapshot.anchor_after(selection.tail());
- if start_position.text_anchor.buffer_id != end_position.text_anchor.buffer_id
- || end_position.text_anchor.buffer_id.is_none()
+ for selection in selections.iter() {
+ if let Some((_, range)) =
+ snapshot.anchor_range_to_buffer_anchor_range(selection.range())
+ && let Some(buffer) = buffer.buffer(range.start.buffer_id)
{
- // Throw away selections spanning multiple buffers.
- continue;
- }
- if let Some(buffer) = buffer.buffer_for_anchor(end_position, cx) {
- applicable_selections.push((
- buffer,
- start_position.text_anchor,
- end_position.text_anchor,
- ));
+ applicable_selections.push((buffer, range.start, range.end));
}
}
})
@@ -9,7 +9,6 @@ use language::Buffer;
use language::Language;
use lsp::LanguageServerId;
use lsp::LanguageServerName;
-use multi_buffer::Anchor;
use project::LanguageServerToQuery;
use project::LocationLink;
use project::Project;
@@ -27,7 +26,12 @@ pub(crate) fn find_specific_language_server_in_selection<F>(
cx: &mut App,
filter_language: F,
language_server_name: LanguageServerName,
-) -> Option<(Anchor, Arc<Language>, LanguageServerId, Entity<Buffer>)>
+) -> Option<(
+ text::Anchor,
+ Arc<Language>,
+ LanguageServerId,
+ Entity<Buffer>,
+)>
where
F: Fn(&Language) -> bool,
{
@@ -40,19 +44,15 @@ where
.iter()
.find_map(|selection| {
let multi_buffer = multi_buffer.read(cx);
- let (position, buffer) = multi_buffer
- .buffer_for_anchor(selection.head(), cx)
- .map(|buffer| (selection.head(), buffer))
- .or_else(|| {
- multi_buffer
- .buffer_for_anchor(selection.tail(), cx)
- .map(|buffer| (selection.tail(), buffer))
- })?;
+ let multi_buffer_snapshot = multi_buffer.snapshot(cx);
+ let (position, buffer) = multi_buffer_snapshot
+ .anchor_to_buffer_anchor(selection.head())
+ .and_then(|(anchor, _)| Some((anchor, multi_buffer.buffer(anchor.buffer_id)?)))?;
if !seen_buffer_ids.insert(buffer.read(cx).remote_id()) {
return None;
}
- let language = buffer.read(cx).language_at(position.text_anchor)?;
+ let language = buffer.read(cx).language_at(position)?;
if filter_language(&language) {
let server_id = buffer.update(cx, |buffer, cx| {
project
@@ -108,7 +108,7 @@ pub fn lsp_tasks(
let buffers = buffer_ids
.iter()
.filter(|&&buffer_id| match for_position {
- Some(for_position) => for_position.buffer_id == Some(buffer_id),
+ Some(for_position) => for_position.buffer_id == buffer_id,
None => true,
})
.filter_map(|&buffer_id| project.read(cx).buffer_for_id(buffer_id, cx))
@@ -194,7 +194,7 @@ mod tests {
use language::{FakeLspAdapter, Language};
use languages::rust_lang;
use lsp::{LanguageServerId, LanguageServerName};
- use multi_buffer::{Anchor, MultiBuffer};
+ use multi_buffer::MultiBuffer;
use project::{FakeFs, Project};
use util::path;
@@ -236,7 +236,7 @@ mod tests {
let filter = |language: &Language| language.name().as_ref() == "Rust";
let assert_result = |result: Option<(
- Anchor,
+ text::Anchor,
Arc<Language>,
LanguageServerId,
Entity<language::Buffer>,
@@ -205,16 +205,17 @@ pub fn deploy_context_menu(
.all::<PointUtf16>(&display_map)
.into_iter()
.any(|s| !s.is_empty());
- let has_git_repo = buffer
- .buffer_id_for_anchor(anchor)
- .is_some_and(|buffer_id| {
- project
- .read(cx)
- .git_store()
- .read(cx)
- .repository_and_path_for_buffer_id(buffer_id, cx)
- .is_some()
- });
+ let has_git_repo =
+ buffer
+ .anchor_to_buffer_anchor(anchor)
+ .is_some_and(|(buffer_anchor, _)| {
+ project
+ .read(cx)
+ .git_store()
+ .read(cx)
+ .repository_and_path_for_buffer_id(buffer_anchor.buffer_id, cx)
+ .is_some()
+ });
let evaluate_selection = window.is_action_available(&EvaluateSelectedText, cx);
let run_to_cursor = window.is_action_available(&RunToCursor, cx);
@@ -588,22 +588,30 @@ pub fn start_of_excerpt(
direction: Direction,
) -> DisplayPoint {
let point = map.display_point_to_point(display_point, Bias::Left);
- let Some(excerpt) = map.buffer_snapshot().excerpt_containing(point..point) else {
+ let Some((_, excerpt_range)) = map.buffer_snapshot().excerpt_containing(point..point) else {
return display_point;
};
match direction {
Direction::Prev => {
- let mut start = excerpt.start_anchor().to_display_point(map);
+ let Some(start_anchor) = map.anchor_in_excerpt(excerpt_range.context.start) else {
+ return display_point;
+ };
+ let mut start = start_anchor.to_display_point(map);
if start >= display_point && start.row() > DisplayRow(0) {
- let Some(excerpt) = map.buffer_snapshot().excerpt_before(excerpt.id()) else {
+ let Some(excerpt) = map.buffer_snapshot().excerpt_before(start_anchor) else {
return display_point;
};
- start = excerpt.start_anchor().to_display_point(map);
+ if let Some(start_anchor) = map.anchor_in_excerpt(excerpt.context.start) {
+ start = start_anchor.to_display_point(map);
+ }
}
start
}
Direction::Next => {
- let mut end = excerpt.end_anchor().to_display_point(map);
+ let Some(end_anchor) = map.anchor_in_excerpt(excerpt_range.context.end) else {
+ return display_point;
+ };
+ let mut end = end_anchor.to_display_point(map);
*end.row_mut() += 1;
map.clip_point(end, Bias::Right)
}
@@ -616,12 +624,15 @@ pub fn end_of_excerpt(
direction: Direction,
) -> DisplayPoint {
let point = map.display_point_to_point(display_point, Bias::Left);
- let Some(excerpt) = map.buffer_snapshot().excerpt_containing(point..point) else {
+ let Some((_, excerpt_range)) = map.buffer_snapshot().excerpt_containing(point..point) else {
return display_point;
};
match direction {
Direction::Prev => {
- let mut start = excerpt.start_anchor().to_display_point(map);
+ let Some(start_anchor) = map.anchor_in_excerpt(excerpt_range.context.start) else {
+ return display_point;
+ };
+ let mut start = start_anchor.to_display_point(map);
if start.row() > DisplayRow(0) {
*start.row_mut() -= 1;
}
@@ -630,18 +641,23 @@ pub fn end_of_excerpt(
start
}
Direction::Next => {
- let mut end = excerpt.end_anchor().to_display_point(map);
+ let Some(end_anchor) = map.anchor_in_excerpt(excerpt_range.context.end) else {
+ return display_point;
+ };
+ let mut end = end_anchor.to_display_point(map);
*end.column_mut() = 0;
if end <= display_point {
*end.row_mut() += 1;
let point_end = map.display_point_to_point(end, Bias::Right);
- let Some(excerpt) = map
+ let Some((_, excerpt_range)) = map
.buffer_snapshot()
.excerpt_containing(point_end..point_end)
else {
return display_point;
};
- end = excerpt.end_anchor().to_display_point(map);
+ if let Some(end_anchor) = map.anchor_in_excerpt(excerpt_range.context.end) {
+ end = end_anchor.to_display_point(map);
+ }
*end.column_mut() = 0;
}
end
@@ -8,9 +8,7 @@ use gpui::{
};
use language::{Buffer, BufferRow, Runnable};
use lsp::LanguageServerName;
-use multi_buffer::{
- Anchor, BufferOffset, MultiBufferOffset, MultiBufferRow, MultiBufferSnapshot, ToPoint as _,
-};
+use multi_buffer::{Anchor, BufferOffset, MultiBufferRow, MultiBufferSnapshot, ToPoint as _};
use project::{
Location, Project, TaskSourceKind,
debugger::breakpoint_store::{Breakpoint, BreakpointSessionState},
@@ -165,7 +163,7 @@ impl Editor {
.update(cx, |editor, cx| {
let multi_buffer = editor.buffer().read(cx);
if multi_buffer.is_singleton() {
- Some((multi_buffer.snapshot(cx), Anchor::min()..Anchor::max()))
+ Some((multi_buffer.snapshot(cx), Anchor::Min..Anchor::Max))
} else {
let display_snapshot =
editor.display_map.update(cx, |map, cx| map.snapshot(cx));
@@ -209,16 +207,8 @@ impl Editor {
.fold(HashMap::default(), |mut acc, (kind, location, task)| {
let buffer = location.target.buffer;
let buffer_snapshot = buffer.read(cx).snapshot();
- let offset = multi_buffer_snapshot.excerpts().find_map(
- |(excerpt_id, snapshot, _)| {
- if snapshot.remote_id() == buffer_snapshot.remote_id() {
- multi_buffer_snapshot
- .anchor_in_excerpt(excerpt_id, location.target.range.start)
- } else {
- None
- }
- },
- );
+ let offset =
+ multi_buffer_snapshot.anchor_in_excerpt(location.target.range.start);
if let Some(offset) = offset {
let task_buffer_range =
location.target.range.to_point(&buffer_snapshot);
@@ -369,20 +359,23 @@ impl Editor {
(selection, buffer, snapshot)
};
let selection_range = selection.range();
- let start = editor_snapshot
+ let Some((_, range)) = editor_snapshot
.display_snapshot
.buffer_snapshot()
- .anchor_after(selection_range.start)
- .text_anchor;
- let end = editor_snapshot
- .display_snapshot
- .buffer_snapshot()
- .anchor_after(selection_range.end)
- .text_anchor;
- let location = Location {
- buffer,
- range: start..end,
+ .anchor_range_to_buffer_anchor_range(
+ editor_snapshot
+ .display_snapshot
+ .buffer_snapshot()
+ .anchor_after(selection_range.start)
+ ..editor_snapshot
+ .display_snapshot
+ .buffer_snapshot()
+ .anchor_before(selection_range.end),
+ )
+ else {
+ return Task::ready(None);
};
+ let location = Location { buffer, range };
let captured_variables = {
let mut variables = TaskVariables::default();
let buffer = location.buffer.read(cx);
@@ -430,9 +423,9 @@ impl Editor {
return HashMap::default();
}
let buffers = if visible_only {
- self.visible_excerpts(true, cx)
- .into_values()
- .map(|(buffer, _, _)| buffer)
+ self.visible_buffers(cx)
+ .into_iter()
+ .filter(|buffer| self.is_lsp_relevant(buffer.read(cx).file(), cx))
.collect()
} else {
self.buffer().read(cx).all_buffers()
@@ -482,19 +475,15 @@ impl Editor {
cx: &mut Context<Self>,
) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
let snapshot = self.buffer.read(cx).snapshot(cx);
- let offset = self
- .selections
- .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
- .head();
- let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
- let offset = excerpt.map_offset_to_buffer(offset);
- let buffer_id = excerpt.buffer().remote_id();
+ let anchor = self.selections.newest_anchor().head();
+ let (anchor, buffer_snapshot) = snapshot.anchor_to_buffer_anchor(anchor)?;
+ let offset = anchor.to_offset(buffer_snapshot);
- let layer = excerpt.buffer().syntax_layer_at(offset)?;
+ let layer = buffer_snapshot.syntax_layer_at(offset)?;
let mut cursor = layer.node().walk();
- while cursor.goto_first_child_for_byte(offset.0).is_some() {
- if cursor.node().end_byte() == offset.0 {
+ while cursor.goto_first_child_for_byte(offset).is_some() {
+ if cursor.node().end_byte() == offset {
cursor.goto_next_sibling();
}
}
@@ -503,18 +492,18 @@ impl Editor {
loop {
let node = cursor.node();
let node_range = node.byte_range();
- let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
+ let symbol_start_row = buffer_snapshot.offset_to_point(node.start_byte()).row;
// Check if this node contains our offset
- if node_range.start <= offset.0 && node_range.end >= offset.0 {
+ if node_range.start <= offset && node_range.end >= offset {
// If it contains offset, check for task
if let Some(tasks) = self
.runnables
.runnables
- .get(&buffer_id)
+ .get(&buffer_snapshot.remote_id())
.and_then(|(_, tasks)| tasks.get(&symbol_start_row))
{
- let buffer = self.buffer.read(cx).buffer(buffer_id)?;
+ let buffer = self.buffer.read(cx).buffer(buffer_snapshot.remote_id())?;
return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
}
}
@@ -88,7 +88,7 @@ pub fn go_to_parent_module(
let request = proto::LspExtGoToParentModule {
project_id,
buffer_id: buffer_id.to_proto(),
- position: Some(serialize_anchor(&trigger_anchor.text_anchor)),
+ position: Some(serialize_anchor(&trigger_anchor)),
};
let response = client
.request(request)
@@ -106,7 +106,7 @@ pub fn go_to_parent_module(
.context("go to parent module via collab")?
} else {
let buffer_snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot());
- let position = trigger_anchor.text_anchor.to_point_utf16(&buffer_snapshot);
+ let position = trigger_anchor.to_point_utf16(&buffer_snapshot);
project
.update(cx, |project, cx| {
project.request_lsp(
@@ -168,7 +168,7 @@ pub fn expand_macro_recursively(
let request = proto::LspExtExpandMacro {
project_id,
buffer_id: buffer_id.to_proto(),
- position: Some(serialize_anchor(&trigger_anchor.text_anchor)),
+ position: Some(serialize_anchor(&trigger_anchor)),
};
let response = client
.request(request)
@@ -180,7 +180,7 @@ pub fn expand_macro_recursively(
}
} else {
let buffer_snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot());
- let position = trigger_anchor.text_anchor.to_point_utf16(&buffer_snapshot);
+ let position = trigger_anchor.to_point_utf16(&buffer_snapshot);
project
.update(cx, |project, cx| {
project.request_lsp(
@@ -195,10 +195,7 @@ pub fn expand_macro_recursively(
};
if macro_expansion.is_empty() {
- log::info!(
- "Empty macro expansion for position {:?}",
- trigger_anchor.text_anchor
- );
+ log::info!("Empty macro expansion for position {:?}", trigger_anchor);
return Ok(());
}
@@ -260,7 +257,7 @@ pub fn open_docs(editor: &mut Editor, _: &OpenDocs, window: &mut Window, cx: &mu
let request = proto::LspExtOpenDocs {
project_id,
buffer_id: buffer_id.to_proto(),
- position: Some(serialize_anchor(&trigger_anchor.text_anchor)),
+ position: Some(serialize_anchor(&trigger_anchor)),
};
let response = client
.request(request)
@@ -272,7 +269,7 @@ pub fn open_docs(editor: &mut Editor, _: &OpenDocs, window: &mut Window, cx: &mu
}
} else {
let buffer_snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot());
- let position = trigger_anchor.text_anchor.to_point_utf16(&buffer_snapshot);
+ let position = trigger_anchor.to_point_utf16(&buffer_snapshot);
project
.update(cx, |project, cx| {
project.request_lsp(
@@ -287,10 +284,7 @@ pub fn open_docs(editor: &mut Editor, _: &OpenDocs, window: &mut Window, cx: &mu
};
if docs_urls.is_empty() {
- log::debug!(
- "Empty docs urls for position {:?}",
- trigger_anchor.text_anchor
- );
+ log::debug!("Empty docs urls for position {:?}", trigger_anchor);
return Ok(());
}
@@ -322,16 +316,18 @@ fn cancel_flycheck_action(
let Some(project) = &editor.project else {
return;
};
+ let multibuffer_snapshot = editor
+ .buffer
+ .read_with(cx, |buffer, cx| buffer.snapshot(cx));
let buffer_id = editor
.selections
.disjoint_anchors_arc()
.iter()
.find_map(|selection| {
- let buffer_id = selection
- .start
- .text_anchor
- .buffer_id
- .or(selection.end.text_anchor.buffer_id)?;
+ let buffer_id = multibuffer_snapshot
+ .anchor_to_buffer_anchor(selection.start)?
+ .0
+ .buffer_id;
let project = project.read(cx);
let entry_id = project
.buffer_for_id(buffer_id, cx)?
@@ -351,16 +347,18 @@ fn run_flycheck_action(
let Some(project) = &editor.project else {
return;
};
+ let multibuffer_snapshot = editor
+ .buffer
+ .read_with(cx, |buffer, cx| buffer.snapshot(cx));
let buffer_id = editor
.selections
.disjoint_anchors_arc()
.iter()
.find_map(|selection| {
- let buffer_id = selection
- .start
- .text_anchor
- .buffer_id
- .or(selection.end.text_anchor.buffer_id)?;
+ let buffer_id = multibuffer_snapshot
+ .anchor_to_buffer_anchor(selection.head())?
+ .0
+ .buffer_id;
let project = project.read(cx);
let entry_id = project
.buffer_for_id(buffer_id, cx)?
@@ -380,16 +378,18 @@ fn clear_flycheck_action(
let Some(project) = &editor.project else {
return;
};
+ let multibuffer_snapshot = editor
+ .buffer
+ .read_with(cx, |buffer, cx| buffer.snapshot(cx));
let buffer_id = editor
.selections
.disjoint_anchors_arc()
.iter()
.find_map(|selection| {
- let buffer_id = selection
- .start
- .text_anchor
- .buffer_id
- .or(selection.end.text_anchor.buffer_id)?;
+ let buffer_id = multibuffer_snapshot
+ .anchor_to_buffer_anchor(selection.head())?
+ .0
+ .buffer_id;
let project = project.read(cx);
let entry_id = project
.buffer_for_id(buffer_id, cx)?
@@ -44,13 +44,13 @@ impl ScrollAnchor {
pub(super) fn new() -> Self {
Self {
offset: gpui::Point::default(),
- anchor: Anchor::min(),
+ anchor: Anchor::Min,
}
}
pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> gpui::Point<ScrollOffset> {
self.offset.apply_along(Axis::Vertical, |offset| {
- if self.anchor == Anchor::min() {
+ if self.anchor == Anchor::Min {
0.
} else {
let scroll_top = self.anchor.to_display_point(snapshot).row().as_f64();
@@ -78,7 +78,7 @@ impl Editor {
let selection_head = self.selections.newest_display(&display_snapshot).head();
let sticky_headers_len = if EditorSettings::get_global(cx).sticky_scroll.enabled
- && let Some((_, _, buffer_snapshot)) = display_snapshot.buffer_snapshot().as_singleton()
+ && let Some(buffer_snapshot) = display_snapshot.buffer_snapshot().as_singleton()
{
let select_head_point =
rope::Point::new(selection_head.to_point(&display_snapshot).row, 0);
@@ -4,7 +4,6 @@ use std::{
sync::Arc,
};
-use collections::HashMap;
use gpui::Pixels;
use itertools::Itertools as _;
use language::{Bias, Point, PointUtf16, Selection, SelectionGoal};
@@ -12,7 +11,7 @@ use multi_buffer::{MultiBufferDimension, MultiBufferOffset};
use util::post_inc;
use crate::{
- Anchor, DisplayPoint, DisplayRow, ExcerptId, MultiBufferSnapshot, SelectMode, ToOffset,
+ Anchor, DisplayPoint, DisplayRow, MultiBufferSnapshot, SelectMode, ToOffset,
display_map::{DisplaySnapshot, ToDisplayPoint},
movement::TextLayoutDetails,
};
@@ -45,8 +44,8 @@ impl SelectionsCollection {
pending: Some(PendingSelection {
selection: Selection {
id: 0,
- start: Anchor::min(),
- end: Anchor::min(),
+ start: Anchor::Min,
+ end: Anchor::Min,
reversed: false,
goal: SelectionGoal::None,
},
@@ -547,13 +546,11 @@ impl SelectionsCollection {
);
assert!(
snapshot.can_resolve(&selection.start),
- "disjoint selection start is not resolvable for the given snapshot:\n{selection:?}, {excerpt:?}",
- excerpt = snapshot.buffer_for_excerpt(selection.start.excerpt_id).map(|snapshot| snapshot.remote_id()),
+ "disjoint selection start is not resolvable for the given snapshot:\n{selection:?}",
);
assert!(
snapshot.can_resolve(&selection.end),
- "disjoint selection end is not resolvable for the given snapshot: {selection:?}, {excerpt:?}",
- excerpt = snapshot.buffer_for_excerpt(selection.end.excerpt_id).map(|snapshot| snapshot.remote_id()),
+ "disjoint selection start is not resolvable for the given snapshot:\n{selection:?}",
);
});
assert!(
@@ -572,17 +569,11 @@ impl SelectionsCollection {
);
assert!(
snapshot.can_resolve(&selection.start),
- "pending selection start is not resolvable for the given snapshot: {pending:?}, {excerpt:?}",
- excerpt = snapshot
- .buffer_for_excerpt(selection.start.excerpt_id)
- .map(|snapshot| snapshot.remote_id()),
+ "pending selection start is not resolvable for the given snapshot: {pending:?}",
);
assert!(
snapshot.can_resolve(&selection.end),
- "pending selection end is not resolvable for the given snapshot: {pending:?}, {excerpt:?}",
- excerpt = snapshot
- .buffer_for_excerpt(selection.end.excerpt_id)
- .map(|snapshot| snapshot.remote_id()),
+ "pending selection end is not resolvable for the given snapshot: {pending:?}",
);
}
}
@@ -665,10 +656,10 @@ impl<'snap, 'a> MutableSelectionsCollection<'snap, 'a> {
self.disjoint
.iter()
.filter(|selection| {
- if let Some(selection_buffer_id) =
- self.snapshot.buffer_id_for_anchor(selection.start)
+ if let Some((selection_buffer_anchor, _)) =
+ self.snapshot.anchor_to_buffer_anchor(selection.start)
{
- let should_remove = selection_buffer_id == buffer_id;
+ let should_remove = selection_buffer_anchor.buffer_id == buffer_id;
changed |= should_remove;
!should_remove
} else {
@@ -683,10 +674,8 @@ impl<'snap, 'a> MutableSelectionsCollection<'snap, 'a> {
let buffer_snapshot = self.snapshot.buffer_snapshot();
let anchor = buffer_snapshot
.excerpts()
- .find(|(_, buffer, _)| buffer.remote_id() == buffer_id)
- .and_then(|(excerpt_id, _, range)| {
- buffer_snapshot.anchor_in_excerpt(excerpt_id, range.context.start)
- })
+ .find(|excerpt| excerpt.context.start.buffer_id == buffer_id)
+ .and_then(|excerpt| buffer_snapshot.anchor_in_excerpt(excerpt.context.start))
.unwrap_or_else(|| self.snapshot.anchor_before(MultiBufferOffset(0)));
self.collection.disjoint = Arc::from([Selection {
id: post_inc(&mut self.collection.next_selection_id),
@@ -1077,80 +1066,6 @@ impl<'snap, 'a> MutableSelectionsCollection<'snap, 'a> {
self.selections_changed = true;
self.pending.as_mut().map(|pending| &mut pending.selection)
}
-
- /// Compute new ranges for any selections that were located in excerpts that have
- /// since been removed.
- ///
- /// Returns a `HashMap` indicating which selections whose former head position
- /// was no longer present. The keys of the map are selection ids. The values are
- /// the id of the new excerpt where the head of the selection has been moved.
- pub fn refresh(&mut self) -> HashMap<usize, ExcerptId> {
- let mut pending = self.collection.pending.take();
- let mut selections_with_lost_position = HashMap::default();
-
- let anchors_with_status = {
- let disjoint_anchors = self
- .disjoint
- .iter()
- .flat_map(|selection| [&selection.start, &selection.end]);
- self.snapshot.refresh_anchors(disjoint_anchors)
- };
- let adjusted_disjoint: Vec<_> = anchors_with_status
- .chunks(2)
- .map(|selection_anchors| {
- let (anchor_ix, start, kept_start) = selection_anchors[0];
- let (_, end, kept_end) = selection_anchors[1];
- let selection = &self.disjoint[anchor_ix / 2];
- let kept_head = if selection.reversed {
- kept_start
- } else {
- kept_end
- };
- if !kept_head {
- selections_with_lost_position.insert(selection.id, selection.head().excerpt_id);
- }
-
- Selection {
- id: selection.id,
- start,
- end,
- reversed: selection.reversed,
- goal: selection.goal,
- }
- })
- .collect();
-
- if !adjusted_disjoint.is_empty() {
- let map = self.display_snapshot();
- let resolved_selections =
- resolve_selections_wrapping_blocks(adjusted_disjoint.iter(), &map).collect();
- self.select::<MultiBufferOffset>(resolved_selections);
- }
-
- if let Some(pending) = pending.as_mut() {
- let anchors = self
- .snapshot
- .refresh_anchors([&pending.selection.start, &pending.selection.end]);
- let (_, start, kept_start) = anchors[0];
- let (_, end, kept_end) = anchors[1];
- let kept_head = if pending.selection.reversed {
- kept_start
- } else {
- kept_end
- };
- if !kept_head {
- selections_with_lost_position
- .insert(pending.selection.id, pending.selection.head().excerpt_id);
- }
-
- pending.selection.start = start;
- pending.selection.end = end;
- }
- self.collection.pending = pending;
- self.selections_changed = true;
-
- selections_with_lost_position
- }
}
impl Deref for MutableSelectionsCollection<'_, '_> {
@@ -148,9 +148,9 @@ impl Editor {
};
let buffers_to_query = self
- .visible_excerpts(true, cx)
- .into_values()
- .map(|(buffer, ..)| buffer)
+ .visible_buffers(cx)
+ .into_iter()
+ .filter(|buffer| self.is_lsp_relevant(buffer.read(cx).file(), cx))
.chain(buffer_id.and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id)))
.filter_map(|editor_buffer| {
let editor_buffer_id = editor_buffer.read(cx).remote_id();
@@ -1214,11 +1214,19 @@ mod tests {
);
// Get the excerpt id for the TOML excerpt and expand it down by 2 lines.
- let toml_excerpt_id =
- editor.read_with(cx, |editor, cx| editor.buffer().read(cx).excerpt_ids()[0]);
+ let toml_anchor = editor.read_with(cx, |editor, cx| {
+ editor
+ .buffer()
+ .read(cx)
+ .snapshot(cx)
+ .anchor_in_excerpt(text::Anchor::min_for_buffer(
+ toml_buffer.read(cx).remote_id(),
+ ))
+ .unwrap()
+ });
editor.update_in(cx, |editor, _, cx| {
editor.buffer().update(cx, |buffer, cx| {
- buffer.expand_excerpts([toml_excerpt_id], 2, ExpandExcerptDirection::Down, cx);
+ buffer.expand_excerpts([toml_anchor], 2, ExpandExcerptDirection::Down, cx);
});
});
@@ -1,5 +1,5 @@
use std::{
- ops::{Bound, Range, RangeInclusive},
+ ops::{Range, RangeInclusive},
sync::Arc,
};
@@ -13,7 +13,7 @@ use gpui::{
use itertools::Itertools;
use language::{Buffer, Capability, HighlightedText};
use multi_buffer::{
- Anchor, BufferOffset, ExcerptId, ExcerptRange, ExpandExcerptDirection, MultiBuffer,
+ Anchor, AnchorRangeExt as _, BufferOffset, ExcerptRange, ExpandExcerptDirection, MultiBuffer,
MultiBufferDiffHunk, MultiBufferPoint, MultiBufferSnapshot, PathKey,
};
use project::Project;
@@ -44,13 +44,11 @@ use crate::{
use zed_actions::assistant::InlineAssist;
pub(crate) fn convert_lhs_rows_to_rhs(
- lhs_excerpt_to_rhs_excerpt: &HashMap<ExcerptId, ExcerptId>,
rhs_snapshot: &MultiBufferSnapshot,
lhs_snapshot: &MultiBufferSnapshot,
- lhs_bounds: (Bound<MultiBufferPoint>, Bound<MultiBufferPoint>),
+ lhs_bounds: Range<MultiBufferPoint>,
) -> Vec<CompanionExcerptPatch> {
patches_for_range(
- lhs_excerpt_to_rhs_excerpt,
lhs_snapshot,
rhs_snapshot,
lhs_bounds,
@@ -59,13 +57,11 @@ pub(crate) fn convert_lhs_rows_to_rhs(
}
pub(crate) fn convert_rhs_rows_to_lhs(
- rhs_excerpt_to_lhs_excerpt: &HashMap<ExcerptId, ExcerptId>,
lhs_snapshot: &MultiBufferSnapshot,
rhs_snapshot: &MultiBufferSnapshot,
- rhs_bounds: (Bound<MultiBufferPoint>, Bound<MultiBufferPoint>),
+ rhs_bounds: Range<MultiBufferPoint>,
) -> Vec<CompanionExcerptPatch> {
patches_for_range(
- rhs_excerpt_to_lhs_excerpt,
rhs_snapshot,
lhs_snapshot,
rhs_bounds,
@@ -73,6 +69,21 @@ pub(crate) fn convert_rhs_rows_to_lhs(
)
}
+fn rhs_range_to_base_text_range(
+ rhs_range: &Range<Point>,
+ diff_snapshot: &BufferDiffSnapshot,
+ rhs_buffer_snapshot: &text::BufferSnapshot,
+) -> Range<Point> {
+ let start = diff_snapshot
+ .buffer_point_to_base_text_range(Point::new(rhs_range.start.row, 0), rhs_buffer_snapshot)
+ .start;
+ let end = diff_snapshot
+ .buffer_point_to_base_text_range(Point::new(rhs_range.end.row, 0), rhs_buffer_snapshot)
+ .end;
+ let end_column = diff_snapshot.base_text().line_len(end.row);
+ Point::new(start.row, 0)..Point::new(end.row, end_column)
+}
+
fn translate_lhs_selections_to_rhs(
selections_by_buffer: &HashMap<BufferId, (Vec<Range<BufferOffset>>, Option<u32>)>,
splittable: &SplittableEditor,
@@ -168,22 +179,18 @@ fn translate_lhs_hunks_to_rhs(
}
fn patches_for_range<F>(
- excerpt_map: &HashMap<ExcerptId, ExcerptId>,
source_snapshot: &MultiBufferSnapshot,
target_snapshot: &MultiBufferSnapshot,
- source_bounds: (Bound<MultiBufferPoint>, Bound<MultiBufferPoint>),
+ source_bounds: Range<MultiBufferPoint>,
translate_fn: F,
) -> Vec<CompanionExcerptPatch>
where
F: Fn(&BufferDiffSnapshot, RangeInclusive<Point>, &text::BufferSnapshot) -> Patch<Point>,
{
- struct PendingExcerpt<'a> {
- source_excerpt_id: ExcerptId,
- target_excerpt_id: ExcerptId,
- source_buffer: &'a text::BufferSnapshot,
- target_buffer: &'a text::BufferSnapshot,
+ struct PendingExcerpt {
+ source_buffer_snapshot: language::BufferSnapshot,
+ source_excerpt_range: ExcerptRange<text::Anchor>,
buffer_point_range: Range<Point>,
- source_context_range: Range<Point>,
}
let mut result = Vec::new();
@@ -201,41 +208,55 @@ where
};
let diff = source_snapshot
- .diff_for_buffer_id(first.source_buffer.remote_id())
+ .diff_for_buffer_id(first.source_buffer_snapshot.remote_id())
.expect("buffer with no diff when creating patches");
- let rhs_buffer = if first.source_buffer.remote_id() == diff.base_text().remote_id() {
- first.target_buffer
+ let source_is_lhs =
+ first.source_buffer_snapshot.remote_id() == diff.base_text().remote_id();
+ let target_buffer_id = if source_is_lhs {
+ diff.buffer_id()
} else {
- first.source_buffer
+ diff.base_text().remote_id()
+ };
+ let target_buffer = target_snapshot
+ .buffer_for_id(target_buffer_id)
+ .expect("missing corresponding buffer");
+ let rhs_buffer = if source_is_lhs {
+ target_buffer
+ } else {
+ &first.source_buffer_snapshot
};
let patch = translate_fn(diff, union_start..=union_end, rhs_buffer);
for excerpt in pending.drain(..) {
+ let target_position = patch.old_to_new(excerpt.buffer_point_range.start);
+ let target_position = target_buffer.anchor_before(target_position);
+ let Some(target_position) = target_snapshot.anchor_in_excerpt(target_position) else {
+ continue;
+ };
+ let Some((target_buffer_snapshot, target_excerpt_range)) =
+ target_snapshot.excerpt_containing(target_position..target_position)
+ else {
+ continue;
+ };
+
result.push(patch_for_excerpt(
source_snapshot,
target_snapshot,
- excerpt.source_excerpt_id,
- excerpt.target_excerpt_id,
- excerpt.target_buffer,
- excerpt.source_context_range,
+ &excerpt.source_buffer_snapshot,
+ target_buffer_snapshot,
+ excerpt.source_excerpt_range,
+ target_excerpt_range,
&patch,
excerpt.buffer_point_range,
));
}
};
- for (source_buffer, buffer_offset_range, source_excerpt_id, source_context_range) in
- source_snapshot.range_to_buffer_ranges_with_context(source_bounds)
+ for (buffer_snapshot, source_range, source_excerpt_range) in
+ source_snapshot.range_to_buffer_ranges(source_bounds)
{
- let Some(target_excerpt_id) = excerpt_map.get(&source_excerpt_id).copied() else {
- continue;
- };
- let Some(target_buffer) = target_snapshot.buffer_for_excerpt(target_excerpt_id) else {
- continue;
- };
-
- let buffer_id = source_buffer.remote_id();
+ let buffer_id = buffer_snapshot.remote_id();
if current_buffer_id != Some(buffer_id) {
if let (Some(start), Some(end)) = (union_context_start.take(), union_context_end.take())
@@ -245,8 +266,8 @@ where
current_buffer_id = Some(buffer_id);
}
- let buffer_point_range = buffer_offset_range.to_point(source_buffer);
- let source_context_range = source_context_range.to_point(source_buffer);
+ let buffer_point_range = source_range.to_point(&buffer_snapshot);
+ let source_context_range = source_excerpt_range.context.to_point(&buffer_snapshot);
union_context_start = Some(union_context_start.map_or(source_context_range.start, |s| {
s.min(source_context_range.start)
@@ -256,12 +277,9 @@ where
}));
pending_excerpts.push(PendingExcerpt {
- source_excerpt_id,
- target_excerpt_id,
- source_buffer,
- target_buffer,
+ source_buffer_snapshot: buffer_snapshot,
+ source_excerpt_range,
buffer_point_range,
- source_context_range,
});
}
@@ -275,55 +293,60 @@ where
fn patch_for_excerpt(
source_snapshot: &MultiBufferSnapshot,
target_snapshot: &MultiBufferSnapshot,
- source_excerpt_id: ExcerptId,
- target_excerpt_id: ExcerptId,
- target_buffer: &text::BufferSnapshot,
- source_context_range: Range<Point>,
+ source_buffer_snapshot: &language::BufferSnapshot,
+ target_buffer_snapshot: &language::BufferSnapshot,
+ source_excerpt_range: ExcerptRange<text::Anchor>,
+ target_excerpt_range: ExcerptRange<text::Anchor>,
patch: &Patch<Point>,
source_edited_range: Range<Point>,
) -> CompanionExcerptPatch {
- let source_multibuffer_range = source_snapshot
- .range_for_excerpt(source_excerpt_id)
- .expect("no excerpt for source id when creating patch");
- let source_excerpt_start_in_multibuffer = source_multibuffer_range.start;
- let source_excerpt_start_in_buffer = source_context_range.start;
- let source_excerpt_end_in_buffer = source_context_range.end;
- let target_multibuffer_range = target_snapshot
- .range_for_excerpt(target_excerpt_id)
- .expect("no excerpt for target id when creating patch");
- let target_excerpt_start_in_multibuffer = target_multibuffer_range.start;
- let target_context_range = target_snapshot
- .context_range_for_excerpt(target_excerpt_id)
- .expect("no range for target id when creating patch");
- let target_excerpt_start_in_buffer = target_context_range.start.to_point(&target_buffer);
- let target_excerpt_end_in_buffer = target_context_range.end.to_point(&target_buffer);
+ let source_buffer_range = source_excerpt_range
+ .context
+ .to_point(source_buffer_snapshot);
+ let source_multibuffer_range = (source_snapshot
+ .anchor_in_buffer(source_excerpt_range.context.start)
+ .expect("buffer should exist in multibuffer")
+ ..source_snapshot
+ .anchor_in_buffer(source_excerpt_range.context.end)
+ .expect("buffer should exist in multibuffer"))
+ .to_point(source_snapshot);
+ let target_buffer_range = target_excerpt_range
+ .context
+ .to_point(target_buffer_snapshot);
+ let target_multibuffer_range = (target_snapshot
+ .anchor_in_buffer(target_excerpt_range.context.start)
+ .expect("buffer should exist in multibuffer")
+ ..target_snapshot
+ .anchor_in_buffer(target_excerpt_range.context.end)
+ .expect("buffer should exist in multibuffer"))
+ .to_point(target_snapshot);
let edits = patch
.edits()
.iter()
- .skip_while(|edit| edit.old.end < source_excerpt_start_in_buffer)
- .take_while(|edit| edit.old.start <= source_excerpt_end_in_buffer)
+ .skip_while(|edit| edit.old.end < source_buffer_range.start)
+ .take_while(|edit| edit.old.start <= source_buffer_range.end)
.map(|edit| {
- let clamped_source_start = edit.old.start.max(source_excerpt_start_in_buffer);
- let clamped_source_end = edit.old.end.min(source_excerpt_end_in_buffer);
- let source_multibuffer_start = source_excerpt_start_in_multibuffer
- + (clamped_source_start - source_excerpt_start_in_buffer);
- let source_multibuffer_end = source_excerpt_start_in_multibuffer
- + (clamped_source_end - source_excerpt_start_in_buffer);
+ let clamped_source_start = edit.old.start.max(source_buffer_range.start);
+ let clamped_source_end = edit.old.end.min(source_buffer_range.end);
+ let source_multibuffer_start =
+ source_multibuffer_range.start + (clamped_source_start - source_buffer_range.start);
+ let source_multibuffer_end =
+ source_multibuffer_range.start + (clamped_source_end - source_buffer_range.start);
let clamped_target_start = edit
.new
.start
- .max(target_excerpt_start_in_buffer)
- .min(target_excerpt_end_in_buffer);
+ .max(target_buffer_range.start)
+ .min(target_buffer_range.end);
let clamped_target_end = edit
.new
.end
- .max(target_excerpt_start_in_buffer)
- .min(target_excerpt_end_in_buffer);
- let target_multibuffer_start = target_excerpt_start_in_multibuffer
- + (clamped_target_start - target_excerpt_start_in_buffer);
- let target_multibuffer_end = target_excerpt_start_in_multibuffer
- + (clamped_target_end - target_excerpt_start_in_buffer);
+ .max(target_buffer_range.start)
+ .min(target_buffer_range.end);
+ let target_multibuffer_start =
+ target_multibuffer_range.start + (clamped_target_start - target_buffer_range.start);
+ let target_multibuffer_end =
+ target_multibuffer_range.start + (clamped_target_end - target_buffer_range.start);
text::Edit {
old: source_multibuffer_start..source_multibuffer_end,
new: target_multibuffer_start..target_multibuffer_end,
@@ -331,8 +354,8 @@ fn patch_for_excerpt(
});
let edits = [text::Edit {
- old: source_excerpt_start_in_multibuffer..source_excerpt_start_in_multibuffer,
- new: target_excerpt_start_in_multibuffer..target_excerpt_start_in_multibuffer,
+ old: source_multibuffer_range.start..source_multibuffer_range.start,
+ new: target_multibuffer_range.start..target_multibuffer_range.start,
}]
.into_iter()
.chain(edits);
@@ -349,21 +372,20 @@ fn patch_for_excerpt(
merged_edits.push(edit);
}
- let edited_range = source_excerpt_start_in_multibuffer
- + (source_edited_range.start - source_excerpt_start_in_buffer)
- ..source_excerpt_start_in_multibuffer
- + (source_edited_range.end - source_excerpt_start_in_buffer);
+ let edited_range = source_multibuffer_range.start
+ + (source_edited_range.start - source_buffer_range.start)
+ ..source_multibuffer_range.start + (source_edited_range.end - source_buffer_range.start);
- let source_excerpt_end = source_excerpt_start_in_multibuffer
- + (source_excerpt_end_in_buffer - source_excerpt_start_in_buffer);
- let target_excerpt_end = target_excerpt_start_in_multibuffer
- + (target_excerpt_end_in_buffer - target_excerpt_start_in_buffer);
+ let source_excerpt_end =
+ source_multibuffer_range.start + (source_buffer_range.end - source_buffer_range.start);
+ let target_excerpt_end =
+ target_multibuffer_range.start + (target_buffer_range.end - target_buffer_range.start);
CompanionExcerptPatch {
patch: Patch::new(merged_edits),
edited_range,
- source_excerpt_range: source_excerpt_start_in_multibuffer..source_excerpt_end,
- target_excerpt_range: target_excerpt_start_in_multibuffer..target_excerpt_end,
+ source_excerpt_range: source_multibuffer_range.start..source_excerpt_end,
+ target_excerpt_range: target_multibuffer_range.start..target_excerpt_end,
}
}
@@ -390,6 +412,7 @@ pub struct SplittableEditor {
struct LhsEditor {
multibuffer: Entity<MultiBuffer>,
editor: Entity<Editor>,
+ companion: Entity<Companion>,
was_last_focused: bool,
_subscriptions: Vec<Subscription>,
}
@@ -470,11 +493,16 @@ impl SplittableEditor {
&rhs_editor,
|this, _, event: &EditorEvent, cx| match event {
EditorEvent::ExpandExcerptsRequested {
- excerpt_ids,
+ excerpt_anchors,
lines,
direction,
} => {
- this.expand_excerpts(excerpt_ids.iter().copied(), *lines, *direction, cx);
+ this.expand_excerpts(
+ excerpt_anchors.iter().copied(),
+ *lines,
+ *direction,
+ cx,
+ );
}
_ => cx.emit(event.clone()),
},
@@ -563,19 +591,31 @@ impl SplittableEditor {
window,
|this, _, event: &EditorEvent, window, cx| match event {
EditorEvent::ExpandExcerptsRequested {
- excerpt_ids,
+ excerpt_anchors,
lines,
direction,
} => {
- if this.lhs.is_some() {
- let rhs_display_map = this.rhs_editor.read(cx).display_map.read(cx);
- let rhs_ids: Vec<_> = excerpt_ids
+ if let Some(lhs) = &this.lhs {
+ let rhs_snapshot = this.rhs_multibuffer.read(cx).snapshot(cx);
+ let lhs_snapshot = lhs.multibuffer.read(cx).snapshot(cx);
+ let rhs_anchors = excerpt_anchors
.iter()
- .filter_map(|id| {
- rhs_display_map.companion_excerpt_to_my_excerpt(*id, cx)
+ .filter_map(|anchor| {
+ let (anchor, lhs_buffer) =
+ lhs_snapshot.anchor_to_buffer_anchor(*anchor)?;
+ let rhs_buffer_id =
+ lhs.companion.read(cx).lhs_to_rhs_buffer(anchor.buffer_id)?;
+ let rhs_buffer = rhs_snapshot.buffer_for_id(rhs_buffer_id)?;
+ let diff = this.rhs_multibuffer.read(cx).diff_for(rhs_buffer_id)?;
+ let diff_snapshot = diff.read(cx).snapshot(cx);
+ let rhs_point = diff_snapshot.base_text_point_to_buffer_point(
+ anchor.to_point(&lhs_buffer),
+ &rhs_buffer,
+ );
+ rhs_snapshot.anchor_in_excerpt(rhs_buffer.anchor_before(rhs_point))
})
- .collect();
- this.expand_excerpts(rhs_ids.into_iter(), *lines, *direction, cx);
+ .collect::<Vec<_>>();
+ this.expand_excerpts(rhs_anchors.into_iter(), *lines, *direction, cx);
}
}
EditorEvent::StageOrUnstageRequested { stage, hunks } => {
@@ -654,15 +694,23 @@ impl SplittableEditor {
}),
);
+ let rhs_display_map = self.rhs_editor.read(cx).display_map.clone();
+ let lhs_display_map = lhs_editor.read(cx).display_map.clone();
+ let rhs_display_map_id = rhs_display_map.entity_id();
+ let companion = cx.new(|_| {
+ Companion::new(
+ rhs_display_map_id,
+ convert_rhs_rows_to_lhs,
+ convert_lhs_rows_to_rhs,
+ )
+ });
let lhs = LhsEditor {
editor: lhs_editor,
multibuffer: lhs_multibuffer,
was_last_focused: false,
+ companion: companion.clone(),
_subscriptions: subscriptions,
};
- let rhs_display_map = self.rhs_editor.read(cx).display_map.clone();
- let lhs_display_map = lhs.editor.read(cx).display_map.clone();
- let rhs_display_map_id = rhs_display_map.entity_id();
self.rhs_editor.update(cx, |editor, cx| {
editor.set_delegate_expand_excerpts(true);
@@ -672,35 +720,21 @@ impl SplittableEditor {
})
});
- let path_diffs: Vec<_> = {
+ let all_paths: Vec<_> = {
let rhs_multibuffer = self.rhs_multibuffer.read(cx);
- rhs_multibuffer
- .paths()
- .filter_map(|path| {
- let excerpt_id = rhs_multibuffer.excerpts_for_path(path).next()?;
- let snapshot = rhs_multibuffer.snapshot(cx);
- let buffer = snapshot.buffer_for_excerpt(excerpt_id)?;
+ let rhs_multibuffer_snapshot = rhs_multibuffer.snapshot(cx);
+ rhs_multibuffer_snapshot
+ .buffers_with_paths()
+ .filter_map(|(buffer, path)| {
let diff = rhs_multibuffer.diff_for(buffer.remote_id())?;
Some((path.clone(), diff))
})
.collect()
};
- let companion = cx.new(|_| {
- Companion::new(
- rhs_display_map_id,
- convert_rhs_rows_to_lhs,
- convert_lhs_rows_to_rhs,
- )
- });
-
self.lhs = Some(lhs);
- let paths_for_sync: Vec<_> = path_diffs
- .into_iter()
- .map(|(path, diff)| (path, vec![], diff))
- .collect();
- self.sync_lhs_for_paths(paths_for_sync, &companion, cx);
+ self.sync_lhs_for_paths(all_paths, &companion, cx);
rhs_display_map.update(cx, |dm, cx| {
dm.set_companion(Some((lhs_display_map, companion.clone())), cx);
@@ -1004,7 +1038,7 @@ impl SplittableEditor {
cx.notify();
}
- pub fn set_excerpts_for_path(
+ pub fn update_excerpts_for_path(
&mut self,
path: PathKey,
buffer: Entity<Buffer>,
@@ -1012,122 +1046,94 @@ impl SplittableEditor {
context_line_count: u32,
diff: Entity<BufferDiff>,
cx: &mut Context<Self>,
- ) -> (Vec<Range<Anchor>>, bool) {
+ ) -> bool {
+ let has_ranges = ranges.clone().into_iter().next().is_some();
let Some(companion) = self.companion(cx) else {
return self.rhs_multibuffer.update(cx, |rhs_multibuffer, cx| {
- let (anchors, added_a_new_excerpt) = rhs_multibuffer.set_excerpts_for_path(
+ let added_a_new_excerpt = rhs_multibuffer.update_excerpts_for_path(
path,
buffer.clone(),
ranges,
context_line_count,
cx,
);
- if !anchors.is_empty()
+ if has_ranges
&& rhs_multibuffer
.diff_for(buffer.read(cx).remote_id())
.is_none_or(|old_diff| old_diff.entity_id() != diff.entity_id())
{
rhs_multibuffer.add_diff(diff, cx);
}
- (anchors, added_a_new_excerpt)
+ added_a_new_excerpt
});
};
- let old_rhs_ids: Vec<ExcerptId> = self
- .rhs_multibuffer
- .read(cx)
- .excerpts_for_path(&path)
- .collect();
-
let result = self.rhs_multibuffer.update(cx, |rhs_multibuffer, cx| {
- let (anchors, added_a_new_excerpt) = rhs_multibuffer.set_excerpts_for_path(
+ let added_a_new_excerpt = rhs_multibuffer.update_excerpts_for_path(
path.clone(),
buffer.clone(),
ranges,
context_line_count,
cx,
);
- if !anchors.is_empty()
+ if has_ranges
&& rhs_multibuffer
.diff_for(buffer.read(cx).remote_id())
.is_none_or(|old_diff| old_diff.entity_id() != diff.entity_id())
{
rhs_multibuffer.add_diff(diff.clone(), cx);
}
- (anchors, added_a_new_excerpt)
+ added_a_new_excerpt
});
- self.sync_lhs_for_paths(vec![(path, old_rhs_ids, diff)], &companion, cx);
+ self.sync_lhs_for_paths(vec![(path, diff)], &companion, cx);
result
}
fn expand_excerpts(
&mut self,
- excerpt_ids: impl Iterator<Item = ExcerptId> + Clone,
+ excerpt_anchors: impl Iterator<Item = Anchor> + Clone,
lines: u32,
direction: ExpandExcerptDirection,
cx: &mut Context<Self>,
) {
let Some(companion) = self.companion(cx) else {
self.rhs_multibuffer.update(cx, |rhs_multibuffer, cx| {
- rhs_multibuffer.expand_excerpts(excerpt_ids, lines, direction, cx);
+ rhs_multibuffer.expand_excerpts(excerpt_anchors, lines, direction, cx);
});
return;
};
- let paths_with_old_ids: Vec<_> = self.rhs_multibuffer.update(cx, |rhs_multibuffer, cx| {
+ let paths: Vec<_> = self.rhs_multibuffer.update(cx, |rhs_multibuffer, cx| {
let snapshot = rhs_multibuffer.snapshot(cx);
- let paths = excerpt_ids
+ let paths = excerpt_anchors
.clone()
- .filter_map(|excerpt_id| {
- let path = rhs_multibuffer.path_for_excerpt(excerpt_id)?;
- let buffer = snapshot.buffer_for_excerpt(excerpt_id)?;
- let diff = rhs_multibuffer.diff_for(buffer.remote_id())?;
- Some((path, diff))
+ .filter_map(|anchor| {
+ let (anchor, _) = snapshot.anchor_to_buffer_anchor(anchor)?;
+ let path = snapshot.path_for_buffer(anchor.buffer_id)?;
+ let diff = rhs_multibuffer.diff_for(anchor.buffer_id)?;
+ Some((path.clone(), diff))
})
.collect::<HashMap<_, _>>()
.into_iter()
- .map(|(path, diff)| {
- let old_ids = rhs_multibuffer.excerpts_for_path(&path).collect();
- (path, old_ids, diff)
- })
.collect();
- rhs_multibuffer.expand_excerpts(excerpt_ids, lines, direction, cx);
+ rhs_multibuffer.expand_excerpts(excerpt_anchors, lines, direction, cx);
paths
});
- self.sync_lhs_for_paths(paths_with_old_ids, &companion, cx);
+ self.sync_lhs_for_paths(paths, &companion, cx);
}
pub fn remove_excerpts_for_path(&mut self, path: PathKey, cx: &mut Context<Self>) {
- let Some(lhs) = &self.lhs else {
- self.rhs_multibuffer.update(cx, |rhs_multibuffer, cx| {
- rhs_multibuffer.remove_excerpts_for_path(path, cx);
- });
- return;
- };
-
- let rhs_excerpt_ids: Vec<ExcerptId> = self
- .rhs_multibuffer
- .read(cx)
- .excerpts_for_path(&path)
- .collect();
- let lhs_excerpt_ids: Vec<ExcerptId> =
- lhs.multibuffer.read(cx).excerpts_for_path(&path).collect();
+ self.rhs_multibuffer.update(cx, |rhs_multibuffer, cx| {
+ rhs_multibuffer.remove_excerpts(path.clone(), cx);
+ });
- let rhs_display_map = self.rhs_editor.read(cx).display_map.clone();
- if let Some(companion) = rhs_display_map.read(cx).companion().cloned() {
- companion.update(cx, |c, _| {
- c.remove_excerpt_mappings(lhs_excerpt_ids, rhs_excerpt_ids);
+ if let Some(lhs) = &self.lhs {
+ lhs.multibuffer.update(cx, |lhs_multibuffer, cx| {
+ lhs_multibuffer.remove_excerpts(path, cx);
});
}
-
- self.rhs_multibuffer.update(cx, |rhs_multibuffer, cx| {
- rhs_multibuffer.remove_excerpts_for_path(path.clone(), cx);
- });
- lhs.multibuffer.update(cx, |lhs_multibuffer, cx| {
- lhs_multibuffer.remove_excerpts_for_path(path, cx);
- });
}
fn search_token(&self) -> SearchToken {
@@ -1151,122 +1157,95 @@ impl SplittableEditor {
fn sync_lhs_for_paths(
&self,
- paths_with_old_rhs_ids: Vec<(PathKey, Vec<ExcerptId>, Entity<BufferDiff>)>,
+ paths: Vec<(PathKey, Entity<BufferDiff>)>,
companion: &Entity<Companion>,
cx: &mut Context<Self>,
) {
let Some(lhs) = &self.lhs else { return };
self.rhs_multibuffer.update(cx, |rhs_multibuffer, cx| {
- for (path, old_rhs_ids, diff) in paths_with_old_rhs_ids {
- let old_lhs_ids: Vec<ExcerptId> =
- lhs.multibuffer.read(cx).excerpts_for_path(&path).collect();
-
- companion.update(cx, |c, _| {
- c.remove_excerpt_mappings(old_lhs_ids, old_rhs_ids);
- });
-
- let rhs_excerpt_ids: Vec<ExcerptId> =
- rhs_multibuffer.excerpts_for_path(&path).collect();
- let Some(excerpt_id) = rhs_excerpt_ids.first().copied() else {
+ for (path, diff) in paths {
+ let main_buffer_id = diff.read(cx).buffer_id;
+ let Some(main_buffer) = rhs_multibuffer.buffer(diff.read(cx).buffer_id) else {
lhs.multibuffer.update(cx, |lhs_multibuffer, lhs_cx| {
- lhs_multibuffer.remove_excerpts_for_path(path, lhs_cx);
+ lhs_multibuffer.remove_excerpts(path, lhs_cx);
});
continue;
};
- let Some(main_buffer_snapshot) = rhs_multibuffer
- .snapshot(cx)
- .buffer_for_excerpt(excerpt_id)
- .cloned()
- else {
- continue;
- };
- let Some(main_buffer) = rhs_multibuffer.buffer(main_buffer_snapshot.remote_id())
- else {
- continue;
- };
+ let main_buffer_snapshot = main_buffer.read(cx).snapshot();
let base_text_buffer = diff.read(cx).base_text_buffer().clone();
let diff_snapshot = diff.read(cx).snapshot(cx);
let base_text_buffer_snapshot = base_text_buffer.read(cx).snapshot();
- let lhs_ranges: Vec<ExcerptRange<Point>> = rhs_multibuffer
- .excerpts_for_buffer(main_buffer_snapshot.remote_id(), cx)
+ let mut paired_ranges: Vec<(Range<Point>, ExcerptRange<text::Anchor>)> = Vec::new();
+
+ let mut have_excerpt = false;
+ let mut did_merge = false;
+ let rhs_multibuffer_snapshot = rhs_multibuffer.snapshot(cx);
+ for info in rhs_multibuffer_snapshot.excerpts_for_buffer(main_buffer_id) {
+ have_excerpt = true;
+ let rhs_context = info.context.to_point(&main_buffer_snapshot);
+ let lhs_context = rhs_range_to_base_text_range(
+ &rhs_context,
+ &diff_snapshot,
+ &main_buffer_snapshot,
+ );
+
+ if let Some((prev_lhs_context, prev_rhs_range)) = paired_ranges.last_mut()
+ && prev_lhs_context.end >= lhs_context.start
+ {
+ did_merge = true;
+ prev_lhs_context.end = lhs_context.end;
+ prev_rhs_range.context.end = info.context.end;
+ continue;
+ }
+
+ paired_ranges.push((lhs_context, info));
+ }
+
+ let (lhs_ranges, rhs_ranges): (Vec<_>, Vec<_>) = paired_ranges.into_iter().unzip();
+ let lhs_ranges = lhs_ranges
.into_iter()
- .filter(|(id, _, _)| rhs_excerpt_ids.contains(id))
- .map(|(_, _, excerpt_range)| {
- let to_base_text = |range: Range<Point>| {
- let start = diff_snapshot
- .buffer_point_to_base_text_range(
- Point::new(range.start.row, 0),
- &main_buffer_snapshot,
- )
- .start;
- let end = diff_snapshot
- .buffer_point_to_base_text_range(
- Point::new(range.end.row, 0),
- &main_buffer_snapshot,
- )
- .end;
- let end_column = diff_snapshot.base_text().line_len(end.row);
- Point::new(start.row, 0)..Point::new(end.row, end_column)
- };
- let primary = excerpt_range.primary.to_point(&main_buffer_snapshot);
- let context = excerpt_range.context.to_point(&main_buffer_snapshot);
- ExcerptRange {
- primary: to_base_text(primary),
- context: to_base_text(context),
- }
+ .map(|range| {
+ ExcerptRange::new(base_text_buffer_snapshot.anchor_range_outside(range))
})
- .collect();
+ .collect::<Vec<_>>();
- let groups = lhs.multibuffer.update(cx, |lhs_multibuffer, lhs_cx| {
- let lhs_result = lhs_multibuffer.update_path_excerpts(
- path,
+ lhs.multibuffer.update(cx, |lhs_multibuffer, lhs_cx| {
+ lhs_multibuffer.update_path_excerpts(
+ path.clone(),
base_text_buffer,
&base_text_buffer_snapshot,
- lhs_ranges,
+ &lhs_ranges,
lhs_cx,
);
- if !lhs_result.excerpt_ids.is_empty()
+ if have_excerpt
&& lhs_multibuffer
.diff_for(base_text_buffer_snapshot.remote_id())
.is_none_or(|old_diff| old_diff.entity_id() != diff.entity_id())
{
- lhs_multibuffer.add_inverted_diff(diff.clone(), main_buffer, lhs_cx);
- }
-
- let mut groups = Vec::new();
- for (lhs_id, chunk) in &lhs_result
- .excerpt_ids
- .iter()
- .copied()
- .zip(rhs_excerpt_ids)
- .chunk_by(|(lhs_id, _)| *lhs_id)
- {
- groups.push((lhs_id, chunk.map(|(_, rhs_id)| rhs_id).collect::<Vec<_>>()));
+ lhs_multibuffer.add_inverted_diff(
+ diff.clone(),
+ main_buffer.clone(),
+ lhs_cx,
+ );
}
- groups
});
- let pairs = groups
- .into_iter()
- .map(|(lhs_id, rhs_group)| {
- let rhs_id = if rhs_group.len() == 1 {
- rhs_group[0]
- } else {
- rhs_multibuffer.merge_excerpts(&rhs_group, cx)
- };
- (lhs_id, rhs_id)
- })
- .collect::<Vec<_>>();
+ if did_merge {
+ rhs_multibuffer.update_path_excerpts(
+ path,
+ main_buffer,
+ &main_buffer_snapshot,
+ &rhs_ranges,
+ cx,
+ );
+ }
let lhs_buffer_id = diff.read(cx).base_text(cx).remote_id();
let rhs_buffer_id = diff.read(cx).buffer_id;
companion.update(cx, |c, _| {
- for (lhs_id, rhs_id) in pairs {
- c.add_excerpt_mapping(lhs_id, rhs_id);
- }
c.add_buffer_mapping(lhs_buffer_id, rhs_buffer_id);
});
}
@@ -1312,7 +1291,7 @@ impl SplittableEditor {
use crate::display_map::DisplayRow;
self.debug_print(cx);
- self.check_excerpt_mapping_invariants(cx);
+ self.check_excerpt_invariants(quiesced, cx);
let lhs = self.lhs.as_ref().unwrap();
@@ -1362,15 +1341,21 @@ impl SplittableEditor {
let (lhs_point, rhs_point) =
if lhs_hunk.row_range.is_empty() || rhs_hunk.row_range.is_empty() {
+ use multi_buffer::ToPoint as _;
+
let lhs_end = Point::new(lhs_hunk.row_range.end.0, 0);
let rhs_end = Point::new(rhs_hunk.row_range.end.0, 0);
- let lhs_exceeds = lhs_snapshot
- .range_for_excerpt(lhs_hunk.excerpt_id)
- .map_or(false, |range| lhs_end >= range.end);
- let rhs_exceeds = rhs_snapshot
- .range_for_excerpt(rhs_hunk.excerpt_id)
- .map_or(false, |range| rhs_end >= range.end);
+ let lhs_excerpt_end = lhs_snapshot
+ .anchor_in_excerpt(lhs_hunk.excerpt_range.context.end)
+ .unwrap()
+ .to_point(&lhs_snapshot);
+ let lhs_exceeds = lhs_end >= lhs_excerpt_end;
+ let rhs_excerpt_end = rhs_snapshot
+ .anchor_in_excerpt(rhs_hunk.excerpt_range.context.end)
+ .unwrap()
+ .to_point(&rhs_snapshot);
+ let rhs_exceeds = rhs_end >= rhs_excerpt_end;
if lhs_exceeds != rhs_exceeds {
continue;
}
@@ -1664,109 +1649,53 @@ impl SplittableEditor {
eprintln!();
}
- fn check_excerpt_mapping_invariants(&self, cx: &gpui::App) {
- use multi_buffer::{ExcerptId, PathKey};
-
+ fn check_excerpt_invariants(&self, quiesced: bool, cx: &gpui::App) {
let lhs = self.lhs.as_ref().expect("should have lhs editor");
- let rhs_excerpt_ids = self.rhs_multibuffer.read(cx).excerpt_ids();
- let lhs_excerpt_ids = lhs.multibuffer.read(cx).excerpt_ids();
- assert_eq!(
- rhs_excerpt_ids.len(),
- lhs_excerpt_ids.len(),
- "excerpt count mismatch: rhs has {}, lhs has {}",
- rhs_excerpt_ids.len(),
- lhs_excerpt_ids.len(),
- );
-
- let rhs_display_map = self.rhs_editor.read(cx).display_map.clone();
- let companion = rhs_display_map
- .read(cx)
- .companion()
- .cloned()
- .expect("should have companion");
- let (lhs_to_rhs, rhs_to_lhs) = {
- let c = companion.read(cx);
- let (l, r) = c.excerpt_mappings();
- (l.clone(), r.clone())
- };
-
- assert_eq!(
- lhs_to_rhs.len(),
- rhs_to_lhs.len(),
- "mapping size mismatch: lhs_to_rhs has {}, rhs_to_lhs has {}",
- lhs_to_rhs.len(),
- rhs_to_lhs.len(),
- );
+ let rhs_snapshot = self.rhs_multibuffer.read(cx).snapshot(cx);
+ let rhs_excerpts = rhs_snapshot.excerpts().collect::<Vec<_>>();
+ let lhs_snapshot = lhs.multibuffer.read(cx).snapshot(cx);
+ let lhs_excerpts = lhs_snapshot.excerpts().collect::<Vec<_>>();
+ assert_eq!(lhs_excerpts.len(), rhs_excerpts.len());
- for (&lhs_id, &rhs_id) in &lhs_to_rhs {
- let reverse = rhs_to_lhs.get(&rhs_id);
- assert_eq!(
- reverse,
- Some(&lhs_id),
- "lhs_to_rhs maps {lhs_id:?} -> {rhs_id:?}, but rhs_to_lhs maps {rhs_id:?} -> {reverse:?}",
- );
- }
- for (&rhs_id, &lhs_id) in &rhs_to_lhs {
- let reverse = lhs_to_rhs.get(&lhs_id);
+ for (lhs_excerpt, rhs_excerpt) in lhs_excerpts.into_iter().zip(rhs_excerpts) {
assert_eq!(
- reverse,
- Some(&rhs_id),
- "rhs_to_lhs maps {rhs_id:?} -> {lhs_id:?}, but lhs_to_rhs maps {lhs_id:?} -> {reverse:?}",
+ lhs_snapshot
+ .path_for_buffer(lhs_excerpt.context.start.buffer_id)
+ .unwrap(),
+ rhs_snapshot
+ .path_for_buffer(rhs_excerpt.context.start.buffer_id)
+ .unwrap(),
+ "corresponding excerpts should have the same path"
);
- }
-
- assert_eq!(
- lhs_to_rhs.len(),
- rhs_excerpt_ids.len(),
- "mapping covers {} excerpts but rhs has {}",
- lhs_to_rhs.len(),
- rhs_excerpt_ids.len(),
- );
-
- let rhs_mapped_order: Vec<ExcerptId> = rhs_excerpt_ids
- .iter()
- .map(|rhs_id| {
- *rhs_to_lhs.get(rhs_id).unwrap_or_else(|| {
- panic!("rhs excerpt {rhs_id:?} has no mapping in rhs_to_lhs")
- })
- })
- .collect();
- assert_eq!(
- rhs_mapped_order, lhs_excerpt_ids,
- "excerpt ordering mismatch: mapping rhs order through rhs_to_lhs doesn't match lhs order",
- );
-
- let rhs_paths: Vec<PathKey> = self.rhs_multibuffer.read(cx).paths().cloned().collect();
- let lhs_paths: Vec<PathKey> = lhs.multibuffer.read(cx).paths().cloned().collect();
- assert_eq!(
- rhs_paths, lhs_paths,
- "path set mismatch between rhs and lhs"
- );
-
- for path in &rhs_paths {
- let rhs_path_excerpts: Vec<ExcerptId> = self
+ let diff = self
.rhs_multibuffer
.read(cx)
- .excerpts_for_path(path)
- .collect();
- let lhs_path_excerpts: Vec<ExcerptId> =
- lhs.multibuffer.read(cx).excerpts_for_path(path).collect();
+ .diff_for(rhs_excerpt.context.start.buffer_id)
+ .expect("missing diff");
assert_eq!(
- rhs_path_excerpts.len(),
- lhs_path_excerpts.len(),
- "excerpt count mismatch for path {path:?}: rhs has {}, lhs has {}",
- rhs_path_excerpts.len(),
- lhs_path_excerpts.len(),
- );
- let rhs_path_mapped: Vec<ExcerptId> = rhs_path_excerpts
- .iter()
- .map(|rhs_id| *rhs_to_lhs.get(rhs_id).unwrap())
- .collect();
- assert_eq!(
- rhs_path_mapped, lhs_path_excerpts,
- "per-path excerpt ordering mismatch for {path:?}",
+ lhs_excerpt.context.start.buffer_id,
+ diff.read(cx).base_text(cx).remote_id(),
+ "corresponding lhs excerpt should show diff base text"
);
+
+ if quiesced {
+ let diff_snapshot = diff.read(cx).snapshot(cx);
+ let lhs_buffer_snapshot = lhs_snapshot
+ .buffer_for_id(lhs_excerpt.context.start.buffer_id)
+ .unwrap();
+ let rhs_buffer_snapshot = rhs_snapshot
+ .buffer_for_id(rhs_excerpt.context.start.buffer_id)
+ .unwrap();
+ let lhs_range = lhs_excerpt.context.to_point(&lhs_buffer_snapshot);
+ let rhs_range = rhs_excerpt.context.to_point(&rhs_buffer_snapshot);
+ let expected_lhs_range =
+ rhs_range_to_base_text_range(&rhs_range, &diff_snapshot, &rhs_buffer_snapshot);
+ assert_eq!(
+ lhs_range, expected_lhs_range,
+ "corresponding lhs excerpt should have a matching range"
+ )
+ }
}
}
}
@@ -2316,7 +2245,7 @@ mod tests {
let context_lines = rng.random_range(0..2);
editor.update(cx, |editor, cx| {
let path = PathKey::for_buffer(&buffer, cx);
- editor.set_excerpts_for_path(path, buffer, ranges, context_lines, diff, cx);
+ editor.update_excerpts_for_path(path, buffer, ranges, context_lines, diff, cx);
});
editor.update(cx, |editor, cx| {
editor.check_invariants(true, cx);
@@ -2351,7 +2280,14 @@ mod tests {
let context_lines = rng.random_range(0..2);
editor.update(cx, |editor, cx| {
let path = PathKey::for_buffer(&buffer, cx);
- editor.set_excerpts_for_path(path, buffer, ranges, context_lines, diff, cx);
+ editor.update_excerpts_for_path(
+ path,
+ buffer,
+ ranges,
+ context_lines,
+ diff,
+ cx,
+ );
});
}
15..=29 => {
@@ -7,7 +7,7 @@ use gpui::{
ParentElement, Pixels, StatefulInteractiveElement, Styled, TextStyleRefinement, Window, div,
linear_color_stop, linear_gradient, point, px, size,
};
-use multi_buffer::{Anchor, ExcerptId};
+use multi_buffer::{Anchor, ExcerptBoundaryInfo};
use settings::Settings;
use smallvec::smallvec;
use text::BufferId;
@@ -429,7 +429,7 @@ impl SplitBufferHeadersElement {
let sticky_header_excerpt_id = snapshot
.sticky_header_excerpt(scroll_position.y)
- .map(|e| e.excerpt.id);
+ .map(|e| e.excerpt);
let non_sticky_headers = self.build_non_sticky_headers(
&snapshot,
@@ -476,9 +476,10 @@ impl SplitBufferHeadersElement {
let mut anchors_by_buffer: HashMap<BufferId, (usize, Anchor)> = HashMap::default();
for selection in all_anchor_selections.iter() {
let head = selection.head();
- if let Some(buffer_id) = head.text_anchor.buffer_id {
+ if let Some((text_anchor, _)) = snapshot.buffer_snapshot().anchor_to_buffer_anchor(head)
+ {
anchors_by_buffer
- .entry(buffer_id)
+ .entry(text_anchor.buffer_id)
.and_modify(|(latest_id, latest_anchor)| {
if selection.id > *latest_id {
*latest_id = selection.id;
@@ -520,7 +521,7 @@ impl SplitBufferHeadersElement {
);
let editor_bg_color = cx.theme().colors().editor_background;
- let selected = selected_buffer_ids.contains(&excerpt.buffer_id);
+ let selected = selected_buffer_ids.contains(&excerpt.buffer_id());
let mut header = v_flex()
.id("sticky-buffer-header")
@@ -594,7 +595,7 @@ impl SplitBufferHeadersElement {
end_row: DisplayRow,
selected_buffer_ids: &HashSet<BufferId>,
latest_selection_anchors: &HashMap<BufferId, Anchor>,
- sticky_header_excerpt_id: Option<ExcerptId>,
+ sticky_header: Option<&ExcerptBoundaryInfo>,
window: &mut Window,
cx: &mut App,
) -> Vec<BufferHeaderLayout> {
@@ -603,7 +604,7 @@ impl SplitBufferHeadersElement {
for (block_row, block) in snapshot.blocks_in_range(start_row..end_row) {
let (excerpt, is_folded) = match block {
Block::BufferHeader { excerpt, .. } => {
- if sticky_header_excerpt_id == Some(excerpt.id) {
+ if sticky_header == Some(excerpt) {
continue;
}
(excerpt, false)
@@ -613,7 +614,7 @@ impl SplitBufferHeadersElement {
Block::ExcerptBoundary { .. } | Block::Custom(_) | Block::Spacer { .. } => continue,
};
- let selected = selected_buffer_ids.contains(&excerpt.buffer_id);
+ let selected = selected_buffer_ids.contains(&excerpt.buffer_id());
let jump_data = header_jump_data(
snapshot,
block_row,
@@ -0,0 +1,101 @@
+use crate::Editor;
+
+use collections::HashMap;
+use gpui::{App, Task, Window};
+use lsp::LanguageServerName;
+use project::{Location, project_settings::ProjectSettings};
+use settings::Settings as _;
+use task::{TaskContext, TaskVariables, VariableName};
+use text::{BufferId, ToOffset, ToPoint};
+
+impl Editor {
+ pub fn task_context(&self, window: &mut Window, cx: &mut App) -> Task<Option<TaskContext>> {
+ let Some(project) = self.project.clone() else {
+ return Task::ready(None);
+ };
+ let display_snapshot = self.display_snapshot(cx);
+ let selection = self.selections.newest_adjusted(&display_snapshot);
+ let start = display_snapshot
+ .buffer_snapshot()
+ .anchor_after(selection.start);
+ let end = display_snapshot
+ .buffer_snapshot()
+ .anchor_after(selection.end);
+ let Some((buffer_snapshot, range)) = display_snapshot
+ .buffer_snapshot()
+ .anchor_range_to_buffer_anchor_range(start..end)
+ else {
+ return Task::ready(None);
+ };
+ let Some(buffer) = self.buffer.read(cx).buffer(buffer_snapshot.remote_id()) else {
+ return Task::ready(None);
+ };
+ let location = Location { buffer, range };
+ let captured_variables = {
+ let mut variables = TaskVariables::default();
+ let buffer = location.buffer.read(cx);
+ let buffer_id = buffer.remote_id();
+ let snapshot = buffer.snapshot();
+ let starting_point = location.range.start.to_point(&snapshot);
+ let starting_offset = starting_point.to_offset(&snapshot);
+ for (_, tasks) in self
+ .tasks
+ .range((buffer_id, 0)..(buffer_id, starting_point.row + 1))
+ {
+ if !tasks
+ .context_range
+ .contains(&crate::BufferOffset(starting_offset))
+ {
+ continue;
+ }
+ for (capture_name, value) in tasks.extra_variables.iter() {
+ variables.insert(
+ VariableName::Custom(capture_name.to_owned().into()),
+ value.clone(),
+ );
+ }
+ }
+ variables
+ };
+
+ project.update(cx, |project, cx| {
+ project.task_store().update(cx, |task_store, cx| {
+ task_store.task_context_for_location(captured_variables, location, cx)
+ })
+ })
+ }
+
+ pub fn lsp_task_sources(&self, cx: &App) -> HashMap<LanguageServerName, Vec<BufferId>> {
+ let lsp_settings = &ProjectSettings::get_global(cx).lsp;
+
+ self.buffer()
+ .read(cx)
+ .all_buffers()
+ .into_iter()
+ .filter_map(|buffer| {
+ let lsp_tasks_source = buffer
+ .read(cx)
+ .language()?
+ .context_provider()?
+ .lsp_task_source()?;
+ if lsp_settings
+ .get(&lsp_tasks_source)
+ .is_none_or(|s| s.enable_lsp_tasks)
+ {
+ let buffer_id = buffer.read(cx).remote_id();
+ Some((lsp_tasks_source, buffer_id))
+ } else {
+ None
+ }
+ })
+ .fold(
+ HashMap::default(),
+ |mut acc, (lsp_task_source, buffer_id)| {
+ acc.entry(lsp_task_source)
+ .or_insert_with(Vec::new)
+ .push(buffer_id);
+ acc
+ },
+ )
+ }
+}
@@ -245,7 +245,7 @@ pub fn editor_content_with_blocks_and_size(
format!(
"§ {}",
first_excerpt
- .buffer
+ .buffer(snapshot.buffer_snapshot())
.file()
.map(|file| file.file_name(cx))
.unwrap_or("<no file>")
@@ -274,7 +274,7 @@ pub fn editor_content_with_blocks_and_size(
format!(
"§ {}",
excerpt
- .buffer
+ .buffer(snapshot.buffer_snapshot())
.file()
.map(|file| file.file_name(cx))
.unwrap_or("<no file>")
@@ -1,5 +1,5 @@
use crate::{
- AnchorRangeExt, DisplayPoint, Editor, ExcerptId, MultiBuffer, MultiBufferSnapshot, RowExt,
+ DisplayPoint, Editor, MultiBuffer, MultiBufferSnapshot, RowExt,
display_map::{HighlightKey, ToDisplayPoint},
};
use buffer_diff::DiffHunkStatusKind;
@@ -13,7 +13,9 @@ use gpui::{
};
use itertools::Itertools;
use language::{Buffer, BufferSnapshot, LanguageRegistry};
-use multi_buffer::{Anchor, ExcerptRange, MultiBufferOffset, MultiBufferRow, PathKey};
+use multi_buffer::{
+ Anchor, AnchorRangeExt, ExcerptRange, MultiBufferOffset, MultiBufferRow, PathKey,
+};
use parking_lot::RwLock;
use project::{FakeFs, Project};
use std::{
@@ -464,7 +466,21 @@ impl EditorTestContext {
let selections = editor.selections.disjoint_anchors_arc();
let excerpts = multibuffer_snapshot
.excerpts()
- .map(|(e_id, snapshot, range)| (e_id, snapshot.clone(), range))
+ .map(|info| {
+ (
+ multibuffer_snapshot
+ .buffer_for_id(info.context.start.buffer_id)
+ .cloned()
+ .unwrap(),
+ multibuffer_snapshot
+ .anchor_in_excerpt(info.context.start)
+ .unwrap()
+ ..multibuffer_snapshot
+ .anchor_in_excerpt(info.context.end)
+ .unwrap(),
+ info,
+ )
+ })
.collect::<Vec<_>>();
(multibuffer_snapshot, selections, excerpts)
@@ -478,14 +494,23 @@ impl EditorTestContext {
fmt_additional_notes(),
);
- for (ix, (excerpt_id, snapshot, range)) in excerpts.into_iter().enumerate() {
+ for (ix, (snapshot, multibuffer_range, excerpt_range)) in excerpts.into_iter().enumerate() {
let is_folded = self
.update_editor(|editor, _, cx| editor.is_buffer_folded(snapshot.remote_id(), cx));
let (expected_text, expected_selections) =
marked_text_ranges(expected_excerpts[ix], true);
if expected_text == "[FOLDED]\n" {
assert!(is_folded, "excerpt {} should be folded", ix);
- let is_selected = selections.iter().any(|s| s.head().excerpt_id == excerpt_id);
+ let is_selected = selections.iter().any(|s| {
+ multibuffer_range
+ .start
+ .cmp(&s.head(), &multibuffer_snapshot)
+ .is_le()
+ && multibuffer_range
+ .end
+ .cmp(&s.head(), &multibuffer_snapshot)
+ .is_ge()
+ });
if !expected_selections.is_empty() {
assert!(
is_selected,
@@ -510,7 +535,7 @@ impl EditorTestContext {
);
assert_eq!(
multibuffer_snapshot
- .text_for_range(Anchor::range_in_buffer(excerpt_id, range.context.clone()))
+ .text_for_range(multibuffer_range.clone())
.collect::<String>(),
expected_text,
"{}",
@@ -519,13 +544,24 @@ impl EditorTestContext {
let selections = selections
.iter()
- .filter(|s| s.head().excerpt_id == excerpt_id)
- .map(|s| {
- let head = text::ToOffset::to_offset(&s.head().text_anchor, &snapshot)
- - text::ToOffset::to_offset(&range.context.start, &snapshot);
- let tail = text::ToOffset::to_offset(&s.head().text_anchor, &snapshot)
- - text::ToOffset::to_offset(&range.context.start, &snapshot);
- tail..head
+ .filter(|s| {
+ multibuffer_range
+ .start
+ .cmp(&s.head(), &multibuffer_snapshot)
+ .is_le()
+ && multibuffer_range
+ .end
+ .cmp(&s.head(), &multibuffer_snapshot)
+ .is_ge()
+ })
+ .filter_map(|s| {
+ let (head_anchor, buffer_snapshot) =
+ multibuffer_snapshot.anchor_to_buffer_anchor(s.head())?;
+ let head = text::ToOffset::to_offset(&head_anchor, buffer_snapshot)
+ - text::ToOffset::to_offset(&excerpt_range.context.start, buffer_snapshot);
+ let tail = text::ToOffset::to_offset(&head_anchor, buffer_snapshot)
+ - text::ToOffset::to_offset(&excerpt_range.context.start, buffer_snapshot);
+ Some(tail..head)
})
.collect::<Vec<_>>();
// todo: selections that cross excerpt boundaries..
@@ -546,9 +582,12 @@ impl EditorTestContext {
let selections = editor.selections.disjoint_anchors_arc().to_vec();
let excerpts = multibuffer_snapshot
.excerpts()
- .map(|(e_id, snapshot, range)| {
- let is_folded = editor.is_buffer_folded(snapshot.remote_id(), cx);
- (e_id, snapshot.clone(), range, is_folded)
+ .map(|info| {
+ let buffer_snapshot = multibuffer_snapshot
+ .buffer_for_id(info.context.start.buffer_id)
+ .unwrap();
+ let is_folded = editor.is_buffer_folded(buffer_snapshot.remote_id(), cx);
+ (buffer_snapshot.clone(), info, is_folded)
})
.collect::<Vec<_>>();
@@ -673,7 +712,7 @@ impl EditorTestContext {
struct FormatMultiBufferAsMarkedText {
multibuffer_snapshot: MultiBufferSnapshot,
selections: Vec<Selection<Anchor>>,
- excerpts: Vec<(ExcerptId, BufferSnapshot, ExcerptRange<text::Anchor>, bool)>,
+ excerpts: Vec<(BufferSnapshot, ExcerptRange<text::Anchor>, bool)>,
}
impl std::fmt::Display for FormatMultiBufferAsMarkedText {
@@ -684,25 +723,40 @@ impl std::fmt::Display for FormatMultiBufferAsMarkedText {
excerpts,
} = self;
- for (excerpt_id, snapshot, range, is_folded) in excerpts.into_iter() {
+ for (_snapshot, range, is_folded) in excerpts.into_iter() {
write!(f, "[EXCERPT]\n")?;
if *is_folded {
write!(f, "[FOLDED]\n")?;
}
+ let multibuffer_range = multibuffer_snapshot
+ .buffer_anchor_range_to_anchor_range(range.context.clone())
+ .unwrap();
+
let mut text = multibuffer_snapshot
- .text_for_range(Anchor::range_in_buffer(*excerpt_id, range.context.clone()))
+ .text_for_range(multibuffer_range.clone())
.collect::<String>();
let selections = selections
.iter()
- .filter(|&s| s.head().excerpt_id == *excerpt_id)
- .map(|s| {
- let head = text::ToOffset::to_offset(&s.head().text_anchor, &snapshot)
- - text::ToOffset::to_offset(&range.context.start, &snapshot);
- let tail = text::ToOffset::to_offset(&s.head().text_anchor, &snapshot)
- - text::ToOffset::to_offset(&range.context.start, &snapshot);
- tail..head
+ .filter(|&s| {
+ multibuffer_range
+ .start
+ .cmp(&s.head(), multibuffer_snapshot)
+ .is_le()
+ && multibuffer_range
+ .end
+ .cmp(&s.head(), multibuffer_snapshot)
+ .is_ge()
+ })
+ .filter_map(|s| {
+ let (head_anchor, buffer_snapshot) =
+ multibuffer_snapshot.anchor_to_buffer_anchor(s.head())?;
+ let head = text::ToOffset::to_offset(&head_anchor, buffer_snapshot)
+ - text::ToOffset::to_offset(&range.context.start, buffer_snapshot);
+ let tail = text::ToOffset::to_offset(&head_anchor, buffer_snapshot)
+ - text::ToOffset::to_offset(&range.context.start, buffer_snapshot);
+ Some(tail..head)
})
.rev()
.collect::<Vec<_>>();
@@ -47,7 +47,7 @@ impl ActiveBufferEncoding {
self.is_shared = project.is_shared();
self.is_via_remote_server = project.is_via_remote_server();
- if let Some((_, buffer, _)) = editor.read(cx).active_excerpt(cx) {
+ if let Some(buffer) = editor.read(cx).active_buffer(cx) {
let buffer = buffer.read(cx);
self.active_encoding = Some(buffer.encoding());
self.has_bom = buffer.has_bom();
@@ -47,11 +47,11 @@ impl EncodingSelector {
window: &mut Window,
cx: &mut Context<Workspace>,
) -> Option<()> {
- let (_, buffer, _) = workspace
+ let buffer = workspace
.active_item(cx)?
.act_as::<Editor>(cx)?
.read(cx)
- .active_excerpt(cx)?;
+ .active_buffer(cx)?;
let buffer_handle = buffer.read(cx);
let project = workspace.project().read(cx);
@@ -212,7 +212,7 @@ impl CommitView {
editor.insert_blocks(
[BlockProperties {
- placement: BlockPlacement::Above(editor::Anchor::min()),
+ placement: BlockPlacement::Above(editor::Anchor::Min),
height: Some(1),
style: BlockStyle::Sticky,
render: Arc::new(|_| gpui::Empty.into_any_element()),
@@ -223,7 +223,10 @@ impl CommitView {
editor
.buffer()
.read(cx)
- .buffer_anchor_to_anchor(&message_buffer, Anchor::MAX, cx)
+ .snapshot(cx)
+ .anchor_in_buffer(Anchor::max_for_buffer(
+ message_buffer.read(cx).remote_id(),
+ ))
.map(|anchor| BlockProperties {
placement: BlockPlacement::Below(anchor),
height: Some(1),
@@ -2,7 +2,7 @@ use agent_settings::AgentSettings;
use collections::{HashMap, HashSet};
use editor::{
ConflictsOurs, ConflictsOursMarker, ConflictsOuter, ConflictsTheirs, ConflictsTheirsMarker,
- Editor, EditorEvent, ExcerptId, MultiBuffer, RowHighlightOptions,
+ Editor, EditorEvent, MultiBuffer, RowHighlightOptions,
display_map::{BlockContext, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId},
};
use gpui::{
@@ -67,62 +67,22 @@ pub fn register_editor(editor: &mut Editor, buffer: Entity<MultiBuffer>, cx: &mu
let buffers = buffer.read(cx).all_buffers();
for buffer in buffers {
- buffer_added(editor, buffer, cx);
+ buffer_ranges_updated(editor, buffer, cx);
}
cx.subscribe(&cx.entity(), |editor, _, event, cx| match event {
- EditorEvent::ExcerptsAdded { buffer, .. } => buffer_added(editor, buffer.clone(), cx),
- EditorEvent::ExcerptsExpanded { ids } => {
- let multibuffer = editor.buffer().read(cx).snapshot(cx);
- for excerpt_id in ids {
- let Some(buffer) = multibuffer.buffer_for_excerpt(*excerpt_id) else {
- continue;
- };
- let addon = editor.addon::<ConflictAddon>().unwrap();
- let Some(conflict_set) = addon.conflict_set(buffer.remote_id()).clone() else {
- return;
- };
- excerpt_for_buffer_updated(editor, conflict_set, cx);
- }
+ EditorEvent::BufferRangesUpdated { buffer, .. } => {
+ buffer_ranges_updated(editor, buffer.clone(), cx)
+ }
+ EditorEvent::BuffersRemoved { removed_buffer_ids } => {
+ buffers_removed(editor, removed_buffer_ids, cx)
}
- EditorEvent::ExcerptsRemoved {
- removed_buffer_ids, ..
- } => buffers_removed(editor, removed_buffer_ids, cx),
_ => {}
})
.detach();
}
-fn excerpt_for_buffer_updated(
- editor: &mut Editor,
- conflict_set: Entity<ConflictSet>,
- cx: &mut Context<Editor>,
-) {
- let conflicts_len = conflict_set.read(cx).snapshot().conflicts.len();
- let buffer_id = conflict_set.read(cx).snapshot().buffer_id;
- let Some(buffer_conflicts) = editor
- .addon_mut::<ConflictAddon>()
- .unwrap()
- .buffers
- .get(&buffer_id)
- else {
- return;
- };
- let addon_conflicts_len = buffer_conflicts.block_ids.len();
- conflicts_updated(
- editor,
- conflict_set,
- &ConflictSetUpdate {
- buffer_range: None,
- old_range: 0..addon_conflicts_len,
- new_range: 0..conflicts_len,
- },
- cx,
- );
-}
-
-#[ztracing::instrument(skip_all)]
-fn buffer_added(editor: &mut Editor, buffer: Entity<Buffer>, cx: &mut Context<Editor>) {
+fn buffer_ranges_updated(editor: &mut Editor, buffer: Entity<Buffer>, cx: &mut Context<Editor>) {
let Some(project) = editor.project() else {
return;
};
@@ -188,14 +148,6 @@ fn conflicts_updated(
let conflict_set = conflict_set.read(cx).snapshot();
let multibuffer = editor.buffer().read(cx);
let snapshot = multibuffer.snapshot(cx);
- let excerpts = multibuffer.excerpts_for_buffer(buffer_id, cx);
- let Some(buffer_snapshot) = excerpts
- .first()
- .and_then(|(excerpt_id, _, _)| snapshot.buffer_for_excerpt(*excerpt_id))
- else {
- return;
- };
-
let old_range = maybe!({
let conflict_addon = editor.addon_mut::<ConflictAddon>().unwrap();
let buffer_conflicts = conflict_addon.buffers.get(&buffer_id)?;
@@ -230,23 +182,7 @@ fn conflicts_updated(
let mut removed_highlighted_ranges = Vec::new();
let mut removed_block_ids = HashSet::default();
for (conflict_range, block_id) in old_conflicts {
- let Some((excerpt_id, _, _)) = excerpts.iter().find(|(_, _, range)| {
- let precedes_start = range
- .context
- .start
- .cmp(&conflict_range.start, buffer_snapshot)
- .is_le();
- let follows_end = range
- .context
- .end
- .cmp(&conflict_range.start, buffer_snapshot)
- .is_ge();
- precedes_start && follows_end
- }) else {
- continue;
- };
- let excerpt_id = *excerpt_id;
- let Some(range) = snapshot.anchor_range_in_excerpt(excerpt_id, conflict_range) else {
+ let Some(range) = snapshot.buffer_anchor_range_to_anchor_range(conflict_range) else {
continue;
};
removed_highlighted_ranges.push(range.clone());
@@ -272,26 +208,9 @@ fn conflicts_updated(
let new_conflicts = &conflict_set.conflicts[event.new_range.clone()];
let mut blocks = Vec::new();
for conflict in new_conflicts {
- let Some((excerpt_id, _, _)) = excerpts.iter().find(|(_, _, range)| {
- let precedes_start = range
- .context
- .start
- .cmp(&conflict.range.start, buffer_snapshot)
- .is_le();
- let follows_end = range
- .context
- .end
- .cmp(&conflict.range.start, buffer_snapshot)
- .is_ge();
- precedes_start && follows_end
- }) else {
- continue;
- };
- let excerpt_id = *excerpt_id;
-
- update_conflict_highlighting(editor, conflict, &snapshot, excerpt_id, cx);
+ update_conflict_highlighting(editor, conflict, &snapshot, cx);
- let Some(anchor) = snapshot.anchor_in_excerpt(excerpt_id, conflict.range.start) else {
+ let Some(anchor) = snapshot.anchor_in_excerpt(conflict.range.start) else {
continue;
};
@@ -302,7 +221,7 @@ fn conflicts_updated(
style: BlockStyle::Sticky,
render: Arc::new({
let conflict = conflict.clone();
- move |cx| render_conflict_buttons(&conflict, excerpt_id, editor_handle.clone(), cx)
+ move |cx| render_conflict_buttons(&conflict, editor_handle.clone(), cx)
}),
priority: 0,
})
@@ -328,14 +247,13 @@ fn update_conflict_highlighting(
editor: &mut Editor,
conflict: &ConflictRegion,
buffer: &editor::MultiBufferSnapshot,
- excerpt_id: editor::ExcerptId,
cx: &mut Context<Editor>,
) -> Option<()> {
log::debug!("update conflict highlighting for {conflict:?}");
- let outer = buffer.anchor_range_in_excerpt(excerpt_id, conflict.range.clone())?;
- let ours = buffer.anchor_range_in_excerpt(excerpt_id, conflict.ours.clone())?;
- let theirs = buffer.anchor_range_in_excerpt(excerpt_id, conflict.theirs.clone())?;
+ let outer = buffer.buffer_anchor_range_to_anchor_range(conflict.range.clone())?;
+ let ours = buffer.buffer_anchor_range_to_anchor_range(conflict.ours.clone())?;
+ let theirs = buffer.buffer_anchor_range_to_anchor_range(conflict.theirs.clone())?;
let ours_background = cx.theme().colors().version_control_conflict_marker_ours;
let theirs_background = cx.theme().colors().version_control_conflict_marker_theirs;
@@ -373,7 +291,6 @@ fn update_conflict_highlighting(
fn render_conflict_buttons(
conflict: &ConflictRegion,
- excerpt_id: ExcerptId,
editor: WeakEntity<Editor>,
cx: &mut BlockContext,
) -> AnyElement {
@@ -395,7 +312,6 @@ fn render_conflict_buttons(
move |_, window, cx| {
resolve_conflict(
editor.clone(),
- excerpt_id,
conflict.clone(),
vec![ours.clone()],
window,
@@ -415,7 +331,6 @@ fn render_conflict_buttons(
move |_, window, cx| {
resolve_conflict(
editor.clone(),
- excerpt_id,
conflict.clone(),
vec![theirs.clone()],
window,
@@ -436,7 +351,6 @@ fn render_conflict_buttons(
move |_, window, cx| {
resolve_conflict(
editor.clone(),
- excerpt_id,
conflict.clone(),
vec![ours.clone(), theirs.clone()],
window,
@@ -461,7 +375,7 @@ fn render_conflict_buttons(
let content = editor
.update(cx, |editor, cx| {
let multibuffer = editor.buffer().read(cx);
- let buffer_id = conflict.ours.end.buffer_id?;
+ let buffer_id = conflict.ours.end.buffer_id;
let buffer = multibuffer.buffer(buffer_id)?;
let buffer_read = buffer.read(cx);
let snapshot = buffer_read.snapshot();
@@ -589,7 +503,6 @@ pub(crate) fn register_conflict_notification(
pub(crate) fn resolve_conflict(
editor: WeakEntity<Editor>,
- excerpt_id: ExcerptId,
resolved_conflict: ConflictRegion,
ranges: Vec<Range<Anchor>>,
window: &mut Window,
@@ -601,7 +514,7 @@ pub(crate) fn resolve_conflict(
let workspace = editor.workspace()?;
let project = editor.project()?.clone();
let multibuffer = editor.buffer().clone();
- let buffer_id = resolved_conflict.ours.end.buffer_id?;
+ let buffer_id = resolved_conflict.ours.end.buffer_id;
let buffer = multibuffer.read(cx).buffer(buffer_id)?;
resolved_conflict.resolve(buffer.clone(), &ranges, cx);
let conflict_addon = editor.addon_mut::<ConflictAddon>().unwrap();
@@ -620,7 +533,7 @@ pub(crate) fn resolve_conflict(
.ok()?;
let &(_, block_id) = &state.block_ids[ix];
let range =
- snapshot.anchor_range_in_excerpt(excerpt_id, resolved_conflict.range)?;
+ snapshot.buffer_anchor_range_to_anchor_range(resolved_conflict.range)?;
editor.remove_gutter_highlights::<ConflictsOuter>(vec![range.clone()], cx);
@@ -49,7 +49,7 @@ use language_model::{
LanguageModelRequestMessage, Role,
};
use menu;
-use multi_buffer::ExcerptInfo;
+use multi_buffer::ExcerptBoundaryInfo;
use notifications::status_toast::{StatusToast, ToastIcon};
use panel::{PanelHeader, panel_button, panel_filled_button, panel_icon_button};
use project::{
@@ -5754,11 +5754,12 @@ impl editor::Addon for GitPanelAddon {
fn render_buffer_header_controls(
&self,
- excerpt_info: &ExcerptInfo,
+ _excerpt_info: &ExcerptBoundaryInfo,
+ buffer: &language::BufferSnapshot,
window: &Window,
cx: &App,
) -> Option<AnyElement> {
- let file = excerpt_info.buffer.file()?;
+ let file = buffer.file()?;
let git_panel = self.workspace.upgrade()?.read(cx).panel::<GitPanel>(cx)?;
git_panel
@@ -501,9 +501,11 @@ impl ProjectDiff {
pub fn active_path(&self, cx: &App) -> Option<ProjectPath> {
let editor = self.editor.read(cx).focused_editor().read(cx);
+ let multibuffer = editor.buffer().read(cx);
let position = editor.selections.newest_anchor().head();
- let multi_buffer = editor.buffer().read(cx);
- let (_, buffer, _) = multi_buffer.excerpt_containing(position, cx)?;
+ let snapshot = multibuffer.snapshot(cx);
+ let (text_anchor, _) = snapshot.anchor_to_buffer_anchor(position)?;
+ let buffer = multibuffer.buffer(text_anchor.buffer_id)?;
let file = buffer.read(cx).file()?;
Some(ProjectPath {
@@ -516,9 +518,7 @@ impl ProjectDiff {
self.editor.update(cx, |editor, cx| {
editor.rhs_editor().update(cx, |editor, cx| {
editor.change_selections(Default::default(), window, cx, |s| {
- s.select_ranges(vec![
- multi_buffer::Anchor::min()..multi_buffer::Anchor::min(),
- ]);
+ s.select_ranges(vec![multi_buffer::Anchor::Min..multi_buffer::Anchor::Min]);
});
});
});
@@ -569,17 +569,17 @@ impl ProjectDiff {
.collect::<Vec<_>>();
if !ranges.iter().any(|range| range.start != range.end) {
selection = false;
- if let Some((excerpt_id, _, range)) = self
- .editor
- .read(cx)
- .rhs_editor()
- .read(cx)
- .active_excerpt(cx)
+ let anchor = editor.selections.newest_anchor().head();
+ if let Some((_, excerpt_range)) = snapshot.excerpt_containing(anchor..anchor)
+ && let Some(range) = snapshot
+ .anchor_in_buffer(excerpt_range.context.start)
+ .zip(snapshot.anchor_in_buffer(excerpt_range.context.end))
+ .map(|(start, end)| start..end)
{
- ranges = vec![multi_buffer::Anchor::range_in_buffer(excerpt_id, range)];
+ ranges = vec![range];
} else {
ranges = Vec::default();
- }
+ };
}
let mut has_staged_hunks = false;
let mut has_unstaged_hunks = false;
@@ -715,7 +715,7 @@ impl ProjectDiff {
let (was_empty, is_excerpt_newly_added) = self.editor.update(cx, |editor, cx| {
let was_empty = editor.rhs_editor().read(cx).buffer().read(cx).is_empty();
- let (_, is_newly_added) = editor.set_excerpts_for_path(
+ let is_newly_added = editor.update_excerpts_for_path(
path_key.clone(),
buffer,
excerpt_ranges,
@@ -735,7 +735,7 @@ impl ProjectDiff {
cx,
|selections| {
selections.select_ranges([
- multi_buffer::Anchor::min()..multi_buffer::Anchor::min()
+ multi_buffer::Anchor::Min..multi_buffer::Anchor::Min
])
},
);
@@ -785,8 +785,9 @@ impl ProjectDiff {
let mut previous_paths = this
.multibuffer
.read(cx)
- .paths()
- .cloned()
+ .snapshot(cx)
+ .buffers_with_paths()
+ .map(|(_, path_key)| path_key.clone())
.collect::<HashSet<_>>();
if let Some(repo) = repo {
@@ -877,10 +878,23 @@ impl ProjectDiff {
#[cfg(any(test, feature = "test-support"))]
pub fn excerpt_paths(&self, cx: &App) -> Vec<std::sync::Arc<util::rel_path::RelPath>> {
- self.multibuffer
+ let snapshot = self
+ .editor()
+ .read(cx)
+ .rhs_editor()
+ .read(cx)
+ .buffer()
.read(cx)
- .paths()
- .map(|key| key.path.clone())
+ .snapshot(cx);
+ snapshot
+ .excerpts()
+ .map(|excerpt| {
+ snapshot
+ .path_for_buffer(excerpt.context.start.buffer_id)
+ .unwrap()
+ .path
+ .clone()
+ })
.collect()
}
}
@@ -1937,7 +1951,7 @@ mod tests {
let snapshot = buffer_editor.snapshot(window, cx);
let snapshot = &snapshot.buffer_snapshot();
let prev_buffer_hunks = buffer_editor
- .diff_hunks_in_ranges(&[editor::Anchor::min()..editor::Anchor::max()], snapshot)
+ .diff_hunks_in_ranges(&[editor::Anchor::Min..editor::Anchor::Max], snapshot)
.collect::<Vec<_>>();
buffer_editor.git_restore(&Default::default(), window, cx);
prev_buffer_hunks
@@ -1950,7 +1964,7 @@ mod tests {
let snapshot = buffer_editor.snapshot(window, cx);
let snapshot = &snapshot.buffer_snapshot();
buffer_editor
- .diff_hunks_in_ranges(&[editor::Anchor::min()..editor::Anchor::max()], snapshot)
+ .diff_hunks_in_ranges(&[editor::Anchor::Min..editor::Anchor::Max], snapshot)
.collect::<Vec<_>>()
});
assert_eq!(new_buffer_hunks.as_slice(), &[]);
@@ -2209,9 +2223,14 @@ mod tests {
cx.update(|window, cx| {
let editor = diff.read(cx).editor.read(cx).rhs_editor().clone();
- let excerpt_ids = editor.read(cx).buffer().read(cx).excerpt_ids();
- assert_eq!(excerpt_ids.len(), 1);
- let excerpt_id = excerpt_ids[0];
+ let excerpts = editor
+ .read(cx)
+ .buffer()
+ .read(cx)
+ .snapshot(cx)
+ .excerpts()
+ .collect::<Vec<_>>();
+ assert_eq!(excerpts.len(), 1);
let buffer = editor
.read(cx)
.buffer()
@@ -2239,7 +2258,6 @@ mod tests {
resolve_conflict(
editor.downgrade(),
- excerpt_id,
snapshot.conflicts[0].clone(),
vec![ours_range],
window,
@@ -11,7 +11,7 @@ use gpui::{
AnyElement, App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, FocusHandle,
Focusable, IntoElement, Render, Task, Window,
};
-use language::{self, Buffer, Point};
+use language::{self, Buffer, OffsetRangeExt, Point};
use project::Project;
use settings::Settings;
use std::{
@@ -52,36 +52,26 @@ impl TextDiffView {
let selection_data = source_editor.update(cx, |editor, cx| {
let multibuffer = editor.buffer();
- let selections = editor.selections.all::<Point>(&editor.display_snapshot(cx));
- let first_selection = selections.first()?;
-
- let (source_buffer, buffer_start, start_excerpt) = multibuffer
- .read(cx)
- .point_to_buffer_point(first_selection.start, cx)?;
- let buffer_end = multibuffer
- .read(cx)
- .point_to_buffer_point(first_selection.end, cx)
- .and_then(|(buf, pt, end_excerpt)| {
- (buf.read(cx).remote_id() == source_buffer.read(cx).remote_id()
- && end_excerpt == start_excerpt)
- .then_some(pt)
- })
- .unwrap_or(buffer_start);
+ let multibuffer_snapshot = multibuffer.read(cx).snapshot(cx);
+ let first_selection = editor.selections.newest_anchor();
- let buffer_snapshot = source_buffer.read(cx);
- let max_point = buffer_snapshot.max_point();
+ let (source_buffer, buffer_range) = multibuffer_snapshot
+ .anchor_range_to_buffer_anchor_range(first_selection.range())?;
+ let max_point = source_buffer.max_point();
+ let buffer_range = buffer_range.to_point(source_buffer);
+ let source_buffer = multibuffer.read(cx).buffer(source_buffer.remote_id())?;
- if first_selection.is_empty() {
+ if buffer_range.is_empty() {
let full_range = Point::new(0, 0)..max_point;
return Some((source_buffer, full_range));
}
- let expanded_start = Point::new(buffer_start.row, 0);
- let expanded_end = if buffer_end.column > 0 {
- let next_row = buffer_end.row + 1;
+ let expanded_start = Point::new(buffer_range.start.row, 0);
+ let expanded_end = if buffer_range.end.column > 0 {
+ let next_row = buffer_range.end.row + 1;
cmp::min(max_point, Point::new(next_row, 0))
} else {
- buffer_end
+ buffer_range.end
};
Some((source_buffer, expanded_start..expanded_end))
});
@@ -42,23 +42,22 @@ impl UserCaretPosition {
snapshot: &MultiBufferSnapshot,
) -> Self {
let selection_end = selection.head();
- let (line, character) = if let Some((buffer_snapshot, point, _)) =
- snapshot.point_to_buffer_point(selection_end)
- {
- let line_start = Point::new(point.row, 0);
+ let (line, character) =
+ if let Some((buffer_snapshot, point)) = snapshot.point_to_buffer_point(selection_end) {
+ let line_start = Point::new(point.row, 0);
- let chars_to_last_position = buffer_snapshot
- .text_summary_for_range::<text::TextSummary, _>(line_start..point)
- .chars as u32;
- (line_start.row, chars_to_last_position)
- } else {
- let line_start = Point::new(selection_end.row, 0);
+ let chars_to_last_position = buffer_snapshot
+ .text_summary_for_range::<text::TextSummary, _>(line_start..point)
+ .chars as u32;
+ (line_start.row, chars_to_last_position)
+ } else {
+ let line_start = Point::new(selection_end.row, 0);
- let chars_to_last_position = snapshot
- .text_summary_for_range::<MBTextSummary, _>(line_start..selection_end)
- .chars as u32;
- (selection_end.row, chars_to_last_position)
- };
+ let chars_to_last_position = snapshot
+ .text_summary_for_range::<MBTextSummary, _>(line_start..selection_end)
+ .chars as u32;
+ (selection_end.row, chars_to_last_position)
+ };
Self {
line: NonZeroU32::new(line + 1).expect("added 1"),
@@ -232,7 +231,7 @@ impl Render for CursorPosition {
if let Some(editor) = workspace
.active_item(cx)
.and_then(|item| item.act_as::<Editor>(cx))
- && let Some((_, buffer, _)) = editor.read(cx).active_excerpt(cx)
+ && let Some(buffer) = editor.read(cx).active_buffer(cx)
{
workspace.toggle_modal(window, cx, |window, cx| {
crate::GoToLine::new(editor, buffer, window, cx)
@@ -63,7 +63,7 @@ impl GoToLine {
return;
};
let editor = editor_handle.read(cx);
- let Some((_, buffer, _)) = editor.active_excerpt(cx) else {
+ let Some(buffer) = editor.active_buffer(cx) else {
return;
};
workspace.update(cx, |workspace, cx| {
@@ -93,11 +93,9 @@ impl GoToLine {
let last_line = editor
.buffer()
.read(cx)
- .excerpts_for_buffer(snapshot.remote_id(), cx)
- .into_iter()
- .map(move |(_, _, range)| {
- text::ToPoint::to_point(&range.context.end, &snapshot).row
- })
+ .snapshot(cx)
+ .excerpts_for_buffer(snapshot.remote_id())
+ .map(move |range| text::ToPoint::to_point(&range.context.end, &snapshot).row)
.max()
.unwrap_or(0);
@@ -230,7 +228,7 @@ impl GoToLine {
let character = query_char.unwrap_or(0).saturating_sub(1);
let target_multi_buffer_row = MultiBufferRow(row);
- let (buffer_snapshot, target_in_buffer, _) = snapshot.point_to_buffer_point(Point::new(
+ let (buffer_snapshot, target_in_buffer) = snapshot.point_to_buffer_point(Point::new(
target_multi_buffer_row.min(snapshot.max_row()).0,
0,
))?;
@@ -5496,6 +5496,8 @@ pub enum ElementId {
CodeLocation(core::panic::Location<'static>),
/// A labeled child of an element.
NamedChild(Arc<ElementId>, SharedString),
+ /// A byte array ID (used for text-anchors)
+ OpaqueId([u8; 20]),
}
impl ElementId {
@@ -5517,6 +5519,7 @@ impl Display for ElementId {
ElementId::Path(path) => write!(f, "{}", path.display())?,
ElementId::CodeLocation(location) => write!(f, "{}", location)?,
ElementId::NamedChild(id, name) => write!(f, "{}-{}", id, name)?,
+ ElementId::OpaqueId(opaque_id) => write!(f, "{:x?}", opaque_id)?,
}
Ok(())
@@ -5631,6 +5634,12 @@ impl From<&'static core::panic::Location<'static>> for ElementId {
}
}
+impl From<[u8; 20]> for ElementId {
+ fn from(opaque_id: [u8; 20]) -> Self {
+ ElementId::OpaqueId(opaque_id)
+ }
+}
+
/// A rectangle to be rendered in the window at the given position and size.
/// Passed as an argument [`Window::paint_quad`].
#[derive(Clone)]
@@ -1,7 +1,6 @@
use anyhow::{Result, anyhow};
use editor::{
- Bias, CompletionProvider, Editor, EditorEvent, EditorMode, ExcerptId, MinimapVisibility,
- MultiBuffer,
+ Bias, CompletionProvider, Editor, EditorEvent, EditorMode, MinimapVisibility, MultiBuffer,
};
use fuzzy::StringMatch;
use gpui::{
@@ -641,7 +640,6 @@ struct RustStyleCompletionProvider {
impl CompletionProvider for RustStyleCompletionProvider {
fn completions(
&self,
- _excerpt_id: ExcerptId,
buffer: &Entity<Buffer>,
position: Anchor,
_: editor::CompletionContext,
@@ -26,7 +26,6 @@ impl ActionCompletionProvider {
impl CompletionProvider for ActionCompletionProvider {
fn completions(
&self,
- _excerpt_id: editor::ExcerptId,
buffer: &Entity<language::Buffer>,
buffer_position: language::Anchor,
_trigger: editor::CompletionContext,
@@ -3480,7 +3480,6 @@ struct KeyContextCompletionProvider {
impl CompletionProvider for KeyContextCompletionProvider {
fn completions(
&self,
- _excerpt_id: editor::ExcerptId,
buffer: &Entity<language::Buffer>,
buffer_position: language::Anchor,
_trigger: editor::CompletionContext,
@@ -326,23 +326,17 @@ impl DiagnosticEntry<Anchor> {
}
}
-impl Default for Summary {
- fn default() -> Self {
- Self {
- start: Anchor::MIN,
- end: Anchor::MAX,
- min_start: Anchor::MAX,
- max_end: Anchor::MIN,
- count: 0,
- }
- }
-}
-
impl sum_tree::Summary for Summary {
type Context<'a> = &'a text::BufferSnapshot;
- fn zero(_cx: Self::Context<'_>) -> Self {
- Default::default()
+ fn zero(buffer: &text::BufferSnapshot) -> Self {
+ Self {
+ start: Anchor::min_for_buffer(buffer.remote_id()),
+ end: Anchor::max_for_buffer(buffer.remote_id()),
+ min_start: Anchor::max_for_buffer(buffer.remote_id()),
+ max_end: Anchor::min_for_buffer(buffer.remote_id()),
+ count: 0,
+ }
}
fn add_summary(&mut self, other: &Self, buffer: Self::Context<'_>) {
@@ -174,11 +174,11 @@ pub fn serialize_selection(selection: &Selection<Anchor>) -> proto::Selection {
id: selection.id as u64,
start: Some(proto::EditorAnchor {
anchor: Some(serialize_anchor(&selection.start)),
- excerpt_id: 0,
+ excerpt_id: None,
}),
end: Some(proto::EditorAnchor {
anchor: Some(serialize_anchor(&selection.end)),
- excerpt_id: 0,
+ excerpt_id: None,
}),
reversed: selection.reversed,
}
@@ -260,7 +260,7 @@ pub fn serialize_anchor(anchor: &Anchor) -> proto::Anchor {
Bias::Left => proto::Bias::Left as i32,
Bias::Right => proto::Bias::Right as i32,
},
- buffer_id: anchor.buffer_id.map(Into::into),
+ buffer_id: Some(anchor.buffer_id.into()),
}
}
@@ -498,7 +498,7 @@ pub fn deserialize_anchor(anchor: proto::Anchor) -> Option<Anchor> {
timestamp,
anchor.offset as u32,
bias,
- buffer_id,
+ buffer_id?,
))
}
@@ -18,7 +18,7 @@ use std::{
};
use streaming_iterator::StreamingIterator;
use sum_tree::{Bias, Dimensions, SeekTarget, SumTree};
-use text::{Anchor, BufferSnapshot, OffsetRangeExt, Point, Rope, ToOffset, ToPoint};
+use text::{Anchor, BufferId, BufferSnapshot, OffsetRangeExt, Point, Rope, ToOffset, ToPoint};
use tree_sitter::{
Node, Query, QueryCapture, QueryCaptures, QueryCursor, QueryMatch, QueryMatches,
QueryPredicateArg,
@@ -56,7 +56,15 @@ impl Drop for SyntaxSnapshot {
// This does allocate a new Arc, but it's cheap and avoids blocking the main thread without needing to use an `Option` or `MaybeUninit`.
let _ = DROP_TX.send(std::mem::replace(
&mut self.layers,
- SumTree::from_summary(Default::default()),
+ SumTree::from_summary(SyntaxLayerSummary {
+ min_depth: Default::default(),
+ max_depth: Default::default(),
+ // Deliberately bogus anchors, doesn't matter in this context
+ range: Anchor::min_min_range_for_buffer(BufferId::new(1).unwrap()),
+ last_layer_range: Anchor::min_min_range_for_buffer(BufferId::new(1).unwrap()),
+ last_layer_language: Default::default(),
+ contains_unknown_injections: Default::default(),
+ }),
));
}
}
@@ -588,7 +596,7 @@ impl SyntaxSnapshot {
let bounded_position = SyntaxLayerPositionBeforeChange {
position: position.clone(),
- change: changed_regions.start_position(),
+ change: changed_regions.start_position(text.remote_id()),
};
if bounded_position.cmp(cursor.start(), text).is_gt() {
let slice = cursor.slice(&bounded_position, Bias::Left);
@@ -1946,11 +1954,11 @@ impl ChangedRegion {
}
impl ChangeRegionSet {
- fn start_position(&self) -> ChangeStartPosition {
+ fn start_position(&self, buffer_id: BufferId) -> ChangeStartPosition {
self.0.first().map_or(
ChangeStartPosition {
depth: usize::MAX,
- position: Anchor::MAX,
+ position: Anchor::max_for_buffer(buffer_id),
},
|region| ChangeStartPosition {
depth: region.depth,
@@ -1999,32 +2007,28 @@ impl ChangeRegionSet {
}
}
-impl Default for SyntaxLayerSummary {
- fn default() -> Self {
+impl sum_tree::Summary for SyntaxLayerSummary {
+ type Context<'a> = &'a BufferSnapshot;
+
+ fn zero(buffer: &BufferSnapshot) -> Self {
Self {
max_depth: 0,
min_depth: 0,
- range: Anchor::MAX..Anchor::MIN,
- last_layer_range: Anchor::MIN..Anchor::MAX,
+ range: Anchor::max_for_buffer(buffer.remote_id())
+ ..Anchor::min_for_buffer(buffer.remote_id()),
+ last_layer_range: Anchor::min_for_buffer(buffer.remote_id())
+ ..Anchor::max_for_buffer(buffer.remote_id()),
last_layer_language: None,
contains_unknown_injections: false,
}
}
-}
-
-impl sum_tree::Summary for SyntaxLayerSummary {
- type Context<'a> = &'a BufferSnapshot;
-
- fn zero(_cx: &BufferSnapshot) -> Self {
- Default::default()
- }
fn add_summary(&mut self, other: &Self, buffer: Self::Context<'_>) {
if other.max_depth > self.max_depth {
self.max_depth = other.max_depth;
self.range = other.range.clone();
} else {
- if self.range == (Anchor::MAX..Anchor::MAX) {
+ if self.range.start.is_max() && self.range.end.is_max() {
self.range.start = other.range.start;
}
if other.range.end.cmp(&self.range.end, buffer).is_gt() {
@@ -29,7 +29,7 @@ impl ActiveBufferLanguage {
self.active_language = Some(None);
let editor = editor.read(cx);
- if let Some((_, buffer, _)) = editor.active_excerpt(cx)
+ if let Some(buffer) = editor.active_buffer(cx)
&& let Some(language) = buffer.read(cx).language()
{
self.active_language = Some(Some(language.name()));
@@ -51,11 +51,11 @@ impl LanguageSelector {
cx: &mut Context<Workspace>,
) -> Option<()> {
let registry = workspace.app_state().languages.clone();
- let (_, buffer, _) = workspace
+ let buffer = workspace
.active_item(cx)?
.act_as::<Editor>(cx)?
.read(cx)
- .active_excerpt(cx)?;
+ .active_buffer(cx)?;
let project = workspace.project().clone();
workspace.toggle_modal(window, cx, move |window, cx| {
@@ -414,10 +414,10 @@ mod tests {
) -> Entity<Editor> {
let editor = open_new_buffer_editor(workspace, project, cx).await;
// Ensure the buffer has no language after the editor is created
- let (_, buffer, _) = editor.read_with(cx, |editor, cx| {
+ let buffer = editor.read_with(cx, |editor, cx| {
editor
- .active_excerpt(cx)
- .expect("editor should have an active excerpt")
+ .active_buffer(cx)
+ .expect("editor should have an active buffer")
});
buffer.update(cx, |buffer, cx| {
buffer.set_language(None, cx);
@@ -454,8 +454,8 @@ mod tests {
.await
.expect("language should exist in registry");
editor.update(cx, move |editor, cx| {
- let (_, buffer, _) = editor
- .active_excerpt(cx)
+ let buffer = editor
+ .active_buffer(cx)
.expect("editor should have an active excerpt");
buffer.update(cx, |buffer, cx| {
buffer.set_language(Some(language), cx);
@@ -578,6 +578,15 @@ mod tests {
assert_selected_language_for_editor(&workspace, &rust_editor, Some("Rust"), cx);
assert_selected_language_for_editor(&workspace, &typescript_editor, Some("TypeScript"), cx);
+ // Ensure the empty editor's buffer has no language before asserting
+ let buffer = empty_editor.read_with(cx, |editor, cx| {
+ editor
+ .active_buffer(cx)
+ .expect("editor should have an active excerpt")
+ });
+ buffer.update(cx, |buffer, cx| {
+ buffer.set_language(None, cx);
+ });
assert_selected_language_for_editor(&workspace, &empty_editor, None, cx);
}
@@ -1,5 +1,5 @@
use editor::{
- Anchor, Editor, ExcerptId, HighlightKey, MultiBufferSnapshot, SelectionEffects, ToPoint,
+ Anchor, Editor, HighlightKey, MultiBufferSnapshot, SelectionEffects, ToPoint,
scroll::Autoscroll,
};
use gpui::{
@@ -8,8 +8,7 @@ use gpui::{
MouseDownEvent, MouseMoveEvent, ParentElement, Render, ScrollStrategy, SharedString, Styled,
Task, UniformListScrollHandle, WeakEntity, Window, actions, div, rems, uniform_list,
};
-use language::ToOffset;
-
+use language::{BufferId, Point, ToOffset};
use menu::{SelectNext, SelectPrevious};
use std::{mem, ops::Range};
use theme::ActiveTheme;
@@ -114,12 +113,12 @@ impl HighlightCategory {
#[derive(Debug, Clone)]
struct HighlightEntry {
- excerpt_id: ExcerptId,
range: Range<Anchor>,
+ buffer_id: BufferId,
+ buffer_point_range: Range<Point>,
range_display: SharedString,
style: HighlightStyle,
category: HighlightCategory,
- sort_key: (ExcerptId, u32, u32, u32, u32),
}
/// An item in the display list: either a separator between excerpts or a highlight entry.
@@ -319,20 +318,18 @@ impl HighlightsTreeView {
display_map.update(cx, |display_map, cx| {
for (key, text_highlights) in display_map.all_text_highlights() {
for range in &text_highlights.1 {
- let excerpt_id = range.start.excerpt_id;
- let (range_display, sort_key) = format_anchor_range(
- range,
- excerpt_id,
- &multi_buffer_snapshot,
- is_singleton,
- );
+ let Some((range_display, buffer_id, buffer_point_range)) =
+ format_anchor_range(range, &multi_buffer_snapshot)
+ else {
+ continue;
+ };
entries.push(HighlightEntry {
- excerpt_id,
range: range.clone(),
+ buffer_id,
range_display,
style: text_highlights.0,
category: HighlightCategory::Text(*key),
- sort_key,
+ buffer_point_range,
});
}
}
@@ -345,13 +342,11 @@ impl HighlightsTreeView {
.and_then(|buf| buf.read(cx).language().map(|l| l.name()));
for token in tokens.iter() {
let range = token.range.start..token.range.end;
- let excerpt_id = range.start.excerpt_id;
- let (range_display, sort_key) = format_anchor_range(
- &range,
- excerpt_id,
- &multi_buffer_snapshot,
- is_singleton,
- );
+ let Some((range_display, entry_buffer_id, buffer_point_range)) =
+ format_anchor_range(&range, &multi_buffer_snapshot)
+ else {
+ continue;
+ };
let Some(stylizer) = lsp_store.get_or_create_token_stylizer(
token.server_id,
language_name.as_ref(),
@@ -388,8 +383,8 @@ impl HighlightsTreeView {
});
entries.push(HighlightEntry {
- excerpt_id,
range,
+ buffer_id: entry_buffer_id,
range_display,
style: interner[token.style],
category: HighlightCategory::SemanticToken {
@@ -399,7 +394,7 @@ impl HighlightsTreeView {
.map(SharedString::from),
theme_key,
},
- sort_key,
+ buffer_point_range,
});
}
}
@@ -407,7 +402,13 @@ impl HighlightsTreeView {
});
let syntax_theme = cx.theme().syntax().clone();
- for (excerpt_id, buffer_snapshot, excerpt_range) in multi_buffer_snapshot.excerpts() {
+ for excerpt_range in multi_buffer_snapshot.excerpts() {
+ let Some(buffer_snapshot) =
+ multi_buffer_snapshot.buffer_for_id(excerpt_range.context.start.buffer_id)
+ else {
+ continue;
+ };
+
let start_offset = excerpt_range.context.start.to_offset(buffer_snapshot);
let end_offset = excerpt_range.context.end.to_offset(buffer_snapshot);
let range = start_offset..end_offset;
@@ -438,8 +439,8 @@ impl HighlightsTreeView {
let start_anchor = buffer_snapshot.anchor_before(capture.node.start_byte());
let end_anchor = buffer_snapshot.anchor_after(capture.node.end_byte());
- let start = multi_buffer_snapshot.anchor_in_excerpt(excerpt_id, start_anchor);
- let end = multi_buffer_snapshot.anchor_in_excerpt(excerpt_id, end_anchor);
+ let start = multi_buffer_snapshot.anchor_in_excerpt(start_anchor);
+ let end = multi_buffer_snapshot.anchor_in_excerpt(end_anchor);
let (start, end) = match (start, end) {
(Some(s), Some(e)) => (s, e),
@@ -447,29 +448,38 @@ impl HighlightsTreeView {
};
let range = start..end;
- let (range_display, sort_key) =
- format_anchor_range(&range, excerpt_id, &multi_buffer_snapshot, is_singleton);
+ let Some((range_display, buffer_id, buffer_point_range)) =
+ format_anchor_range(&range, &multi_buffer_snapshot)
+ else {
+ continue;
+ };
entries.push(HighlightEntry {
- excerpt_id,
range,
+ buffer_id,
range_display,
style,
category: HighlightCategory::SyntaxToken {
capture_name,
theme_key,
},
- sort_key,
+ buffer_point_range,
});
}
}
entries.sort_by(|a, b| {
- a.sort_key
- .cmp(&b.sort_key)
+ a.buffer_id
+ .cmp(&b.buffer_id)
+ .then_with(|| a.buffer_point_range.start.cmp(&b.buffer_point_range.start))
+ .then_with(|| a.buffer_point_range.end.cmp(&b.buffer_point_range.end))
.then_with(|| a.category.cmp(&b.category))
});
- entries.dedup_by(|a, b| a.sort_key == b.sort_key && a.category == b.category);
+ entries.dedup_by(|a, b| {
+ a.buffer_id == b.buffer_id
+ && a.buffer_point_range == b.buffer_point_range
+ && a.category == b.category
+ });
self.cached_entries = entries;
self.rebuild_display_items(&multi_buffer_snapshot, cx);
@@ -485,7 +495,7 @@ impl HighlightsTreeView {
fn rebuild_display_items(&mut self, snapshot: &MultiBufferSnapshot, cx: &App) {
self.display_items.clear();
- let mut last_excerpt_id: Option<ExcerptId> = None;
+ let mut last_range_end: Option<Anchor> = None;
for (entry_ix, entry) in self.cached_entries.iter().enumerate() {
if !self.should_show_entry(entry) {
@@ -493,11 +503,14 @@ impl HighlightsTreeView {
}
if !self.is_singleton {
- let excerpt_changed =
- last_excerpt_id.is_none_or(|last_id| last_id != entry.excerpt_id);
+ let excerpt_changed = last_range_end.is_none_or(|anchor| {
+ snapshot
+ .excerpt_containing(anchor..entry.range.start)
+ .is_none()
+ });
if excerpt_changed {
- last_excerpt_id = Some(entry.excerpt_id);
- let label = excerpt_label_for(entry.excerpt_id, snapshot, cx);
+ last_range_end = Some(entry.range.end);
+ let label = excerpt_label_for(entry, snapshot, cx);
self.display_items
.push(DisplayItem::ExcerptSeparator { label });
}
@@ -516,10 +529,6 @@ impl HighlightsTreeView {
}
fn scroll_to_cursor_position(&mut self, cursor: &Anchor, snapshot: &MultiBufferSnapshot) {
- let cursor_point = cursor.to_point(snapshot);
- let cursor_key = (cursor_point.row, cursor_point.column);
- let cursor_excerpt = cursor.excerpt_id;
-
let best = self
.display_items
.iter()
@@ -532,17 +541,18 @@ impl HighlightsTreeView {
_ => None,
})
.filter(|(_, _, entry)| {
- let (excerpt_id, start_row, start_col, end_row, end_col) = entry.sort_key;
- if !self.is_singleton && excerpt_id != cursor_excerpt {
- return false;
- }
- let start = (start_row, start_col);
- let end = (end_row, end_col);
- cursor_key >= start && cursor_key <= end
+ entry.range.start.cmp(&cursor, snapshot).is_le()
+ && cursor.cmp(&entry.range.end, snapshot).is_lt()
})
.min_by_key(|(_, _, entry)| {
- let (_, start_row, start_col, end_row, end_col) = entry.sort_key;
- (end_row - start_row, end_col.saturating_sub(start_col))
+ (
+ entry.buffer_point_range.end.row - entry.buffer_point_range.start.row,
+ entry
+ .buffer_point_range
+ .end
+ .column
+ .saturating_sub(entry.buffer_point_range.start.column),
+ )
})
.map(|(display_ix, entry_ix, _)| (display_ix, entry_ix));
@@ -1076,12 +1086,13 @@ impl ToolbarItemView for HighlightsTreeToolbarItemView {
}
fn excerpt_label_for(
- excerpt_id: ExcerptId,
+ entry: &HighlightEntry,
snapshot: &MultiBufferSnapshot,
cx: &App,
) -> SharedString {
- let buffer = snapshot.buffer_for_excerpt(excerpt_id);
- let path_label = buffer
+ let path_label = snapshot
+ .anchor_to_buffer_anchor(entry.range.start)
+ .and_then(|(anchor, _)| snapshot.buffer_for_id(anchor.buffer_id))
.and_then(|buf| buf.file())
.map(|file| {
let full_path = file.full_path(cx);
@@ -1093,50 +1104,21 @@ fn excerpt_label_for(
fn format_anchor_range(
range: &Range<Anchor>,
- excerpt_id: ExcerptId,
snapshot: &MultiBufferSnapshot,
- is_singleton: bool,
-) -> (SharedString, (ExcerptId, u32, u32, u32, u32)) {
- if is_singleton {
- let start = range.start.to_point(snapshot);
- let end = range.end.to_point(snapshot);
- let display = SharedString::from(format!(
- "[{}:{} - {}:{}]",
- start.row + 1,
- start.column + 1,
- end.row + 1,
- end.column + 1,
- ));
- let sort_key = (excerpt_id, start.row, start.column, end.row, end.column);
- (display, sort_key)
- } else {
- let buffer = snapshot.buffer_for_excerpt(excerpt_id);
- if let Some(buffer) = buffer {
- let start = language::ToPoint::to_point(&range.start.text_anchor, buffer);
- let end = language::ToPoint::to_point(&range.end.text_anchor, buffer);
- let display = SharedString::from(format!(
- "[{}:{} - {}:{}]",
- start.row + 1,
- start.column + 1,
- end.row + 1,
- end.column + 1,
- ));
- let sort_key = (excerpt_id, start.row, start.column, end.row, end.column);
- (display, sort_key)
- } else {
- let start = range.start.to_point(snapshot);
- let end = range.end.to_point(snapshot);
- let display = SharedString::from(format!(
- "[{}:{} - {}:{}]",
- start.row + 1,
- start.column + 1,
- end.row + 1,
- end.column + 1,
- ));
- let sort_key = (excerpt_id, start.row, start.column, end.row, end.column);
- (display, sort_key)
- }
- }
+) -> Option<(SharedString, BufferId, Range<Point>)> {
+ let start = range.start.to_point(snapshot);
+ let end = range.end.to_point(snapshot);
+ let ((start_buffer, start), (_, end)) = snapshot
+ .point_to_buffer_point(start)
+ .zip(snapshot.point_to_buffer_point(end))?;
+ let display = SharedString::from(format!(
+ "[{}:{} - {}:{}]",
+ start.row + 1,
+ start.column + 1,
+ end.row + 1,
+ end.column + 1,
+ ));
+ Some((display, start_buffer.remote_id(), start..end))
}
fn render_style_preview(style: HighlightStyle, selected: bool, cx: &App) -> Div {
@@ -1179,13 +1179,20 @@ impl StatusItemView for LspButton {
.and_then(|active_editor| active_editor.editor.upgrade())
.as_ref()
{
- let editor_buffers =
- HashSet::from_iter(editor.read(cx).buffer().read(cx).excerpt_buffer_ids());
+ let editor_buffers = HashSet::from_iter(
+ editor
+ .read(cx)
+ .buffer()
+ .read(cx)
+ .snapshot(cx)
+ .excerpts()
+ .map(|excerpt| excerpt.context.start.buffer_id),
+ );
let _editor_subscription = cx.subscribe_in(
&editor,
window,
|lsp_button, _, e: &EditorEvent, window, cx| match e {
- EditorEvent::ExcerptsAdded { buffer, .. } => {
+ EditorEvent::BufferRangesUpdated { buffer, .. } => {
let updated = lsp_button.server_state.update(cx, |state, cx| {
if let Some(active_editor) = state.active_editor.as_mut() {
let buffer_id = buffer.read(cx).remote_id();
@@ -1198,9 +1205,7 @@ impl StatusItemView for LspButton {
lsp_button.refresh_lsp_menu(false, window, cx);
}
}
- EditorEvent::ExcerptsRemoved {
- removed_buffer_ids, ..
- } => {
+ EditorEvent::BuffersRemoved { removed_buffer_ids } => {
let removed = lsp_button.server_state.update(cx, |state, _| {
let mut removed = false;
if let Some(active_editor) = state.active_editor.as_mut() {
@@ -1,7 +1,6 @@
use command_palette_hooks::CommandPaletteFilter;
use editor::{
- Anchor, Editor, ExcerptId, HighlightKey, MultiBufferOffset, SelectionEffects,
- scroll::Autoscroll,
+ Anchor, Editor, HighlightKey, MultiBufferOffset, SelectionEffects, scroll::Autoscroll,
};
use gpui::{
App, AppContext as _, Context, Div, Entity, EntityId, EventEmitter, FocusHandle, Focusable,
@@ -125,7 +124,6 @@ impl EditorState {
#[derive(Clone)]
struct BufferState {
buffer: Entity<Buffer>,
- excerpt_id: ExcerptId,
active_layer: Option<OwnedSyntaxLayer>,
}
@@ -253,18 +251,18 @@ impl SyntaxTreeView {
let snapshot = editor_state
.editor
.update(cx, |editor, cx| editor.snapshot(window, cx));
- let (buffer, range, excerpt_id) = editor_state.editor.update(cx, |editor, cx| {
+ let (buffer, range) = editor_state.editor.update(cx, |editor, cx| {
let selection_range = editor
.selections
.last::<MultiBufferOffset>(&editor.display_snapshot(cx))
.range();
let multi_buffer = editor.buffer().read(cx);
- let (buffer, range, excerpt_id) = snapshot
+ let (buffer, range, _) = snapshot
.buffer_snapshot()
- .range_to_buffer_ranges(selection_range.start..=selection_range.end)
+ .range_to_buffer_ranges(selection_range.start..selection_range.end)
.pop()?;
let buffer = multi_buffer.buffer(buffer.remote_id()).unwrap();
- Some((buffer, range, excerpt_id))
+ Some((buffer, range))
})?;
// If the cursor has moved into a different excerpt, retrieve a new syntax layer
@@ -273,16 +271,14 @@ impl SyntaxTreeView {
.active_buffer
.get_or_insert_with(|| BufferState {
buffer: buffer.clone(),
- excerpt_id,
active_layer: None,
});
let mut prev_layer = None;
if did_reparse {
prev_layer = buffer_state.active_layer.take();
}
- if buffer_state.buffer != buffer || buffer_state.excerpt_id != excerpt_id {
+ if buffer_state.buffer != buffer {
buffer_state.buffer = buffer.clone();
- buffer_state.excerpt_id = excerpt_id;
buffer_state.active_layer = None;
}
@@ -360,8 +356,7 @@ impl SyntaxTreeView {
// Build a multibuffer anchor range.
let multibuffer = editor_state.editor.read(cx).buffer();
let multibuffer = multibuffer.read(cx).snapshot(cx);
- let excerpt_id = buffer_state.excerpt_id;
- let range = multibuffer.anchor_range_in_excerpt(excerpt_id, range)?;
+ let range = multibuffer.buffer_anchor_range_to_anchor_range(range)?;
let key = cx.entity_id().as_u64() as usize;
// Update the editor with the anchor range.
@@ -18,7 +18,7 @@ impl LineEndingIndicator {
self.line_ending = None;
self.active_editor = None;
- if let Some((_, buffer, _)) = editor.read(cx).active_excerpt(cx) {
+ if let Some(buffer) = editor.read(cx).active_buffer(cx) {
let line_ending = buffer.read(cx).line_ending();
self.line_ending = Some(line_ending);
self.active_editor = Some(editor.downgrade());
@@ -40,7 +40,7 @@ impl LineEndingSelector {
fn toggle(editor: &WeakEntity<Editor>, window: &mut Window, cx: &mut App) {
let Some((workspace, buffer)) = editor
.update(cx, |editor, cx| {
- Some((editor.workspace()?, editor.active_excerpt(cx)?.1))
+ Some((editor.workspace()?, editor.active_buffer(cx)?))
})
.ok()
.flatten()
@@ -295,7 +295,7 @@ impl MarkdownPreviewView {
EditorEvent::Edited { .. }
| EditorEvent::BufferEdited { .. }
| EditorEvent::DirtyChanged
- | EditorEvent::ExcerptsEdited { .. } => {
+ | EditorEvent::BuffersEdited { .. } => {
this.update_markdown_from_active_editor(true, false, window, cx);
}
EditorEvent::SelectionsChanged { .. } => {
@@ -1,192 +1,331 @@
-use crate::{MultiBufferDimension, MultiBufferOffset, MultiBufferOffsetUtf16};
+use crate::{
+ ExcerptSummary, MultiBufferDimension, MultiBufferOffset, MultiBufferOffsetUtf16, PathKey,
+ PathKeyIndex, find_diff_state,
+};
-use super::{ExcerptId, MultiBufferSnapshot, ToOffset, ToPoint};
-use language::Point;
+use super::{MultiBufferSnapshot, ToOffset, ToPoint};
+use language::{BufferSnapshot, Point};
use std::{
cmp::Ordering,
ops::{Add, AddAssign, Range, Sub},
};
use sum_tree::Bias;
+use text::BufferId;
+
+/// A multibuffer anchor derived from an anchor into a specific excerpted buffer.
+#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
+pub struct ExcerptAnchor {
+ pub(crate) text_anchor: text::Anchor,
+ pub(crate) path: PathKeyIndex,
+ pub(crate) diff_base_anchor: Option<text::Anchor>,
+}
/// A stable reference to a position within a [`MultiBuffer`](super::MultiBuffer).
///
/// Unlike simple offsets, anchors remain valid as the text is edited, automatically
/// adjusting to reflect insertions and deletions around them.
#[derive(Clone, Copy, Eq, PartialEq, Hash)]
-pub struct Anchor {
- /// Identifies which excerpt within the multi-buffer this anchor belongs to.
- /// A multi-buffer can contain multiple excerpts from different buffers.
- pub excerpt_id: ExcerptId,
- /// The position within the excerpt's underlying buffer. This is a stable
- /// reference that remains valid as the buffer text is edited.
- pub text_anchor: text::Anchor,
- /// When present, indicates this anchor points into deleted text within an
- /// expanded diff hunk. The anchor references a position in the diff base
- /// (original) text rather than the current buffer text. This is used when
- /// displaying inline diffs where deleted lines are shown.
- pub diff_base_anchor: Option<text::Anchor>,
+pub enum Anchor {
+ /// An anchor that always resolves to the start of the multibuffer.
+ Min,
+ /// An anchor that's attached to a specific excerpted buffer.
+ Excerpt(ExcerptAnchor),
+ /// An anchor that always resolves to the end of the multibuffer.
+ Max,
}
-impl std::fmt::Debug for Anchor {
+pub(crate) enum AnchorSeekTarget {
+ Excerpt {
+ path_key: PathKey,
+ anchor: ExcerptAnchor,
+ // None when the buffer no longer exists in the multibuffer
+ snapshot: Option<BufferSnapshot>,
+ },
+ Empty,
+}
+
+impl std::fmt::Debug for AnchorSeekTarget {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- if self.is_min() {
- return write!(f, "Anchor::min({:?})", self.text_anchor.buffer_id);
+ match self {
+ Self::Excerpt {
+ path_key,
+ anchor,
+ snapshot: _,
+ } => f
+ .debug_struct("Excerpt")
+ .field("path_key", path_key)
+ .field("anchor", anchor)
+ .finish(),
+ Self::Empty => write!(f, "Empty"),
}
- if self.is_max() {
- return write!(f, "Anchor::max({:?})", self.text_anchor.buffer_id);
+ }
+}
+
+impl std::fmt::Debug for Anchor {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Anchor::Min => write!(f, "Anchor::Min"),
+ Anchor::Max => write!(f, "Anchor::Max"),
+ Anchor::Excerpt(excerpt_anchor) => write!(f, "{excerpt_anchor:?}"),
}
+ }
+}
- f.debug_struct("Anchor")
- .field("excerpt_id", &self.excerpt_id)
- .field("text_anchor", &self.text_anchor)
- .field("diff_base_anchor", &self.diff_base_anchor)
- .finish()
+impl From<ExcerptAnchor> for Anchor {
+ fn from(anchor: ExcerptAnchor) -> Self {
+ Anchor::Excerpt(anchor)
}
}
-impl Anchor {
- pub fn with_diff_base_anchor(self, diff_base_anchor: text::Anchor) -> Self {
- Self {
- diff_base_anchor: Some(diff_base_anchor),
- ..self
+impl ExcerptAnchor {
+ pub(crate) fn buffer_id(&self) -> BufferId {
+ self.text_anchor.buffer_id
+ }
+
+ pub(crate) fn text_anchor(&self) -> text::Anchor {
+ self.text_anchor
+ }
+
+ pub(crate) fn with_diff_base_anchor(mut self, diff_base_anchor: text::Anchor) -> Self {
+ self.diff_base_anchor = Some(diff_base_anchor);
+ self
+ }
+
+ pub(crate) fn cmp(&self, other: &Self, snapshot: &MultiBufferSnapshot) -> Ordering {
+ let Some(self_path_key) = snapshot.path_keys_by_index.get(&self.path) else {
+ panic!("anchor's path was never added to multibuffer")
+ };
+ let Some(other_path_key) = snapshot.path_keys_by_index.get(&other.path) else {
+ panic!("anchor's path was never added to multibuffer")
+ };
+
+ if self_path_key.cmp(other_path_key) != Ordering::Equal {
+ return self_path_key.cmp(other_path_key);
+ }
+
+ // in the case that you removed the buffer containing self,
+ // and added the buffer containing other with the same path key
+ // (ordering is arbitrary but consistent)
+ if self.text_anchor.buffer_id != other.text_anchor.buffer_id {
+ return self.text_anchor.buffer_id.cmp(&other.text_anchor.buffer_id);
+ }
+
+ let Some(buffer) = snapshot.buffer_for_path(&self_path_key) else {
+ return Ordering::Equal;
+ };
+ // Comparing two anchors into buffer A that formerly existed at path P,
+ // when path P has since been reused for a different buffer B
+ if buffer.remote_id() != self.text_anchor.buffer_id {
+ return Ordering::Equal;
+ };
+ assert_eq!(self.text_anchor.buffer_id, buffer.remote_id());
+ let text_cmp = self.text_anchor().cmp(&other.text_anchor(), buffer);
+ if text_cmp != Ordering::Equal {
+ return text_cmp;
+ }
+
+ if (self.diff_base_anchor.is_some() || other.diff_base_anchor.is_some())
+ && let Some(base_text) = find_diff_state(&snapshot.diffs, self.text_anchor.buffer_id)
+ .map(|diff| diff.base_text())
+ {
+ let self_anchor = self.diff_base_anchor.filter(|a| a.is_valid(base_text));
+ let other_anchor = other.diff_base_anchor.filter(|a| a.is_valid(base_text));
+ return match (self_anchor, other_anchor) {
+ (Some(a), Some(b)) => a.cmp(&b, base_text),
+ (Some(_), None) => match other.text_anchor().bias {
+ Bias::Left => Ordering::Greater,
+ Bias::Right => Ordering::Less,
+ },
+ (None, Some(_)) => match self.text_anchor().bias {
+ Bias::Left => Ordering::Less,
+ Bias::Right => Ordering::Greater,
+ },
+ (None, None) => Ordering::Equal,
+ };
}
+
+ Ordering::Equal
}
- pub fn in_buffer(excerpt_id: ExcerptId, text_anchor: text::Anchor) -> Self {
- Self {
- excerpt_id,
- text_anchor,
- diff_base_anchor: None,
+ fn bias_left(&self, snapshot: &MultiBufferSnapshot) -> Self {
+ if self.text_anchor.bias == Bias::Left {
+ return *self;
+ }
+ let Some(buffer) = snapshot.buffer_for_id(self.text_anchor.buffer_id) else {
+ return *self;
+ };
+ let text_anchor = self.text_anchor().bias_left(&buffer);
+ let ret = Self::in_buffer(self.path, text_anchor);
+ if let Some(diff_base_anchor) = self.diff_base_anchor {
+ if let Some(diff) = find_diff_state(&snapshot.diffs, self.text_anchor.buffer_id)
+ && diff_base_anchor.is_valid(&diff.base_text())
+ {
+ ret.with_diff_base_anchor(diff_base_anchor.bias_left(diff.base_text()))
+ } else {
+ ret.with_diff_base_anchor(diff_base_anchor)
+ }
+ } else {
+ ret
}
}
- pub fn range_in_buffer(excerpt_id: ExcerptId, range: Range<text::Anchor>) -> Range<Self> {
- Self::in_buffer(excerpt_id, range.start)..Self::in_buffer(excerpt_id, range.end)
+ fn bias_right(&self, snapshot: &MultiBufferSnapshot) -> Self {
+ if self.text_anchor.bias == Bias::Right {
+ return *self;
+ }
+ let Some(buffer) = snapshot.buffer_for_id(self.text_anchor.buffer_id) else {
+ return *self;
+ };
+ let text_anchor = self.text_anchor().bias_right(&buffer);
+ let ret = Self::in_buffer(self.path, text_anchor);
+ if let Some(diff_base_anchor) = self.diff_base_anchor {
+ if let Some(diff) = find_diff_state(&snapshot.diffs, self.text_anchor.buffer_id)
+ && diff_base_anchor.is_valid(&diff.base_text())
+ {
+ ret.with_diff_base_anchor(diff_base_anchor.bias_right(diff.base_text()))
+ } else {
+ ret.with_diff_base_anchor(diff_base_anchor)
+ }
+ } else {
+ ret
+ }
}
- pub fn min() -> Self {
- Self {
- excerpt_id: ExcerptId::min(),
- text_anchor: text::Anchor::MIN,
+ #[track_caller]
+ pub(crate) fn in_buffer(path: PathKeyIndex, text_anchor: text::Anchor) -> Self {
+ ExcerptAnchor {
+ path,
diff_base_anchor: None,
+ text_anchor,
}
}
- pub fn max() -> Self {
- Self {
- excerpt_id: ExcerptId::max(),
- text_anchor: text::Anchor::MAX,
- diff_base_anchor: None,
+ fn is_valid(&self, snapshot: &MultiBufferSnapshot) -> bool {
+ let Some(target) = self.try_seek_target(snapshot) else {
+ return false;
+ };
+ let Some(buffer_snapshot) = snapshot.buffer_for_id(self.buffer_id()) else {
+ return false;
+ };
+ // Early check to avoid invalid comparisons when seeking
+ if !buffer_snapshot.can_resolve(&self.text_anchor) {
+ return false;
}
+ let mut cursor = snapshot.excerpts.cursor::<ExcerptSummary>(());
+ cursor.seek(&target, Bias::Left);
+ let Some(excerpt) = cursor.item() else {
+ return false;
+ };
+ let is_valid = self.text_anchor == excerpt.range.context.start
+ || self.text_anchor == excerpt.range.context.end
+ || self.text_anchor.is_valid(&buffer_snapshot);
+ is_valid
+ && excerpt
+ .range
+ .context
+ .start
+ .cmp(&self.text_anchor(), buffer_snapshot)
+ .is_le()
+ && excerpt
+ .range
+ .context
+ .end
+ .cmp(&self.text_anchor(), buffer_snapshot)
+ .is_ge()
+ }
+
+ pub(crate) fn seek_target(&self, snapshot: &MultiBufferSnapshot) -> AnchorSeekTarget {
+ self.try_seek_target(snapshot)
+ .expect("anchor is from different multi-buffer")
+ }
+
+ pub(crate) fn try_seek_target(
+ &self,
+ snapshot: &MultiBufferSnapshot,
+ ) -> Option<AnchorSeekTarget> {
+ let path_key = snapshot.try_path_for_anchor(*self)?;
+ let buffer = snapshot.buffer_for_path(&path_key).cloned();
+ Some(AnchorSeekTarget::Excerpt {
+ path_key,
+ anchor: *self,
+ snapshot: buffer,
+ })
+ }
+}
+
+impl ToOffset for ExcerptAnchor {
+ fn to_offset(&self, snapshot: &MultiBufferSnapshot) -> MultiBufferOffset {
+ Anchor::from(*self).to_offset(snapshot)
+ }
+
+ fn to_offset_utf16(&self, snapshot: &MultiBufferSnapshot) -> MultiBufferOffsetUtf16 {
+ Anchor::from(*self).to_offset_utf16(snapshot)
+ }
+}
+
+impl ToPoint for ExcerptAnchor {
+ fn to_point(&self, snapshot: &MultiBufferSnapshot) -> Point {
+ Anchor::from(*self).to_point(snapshot)
}
+ fn to_point_utf16(&self, snapshot: &MultiBufferSnapshot) -> rope::PointUtf16 {
+ Anchor::from(*self).to_point_utf16(snapshot)
+ }
+}
+
+impl Anchor {
pub fn is_min(&self) -> bool {
- self.excerpt_id == ExcerptId::min()
- && self.text_anchor.is_min()
- && self.diff_base_anchor.is_none()
+ matches!(self, Self::Min)
}
pub fn is_max(&self) -> bool {
- self.excerpt_id == ExcerptId::max()
- && self.text_anchor.is_max()
- && self.diff_base_anchor.is_none()
+ matches!(self, Self::Max)
}
- pub fn cmp(&self, other: &Anchor, snapshot: &MultiBufferSnapshot) -> Ordering {
- if self == other {
- return Ordering::Equal;
- }
+ pub(crate) fn in_buffer(path: PathKeyIndex, text_anchor: text::Anchor) -> Self {
+ Self::Excerpt(ExcerptAnchor::in_buffer(path, text_anchor))
+ }
- let self_excerpt_id = snapshot.latest_excerpt_id(self.excerpt_id);
- let other_excerpt_id = snapshot.latest_excerpt_id(other.excerpt_id);
+ pub(crate) fn range_in_buffer(path: PathKeyIndex, range: Range<text::Anchor>) -> Range<Self> {
+ Self::in_buffer(path, range.start)..Self::in_buffer(path, range.end)
+ }
- let excerpt_id_cmp = self_excerpt_id.cmp(&other_excerpt_id, snapshot);
- if excerpt_id_cmp.is_ne() {
- return excerpt_id_cmp;
- }
- if self_excerpt_id == ExcerptId::max()
- && self.text_anchor.is_max()
- && self.text_anchor.is_max()
- && self.diff_base_anchor.is_none()
- && other.diff_base_anchor.is_none()
- {
- return Ordering::Equal;
- }
- if let Some(excerpt) = snapshot.excerpt(self_excerpt_id) {
- let text_cmp = self.text_anchor.cmp(&other.text_anchor, &excerpt.buffer);
- if text_cmp.is_ne() {
- return text_cmp;
- }
- if (self.diff_base_anchor.is_some() || other.diff_base_anchor.is_some())
- && let Some(base_text) = snapshot
- .diff_state(excerpt.buffer_id)
- .map(|diff| diff.base_text())
- {
- let self_anchor = self.diff_base_anchor.filter(|a| a.is_valid(base_text));
- let other_anchor = other.diff_base_anchor.filter(|a| a.is_valid(base_text));
- return match (self_anchor, other_anchor) {
- (Some(a), Some(b)) => a.cmp(&b, base_text),
- (Some(_), None) => match other.text_anchor.bias {
- Bias::Left => Ordering::Greater,
- Bias::Right => Ordering::Less,
- },
- (None, Some(_)) => match self.text_anchor.bias {
- Bias::Left => Ordering::Less,
- Bias::Right => Ordering::Greater,
- },
- (None, None) => Ordering::Equal,
- };
+ pub fn cmp(&self, other: &Anchor, snapshot: &MultiBufferSnapshot) -> Ordering {
+ match (self, other) {
+ (Anchor::Min, Anchor::Min) => return Ordering::Equal,
+ (Anchor::Max, Anchor::Max) => return Ordering::Equal,
+ (Anchor::Min, _) => return Ordering::Less,
+ (Anchor::Max, _) => return Ordering::Greater,
+ (_, Anchor::Max) => return Ordering::Less,
+ (_, Anchor::Min) => return Ordering::Greater,
+ (Anchor::Excerpt(self_excerpt_anchor), Anchor::Excerpt(other_excerpt_anchor)) => {
+ self_excerpt_anchor.cmp(other_excerpt_anchor, snapshot)
}
}
- Ordering::Equal
}
pub fn bias(&self) -> Bias {
- self.text_anchor.bias
+ match self {
+ Anchor::Min => Bias::Left,
+ Anchor::Max => Bias::Right,
+ Anchor::Excerpt(anchor) => anchor.text_anchor.bias,
+ }
}
pub fn bias_left(&self, snapshot: &MultiBufferSnapshot) -> Anchor {
- if self.text_anchor.bias != Bias::Left
- && let Some(excerpt) = snapshot.excerpt(self.excerpt_id)
- {
- return Self {
- excerpt_id: excerpt.id,
- text_anchor: self.text_anchor.bias_left(&excerpt.buffer),
- diff_base_anchor: self.diff_base_anchor.map(|a| {
- if let Some(base_text) = snapshot
- .diff_state(excerpt.buffer_id)
- .map(|diff| diff.base_text())
- && a.is_valid(&base_text)
- {
- return a.bias_left(base_text);
- }
- a
- }),
- };
+ match self {
+ Anchor::Min => *self,
+ Anchor::Max => snapshot.anchor_before(snapshot.max_point()),
+ Anchor::Excerpt(anchor) => Anchor::Excerpt(anchor.bias_left(snapshot)),
}
- *self
}
pub fn bias_right(&self, snapshot: &MultiBufferSnapshot) -> Anchor {
- if self.text_anchor.bias != Bias::Right
- && let Some(excerpt) = snapshot.excerpt(self.excerpt_id)
- {
- return Self {
- excerpt_id: excerpt.id,
- text_anchor: self.text_anchor.bias_right(&excerpt.buffer),
- diff_base_anchor: self.diff_base_anchor.map(|a| {
- if let Some(base_text) = snapshot
- .diff_state(excerpt.buffer_id)
- .map(|diff| diff.base_text())
- && a.is_valid(&base_text)
- {
- return a.bias_right(base_text);
- }
- a
- }),
- };
+ match self {
+ Anchor::Max => *self,
+ Anchor::Min => snapshot.anchor_after(Point::zero()),
+ Anchor::Excerpt(anchor) => Anchor::Excerpt(anchor.bias_right(snapshot)),
}
- *self
}
pub fn summary<D>(&self, snapshot: &MultiBufferSnapshot) -> D
@@ -203,16 +342,111 @@ impl Anchor {
}
pub fn is_valid(&self, snapshot: &MultiBufferSnapshot) -> bool {
- if self.is_min() || self.is_max() {
- true
- } else if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) {
- (self.text_anchor == excerpt.range.context.start
- || self.text_anchor == excerpt.range.context.end
- || self.text_anchor.is_valid(&excerpt.buffer))
- && excerpt.contains(self)
- } else {
- false
+ match self {
+ Anchor::Min | Anchor::Max => true,
+ Anchor::Excerpt(excerpt_anchor) => excerpt_anchor.is_valid(snapshot),
+ }
+ }
+
+ fn to_excerpt_anchor(&self, snapshot: &MultiBufferSnapshot) -> Option<ExcerptAnchor> {
+ match self {
+ Anchor::Min => {
+ let excerpt = snapshot.excerpts.first()?;
+
+ Some(ExcerptAnchor {
+ text_anchor: excerpt.range.context.start,
+ path: excerpt.path_key_index,
+ diff_base_anchor: None,
+ })
+ }
+ Anchor::Excerpt(excerpt_anchor) => Some(*excerpt_anchor),
+ Anchor::Max => {
+ let excerpt = snapshot.excerpts.last()?;
+
+ Some(ExcerptAnchor {
+ text_anchor: excerpt.range.context.end,
+ path: excerpt.path_key_index,
+ diff_base_anchor: None,
+ })
+ }
+ }
+ }
+
+ pub(crate) fn seek_target(&self, snapshot: &MultiBufferSnapshot) -> AnchorSeekTarget {
+ let Some(excerpt_anchor) = self.to_excerpt_anchor(snapshot) else {
+ return AnchorSeekTarget::Empty;
+ };
+
+ excerpt_anchor.seek_target(snapshot)
+ }
+
+ pub(crate) fn excerpt_anchor(&self) -> Option<ExcerptAnchor> {
+ match self {
+ Anchor::Min | Anchor::Max => None,
+ Anchor::Excerpt(excerpt_anchor) => Some(*excerpt_anchor),
+ }
+ }
+
+ pub(crate) fn text_anchor(&self) -> Option<text::Anchor> {
+ match self {
+ Anchor::Min | Anchor::Max => None,
+ Anchor::Excerpt(excerpt_anchor) => Some(excerpt_anchor.text_anchor()),
+ }
+ }
+
+ pub fn opaque_id(&self) -> Option<[u8; 20]> {
+ self.text_anchor().map(|a| a.opaque_id())
+ }
+
+ /// Note: anchor_to_buffer_anchor is probably what you want
+ pub fn raw_text_anchor(&self) -> Option<text::Anchor> {
+ match self {
+ Anchor::Min | Anchor::Max => None,
+ Anchor::Excerpt(excerpt_anchor) => Some(excerpt_anchor.text_anchor),
+ }
+ }
+
+ pub(crate) fn try_seek_target(
+ &self,
+ snapshot: &MultiBufferSnapshot,
+ ) -> Option<AnchorSeekTarget> {
+ let Some(excerpt_anchor) = self.to_excerpt_anchor(snapshot) else {
+ return Some(AnchorSeekTarget::Empty);
+ };
+ excerpt_anchor.try_seek_target(snapshot)
+ }
+
+ /// Returns the text anchor for this anchor.
+ /// Panics if the anchor is from a different buffer.
+ pub fn text_anchor_in(&self, buffer: &BufferSnapshot) -> text::Anchor {
+ match self {
+ Anchor::Min => text::Anchor::min_for_buffer(buffer.remote_id()),
+ Anchor::Excerpt(excerpt_anchor) => {
+ let text_anchor = excerpt_anchor.text_anchor;
+ assert_eq!(text_anchor.buffer_id, buffer.remote_id());
+ text_anchor
+ }
+ Anchor::Max => text::Anchor::max_for_buffer(buffer.remote_id()),
+ }
+ }
+
+ pub fn diff_base_anchor(&self) -> Option<text::Anchor> {
+ self.excerpt_anchor()?.diff_base_anchor
+ }
+
+ #[cfg(any(test, feature = "test-support"))]
+ pub fn expect_text_anchor(&self) -> text::Anchor {
+ self.excerpt_anchor().unwrap().text_anchor
+ }
+
+ pub fn with_diff_base_anchor(mut self, diff_base_anchor: text::Anchor) -> Self {
+ match &mut self {
+ Anchor::Min | Anchor::Max => {}
+ Anchor::Excerpt(excerpt_anchor) => {
+ excerpt_anchor.diff_base_anchor = Some(diff_base_anchor);
+ }
}
+ self
}
}
@@ -8,6 +8,7 @@ use self::transaction::History;
pub use anchor::{Anchor, AnchorRangeExt};
+use anchor::{AnchorSeekTarget, ExcerptAnchor};
use anyhow::{Result, anyhow};
use buffer_diff::{
BufferDiff, BufferDiffEvent, BufferDiffSnapshot, DiffChanged, DiffHunkSecondaryStatus,
@@ -15,14 +16,14 @@ use buffer_diff::{
};
use clock::ReplicaId;
use collections::{BTreeMap, Bound, HashMap, HashSet};
-use gpui::{App, Context, Entity, EntityId, EventEmitter};
+use gpui::{App, Context, Entity, EventEmitter};
use itertools::Itertools;
use language::{
- AutoindentMode, BracketMatch, Buffer, BufferChunks, BufferRow, BufferSnapshot, Capability,
- CharClassifier, CharKind, CharScopeContext, Chunk, CursorShape, DiagnosticEntryRef, File,
- IndentGuideSettings, IndentSize, Language, LanguageScope, OffsetRangeExt, OffsetUtf16, Outline,
- OutlineItem, Point, PointUtf16, Selection, TextDimension, TextObject, ToOffset as _,
- ToPoint as _, TransactionId, TreeSitterOptions, Unclipped,
+ AutoindentMode, Buffer, BufferChunks, BufferRow, BufferSnapshot, Capability, CharClassifier,
+ CharKind, CharScopeContext, Chunk, CursorShape, DiagnosticEntryRef, File, IndentGuideSettings,
+ IndentSize, Language, LanguageScope, OffsetRangeExt, OffsetUtf16, Outline, OutlineItem, Point,
+ PointUtf16, Selection, TextDimension, TextObject, ToOffset as _, ToPoint as _, TransactionId,
+ TreeSitterOptions, Unclipped,
language_settings::{AllLanguageSettings, LanguageSettings},
};
@@ -37,7 +38,8 @@ use std::{
any::type_name,
borrow::Cow,
cell::{Cell, OnceCell, Ref, RefCell},
- cmp, fmt,
+ cmp::{self, Ordering},
+ fmt,
future::Future,
io,
iter::{self, FromIterator},
@@ -51,15 +53,13 @@ use std::{
use sum_tree::{Bias, Cursor, Dimension, Dimensions, SumTree, TreeMap};
use text::{
BufferId, Edit, LineIndent, TextSummary,
- locator::Locator,
subscription::{Subscription, Topic},
};
use theme::SyntaxTheme;
use unicode_segmentation::UnicodeSegmentation;
-use util::post_inc;
use ztracing::instrument;
-pub use self::path_key::{PathExcerptInsertResult, PathKey};
+pub use self::path_key::PathKey;
pub static EXCERPT_CONTEXT_LINES: OnceLock<fn(&App) -> u32> = OnceLock::new();
@@ -67,9 +67,6 @@ pub fn excerpt_context_lines(cx: &App) -> u32 {
EXCERPT_CONTEXT_LINES.get().map(|f| f(cx)).unwrap_or(2)
}
-#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
-pub struct ExcerptId(u32);
-
/// One or more [`Buffers`](Buffer) being edited in a single view.
///
/// See <https://zed.dev/features#multi-buffers>
@@ -79,10 +76,6 @@ pub struct MultiBuffer {
snapshot: RefCell<MultiBufferSnapshot>,
/// Contains the state of the buffers being edited
buffers: BTreeMap<BufferId, BufferState>,
- /// Mapping from path keys to their excerpts.
- excerpts_by_path: BTreeMap<PathKey, Vec<ExcerptId>>,
- /// Mapping from excerpt IDs to their path key.
- paths_by_excerpt: HashMap<ExcerptId, PathKey>,
/// Mapping from buffer IDs to their diff states
diffs: HashMap<BufferId, DiffState>,
subscriptions: Topic<MultiBufferOffset>,
@@ -98,24 +91,20 @@ pub struct MultiBuffer {
buffer_changed_since_sync: Rc<Cell<bool>>,
}
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+struct PathKeyIndex(u64);
+
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Event {
- ExcerptsAdded {
+ BufferRangesUpdated {
buffer: Entity<Buffer>,
- predecessor: ExcerptId,
- excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
+ path_key: PathKey,
+ ranges: Vec<ExcerptRange<text::Anchor>>,
},
- ExcerptsRemoved {
- ids: Vec<ExcerptId>,
- /// Contains only buffer IDs for which all excerpts have been removed.
- /// Buffers that still have remaining excerpts are never included.
+ BuffersRemoved {
removed_buffer_ids: Vec<BufferId>,
},
- ExcerptsExpanded {
- ids: Vec<ExcerptId>,
- },
- ExcerptsEdited {
- excerpt_ids: Vec<ExcerptId>,
+ BuffersEdited {
buffer_ids: Vec<BufferId>,
},
DiffHunksToggled,
@@ -145,14 +134,14 @@ pub struct MultiBufferDiffHunk {
pub buffer_id: BufferId,
/// The range of the underlying buffer that this hunk corresponds to.
pub buffer_range: Range<text::Anchor>,
- /// The excerpt that contains the diff hunk.
- pub excerpt_id: ExcerptId,
/// The range within the buffer's diff base that this hunk corresponds to.
pub diff_base_byte_range: Range<BufferOffset>,
/// The status of this hunk (added/modified/deleted and secondary status).
pub status: DiffHunkStatus,
/// The word diffs for this hunk.
pub word_diffs: Vec<Range<MultiBufferOffset>>,
+ pub excerpt_range: ExcerptRange<text::Anchor>,
+ pub multi_buffer_range: Range<Anchor>,
}
impl MultiBufferDiffHunk {
@@ -165,17 +154,12 @@ impl MultiBufferDiffHunk {
&& self.buffer_range.start.is_min()
&& self.buffer_range.end.is_max()
}
-
- pub fn multi_buffer_range(&self) -> Range<Anchor> {
- let start = Anchor::in_buffer(self.excerpt_id, self.buffer_range.start);
- let end = Anchor::in_buffer(self.excerpt_id, self.buffer_range.end);
- start..end
- }
}
pub type MultiBufferPoint = Point;
+/// ExcerptOffset is offset into the non-deleted text of the multibuffer
type ExcerptOffset = ExcerptDimension<MultiBufferOffset>;
-type ExcerptPoint = ExcerptDimension<Point>;
+/// ExcerptOffset is based on the non-deleted text of the multibuffer
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq, Hash, serde::Deserialize)]
#[serde(transparent)]
@@ -518,10 +502,6 @@ pub trait ToPoint: 'static + fmt::Debug {
struct BufferState {
buffer: Entity<Buffer>,
- last_version: RefCell<clock::Global>,
- last_non_text_state_update_count: Cell<usize>,
- // Note, any changes to this field value require updating snapshot.buffer_locators as well
- excerpts: Vec<Locator>,
_subscriptions: [gpui::Subscription; 2],
}
@@ -694,15 +674,31 @@ impl DiffState {
}
}
+#[derive(Clone)]
+struct BufferStateSnapshot {
+ path_key: PathKey,
+ path_key_index: PathKeyIndex,
+ buffer_snapshot: BufferSnapshot,
+}
+
+impl fmt::Debug for BufferStateSnapshot {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_struct("BufferStateSnapshot")
+ .field("path_key", &self.path_key)
+ .field("buffer_id", &self.buffer_snapshot.remote_id())
+ .finish()
+ }
+}
+
/// The contents of a [`MultiBuffer`] at a single point in time.
#[derive(Clone, Default)]
pub struct MultiBufferSnapshot {
excerpts: SumTree<Excerpt>,
- buffer_locators: TreeMap<BufferId, Arc<[Locator]>>,
+ buffers: TreeMap<BufferId, BufferStateSnapshot>,
+ path_keys_by_index: TreeMap<PathKeyIndex, PathKey>,
+ indices_by_path_key: TreeMap<PathKey, PathKeyIndex>,
diffs: SumTree<DiffStateSnapshot>,
diff_transforms: SumTree<DiffTransform>,
- excerpt_ids: SumTree<ExcerptIdMapping>,
- replaced_excerpts: Arc<HashMap<ExcerptId, ExcerptId>>,
non_text_state_update_count: usize,
edit_count: usize,
is_dirty: bool,
@@ -717,24 +713,12 @@ pub struct MultiBufferSnapshot {
show_headers: bool,
}
-// follower: None
-// - BufferContent(Some)
-// - BufferContent(None)
-// - DeletedHunk
-//
-// follower: Some
-// - BufferContent(Some)
-// - BufferContent(None)
-
#[derive(Debug, Clone)]
enum DiffTransform {
- // RealText
BufferContent {
summary: MBTextSummary,
- // modified_hunk_info
inserted_hunk_info: Option<DiffTransformHunkInfo>,
},
- // ExpandedHunkText
DeletedHunk {
summary: TextSummary,
buffer_id: BufferId,
@@ -746,52 +730,71 @@ enum DiffTransform {
#[derive(Clone, Copy, Debug)]
struct DiffTransformHunkInfo {
- excerpt_id: ExcerptId,
+ buffer_id: BufferId,
hunk_start_anchor: text::Anchor,
hunk_secondary_status: DiffHunkSecondaryStatus,
is_logically_deleted: bool,
+ excerpt_end: ExcerptAnchor,
}
impl Eq for DiffTransformHunkInfo {}
impl PartialEq for DiffTransformHunkInfo {
fn eq(&self, other: &DiffTransformHunkInfo) -> bool {
- self.excerpt_id == other.excerpt_id && self.hunk_start_anchor == other.hunk_start_anchor
+ self.buffer_id == other.buffer_id && self.hunk_start_anchor == other.hunk_start_anchor
}
}
impl std::hash::Hash for DiffTransformHunkInfo {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
- self.excerpt_id.hash(state);
+ self.buffer_id.hash(state);
self.hunk_start_anchor.hash(state);
}
}
#[derive(Clone)]
-pub struct ExcerptInfo {
- pub id: ExcerptId,
- pub buffer: Arc<BufferSnapshot>,
- pub buffer_id: BufferId,
+pub struct ExcerptBoundaryInfo {
+ pub start_anchor: Anchor,
pub range: ExcerptRange<text::Anchor>,
pub end_row: MultiBufferRow,
}
-impl std::fmt::Debug for ExcerptInfo {
+impl ExcerptBoundaryInfo {
+ pub fn start_text_anchor(&self) -> text::Anchor {
+ self.range.context.start
+ }
+ pub fn buffer_id(&self) -> BufferId {
+ self.start_text_anchor().buffer_id
+ }
+ pub fn buffer<'a>(&self, snapshot: &'a MultiBufferSnapshot) -> &'a BufferSnapshot {
+ snapshot
+ .buffer_for_id(self.buffer_id())
+ .expect("buffer snapshot not found for excerpt boundary")
+ }
+}
+
+impl std::fmt::Debug for ExcerptBoundaryInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct(type_name::<Self>())
- .field("id", &self.id)
- .field("buffer_id", &self.buffer_id)
- .field("path", &self.buffer.file().map(|f| f.path()))
+ .field("buffer_id", &self.buffer_id())
.field("range", &self.range)
.finish()
}
}
+impl PartialEq for ExcerptBoundaryInfo {
+ fn eq(&self, other: &Self) -> bool {
+ self.start_anchor == other.start_anchor && self.range == other.range
+ }
+}
+
+impl Eq for ExcerptBoundaryInfo {}
+
/// A boundary between `Excerpt`s in a [`MultiBuffer`]
#[derive(Debug)]
pub struct ExcerptBoundary {
- pub prev: Option<ExcerptInfo>,
- pub next: ExcerptInfo,
+ pub prev: Option<ExcerptBoundaryInfo>,
+ pub next: ExcerptBoundaryInfo,
/// The row in the `MultiBuffer` where the boundary is located
pub row: MultiBufferRow,
}
@@ -800,7 +803,7 @@ impl ExcerptBoundary {
pub fn starts_new_buffer(&self) -> bool {
match (self.prev.as_ref(), &self.next) {
(None, _) => true,
- (Some(prev), next) => prev.buffer_id != next.buffer_id,
+ (Some(prev), next) => prev.buffer_id() != next.buffer_id(),
}
}
}
@@ -808,7 +811,7 @@ impl ExcerptBoundary {
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct ExpandInfo {
pub direction: ExpandExcerptDirection,
- pub excerpt_id: ExcerptId,
+ pub start_anchor: Anchor,
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
@@ -822,45 +825,20 @@ pub struct RowInfo {
}
/// A slice into a [`Buffer`] that is being edited in a [`MultiBuffer`].
-#[derive(Clone)]
-struct Excerpt {
- /// The unique identifier for this excerpt
- id: ExcerptId,
+#[derive(Clone, Debug)]
+pub(crate) struct Excerpt {
/// The location of the excerpt in the [`MultiBuffer`]
- locator: Locator,
- /// The buffer being excerpted
- buffer_id: BufferId,
- /// A snapshot of the buffer being excerpted
- buffer: Arc<BufferSnapshot>,
+ pub(crate) path_key: PathKey,
+ pub(crate) path_key_index: PathKeyIndex,
+ pub(crate) buffer_id: BufferId,
/// The range of the buffer to be shown in the excerpt
- range: ExcerptRange<text::Anchor>,
+ pub(crate) range: ExcerptRange<text::Anchor>,
+
/// The last row in the excerpted slice of the buffer
- max_buffer_row: BufferRow,
+ pub(crate) max_buffer_row: BufferRow,
/// A summary of the text in the excerpt
- text_summary: TextSummary,
- has_trailing_newline: bool,
-}
-
-/// A public view into an `Excerpt` in a [`MultiBuffer`].
-///
-/// Contains methods for getting the [`Buffer`] of the excerpt,
-/// as well as mapping offsets to/from buffer and multibuffer coordinates.
-#[derive(Clone)]
-pub struct MultiBufferExcerpt<'a> {
- excerpt: &'a Excerpt,
- diff_transforms:
- sum_tree::Cursor<'a, 'static, DiffTransform, DiffTransforms<MultiBufferOffset>>,
- /// The offset in the multibuffer considering diff transforms.
- offset: MultiBufferOffset,
- /// The offset in the multibuffer without diff transforms.
- excerpt_offset: ExcerptOffset,
- buffer_offset: BufferOffset,
-}
-
-#[derive(Clone, Debug)]
-struct ExcerptIdMapping {
- id: ExcerptId,
- locator: Locator,
+ pub(crate) text_summary: TextSummary,
+ pub(crate) has_trailing_newline: bool,
}
/// A range of text from a single [`Buffer`], to be shown as an `Excerpt`.
@@ -883,16 +861,37 @@ impl<T: Clone> ExcerptRange<T> {
}
}
-#[derive(Clone, Debug, Default)]
+impl ExcerptRange<text::Anchor> {
+ pub fn contains(&self, t: &text::Anchor, snapshot: &BufferSnapshot) -> bool {
+ self.context.start.cmp(t, snapshot).is_le() && self.context.end.cmp(t, snapshot).is_ge()
+ }
+}
+
+#[derive(Clone, Debug)]
pub struct ExcerptSummary {
- excerpt_id: ExcerptId,
- /// The location of the last [`Excerpt`] being summarized
- excerpt_locator: Locator,
+ path_key: PathKey,
+ max_anchor: Option<text::Anchor>,
widest_line_number: u32,
text: MBTextSummary,
count: usize,
}
+impl ExcerptSummary {
+ pub fn min() -> Self {
+ ExcerptSummary {
+ path_key: PathKey::min(),
+ max_anchor: None,
+ widest_line_number: 0,
+ text: MBTextSummary::default(),
+ count: 0,
+ }
+ }
+
+ fn len(&self) -> ExcerptOffset {
+ ExcerptDimension(self.text.len)
+ }
+}
+
#[derive(Debug, Clone)]
pub struct DiffTransformSummary {
input: MBTextSummary,
@@ -1068,13 +1067,13 @@ pub struct MultiBufferChunks<'a> {
excerpts: Cursor<'a, 'static, Excerpt, ExcerptOffset>,
diff_transforms:
Cursor<'a, 'static, DiffTransform, Dimensions<MultiBufferOffset, ExcerptOffset>>,
- diffs: &'a SumTree<DiffStateSnapshot>,
diff_base_chunks: Option<(BufferId, BufferChunks<'a>)>,
buffer_chunk: Option<Chunk<'a>>,
range: Range<MultiBufferOffset>,
excerpt_offset_range: Range<ExcerptOffset>,
excerpt_chunks: Option<ExcerptChunks<'a>>,
language_aware: bool,
+ snapshot: &'a MultiBufferSnapshot,
}
pub struct ReversedMultiBufferChunks<'a> {
@@ -1128,8 +1127,8 @@ impl<'a, MBD: MultiBufferDimension> Dimension<'a, DiffTransformSummary> for Diff
struct MultiBufferCursor<'a, MBD, BD> {
excerpts: Cursor<'a, 'static, Excerpt, ExcerptDimension<MBD>>,
diff_transforms: Cursor<'a, 'static, DiffTransform, DiffTransforms<MBD>>,
- diffs: &'a SumTree<DiffStateSnapshot>,
cached_region: OnceCell<Option<MultiBufferRegion<'a, MBD, BD>>>,
+ snapshot: &'a MultiBufferSnapshot,
}
#[derive(Clone)]
@@ -1144,8 +1143,8 @@ struct MultiBufferRegion<'a, MBD, BD> {
}
struct ExcerptChunks<'a> {
- excerpt_id: ExcerptId,
content_chunks: BufferChunks<'a>,
+ end: ExcerptAnchor,
has_footer: bool,
}
@@ -1155,7 +1154,6 @@ struct BufferEdit {
new_text: Arc<str>,
is_insertion: bool,
original_indent_column: Option<u32>,
- excerpt_id: ExcerptId,
}
#[derive(Clone, Copy, Debug, PartialEq)]
@@ -1258,8 +1256,6 @@ impl MultiBuffer {
singleton: false,
capability,
title: None,
- excerpts_by_path: Default::default(),
- paths_by_excerpt: Default::default(),
buffer_changed_since_sync: Default::default(),
history: History::default(),
}
@@ -1276,11 +1272,6 @@ impl MultiBuffer {
*buffer_id,
BufferState {
buffer: buffer_state.buffer.clone(),
- last_version: buffer_state.last_version.clone(),
- last_non_text_state_update_count: buffer_state
- .last_non_text_state_update_count
- .clone(),
- excerpts: buffer_state.excerpts.clone(),
_subscriptions: [
new_cx.observe(&buffer_state.buffer, |_, _, cx| cx.notify()),
new_cx.subscribe(&buffer_state.buffer, Self::on_buffer_event),
@@ -1295,8 +1286,6 @@ impl MultiBuffer {
Self {
snapshot: RefCell::new(self.snapshot.borrow().clone()),
buffers,
- excerpts_by_path: Default::default(),
- paths_by_excerpt: Default::default(),
diffs: diff_bases,
subscriptions: Default::default(),
singleton: self.singleton,
@@ -1451,7 +1440,7 @@ impl MultiBuffer {
_ => Default::default(),
};
- let (buffer_edits, edited_excerpt_ids) = MultiBuffer::convert_edits_to_buffer_edits(
+ let buffer_edits = MultiBuffer::convert_edits_to_buffer_edits(
edits,
this.snapshot.get_mut(),
&original_indent_columns,
@@ -1472,14 +1461,12 @@ impl MultiBuffer {
mut new_text,
mut is_insertion,
original_indent_column,
- excerpt_id,
}) = edits.next()
{
while let Some(BufferEdit {
range: next_range,
is_insertion: next_is_insertion,
new_text: next_new_text,
- excerpt_id: next_excerpt_id,
..
}) = edits.peek()
{
@@ -1492,9 +1479,7 @@ impl MultiBuffer {
if should_coalesce {
range.end = cmp::max(next_range.end, range.end);
is_insertion |= *next_is_insertion;
- if excerpt_id == *next_excerpt_id {
- new_text = format!("{new_text}{next_new_text}").into();
- }
+ new_text = format!("{new_text}{next_new_text}").into();
edits.next();
} else {
break;
@@ -1542,10 +1527,7 @@ impl MultiBuffer {
})
}
- cx.emit(Event::ExcerptsEdited {
- excerpt_ids: edited_excerpt_ids,
- buffer_ids,
- });
+ cx.emit(Event::BuffersEdited { buffer_ids });
}
}
@@ -1553,9 +1535,8 @@ impl MultiBuffer {
edits: Vec<(Range<MultiBufferOffset>, Arc<str>)>,
snapshot: &MultiBufferSnapshot,
original_indent_columns: &[Option<u32>],
- ) -> (HashMap<BufferId, Vec<BufferEdit>>, Vec<ExcerptId>) {
+ ) -> HashMap<BufferId, Vec<BufferEdit>> {
let mut buffer_edits: HashMap<BufferId, Vec<BufferEdit>> = Default::default();
- let mut edited_excerpt_ids = Vec::new();
let mut cursor = snapshot.cursor::<MultiBufferOffset, BufferOffset>();
for (ix, (range, new_text)) in edits.into_iter().enumerate() {
let original_indent_column = original_indent_columns.get(ix).copied().flatten();
@@ -1600,11 +1581,10 @@ impl MultiBuffer {
let buffer_end =
(end_region.buffer_range.start + end_overshoot).min(end_region.buffer_range.end);
- if start_region.excerpt.id == end_region.excerpt.id {
+ if start_region.excerpt == end_region.excerpt {
if start_region.buffer.capability == Capability::ReadWrite
&& start_region.is_main_buffer
{
- edited_excerpt_ids.push(start_region.excerpt.id);
buffer_edits
.entry(start_region.buffer.remote_id())
.or_default()
@@ -1613,7 +1593,6 @@ impl MultiBuffer {
new_text,
is_insertion: true,
original_indent_column,
- excerpt_id: start_region.excerpt.id,
});
}
} else {
@@ -1622,7 +1601,6 @@ impl MultiBuffer {
if start_region.buffer.capability == Capability::ReadWrite
&& start_region.is_main_buffer
{
- edited_excerpt_ids.push(start_region.excerpt.id);
buffer_edits
.entry(start_region.buffer.remote_id())
.or_default()
@@ -1631,14 +1609,11 @@ impl MultiBuffer {
new_text: new_text.clone(),
is_insertion: true,
original_indent_column,
- excerpt_id: start_region.excerpt.id,
});
}
- let excerpt_id = end_region.excerpt.id;
if end_region.buffer.capability == Capability::ReadWrite
&& end_region.is_main_buffer
{
- edited_excerpt_ids.push(excerpt_id);
buffer_edits
.entry(end_region.buffer.remote_id())
.or_default()
@@ -1647,18 +1622,17 @@ impl MultiBuffer {
new_text: new_text.clone(),
is_insertion: false,
original_indent_column,
- excerpt_id,
});
}
+ let end_region_excerpt = end_region.excerpt.clone();
cursor.seek(&range.start);
cursor.next_excerpt();
while let Some(region) = cursor.region() {
- if region.excerpt.id == excerpt_id {
+ if region.excerpt == &end_region_excerpt {
break;
}
if region.buffer.capability == Capability::ReadWrite && region.is_main_buffer {
- edited_excerpt_ids.push(region.excerpt.id);
buffer_edits
.entry(region.buffer.remote_id())
.or_default()
@@ -1667,14 +1641,13 @@ impl MultiBuffer {
new_text: new_text.clone(),
is_insertion: false,
original_indent_column,
- excerpt_id: region.excerpt.id,
});
}
cursor.next_excerpt();
}
}
}
- (buffer_edits, edited_excerpt_ids)
+ buffer_edits
}
pub fn autoindent_ranges<I, S>(&mut self, ranges: I, cx: &mut Context<Self>)
@@ -1706,7 +1679,7 @@ impl MultiBuffer {
edits: Vec<(Range<MultiBufferOffset>, Arc<str>)>,
cx: &mut Context<MultiBuffer>,
) {
- let (buffer_edits, edited_excerpt_ids) =
+ let buffer_edits =
MultiBuffer::convert_edits_to_buffer_edits(edits, this.snapshot.get_mut(), &[]);
let mut buffer_ids = Vec::new();
@@ -1730,10 +1703,7 @@ impl MultiBuffer {
})
}
- cx.emit(Event::ExcerptsEdited {
- excerpt_ids: edited_excerpt_ids,
- buffer_ids,
- });
+ cx.emit(Event::BuffersEdited { buffer_ids });
}
}
@@ -1746,26 +1716,39 @@ impl MultiBuffer {
) {
let mut selections_by_buffer: HashMap<BufferId, Vec<Selection<text::Anchor>>> =
Default::default();
- let snapshot = self.read(cx);
- let mut cursor = snapshot.excerpts.cursor::<Option<&Locator>>(());
+ let snapshot = self.snapshot(cx);
+ let mut cursor = snapshot.excerpts.cursor::<ExcerptSummary>(());
for selection in selections {
- let start_locator = snapshot.excerpt_locator_for_id(selection.start.excerpt_id);
- let end_locator = snapshot.excerpt_locator_for_id(selection.end.excerpt_id);
+ let start = selection.start.seek_target(&snapshot);
- cursor.seek(&Some(start_locator), Bias::Left);
- while let Some(excerpt) = cursor.item()
- && excerpt.locator <= *end_locator
- {
- let mut start = excerpt.range.context.start;
- let mut end = excerpt.range.context.end;
- if excerpt.id == selection.start.excerpt_id {
- start = selection.start.text_anchor;
- }
- if excerpt.id == selection.end.excerpt_id {
- end = selection.end.text_anchor;
+ cursor.seek(&start, Bias::Left);
+ while let Some(excerpt) = cursor.item() {
+ let excerpt_start =
+ Anchor::in_buffer(excerpt.path_key_index, excerpt.range.context.start);
+ if excerpt_start.cmp(&selection.end, &snapshot).is_gt() {
+ break;
}
+ let buffer = excerpt.buffer_snapshot(&snapshot);
+ let start = *text::Anchor::max(
+ &excerpt.range.context.start,
+ &selection
+ .start
+ .excerpt_anchor()
+ .map(|excerpt_anchor| excerpt_anchor.text_anchor())
+ .unwrap_or(text::Anchor::min_for_buffer(excerpt.buffer_id)),
+ buffer,
+ );
+ let end = *text::Anchor::min(
+ &excerpt.range.context.end,
+ &selection
+ .end
+ .excerpt_anchor()
+ .map(|excerpt_anchor| excerpt_anchor.text_anchor())
+ .unwrap_or(text::Anchor::max_for_buffer(excerpt.buffer_id)),
+ buffer,
+ );
selections_by_buffer
- .entry(excerpt.buffer_id)
+ .entry(buffer.remote_id())
.or_default()
.push(Selection {
id: selection.id,
@@ -1787,25 +1770,9 @@ impl MultiBuffer {
}
}
- for (buffer_id, mut selections) in selections_by_buffer {
+ for (buffer_id, selections) in selections_by_buffer {
self.buffers[&buffer_id].buffer.update(cx, |buffer, cx| {
- selections.sort_unstable_by(|a, b| a.start.cmp(&b.start, buffer));
- let mut selections = selections.into_iter().peekable();
- let merged_selections = Arc::from_iter(iter::from_fn(|| {
- let mut selection = selections.next()?;
- while let Some(next_selection) = selections.peek() {
- if selection.end.cmp(&next_selection.start, buffer).is_ge() {
- let next_selection = selections.next().unwrap();
- if next_selection.end.cmp(&selection.end, buffer).is_ge() {
- selection.end = next_selection.end;
- }
- } else {
- break;
- }
- }
- Some(selection)
- }));
- buffer.set_active_selections(merged_selections, line_mode, cursor_shape, cx);
+ buffer.set_active_selections(selections.into(), line_mode, cursor_shape, cx);
});
}
}
@@ -1821,200 +1788,31 @@ impl MultiBuffer {
#[instrument(skip_all)]
fn merge_excerpt_ranges<'a>(
expanded_ranges: impl IntoIterator<Item = &'a ExcerptRange<Point>> + 'a,
- ) -> (Vec<ExcerptRange<Point>>, Vec<usize>) {
+ ) -> Vec<ExcerptRange<Point>> {
+ let mut sorted: Vec<_> = expanded_ranges.into_iter().collect();
+ sorted.sort_by_key(|range| range.context.start);
let mut merged_ranges: Vec<ExcerptRange<Point>> = Vec::new();
- let mut counts: Vec<usize> = Vec::new();
- for range in expanded_ranges {
+ for range in sorted {
if let Some(last_range) = merged_ranges.last_mut() {
- assert!(
- last_range.context.start <= range.context.start,
- "ranges must be sorted: {last_range:?} <= {range:?}"
- );
if last_range.context.end >= range.context.start
|| last_range.context.end.row + 1 == range.context.start.row
{
last_range.context.end = range.context.end.max(last_range.context.end);
- *counts.last_mut().unwrap() += 1;
continue;
}
}
merged_ranges.push(range.clone());
- counts.push(1);
- }
- (merged_ranges, counts)
- }
-
- pub fn insert_excerpts_after<O>(
- &mut self,
- prev_excerpt_id: ExcerptId,
- buffer: Entity<Buffer>,
- ranges: impl IntoIterator<Item = ExcerptRange<O>>,
- cx: &mut Context<Self>,
- ) -> Vec<ExcerptId>
- where
- O: text::ToOffset,
- {
- let mut ids = Vec::new();
- let mut next_excerpt_id =
- if let Some(last_entry) = self.snapshot.borrow().excerpt_ids.last() {
- last_entry.id.0 + 1
- } else {
- 1
- };
- self.insert_excerpts_with_ids_after(
- prev_excerpt_id,
- buffer,
- ranges.into_iter().map(|range| {
- let id = ExcerptId(post_inc(&mut next_excerpt_id));
- ids.push(id);
- (id, range)
- }),
- cx,
- );
- ids
- }
-
- pub fn insert_excerpts_with_ids_after<O>(
- &mut self,
- prev_excerpt_id: ExcerptId,
- buffer: Entity<Buffer>,
- ranges: impl IntoIterator<Item = (ExcerptId, ExcerptRange<O>)>,
- cx: &mut Context<Self>,
- ) where
- O: text::ToOffset,
- {
- assert_eq!(self.history.transaction_depth(), 0);
- let mut ranges = ranges.into_iter().peekable();
- if ranges.peek().is_none() {
- return Default::default();
- }
-
- self.sync_mut(cx);
-
- let buffer_snapshot = buffer.read(cx).snapshot();
- let buffer_id = buffer_snapshot.remote_id();
-
- let buffer_state = self.buffers.entry(buffer_id).or_insert_with(|| {
- self.buffer_changed_since_sync.replace(true);
- buffer.update(cx, |buffer, _| {
- buffer.record_changes(Rc::downgrade(&self.buffer_changed_since_sync));
- });
- BufferState {
- last_version: RefCell::new(buffer_snapshot.version().clone()),
- last_non_text_state_update_count: Cell::new(
- buffer_snapshot.non_text_state_update_count(),
- ),
- excerpts: Default::default(),
- _subscriptions: [
- cx.observe(&buffer, |_, _, cx| cx.notify()),
- cx.subscribe(&buffer, Self::on_buffer_event),
- ],
- buffer: buffer.clone(),
- }
- });
-
- let mut snapshot = self.snapshot.get_mut();
-
- let mut prev_locator = snapshot.excerpt_locator_for_id(prev_excerpt_id).clone();
- let mut new_excerpt_ids = mem::take(&mut snapshot.excerpt_ids);
- let mut cursor = snapshot.excerpts.cursor::<Option<&Locator>>(());
- let mut new_excerpts = cursor.slice(&prev_locator, Bias::Right);
- prev_locator = cursor.start().unwrap_or(Locator::min_ref()).clone();
-
- let edit_start = ExcerptDimension(new_excerpts.summary().text.len);
- new_excerpts.update_last(
- |excerpt| {
- excerpt.has_trailing_newline = true;
- },
- (),
- );
-
- let next_locator = if let Some(excerpt) = cursor.item() {
- excerpt.locator.clone()
- } else {
- Locator::max()
- };
-
- let mut excerpts = Vec::new();
- let buffer_snapshot = Arc::new(buffer_snapshot);
- while let Some((id, range)) = ranges.next() {
- let locator = Locator::between(&prev_locator, &next_locator);
- if let Err(ix) = buffer_state.excerpts.binary_search(&locator) {
- buffer_state.excerpts.insert(ix, locator.clone());
- }
- let range = ExcerptRange {
- context: buffer_snapshot.anchor_before(&range.context.start)
- ..buffer_snapshot.anchor_after(&range.context.end),
- primary: buffer_snapshot.anchor_before(&range.primary.start)
- ..buffer_snapshot.anchor_after(&range.primary.end),
- };
- excerpts.push((id, range.clone()));
- let excerpt = Excerpt::new(
- id,
- locator.clone(),
- buffer_id,
- buffer_snapshot.clone(),
- range,
- ranges.peek().is_some() || cursor.item().is_some(),
- );
- new_excerpts.push(excerpt, ());
- prev_locator = locator.clone();
-
- if let Some(last_mapping_entry) = new_excerpt_ids.last() {
- assert!(id > last_mapping_entry.id, "excerpt ids must be increasing");
- }
- new_excerpt_ids.push(ExcerptIdMapping { id, locator }, ());
- }
- snapshot
- .buffer_locators
- .insert(buffer_id, buffer_state.excerpts.iter().cloned().collect());
-
- let edit_end = ExcerptDimension(new_excerpts.summary().text.len);
-
- let suffix = cursor.suffix();
- let changed_trailing_excerpt = suffix.is_empty();
- new_excerpts.append(suffix, ());
- drop(cursor);
- snapshot.excerpts = new_excerpts;
- snapshot.excerpt_ids = new_excerpt_ids;
- if changed_trailing_excerpt {
- snapshot.trailing_excerpt_update_count += 1;
}
-
- let edits = Self::sync_diff_transforms(
- &mut snapshot,
- vec![Edit {
- old: edit_start..edit_start,
- new: edit_start..edit_end,
- }],
- DiffChangeKind::BufferEdited,
- );
- if !edits.is_empty() {
- self.subscriptions.publish(edits);
- }
-
- cx.emit(Event::Edited {
- edited_buffer: None,
- is_local: true,
- });
- cx.emit(Event::ExcerptsAdded {
- buffer,
- predecessor: prev_excerpt_id,
- excerpts,
- });
- cx.notify();
+ merged_ranges
}
pub fn clear(&mut self, cx: &mut Context<Self>) {
self.sync_mut(cx);
- let ids = self.excerpt_ids();
let removed_buffer_ids = std::mem::take(&mut self.buffers).into_keys().collect();
- self.excerpts_by_path.clear();
- self.paths_by_excerpt.clear();
+ self.diffs.clear();
let MultiBufferSnapshot {
excerpts,
- buffer_locators,
- diffs: _,
+ diffs,
diff_transforms: _,
non_text_state_update_count: _,
edit_count: _,
@@ -2023,27 +1821,25 @@ impl MultiBuffer {
has_conflict,
has_inverted_diff,
singleton: _,
- excerpt_ids: _,
- replaced_excerpts,
trailing_excerpt_update_count,
all_diff_hunks_expanded: _,
show_deleted_hunks: _,
use_extended_diff_range: _,
show_headers: _,
+ path_keys_by_index: _,
+ indices_by_path_key: _,
+ buffers,
} = self.snapshot.get_mut();
- buffer_locators.clear();
let start = ExcerptDimension(MultiBufferOffset::ZERO);
let prev_len = ExcerptDimension(excerpts.summary().text.len);
*excerpts = Default::default();
+ *buffers = Default::default();
+ *diffs = Default::default();
*trailing_excerpt_update_count += 1;
*is_dirty = false;
*has_deleted_file = false;
*has_conflict = false;
*has_inverted_diff = false;
- match Arc::get_mut(replaced_excerpts) {
- Some(replaced_excerpts) => replaced_excerpts.clear(),
- None => *replaced_excerpts = Default::default(),
- }
let edits = Self::sync_diff_transforms(
self.snapshot.get_mut(),
@@ -2060,118 +1856,16 @@ impl MultiBuffer {
edited_buffer: None,
is_local: true,
});
- cx.emit(Event::ExcerptsRemoved {
- ids,
- removed_buffer_ids,
- });
+ cx.emit(Event::BuffersRemoved { removed_buffer_ids });
cx.notify();
}
- #[ztracing::instrument(skip_all)]
- pub fn excerpts_for_buffer(
- &self,
- buffer_id: BufferId,
- cx: &App,
- ) -> Vec<(ExcerptId, Arc<BufferSnapshot>, ExcerptRange<text::Anchor>)> {
- let mut excerpts = Vec::new();
- let snapshot = self.read(cx);
- let mut cursor = snapshot.excerpts.cursor::<Option<&Locator>>(());
- if let Some(locators) = snapshot.buffer_locators.get(&buffer_id) {
- for locator in &**locators {
- cursor.seek_forward(&Some(locator), Bias::Left);
- if let Some(excerpt) = cursor.item()
- && excerpt.locator == *locator
- {
- excerpts.push((excerpt.id, excerpt.buffer.clone(), excerpt.range.clone()));
- }
- }
- }
-
- excerpts
- }
-
- pub fn excerpt_ranges_for_buffer(&self, buffer_id: BufferId, cx: &App) -> Vec<Range<Point>> {
- let snapshot = self.read(cx);
- let mut excerpts = snapshot
- .excerpts
- .cursor::<Dimensions<Option<&Locator>, ExcerptPoint>>(());
- let mut diff_transforms = snapshot
- .diff_transforms
- .cursor::<Dimensions<ExcerptPoint, OutputDimension<Point>>>(());
- diff_transforms.next();
- let locators = snapshot
- .buffer_locators
- .get(&buffer_id)
- .into_iter()
- .flat_map(|v| &**v);
- let mut result = Vec::new();
- for locator in locators {
- excerpts.seek_forward(&Some(locator), Bias::Left);
- if let Some(excerpt) = excerpts.item()
- && excerpt.locator == *locator
- {
- let excerpt_start = excerpts.start().1;
- let excerpt_end = excerpt_start + excerpt.text_summary.lines;
-
- diff_transforms.seek_forward(&excerpt_start, Bias::Left);
- let overshoot = excerpt_start - diff_transforms.start().0;
- let start = diff_transforms.start().1 + overshoot;
-
- diff_transforms.seek_forward(&excerpt_end, Bias::Right);
- let overshoot = excerpt_end - diff_transforms.start().0;
- let end = diff_transforms.start().1 + overshoot;
-
- result.push(start.0..end.0)
- }
- }
- result
- }
-
- pub fn excerpt_buffer_ids(&self) -> Vec<BufferId> {
- self.snapshot
- .borrow()
- .excerpts
- .iter()
- .map(|entry| entry.buffer_id)
- .collect()
- }
-
- pub fn excerpt_ids(&self) -> Vec<ExcerptId> {
- let snapshot = self.snapshot.borrow();
- let mut ids = Vec::with_capacity(snapshot.excerpts.summary().count);
- ids.extend(snapshot.excerpts.iter().map(|entry| entry.id));
- ids
- }
-
- pub fn excerpt_containing(
- &self,
- position: impl ToOffset,
- cx: &App,
- ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
+ pub fn range_for_buffer(&self, buffer_id: BufferId, cx: &App) -> Option<Range<Point>> {
let snapshot = self.read(cx);
- let offset = position.to_offset(&snapshot);
-
- let mut cursor = snapshot.cursor::<MultiBufferOffset, BufferOffset>();
- cursor.seek(&offset);
- cursor
- .excerpt()
- .or_else(|| snapshot.excerpts.last())
- .map(|excerpt| {
- (
- excerpt.id,
- self.buffers.get(&excerpt.buffer_id).unwrap().buffer.clone(),
- excerpt.range.context.clone(),
- )
- })
- }
-
- pub fn buffer_for_anchor(&self, anchor: Anchor, cx: &App) -> Option<Entity<Buffer>> {
- if let Some(buffer_id) = anchor.text_anchor.buffer_id {
- self.buffer(buffer_id)
- } else {
- let (_, buffer, _) = self.excerpt_containing(anchor, cx)?;
- Some(buffer)
- }
+ let path_key = snapshot.path_key_index_for_buffer(buffer_id)?;
+ let start = Anchor::in_buffer(path_key, text::Anchor::min_for_buffer(buffer_id));
+ let end = Anchor::in_buffer(path_key, text::Anchor::max_for_buffer(buffer_id));
+ Some((start..end).to_point(&snapshot))
}
// If point is at the end of the buffer, the last excerpt is returned
@@ -77,22 +77,19 @@ fn test_buffer_point_to_anchor_at_end_of_singleton_buffer(cx: &mut App) {
let buffer = cx.new(|cx| Buffer::local("abc", cx));
let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
- let excerpt_id = multibuffer
+ let anchor = multibuffer
.read(cx)
- .excerpt_ids()
- .into_iter()
- .next()
+ .buffer_point_to_anchor(&buffer, Point::new(0, 3), cx)
.unwrap();
- let anchor = multibuffer
+ let (anchor, _) = multibuffer
.read(cx)
- .buffer_point_to_anchor(&buffer, Point::new(0, 3), cx);
+ .snapshot(cx)
+ .anchor_to_buffer_anchor(anchor)
+ .unwrap();
assert_eq!(
anchor,
- Some(Anchor::in_buffer(
- excerpt_id,
- buffer.read(cx).snapshot().anchor_after(Point::new(0, 3)),
- ))
+ buffer.read(cx).snapshot().anchor_after(Point::new(0, 3)),
);
}
@@ -346,7 +343,7 @@ fn test_excerpt_boundaries_and_clipping(cx: &mut App) {
);
let snapshot = multibuffer.update(cx, |multibuffer, cx| {
- multibuffer.remove_excerpts_for_path(PathKey::sorted(1), cx);
+ multibuffer.remove_excerpts(PathKey::sorted(1), cx);
multibuffer.snapshot(cx)
});
@@ -373,7 +370,7 @@ fn test_excerpt_boundaries_and_clipping(cx: &mut App) {
boundary.row,
boundary
.next
- .buffer
+ .buffer(snapshot)
.text_for_range(boundary.next.range.context)
.collect::<String>(),
starts_new_buffer,
@@ -440,7 +437,7 @@ async fn test_diff_hunks_in_range(cx: &mut TestAppContext) {
multibuffer.update(cx, |multibuffer, cx| {
multibuffer.add_diff(diff, cx);
- multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
+ multibuffer.expand_diff_hunks(vec![Anchor::Min..Anchor::Max], cx);
});
assert_new_snapshot(
@@ -480,7 +477,7 @@ async fn test_diff_hunks_in_range(cx: &mut TestAppContext) {
);
multibuffer.update(cx, |multibuffer, cx| {
- multibuffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
+ multibuffer.collapse_diff_hunks(vec![Anchor::Min..Anchor::Max], cx);
});
assert_new_snapshot(
@@ -521,7 +518,7 @@ async fn test_diff_hunks_in_range_query_starting_at_added_row(cx: &mut TestAppCo
multibuffer.update(cx, |multibuffer, cx| {
multibuffer.add_diff(diff, cx);
- multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
+ multibuffer.expand_diff_hunks(vec![Anchor::Min..Anchor::Max], cx);
});
assert_new_snapshot(
@@ -766,12 +763,27 @@ fn test_excerpt_events(cx: &mut App) {
cx.subscribe(
&leader_multibuffer,
move |follower, _, event, cx| match event.clone() {
- Event::ExcerptsAdded {
+ Event::BufferRangesUpdated {
buffer,
- predecessor,
- excerpts,
- } => follower.insert_excerpts_with_ids_after(predecessor, buffer, excerpts, cx),
- Event::ExcerptsRemoved { ids, .. } => follower.remove_excerpts(ids, cx),
+ path_key,
+ ranges,
+ } => {
+ let buffer_snapshot = buffer.read(cx).snapshot();
+ follower.set_merged_excerpt_ranges_for_path(
+ path_key,
+ buffer,
+ &buffer_snapshot,
+ ranges,
+ cx,
+ );
+ }
+ Event::BuffersRemoved {
+ removed_buffer_ids, ..
+ } => {
+ for id in removed_buffer_ids {
+ follower.remove_excerpts_for_buffer(id, cx);
+ }
+ }
Event::Edited { .. } => {
*follower_edit_event_count.write() += 1;
}
@@ -885,9 +897,14 @@ fn test_expand_excerpts(cx: &mut App) {
drop(snapshot);
multibuffer.update(cx, |multibuffer, cx| {
- let line_zero = multibuffer.snapshot(cx).anchor_before(Point::new(0, 0));
+ let multibuffer_snapshot = multibuffer.snapshot(cx);
+ let line_zero = multibuffer_snapshot.anchor_before(Point::new(0, 0));
multibuffer.expand_excerpts(
- multibuffer.excerpt_ids(),
+ multibuffer.snapshot(cx).excerpts().map(|excerpt| {
+ multibuffer_snapshot
+ .anchor_in_excerpt(excerpt.context.start)
+ .unwrap()
+ }),
1,
ExpandExcerptDirection::UpAndDown,
cx,
@@ -1184,16 +1201,10 @@ fn test_multibuffer_anchors(cx: &mut App) {
.to_offset(&old_snapshot),
MultiBufferOffset(0)
);
- assert_eq!(Anchor::min().to_offset(&old_snapshot), MultiBufferOffset(0));
- assert_eq!(Anchor::min().to_offset(&old_snapshot), MultiBufferOffset(0));
- assert_eq!(
- Anchor::max().to_offset(&old_snapshot),
- MultiBufferOffset(10)
- );
- assert_eq!(
- Anchor::max().to_offset(&old_snapshot),
- MultiBufferOffset(10)
- );
+ assert_eq!(Anchor::Min.to_offset(&old_snapshot), MultiBufferOffset(0));
+ assert_eq!(Anchor::Min.to_offset(&old_snapshot), MultiBufferOffset(0));
+ assert_eq!(Anchor::Max.to_offset(&old_snapshot), MultiBufferOffset(10));
+ assert_eq!(Anchor::Max.to_offset(&old_snapshot), MultiBufferOffset(10));
buffer_1.update(cx, |buffer, cx| {
buffer.edit([(0..0, "W")], None, cx);
@@ -1270,153 +1281,6 @@ fn test_multibuffer_anchors(cx: &mut App) {
);
}
-#[gpui::test]
-fn test_resolving_anchors_after_replacing_their_excerpts(cx: &mut App) {
- let buffer_1 = cx.new(|cx| Buffer::local("abcd", cx));
- let buffer_2 = cx.new(|cx| Buffer::local("ABCDEFGHIJKLMNOP", cx));
- let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
-
- // Create an insertion id in buffer 1 that doesn't exist in buffer 2.
- // Add an excerpt from buffer 1 that spans this new insertion.
- buffer_1.update(cx, |buffer, cx| buffer.edit([(4..4, "123")], None, cx));
- let excerpt_id_1 = multibuffer.update(cx, |multibuffer, cx| {
- let buffer_1_snapshot = buffer_1.read(cx).snapshot();
- multibuffer.set_excerpt_ranges_for_path(
- PathKey::sorted(0),
- buffer_1,
- &buffer_1_snapshot,
- vec![ExcerptRange::new((0..7).to_point(&buffer_1_snapshot))],
- cx,
- );
- multibuffer.excerpt_ids().into_iter().next().unwrap()
- });
-
- let snapshot_1 = multibuffer.read(cx).snapshot(cx);
- assert_eq!(snapshot_1.text(), "abcd123");
-
- // Replace the buffer 1 excerpt with new excerpts from buffer 2.
- let (excerpt_id_2, _excerpt_id_3) = multibuffer.update(cx, |multibuffer, cx| {
- multibuffer.remove_excerpts_for_path(PathKey::sorted(0), cx);
- let snapshot_2 = buffer_2.read(cx).snapshot();
- multibuffer.set_excerpt_ranges_for_path(
- PathKey::sorted(1),
- buffer_2.clone(),
- &buffer_2.read(cx).snapshot(),
- vec![
- ExcerptRange::new((0..4).to_point(&snapshot_2)),
- ExcerptRange::new((6..10).to_point(&snapshot_2)),
- ExcerptRange::new((12..16).to_point(&snapshot_2)),
- ],
- cx,
- );
- let mut ids = multibuffer
- .excerpts_for_buffer(buffer_2.read(cx).remote_id(), cx)
- .into_iter()
- .map(|(id, _, _)| id);
- (ids.next().unwrap(), ids.next().unwrap())
- });
- let snapshot_2 = multibuffer.read(cx).snapshot(cx);
- assert_eq!(snapshot_2.text(), "ABCD\nGHIJ\nMNOP");
-
- // The old excerpt id doesn't get reused.
- assert_ne!(excerpt_id_2, excerpt_id_1);
-
- // Resolve some anchors from the previous snapshot in the new snapshot.
- // The current excerpts are from a different buffer, so we don't attempt to
- // resolve the old text anchor in the new buffer.
- assert_eq!(
- snapshot_2.summary_for_anchor::<MultiBufferOffset>(
- &snapshot_1.anchor_before(MultiBufferOffset(2))
- ),
- MultiBufferOffset(0)
- );
- assert_eq!(
- snapshot_2.summaries_for_anchors::<MultiBufferOffset, _>(&[
- snapshot_1.anchor_before(MultiBufferOffset(2)),
- snapshot_1.anchor_after(MultiBufferOffset(3))
- ]),
- vec![MultiBufferOffset(0), MultiBufferOffset(0)]
- );
-
- // Refresh anchors from the old snapshot. The return value indicates that both
- // anchors lost their original excerpt.
- let refresh = snapshot_2.refresh_anchors(&[
- snapshot_1.anchor_before(MultiBufferOffset(2)),
- snapshot_1.anchor_after(MultiBufferOffset(3)),
- ]);
- assert_eq!(
- refresh,
- &[
- (0, snapshot_2.anchor_before(MultiBufferOffset(0)), false),
- (1, snapshot_2.anchor_after(MultiBufferOffset(0)), false),
- ]
- );
-
- // Replace the middle excerpt with a smaller excerpt in buffer 2,
- // that intersects the old excerpt.
- multibuffer.update(cx, |multibuffer, cx| {
- let snapshot_2 = buffer_2.read(cx).snapshot();
- multibuffer.set_excerpt_ranges_for_path(
- PathKey::sorted(1),
- buffer_2.clone(),
- &buffer_2.read(cx).snapshot(),
- vec![
- ExcerptRange::new((0..4).to_point(&snapshot_2)),
- ExcerptRange::new((12..16).to_point(&snapshot_2)),
- ],
- cx,
- );
- multibuffer.set_excerpt_ranges_for_path(
- PathKey::sorted(1),
- buffer_2.clone(),
- &buffer_2.read(cx).snapshot(),
- vec![
- ExcerptRange::new((0..4).to_point(&snapshot_2)),
- ExcerptRange::new((5..8).to_point(&snapshot_2)),
- ExcerptRange::new((12..16).to_point(&snapshot_2)),
- ],
- cx,
- );
- });
-
- let snapshot_3 = multibuffer.read(cx).snapshot(cx);
- assert_eq!(snapshot_3.text(), "ABCD\nFGH\nMNOP");
-
- // Resolve some anchors from the previous snapshot in the new snapshot.
- // The third anchor can't be resolved, since its excerpt has been removed,
- // so it resolves to the same position as its predecessor.
- let anchors = [
- snapshot_2.anchor_before(MultiBufferOffset(0)),
- snapshot_2.anchor_after(MultiBufferOffset(2)),
- snapshot_2.anchor_after(MultiBufferOffset(6)),
- snapshot_2.anchor_after(MultiBufferOffset(14)),
- ];
- assert_eq!(
- snapshot_3.summaries_for_anchors::<MultiBufferOffset, _>(&anchors),
- &[
- MultiBufferOffset(0),
- MultiBufferOffset(2),
- MultiBufferOffset(9),
- MultiBufferOffset(13)
- ]
- );
-
- let new_anchors = snapshot_3.refresh_anchors(&anchors);
- assert_eq!(
- new_anchors.iter().map(|a| (a.0, a.2)).collect::<Vec<_>>(),
- &[(0, true), (1, true), (2, true), (3, true)]
- );
- assert_eq!(
- snapshot_3.summaries_for_anchors::<MultiBufferOffset, _>(new_anchors.iter().map(|a| &a.1)),
- &[
- MultiBufferOffset(0),
- MultiBufferOffset(2),
- MultiBufferOffset(7),
- MultiBufferOffset(13)
- ]
- );
-}
-
#[gpui::test]
async fn test_basic_diff_hunks(cx: &mut TestAppContext) {
let text = indoc!(
@@ -1467,7 +1331,7 @@ async fn test_basic_diff_hunks(cx: &mut TestAppContext) {
);
multibuffer.update(cx, |multibuffer, cx| {
- multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
+ multibuffer.expand_diff_hunks(vec![Anchor::Min..Anchor::Max], cx);
});
assert_new_snapshot(
@@ -1513,7 +1377,7 @@ async fn test_basic_diff_hunks(cx: &mut TestAppContext) {
assert_line_indents(&snapshot);
multibuffer.update(cx, |multibuffer, cx| {
- multibuffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
+ multibuffer.collapse_diff_hunks(vec![Anchor::Min..Anchor::Max], cx)
});
assert_new_snapshot(
&multibuffer,
@@ -1700,7 +1564,7 @@ async fn test_repeatedly_expand_a_diff_hunk(cx: &mut TestAppContext) {
});
multibuffer.update(cx, |multibuffer, cx| {
- multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
+ multibuffer.expand_diff_hunks(vec![Anchor::Min..Anchor::Max], cx);
});
assert_new_snapshot(
@@ -1751,7 +1615,7 @@ async fn test_repeatedly_expand_a_diff_hunk(cx: &mut TestAppContext) {
// Now collapse all diff hunks
multibuffer.update(cx, |multibuffer, cx| {
- multibuffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
+ multibuffer.collapse_diff_hunks(vec![Anchor::Min..Anchor::Max], cx);
});
assert_new_snapshot(
@@ -2097,6 +1961,203 @@ fn test_set_excerpts_for_buffer(cx: &mut TestAppContext) {
});
}
+#[gpui::test]
+fn test_update_excerpt_ranges_for_path(cx: &mut TestAppContext) {
+ let buffer = cx.new(|cx| {
+ Buffer::local(
+ indoc! {
+ "row 0
+ row 1
+ row 2
+ row 3
+ row 4
+ row 5
+ row 6
+ row 7
+ row 8
+ row 9
+ row 10
+ row 11
+ row 12
+ row 13
+ row 14
+ "},
+ cx,
+ )
+ });
+ let path = PathKey::with_sort_prefix(0, rel_path("test.rs").into_arc());
+
+ let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
+ multibuffer.update(cx, |multibuffer, cx| {
+ multibuffer.set_excerpts_for_path(
+ path.clone(),
+ buffer.clone(),
+ vec![Point::row_range(2..4), Point::row_range(8..10)],
+ 0,
+ cx,
+ );
+ });
+ assert_excerpts_match(
+ &multibuffer,
+ cx,
+ indoc! {"-----
+ row 2
+ row 3
+ row 4
+ -----
+ row 8
+ row 9
+ row 10
+ "},
+ );
+
+ multibuffer.update(cx, |multibuffer, cx| {
+ multibuffer.update_excerpts_for_path(
+ path.clone(),
+ buffer.clone(),
+ vec![Point::row_range(12..13)],
+ 0,
+ cx,
+ );
+ });
+ assert_excerpts_match(
+ &multibuffer,
+ cx,
+ indoc! {"-----
+ row 12
+ row 13
+ "},
+ );
+
+ multibuffer.update(cx, |multibuffer, cx| {
+ multibuffer.set_excerpts_for_path(
+ path.clone(),
+ buffer.clone(),
+ vec![Point::row_range(2..4)],
+ 0,
+ cx,
+ );
+ });
+ assert_excerpts_match(
+ &multibuffer,
+ cx,
+ indoc! {"-----
+ row 2
+ row 3
+ row 4
+ "},
+ );
+ multibuffer.update(cx, |multibuffer, cx| {
+ multibuffer.update_excerpts_for_path(
+ path.clone(),
+ buffer.clone(),
+ vec![Point::row_range(3..5)],
+ 0,
+ cx,
+ );
+ });
+ assert_excerpts_match(
+ &multibuffer,
+ cx,
+ indoc! {"-----
+ row 2
+ row 3
+ row 4
+ row 5
+ "},
+ );
+
+ multibuffer.update(cx, |multibuffer, cx| {
+ multibuffer.set_excerpts_for_path(
+ path.clone(),
+ buffer.clone(),
+ vec![
+ Point::row_range(0..1),
+ Point::row_range(6..8),
+ Point::row_range(12..13),
+ ],
+ 0,
+ cx,
+ );
+ });
+ assert_excerpts_match(
+ &multibuffer,
+ cx,
+ indoc! {"-----
+ row 0
+ row 1
+ -----
+ row 6
+ row 7
+ row 8
+ -----
+ row 12
+ row 13
+ "},
+ );
+ multibuffer.update(cx, |multibuffer, cx| {
+ multibuffer.update_excerpts_for_path(
+ path.clone(),
+ buffer.clone(),
+ vec![Point::row_range(7..9)],
+ 0,
+ cx,
+ );
+ });
+ assert_excerpts_match(
+ &multibuffer,
+ cx,
+ indoc! {"-----
+ row 6
+ row 7
+ row 8
+ row 9
+ "},
+ );
+
+ multibuffer.update(cx, |multibuffer, cx| {
+ multibuffer.set_excerpts_for_path(
+ path.clone(),
+ buffer.clone(),
+ vec![Point::row_range(2..3), Point::row_range(6..7)],
+ 0,
+ cx,
+ );
+ });
+ assert_excerpts_match(
+ &multibuffer,
+ cx,
+ indoc! {"-----
+ row 2
+ row 3
+ -----
+ row 6
+ row 7
+ "},
+ );
+ multibuffer.update(cx, |multibuffer, cx| {
+ multibuffer.update_excerpts_for_path(
+ path.clone(),
+ buffer.clone(),
+ vec![Point::row_range(3..6)],
+ 0,
+ cx,
+ );
+ });
+ assert_excerpts_match(
+ &multibuffer,
+ cx,
+ indoc! {"-----
+ row 2
+ row 3
+ row 4
+ row 5
+ row 6
+ row 7
+ "},
+ );
+}
+
#[gpui::test]
fn test_set_excerpts_for_buffer_rename(cx: &mut TestAppContext) {
let buf1 = cx.new(|cx| {
@@ -2178,6 +2239,405 @@ fn test_set_excerpts_for_buffer_rename(cx: &mut TestAppContext) {
);
}
+#[gpui::test]
+fn test_set_excerpts_for_path_replaces_previous_buffer(cx: &mut TestAppContext) {
+ let buffer_a = cx.new(|cx| {
+ Buffer::local(
+ indoc! {
+ "alpha
+ beta
+ gamma
+ delta
+ epsilon
+ ",
+ },
+ cx,
+ )
+ });
+ let buffer_b = cx.new(|cx| {
+ Buffer::local(
+ indoc! {
+ "one
+ two
+ three
+ four
+ ",
+ },
+ cx,
+ )
+ });
+ let path: PathKey = PathKey::with_sort_prefix(0, rel_path("shared/path").into_arc());
+
+ let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
+ let removed_buffer_ids: Arc<RwLock<Vec<BufferId>>> = Default::default();
+ multibuffer.update(cx, |_, cx| {
+ let removed_buffer_ids = removed_buffer_ids.clone();
+ cx.subscribe(&multibuffer, move |_, _, event, _| {
+ if let Event::BuffersRemoved {
+ removed_buffer_ids: ids,
+ } = event
+ {
+ removed_buffer_ids.write().extend(ids.iter().copied());
+ }
+ })
+ .detach();
+ });
+
+ let ranges_a = vec![Point::row_range(0..1), Point::row_range(3..4)];
+ multibuffer.update(cx, |multibuffer, cx| {
+ multibuffer.set_excerpts_for_path(path.clone(), buffer_a.clone(), ranges_a.clone(), 0, cx);
+ });
+ let (anchor_a1, anchor_a2) = multibuffer.read_with(cx, |multibuffer, cx| {
+ let snapshot = multibuffer.snapshot(cx);
+ let buffer_snapshot = buffer_a.read(cx).snapshot();
+ let mut anchors = ranges_a.into_iter().filter_map(|range| {
+ let text_range = buffer_snapshot.anchor_range_inside(range);
+ let start = snapshot.anchor_in_buffer(text_range.start)?;
+ let end = snapshot.anchor_in_buffer(text_range.end)?;
+ Some(start..end)
+ });
+ (
+ anchors.next().expect("should have first anchor"),
+ anchors.next().expect("should have second anchor"),
+ )
+ });
+
+ assert_excerpts_match(
+ &multibuffer,
+ cx,
+ indoc! {
+ "-----
+ alpha
+ beta
+ -----
+ delta
+ epsilon
+ "
+ },
+ );
+
+ let buffer_a_id = buffer_a.read_with(cx, |buffer, _| buffer.remote_id());
+ multibuffer.read_with(cx, |multibuffer, cx| {
+ let snapshot = multibuffer.snapshot(cx);
+ assert!(
+ snapshot
+ .excerpts()
+ .any(|excerpt| excerpt.context.start.buffer_id == buffer_a_id),
+ );
+ });
+
+ let ranges_b = vec![Point::row_range(1..2)];
+ multibuffer.update(cx, |multibuffer, cx| {
+ multibuffer.set_excerpts_for_path(path.clone(), buffer_b.clone(), ranges_b.clone(), 1, cx);
+ });
+ let anchor_b = multibuffer.read_with(cx, |multibuffer, cx| {
+ let snapshot = multibuffer.snapshot(cx);
+ let buffer_snapshot = buffer_b.read(cx).snapshot();
+ ranges_b
+ .into_iter()
+ .filter_map(|range| {
+ let text_range = buffer_snapshot.anchor_range_inside(range);
+ let start = snapshot.anchor_in_buffer(text_range.start)?;
+ let end = snapshot.anchor_in_buffer(text_range.end)?;
+ Some(start..end)
+ })
+ .next()
+ .expect("should have an anchor")
+ });
+
+ let buffer_b_id = buffer_b.read_with(cx, |buffer, _| buffer.remote_id());
+ multibuffer.read_with(cx, |multibuffer, cx| {
+ let snapshot = multibuffer.snapshot(cx);
+ assert!(
+ !snapshot
+ .excerpts()
+ .any(|excerpt| excerpt.context.start.buffer_id == buffer_a_id),
+ );
+ assert!(
+ snapshot
+ .excerpts()
+ .any(|excerpt| excerpt.context.start.buffer_id == buffer_b_id),
+ );
+ assert!(
+ multibuffer.buffer(buffer_a_id).is_none(),
+ "old buffer should be fully removed from the multibuffer"
+ );
+ assert!(
+ multibuffer.buffer(buffer_b_id).is_some(),
+ "new buffer should be present in the multibuffer"
+ );
+ });
+ assert!(
+ removed_buffer_ids.read().contains(&buffer_a_id),
+ "BuffersRemoved event should have been emitted for the old buffer"
+ );
+
+ assert_excerpts_match(
+ &multibuffer,
+ cx,
+ indoc! {
+ "-----
+ one
+ two
+ three
+ four
+ "
+ },
+ );
+
+ multibuffer.read_with(cx, |multibuffer, cx| {
+ let snapshot = multibuffer.snapshot(cx);
+ anchor_a1.start.cmp(&anchor_b.start, &snapshot);
+ anchor_a1.end.cmp(&anchor_b.end, &snapshot);
+ anchor_a1.start.cmp(&anchor_a2.start, &snapshot);
+ anchor_a1.end.cmp(&anchor_a2.end, &snapshot);
+ });
+}
+
+#[gpui::test]
+fn test_stale_anchor_after_buffer_removal_and_path_reuse(cx: &mut TestAppContext) {
+ let buffer_a = cx.new(|cx| Buffer::local("aaa\nbbb\nccc\n", cx));
+ let buffer_b = cx.new(|cx| Buffer::local("xxx\nyyy\nzzz\n", cx));
+ let buffer_other = cx.new(|cx| Buffer::local("111\n222\n333\n", cx));
+ let path = PathKey::with_sort_prefix(0, rel_path("the/path").into_arc());
+ let other_path = PathKey::with_sort_prefix(1, rel_path("other/path").into_arc());
+
+ let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
+
+ multibuffer.update(cx, |multibuffer, cx| {
+ multibuffer.set_excerpts_for_path(
+ path.clone(),
+ buffer_a.clone(),
+ [Point::new(0, 0)..Point::new(2, 3)],
+ 0,
+ cx,
+ );
+ multibuffer.set_excerpts_for_path(
+ other_path.clone(),
+ buffer_other.clone(),
+ [Point::new(0, 0)..Point::new(2, 3)],
+ 0,
+ cx,
+ );
+ });
+
+ buffer_a.update(cx, |buffer, cx| {
+ buffer.edit(
+ [(Point::new(1, 0)..Point::new(1, 0), "INSERTED ")],
+ None,
+ cx,
+ );
+ });
+
+ let stale_anchor = multibuffer.read_with(cx, |multibuffer, cx| {
+ let snapshot = multibuffer.snapshot(cx);
+ snapshot.anchor_before(Point::new(1, 5))
+ });
+
+ multibuffer.update(cx, |multibuffer, cx| {
+ multibuffer.remove_excerpts(path.clone(), cx);
+ });
+
+ multibuffer.read_with(cx, |multibuffer, cx| {
+ let snapshot = multibuffer.snapshot(cx);
+ let offset = stale_anchor.to_offset(&snapshot);
+ assert!(
+ offset.0 <= snapshot.len().0,
+ "stale anchor resolved to offset {offset:?} but multibuffer len is {:?}",
+ snapshot.len()
+ );
+ });
+
+ multibuffer.update(cx, |multibuffer, cx| {
+ multibuffer.set_excerpts_for_path(
+ path.clone(),
+ buffer_b.clone(),
+ [Point::new(0, 0)..Point::new(2, 3)],
+ 0,
+ cx,
+ );
+ });
+
+ multibuffer.read_with(cx, |multibuffer, cx| {
+ let snapshot = multibuffer.snapshot(cx);
+ let offset = stale_anchor.to_offset(&snapshot);
+ assert!(
+ offset.0 <= snapshot.len().0,
+ "stale anchor resolved to offset {offset:?} but multibuffer len is {:?}",
+ snapshot.len()
+ );
+ });
+}
+
+#[gpui::test]
+async fn test_map_excerpt_ranges(cx: &mut TestAppContext) {
+ let base_text = indoc!(
+ "
+ {
+ (aaa)
+ (bbb)
+ (ccc)
+ }
+ xxx
+ yyy
+ zzz
+ [
+ (ddd)
+ (EEE)
+ ]
+ "
+ );
+ let text = indoc!(
+ "
+ {
+ (aaa)
+ (CCC)
+ }
+ xxx
+ yyy
+ zzz
+ [
+ (ddd)
+ (EEE)
+ ]
+ "
+ );
+
+ let buffer = cx.new(|cx| Buffer::local(text, cx));
+ let diff = cx
+ .new(|cx| BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx));
+ cx.run_until_parked();
+
+ let multibuffer = cx.new(|cx| {
+ let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
+ multibuffer.set_excerpts_for_path(
+ PathKey::sorted(0),
+ buffer.clone(),
+ [
+ Point::new(0, 0)..Point::new(3, 1),
+ Point::new(7, 0)..Point::new(10, 1),
+ ],
+ 0,
+ cx,
+ );
+ multibuffer.add_diff(diff.clone(), cx);
+ multibuffer
+ });
+
+ multibuffer.update(cx, |multibuffer, cx| {
+ multibuffer.expand_diff_hunks(vec![Anchor::Min..Anchor::Max], cx);
+ });
+ cx.run_until_parked();
+
+ let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx));
+
+ let actual_diff = format_diff(
+ &snapshot.text(),
+ &snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>(),
+ &Default::default(),
+ None,
+ );
+ pretty_assertions::assert_eq!(
+ actual_diff,
+ indoc!(
+ "
+ {
+ (aaa)
+ - (bbb)
+ - (ccc)
+ + (CCC)
+ } [\u{2193}]
+ [ [\u{2191}]
+ (ddd)
+ (EEE)
+ ] [\u{2193}]"
+ )
+ );
+
+ assert_eq!(
+ snapshot.map_excerpt_ranges(
+ snapshot.point_to_offset(Point::new(1, 3))..snapshot.point_to_offset(Point::new(1, 3)),
+ |buffer, excerpt_range, input_range| {
+ assert_eq!(
+ buffer.offset_to_point(input_range.start.0)
+ ..buffer.offset_to_point(input_range.end.0),
+ Point::new(1, 3)..Point::new(1, 3),
+ );
+ assert_eq!(
+ buffer.offset_to_point(excerpt_range.context.start.0)
+ ..buffer.offset_to_point(excerpt_range.context.end.0),
+ Point::new(0, 0)..Point::new(3, 1),
+ );
+ vec![
+ (input_range.start..BufferOffset(input_range.start.0 + 3), ()),
+ (excerpt_range.context, ()),
+ (
+ BufferOffset(text::ToOffset::to_offset(&Point::new(2, 2), buffer))
+ ..BufferOffset(text::ToOffset::to_offset(&Point::new(2, 7), buffer)),
+ (),
+ ),
+ (
+ BufferOffset(text::ToOffset::to_offset(&Point::new(0, 0), buffer))
+ ..BufferOffset(text::ToOffset::to_offset(&Point::new(2, 0), buffer)),
+ (),
+ ),
+ ]
+ },
+ ),
+ Some(vec![
+ (
+ snapshot.point_to_offset(Point::new(1, 3))
+ ..snapshot.point_to_offset(Point::new(1, 6)),
+ (),
+ ),
+ (
+ snapshot.point_to_offset(Point::zero())..snapshot.point_to_offset(Point::new(5, 1)),
+ ()
+ ),
+ (
+ snapshot.point_to_offset(Point::new(4, 2))
+ ..snapshot.point_to_offset(Point::new(4, 7)),
+ (),
+ ),
+ (
+ snapshot.point_to_offset(Point::zero())..snapshot.point_to_offset(Point::new(4, 0)),
+ ()
+ ),
+ ]),
+ );
+
+ assert_eq!(
+ snapshot.map_excerpt_ranges(
+ snapshot.point_to_offset(Point::new(5, 0))..snapshot.point_to_offset(Point::new(7, 0)),
+ |_, _, range| vec![(range, ())],
+ ),
+ None,
+ );
+
+ assert_eq!(
+ snapshot.map_excerpt_ranges(
+ snapshot.point_to_offset(Point::new(7, 3))..snapshot.point_to_offset(Point::new(7, 6)),
+ |buffer, excerpt_range, input_range| {
+ assert_eq!(
+ buffer.offset_to_point(input_range.start.0)
+ ..buffer.offset_to_point(input_range.end.0),
+ Point::new(8, 3)..Point::new(8, 6),
+ );
+ assert_eq!(
+ buffer.offset_to_point(excerpt_range.context.start.0)
+ ..buffer.offset_to_point(excerpt_range.context.end.0),
+ Point::new(7, 0)..Point::new(10, 1),
+ );
+ vec![(input_range, ())]
+ },
+ ),
+ Some(vec![(
+ snapshot.point_to_offset(Point::new(7, 3))..snapshot.point_to_offset(Point::new(7, 6)),
+ (),
+ )]),
+ );
+}
+
#[gpui::test]
async fn test_diff_hunks_with_multiple_excerpts(cx: &mut TestAppContext) {
let base_text_1 = indoc!(
@@ -2273,7 +2733,7 @@ async fn test_diff_hunks_with_multiple_excerpts(cx: &mut TestAppContext) {
);
multibuffer.update(cx, |multibuffer, cx| {
- multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
+ multibuffer.expand_diff_hunks(vec![Anchor::Min..Anchor::Max], cx);
});
assert_new_snapshot(
@@ -2423,101 +2883,136 @@ struct ReferenceMultibuffer {
excerpts: Vec<ReferenceExcerpt>,
diffs: HashMap<BufferId, Entity<BufferDiff>>,
inverted_diffs: HashMap<BufferId, (Entity<BufferDiff>, Entity<language::Buffer>)>,
+ expanded_diff_hunks_by_buffer: HashMap<BufferId, Vec<text::Anchor>>,
}
-#[derive(Debug)]
+#[derive(Clone, Debug)]
struct ReferenceExcerpt {
- id: ExcerptId,
+ path_key: PathKey,
+ path_key_index: PathKeyIndex,
buffer: Entity<Buffer>,
range: Range<text::Anchor>,
- expanded_diff_hunks: Vec<text::Anchor>,
}
-#[derive(Debug)]
+#[derive(Clone, Debug)]
struct ReferenceRegion {
buffer_id: Option<BufferId>,
range: Range<usize>,
buffer_range: Option<Range<Point>>,
status: Option<DiffHunkStatus>,
- excerpt_id: Option<ExcerptId>,
+ excerpt_range: Option<Range<text::Anchor>>,
+ excerpt_path_key_index: Option<PathKeyIndex>,
}
impl ReferenceMultibuffer {
- fn expand_excerpts(&mut self, excerpts: &HashSet<ExcerptId>, line_count: u32, cx: &App) {
- if line_count == 0 {
+ fn expand_excerpts(
+ &mut self,
+ excerpts: &HashSet<ExcerptRange<text::Anchor>>,
+ line_count: u32,
+ cx: &mut App,
+ ) {
+ use text::AnchorRangeExt as _;
+
+ if line_count == 0 || excerpts.is_empty() {
return;
}
- for id in excerpts {
- let excerpt = self.excerpts.iter_mut().find(|e| e.id == *id).unwrap();
- let snapshot = excerpt.buffer.read(cx).snapshot();
- let mut point_range = excerpt.range.to_point(&snapshot);
- point_range.start = Point::new(point_range.start.row.saturating_sub(line_count), 0);
- point_range.end =
- snapshot.clip_point(Point::new(point_range.end.row + line_count, 0), Bias::Left);
- point_range.end.column = snapshot.line_len(point_range.end.row);
- excerpt.range =
- snapshot.anchor_before(point_range.start)..snapshot.anchor_after(point_range.end);
+ let mut excerpts_by_buffer: HashMap<BufferId, Vec<ExcerptRange<text::Anchor>>> =
+ HashMap::default();
+ for excerpt in excerpts {
+ excerpts_by_buffer
+ .entry(excerpt.context.start.buffer_id)
+ .or_default()
+ .push(excerpt.clone())
}
- }
- fn remove_excerpt(&mut self, id: ExcerptId, cx: &App) {
- let ix = self
- .excerpts
- .iter()
- .position(|excerpt| excerpt.id == id)
- .unwrap();
- let excerpt = self.excerpts.remove(ix);
- let buffer = excerpt.buffer.read(cx);
- let buffer_id = buffer.remote_id();
- log::info!(
- "Removing excerpt {}: {:?}",
- ix,
- buffer
- .text_for_range(excerpt.range.to_offset(buffer))
- .collect::<String>(),
- );
- if !self
- .excerpts
- .iter()
- .any(|excerpt| excerpt.buffer.read(cx).remote_id() == buffer_id)
- {
- self.diffs.remove(&buffer_id);
- self.inverted_diffs.remove(&buffer_id);
+ for (buffer_id, excerpts_to_expand) in excerpts_by_buffer {
+ let mut buffer = None;
+ let mut buffer_snapshot = None;
+ let mut path = None;
+ let mut path_key_index = None;
+ let mut new_ranges =
+ self.excerpts
+ .iter()
+ .filter(|excerpt| excerpt.range.start.buffer_id == buffer_id)
+ .map(|excerpt| {
+ let snapshot = excerpt.buffer.read(cx).snapshot();
+ let mut range = excerpt.range.to_point(&snapshot);
+ if excerpts_to_expand.iter().any(|info| {
+ excerpt.range.contains_anchor(info.context.start, &snapshot)
+ }) {
+ range.start = Point::new(range.start.row.saturating_sub(line_count), 0);
+ range.end = snapshot
+ .clip_point(Point::new(range.end.row + line_count, 0), Bias::Left);
+ range.end.column = snapshot.line_len(range.end.row);
+ }
+ buffer = Some(excerpt.buffer.clone());
+ buffer_snapshot = Some(snapshot);
+ path = Some(excerpt.path_key.clone());
+ path_key_index = Some(excerpt.path_key_index);
+ ExcerptRange::new(range)
+ })
+ .collect::<Vec<_>>();
+
+ new_ranges.sort_by(|l, r| l.context.start.cmp(&r.context.start));
+
+ self.set_excerpts(
+ path.unwrap(),
+ path_key_index.unwrap(),
+ buffer.unwrap(),
+ &buffer_snapshot.unwrap(),
+ new_ranges,
+ cx,
+ );
}
}
- fn insert_excerpt_after(
+ fn set_excerpts(
&mut self,
- prev_id: ExcerptId,
- new_excerpt_id: ExcerptId,
- (buffer_handle, anchor_range): (Entity<Buffer>, Range<text::Anchor>),
+ path_key: PathKey,
+ path_key_index: PathKeyIndex,
+ buffer: Entity<Buffer>,
+ buffer_snapshot: &BufferSnapshot,
+ ranges: Vec<ExcerptRange<Point>>,
+ cx: &mut App,
) {
- let excerpt_ix = if prev_id == ExcerptId::max() {
- self.excerpts.len()
- } else {
- self.excerpts
- .iter()
- .position(|excerpt| excerpt.id == prev_id)
- .unwrap()
- + 1
- };
- self.excerpts.insert(
- excerpt_ix,
- ReferenceExcerpt {
- id: new_excerpt_id,
- buffer: buffer_handle,
- range: anchor_range,
- expanded_diff_hunks: Vec::new(),
- },
+ self.excerpts.retain(|excerpt| {
+ excerpt.path_key != path_key && excerpt.buffer.entity_id() != buffer.entity_id()
+ });
+
+ let ranges = MultiBuffer::merge_excerpt_ranges(&ranges);
+
+ let (Ok(ix) | Err(ix)) = self
+ .excerpts
+ .binary_search_by(|probe| probe.path_key.cmp(&path_key));
+ self.excerpts.splice(
+ ix..ix,
+ ranges.into_iter().map(|range| ReferenceExcerpt {
+ path_key: path_key.clone(),
+ path_key_index,
+ buffer: buffer.clone(),
+ range: buffer_snapshot.anchor_before(range.context.start)
+ ..buffer_snapshot.anchor_after(range.context.end),
+ }),
);
+ self.update_expanded_diff_hunks_for_buffer(buffer_snapshot.remote_id(), cx);
}
- fn expand_diff_hunks(&mut self, excerpt_id: ExcerptId, range: Range<text::Anchor>, cx: &App) {
+ fn expand_diff_hunks(&mut self, path_key: PathKey, range: Range<text::Anchor>, cx: &App) {
let excerpt = self
.excerpts
.iter_mut()
- .find(|e| e.id == excerpt_id)
+ .find(|e| {
+ e.path_key == path_key
+ && e.range
+ .start
+ .cmp(&range.start, &e.buffer.read(cx).snapshot())
+ .is_le()
+ && e.range
+ .end
+ .cmp(&range.end, &e.buffer.read(cx).snapshot())
+ .is_ge()
+ })
.unwrap();
let buffer = excerpt.buffer.read(cx).snapshot();
let buffer_id = buffer.remote_id();
@@ -1,24 +1,20 @@
-use std::{mem, ops::Range, sync::Arc};
+use std::{ops::Range, rc::Rc, sync::Arc};
-use collections::HashSet;
use gpui::{App, AppContext, Context, Entity};
use itertools::Itertools;
use language::{Buffer, BufferSnapshot};
use rope::Point;
-use text::{Bias, OffsetRangeExt, locator::Locator};
-use util::{post_inc, rel_path::RelPath};
+use sum_tree::{Dimensions, SumTree};
+use text::{Bias, BufferId, Edit, OffsetRangeExt, Patch};
+use util::rel_path::RelPath;
use ztracing::instrument;
use crate::{
- Anchor, ExcerptId, ExcerptRange, ExpandExcerptDirection, MultiBuffer, build_excerpt_ranges,
+ Anchor, BufferState, BufferStateSnapshot, DiffChangeKind, Event, Excerpt, ExcerptOffset,
+ ExcerptRange, ExcerptSummary, ExpandExcerptDirection, MultiBuffer, MultiBufferOffset,
+ PathKeyIndex, build_excerpt_ranges, remove_diff_state,
};
-#[derive(Debug, Clone)]
-pub struct PathExcerptInsertResult {
- pub excerpt_ids: Vec<ExcerptId>,
- pub added_new_excerpt: bool,
-}
-
#[derive(PartialEq, Eq, Ord, PartialOrd, Clone, Hash, Debug)]
pub struct PathKey {
// Used by the derived PartialOrd & Ord
@@ -27,6 +23,13 @@ pub struct PathKey {
}
impl PathKey {
+ pub fn min() -> Self {
+ Self {
+ sort_prefix: None,
+ path: RelPath::empty().into_arc(),
+ }
+ }
+
pub fn sorted(sort_prefix: u64) -> Self {
Self {
sort_prefix: Some(sort_prefix),
@@ -55,41 +58,17 @@ impl PathKey {
}
impl MultiBuffer {
- pub fn paths(&self) -> impl Iterator<Item = &PathKey> + '_ {
- self.excerpts_by_path.keys()
- }
-
- pub fn excerpts_for_path(&self, path: &PathKey) -> impl '_ + Iterator<Item = ExcerptId> {
- self.excerpts_by_path
- .get(path)
- .map(|excerpts| excerpts.as_slice())
- .unwrap_or_default()
- .iter()
- .copied()
- }
-
- pub fn path_for_excerpt(&self, excerpt: ExcerptId) -> Option<PathKey> {
- self.paths_by_excerpt.get(&excerpt).cloned()
- }
-
- pub fn remove_excerpts_for_path(&mut self, path: PathKey, cx: &mut Context<Self>) {
- if let Some(to_remove) = self.excerpts_by_path.remove(&path) {
- self.remove_excerpts(to_remove, cx)
- }
- }
-
pub fn buffer_for_path(&self, path: &PathKey, cx: &App) -> Option<Entity<Buffer>> {
- let excerpt_id = self.excerpts_by_path.get(path)?.first()?;
- let snapshot = self.read(cx);
- let excerpt = snapshot.excerpt(*excerpt_id)?;
- self.buffer(excerpt.buffer_id)
+ let snapshot = self.snapshot(cx);
+ let excerpt = snapshot.excerpts_for_path(path).next()?;
+ self.buffer(excerpt.context.start.buffer_id)
}
pub fn location_for_path(&self, path: &PathKey, cx: &App) -> Option<Anchor> {
- let excerpt_id = self.excerpts_by_path.get(path)?.first()?;
- let snapshot = self.read(cx);
- let excerpt = snapshot.excerpt(*excerpt_id)?;
- Some(Anchor::in_buffer(excerpt.id, excerpt.range.context.start))
+ let snapshot = self.snapshot(cx);
+ let excerpt = snapshot.excerpts_for_path(path).next()?;
+ let path_key_index = snapshot.path_key_index_for_buffer(excerpt.context.start.buffer_id)?;
+ Some(Anchor::in_buffer(path_key_index, excerpt.context.start))
}
pub fn set_excerpts_for_buffer(
@@ -98,12 +77,14 @@ impl MultiBuffer {
ranges: impl IntoIterator<Item = Range<Point>>,
context_line_count: u32,
cx: &mut Context<Self>,
- ) -> (Vec<Range<Anchor>>, bool) {
+ ) -> bool {
let path = PathKey::for_buffer(&buffer, cx);
self.set_excerpts_for_path(path, buffer, ranges, context_line_count, cx)
}
/// Sets excerpts, returns `true` if at least one new excerpt was added.
+ ///
+ /// Any existing excerpts for this buffer or this path will be replaced by the provided ranges.
#[instrument(skip_all)]
pub fn set_excerpts_for_path(
&mut self,
@@ -112,20 +93,83 @@ impl MultiBuffer {
ranges: impl IntoIterator<Item = Range<Point>>,
context_line_count: u32,
cx: &mut Context<Self>,
- ) -> (Vec<Range<Anchor>>, bool) {
+ ) -> bool {
let buffer_snapshot = buffer.read(cx).snapshot();
+ let ranges: Vec<_> = ranges.into_iter().collect();
let excerpt_ranges = build_excerpt_ranges(ranges, context_line_count, &buffer_snapshot);
- let (new, counts) = Self::merge_excerpt_ranges(&excerpt_ranges);
- self.set_merged_excerpt_ranges_for_path(
- path,
- buffer,
- excerpt_ranges,
+ let merged = Self::merge_excerpt_ranges(&excerpt_ranges);
+ let (inserted, _path_key_index) =
+ self.set_merged_excerpt_ranges_for_path(path, buffer, &buffer_snapshot, merged, cx);
+ inserted
+ }
+
+ /// Like [`Self::set_excerpts_for_path`], but expands the provided ranges to cover any overlapping existing excerpts
+ /// for the same buffer and path.
+ ///
+ /// Existing excerpts that do not overlap any of the provided ranges are discarded.
+ pub fn update_excerpts_for_path(
+ &mut self,
+ path: PathKey,
+ buffer: Entity<Buffer>,
+ ranges: impl IntoIterator<Item = Range<Point>>,
+ context_line_count: u32,
+ cx: &mut Context<Self>,
+ ) -> bool {
+ let buffer_snapshot = buffer.read(cx).snapshot();
+ let ranges: Vec<_> = ranges.into_iter().collect();
+ let excerpt_ranges = build_excerpt_ranges(ranges, context_line_count, &buffer_snapshot);
+ let merged = self.merge_new_with_existing_excerpt_ranges(
+ &path,
&buffer_snapshot,
- new,
- counts,
+ excerpt_ranges,
cx,
- )
+ );
+
+ let (inserted, _path_key_index) =
+ self.set_merged_excerpt_ranges_for_path(path, buffer, &buffer_snapshot, merged, cx);
+ inserted
+ }
+
+ pub fn merge_new_with_existing_excerpt_ranges(
+ &self,
+ path: &PathKey,
+ buffer_snapshot: &BufferSnapshot,
+ mut excerpt_ranges: Vec<ExcerptRange<Point>>,
+ cx: &App,
+ ) -> Vec<ExcerptRange<Point>> {
+ let multibuffer_snapshot = self.snapshot(cx);
+
+ if multibuffer_snapshot.path_for_buffer(buffer_snapshot.remote_id()) == Some(path) {
+ excerpt_ranges.sort_by_key(|range| range.context.start);
+ let mut combined_ranges = Vec::new();
+ let mut new_ranges = excerpt_ranges.into_iter().peekable();
+ for existing_range in
+ multibuffer_snapshot.excerpts_for_buffer(buffer_snapshot.remote_id())
+ {
+ let existing_range = ExcerptRange {
+ context: existing_range.context.to_point(buffer_snapshot),
+ primary: existing_range.primary.to_point(buffer_snapshot),
+ };
+ while let Some(new_range) = new_ranges.peek()
+ && new_range.context.end < existing_range.context.start
+ {
+ combined_ranges.push(new_range.clone());
+ new_ranges.next();
+ }
+
+ if let Some(new_range) = new_ranges.peek()
+ && new_range.context.start <= existing_range.context.end
+ {
+ combined_ranges.push(existing_range)
+ }
+ }
+ combined_ranges.extend(new_ranges);
+ excerpt_ranges = combined_ranges;
+ }
+
+ excerpt_ranges.sort_by_key(|range| range.context.start);
+ Self::merge_excerpt_ranges(&excerpt_ranges)
}
pub fn set_excerpt_ranges_for_path(
@@ -135,17 +179,11 @@ impl MultiBuffer {
buffer_snapshot: &BufferSnapshot,
excerpt_ranges: Vec<ExcerptRange<Point>>,
cx: &mut Context<Self>,
- ) -> (Vec<Range<Anchor>>, bool) {
- let (new, counts) = Self::merge_excerpt_ranges(&excerpt_ranges);
- self.set_merged_excerpt_ranges_for_path(
- path,
- buffer,
- excerpt_ranges,
- buffer_snapshot,
- new,
- counts,
- cx,
- )
+ ) -> bool {
+ let merged = Self::merge_excerpt_ranges(&excerpt_ranges);
+ let (inserted, _path_key_index) =
+ self.set_merged_excerpt_ranges_for_path(path, buffer, buffer_snapshot, merged, cx);
+ inserted
}
pub fn set_anchored_excerpts_for_path(
@@ -161,350 +199,505 @@ impl MultiBuffer {
let mut app = cx.to_async();
async move {
let snapshot = buffer_snapshot.clone();
- let (excerpt_ranges, new, counts) = app
+ let (ranges, merged_excerpt_ranges) = app
.background_spawn(async move {
- let ranges = ranges.into_iter().map(|range| range.to_point(&snapshot));
+ let point_ranges = ranges.iter().map(|range| range.to_point(&snapshot));
let excerpt_ranges =
- build_excerpt_ranges(ranges, context_line_count, &snapshot);
- let (new, counts) = Self::merge_excerpt_ranges(&excerpt_ranges);
- (excerpt_ranges, new, counts)
+ build_excerpt_ranges(point_ranges, context_line_count, &snapshot);
+ let merged = Self::merge_excerpt_ranges(&excerpt_ranges);
+ (ranges, merged)
})
.await;
multi_buffer
.update(&mut app, move |multi_buffer, cx| {
- let (ranges, _) = multi_buffer.set_merged_excerpt_ranges_for_path(
+ let (_, path_key_index) = multi_buffer.set_merged_excerpt_ranges_for_path(
path_key,
buffer,
- excerpt_ranges,
&buffer_snapshot,
- new,
- counts,
+ merged_excerpt_ranges,
cx,
);
ranges
+ .into_iter()
+ .map(|range| Anchor::range_in_buffer(path_key_index, range))
+ .collect()
})
.ok()
.unwrap_or_default()
}
}
- pub(super) fn expand_excerpts_with_paths(
+ pub fn expand_excerpts(
&mut self,
- ids: impl IntoIterator<Item = ExcerptId>,
+ anchors: impl IntoIterator<Item = Anchor>,
line_count: u32,
direction: ExpandExcerptDirection,
cx: &mut Context<Self>,
) {
- let mut sorted_ids: Vec<ExcerptId> = ids.into_iter().collect();
- sorted_ids.sort_by(|a, b| {
- let path_a = self.paths_by_excerpt.get(a);
- let path_b = self.paths_by_excerpt.get(b);
- path_a.cmp(&path_b)
- });
- let grouped = sorted_ids
- .into_iter()
- .chunk_by(|id| self.paths_by_excerpt.get(id).cloned())
+ if line_count == 0 {
+ return;
+ }
+
+ let snapshot = self.snapshot(cx);
+ let mut sorted_anchors = anchors
.into_iter()
- .filter_map(|(k, v)| Some((k?, v.into_iter().collect::<Vec<_>>())))
+ .filter_map(|anchor| anchor.excerpt_anchor())
.collect::<Vec<_>>();
- let snapshot = self.snapshot(cx);
-
- for (path, ids) in grouped.into_iter() {
- let Some(excerpt_ids) = self.excerpts_by_path.get(&path) else {
+ if sorted_anchors.is_empty() {
+ return;
+ }
+ sorted_anchors.sort_by(|a, b| a.cmp(b, &snapshot));
+ let buffers = sorted_anchors.into_iter().chunk_by(|anchor| anchor.path);
+ let mut cursor = snapshot.excerpts.cursor::<ExcerptSummary>(());
+
+ for (path_index, excerpt_anchors) in &buffers {
+ let path = snapshot
+ .path_keys_by_index
+ .get(&path_index)
+ .expect("anchor from wrong multibuffer");
+
+ let mut excerpt_anchors = excerpt_anchors.peekable();
+ let mut ranges = Vec::new();
+
+ cursor.seek_forward(path, Bias::Left);
+ let Some((buffer, buffer_snapshot)) = cursor
+ .item()
+ .map(|excerpt| (excerpt.buffer(&self), excerpt.buffer_snapshot(&snapshot)))
+ else {
continue;
};
- let ids_to_expand = HashSet::from_iter(ids);
- let mut excerpt_id_ = None;
- let expanded_ranges = excerpt_ids.iter().filter_map(|excerpt_id| {
- let excerpt = snapshot.excerpt(*excerpt_id)?;
- let excerpt_id = excerpt.id;
- if excerpt_id_.is_none() {
- excerpt_id_ = Some(excerpt_id);
+ while let Some(excerpt) = cursor.item()
+ && &excerpt.path_key == path
+ {
+ let mut range = ExcerptRange {
+ context: excerpt.range.context.to_point(buffer_snapshot),
+ primary: excerpt.range.primary.to_point(buffer_snapshot),
+ };
+
+ let mut needs_expand = false;
+ while excerpt_anchors.peek().is_some_and(|anchor| {
+ excerpt
+ .range
+ .contains(&anchor.text_anchor(), buffer_snapshot)
+ }) {
+ needs_expand = true;
+ excerpt_anchors.next();
}
- let mut context = excerpt.range.context.to_point(&excerpt.buffer);
- if ids_to_expand.contains(&excerpt_id) {
+ if needs_expand {
match direction {
ExpandExcerptDirection::Up => {
- context.start.row = context.start.row.saturating_sub(line_count);
- context.start.column = 0;
+ range.context.start.row =
+ range.context.start.row.saturating_sub(line_count);
+ range.context.start.column = 0;
}
ExpandExcerptDirection::Down => {
- context.end.row =
- (context.end.row + line_count).min(excerpt.buffer.max_point().row);
- context.end.column = excerpt.buffer.line_len(context.end.row);
+ range.context.end.row = (range.context.end.row + line_count)
+ .min(excerpt.buffer_snapshot(&snapshot).max_point().row);
+ range.context.end.column = excerpt
+ .buffer_snapshot(&snapshot)
+ .line_len(range.context.end.row);
}
ExpandExcerptDirection::UpAndDown => {
- context.start.row = context.start.row.saturating_sub(line_count);
- context.start.column = 0;
- context.end.row =
- (context.end.row + line_count).min(excerpt.buffer.max_point().row);
- context.end.column = excerpt.buffer.line_len(context.end.row);
+ range.context.start.row =
+ range.context.start.row.saturating_sub(line_count);
+ range.context.start.column = 0;
+ range.context.end.row = (range.context.end.row + line_count)
+ .min(excerpt.buffer_snapshot(&snapshot).max_point().row);
+ range.context.end.column = excerpt
+ .buffer_snapshot(&snapshot)
+ .line_len(range.context.end.row);
}
}
}
- Some(ExcerptRange {
- context,
- primary: excerpt.range.primary.to_point(&excerpt.buffer),
- })
- });
- let mut merged_ranges: Vec<ExcerptRange<Point>> = Vec::new();
- for range in expanded_ranges {
- if let Some(last_range) = merged_ranges.last_mut()
- && last_range.context.end >= range.context.start
- {
- last_range.context.end = range.context.end;
- continue;
- }
- merged_ranges.push(range)
+ ranges.push(range);
+ cursor.next();
}
- let Some(excerpt_id) = excerpt_id_ else {
- continue;
- };
- let Some(buffer_id) = &snapshot.buffer_id_for_excerpt(excerpt_id) else {
- continue;
- };
- let Some(buffer) = self.buffers.get(buffer_id).map(|b| b.buffer.clone()) else {
- continue;
- };
+ ranges.sort_by(|l, r| l.context.start.cmp(&r.context.start));
- let buffer_snapshot = buffer.read(cx).snapshot();
- self.update_path_excerpts(path.clone(), buffer, &buffer_snapshot, merged_ranges, cx);
+ self.set_excerpt_ranges_for_path(path.clone(), buffer, buffer_snapshot, ranges, cx);
}
}
/// Sets excerpts, returns `true` if at least one new excerpt was added.
- fn set_merged_excerpt_ranges_for_path(
+ pub(crate) fn set_merged_excerpt_ranges_for_path<T>(
&mut self,
path: PathKey,
buffer: Entity<Buffer>,
- ranges: Vec<ExcerptRange<Point>>,
buffer_snapshot: &BufferSnapshot,
- new: Vec<ExcerptRange<Point>>,
- counts: Vec<usize>,
+ new: Vec<ExcerptRange<T>>,
cx: &mut Context<Self>,
- ) -> (Vec<Range<Anchor>>, bool) {
- let insert_result = self.update_path_excerpts(path, buffer, buffer_snapshot, new, cx);
-
- let mut result = Vec::new();
- let mut ranges = ranges.into_iter();
- for (excerpt_id, range_count) in insert_result
- .excerpt_ids
+ ) -> (bool, PathKeyIndex)
+ where
+ T: language::ToOffset,
+ {
+ let anchor_ranges = new
.into_iter()
- .zip(counts.into_iter())
- {
- for range in ranges.by_ref().take(range_count) {
- let range = Anchor::range_in_buffer(
- excerpt_id,
- buffer_snapshot.anchor_before(&range.primary.start)
- ..buffer_snapshot.anchor_after(&range.primary.end),
- );
- result.push(range)
- }
+ .map(|r| ExcerptRange {
+ context: buffer_snapshot.anchor_before(r.context.start)
+ ..buffer_snapshot.anchor_after(r.context.end),
+ primary: buffer_snapshot.anchor_before(r.primary.start)
+ ..buffer_snapshot.anchor_after(r.primary.end),
+ })
+ .collect::<Vec<_>>();
+ let inserted =
+ self.update_path_excerpts(path.clone(), buffer, buffer_snapshot, &anchor_ranges, cx);
+ let path_key_index = self.get_or_create_path_key_index(&path);
+ (inserted, path_key_index)
+ }
+
+ pub(crate) fn get_or_create_path_key_index(&mut self, path_key: &PathKey) -> PathKeyIndex {
+ let mut snapshot = self.snapshot.borrow_mut();
+
+ if let Some(&existing) = snapshot.indices_by_path_key.get(path_key) {
+ return existing;
}
- (result, insert_result.added_new_excerpt)
+
+ let index = snapshot
+ .path_keys_by_index
+ .last()
+ .map(|(index, _)| PathKeyIndex(index.0 + 1))
+ .unwrap_or(PathKeyIndex(0));
+ snapshot.path_keys_by_index.insert(index, path_key.clone());
+ snapshot.indices_by_path_key.insert(path_key.clone(), index);
+ index
}
pub fn update_path_excerpts(
&mut self,
- path: PathKey,
+ path_key: PathKey,
buffer: Entity<Buffer>,
buffer_snapshot: &BufferSnapshot,
- new: Vec<ExcerptRange<Point>>,
+ to_insert: &Vec<ExcerptRange<text::Anchor>>,
cx: &mut Context<Self>,
- ) -> PathExcerptInsertResult {
- let mut insert_after = self
- .excerpts_by_path
- .range(..path.clone())
- .next_back()
- .and_then(|(_, value)| value.last().copied())
- .unwrap_or(ExcerptId::min());
-
- let existing = self
- .excerpts_by_path
- .get(&path)
- .cloned()
- .unwrap_or_default();
- let mut new_iter = new.into_iter().peekable();
- let mut existing_iter = existing.into_iter().peekable();
-
- let mut excerpt_ids = Vec::new();
- let mut to_remove = Vec::new();
- let mut to_insert: Vec<(ExcerptId, ExcerptRange<Point>)> = Vec::new();
- let mut added_a_new_excerpt = false;
- let snapshot = self.snapshot(cx);
+ ) -> bool {
+ let path_key_index = self.get_or_create_path_key_index(&path_key);
+ if let Some(old_path_key) = self
+ .snapshot(cx)
+ .path_for_buffer(buffer_snapshot.remote_id())
+ && old_path_key != &path_key
+ {
+ self.remove_excerpts(old_path_key.clone(), cx);
+ }
- let mut next_excerpt_id =
- if let Some(last_entry) = self.snapshot.get_mut().excerpt_ids.last() {
- last_entry.id.0 + 1
- } else {
- 1
- };
+ if to_insert.len() == 0 {
+ self.remove_excerpts(path_key.clone(), cx);
- let mut next_excerpt_id = move || ExcerptId(post_inc(&mut next_excerpt_id));
+ return false;
+ }
+ assert_eq!(self.history.transaction_depth(), 0);
+ self.sync_mut(cx);
- let mut excerpts_cursor = snapshot.excerpts.cursor::<Option<&Locator>>(());
- excerpts_cursor.next();
+ let buffer_id = buffer_snapshot.remote_id();
- loop {
- let existing = if let Some(&existing_id) = existing_iter.peek() {
- let locator = snapshot.excerpt_locator_for_id(existing_id);
- excerpts_cursor.seek_forward(&Some(locator), Bias::Left);
- if let Some(excerpt) = excerpts_cursor.item() {
- if excerpt.buffer_id != buffer_snapshot.remote_id() {
- to_remove.push(existing_id);
- existing_iter.next();
- continue;
- }
- Some((existing_id, excerpt.range.context.to_point(buffer_snapshot)))
- } else {
- None
- }
- } else {
- None
+ let mut snapshot = self.snapshot.get_mut();
+ let mut cursor = snapshot
+ .excerpts
+ .cursor::<Dimensions<PathKey, ExcerptOffset>>(());
+ let mut new_excerpts = SumTree::new(());
+
+ let new_ranges = to_insert.clone();
+ let mut to_insert = to_insert.iter().peekable();
+ let mut patch = Patch::empty();
+ let mut added_new_excerpt = false;
+
+ new_excerpts.append(cursor.slice(&path_key, Bias::Left), ());
+
+ // handle the case where the path key used to be associated
+ // with a different buffer by removing its excerpts.
+ if let Some(excerpt) = cursor.item()
+ && &excerpt.path_key == &path_key
+ && excerpt.buffer_id != buffer_id
+ {
+ let old_buffer_id = excerpt.buffer_id;
+ self.buffers.remove(&old_buffer_id);
+ snapshot.buffers.remove(&old_buffer_id);
+ remove_diff_state(&mut snapshot.diffs, old_buffer_id);
+ self.diffs.remove(&old_buffer_id);
+ let before = cursor.position.1;
+ cursor.seek_forward(&path_key, Bias::Right);
+ let after = cursor.position.1;
+ patch.push(Edit {
+ old: before..after,
+ new: new_excerpts.summary().len()..new_excerpts.summary().len(),
+ });
+ cx.emit(Event::BuffersRemoved {
+ removed_buffer_ids: vec![old_buffer_id],
+ });
+ }
+
+ while let Some(excerpt) = cursor.item()
+ && excerpt.path_key == path_key
+ {
+ assert_eq!(excerpt.buffer_id, buffer_id);
+ let Some(next_excerpt) = to_insert.peek() else {
+ break;
};
+ if &excerpt.range == *next_excerpt {
+ let before = new_excerpts.summary().len();
+ new_excerpts.update_last(
+ |prev_excerpt| {
+ if !prev_excerpt.has_trailing_newline {
+ prev_excerpt.has_trailing_newline = true;
+ patch.push(Edit {
+ old: cursor.position.1..cursor.position.1,
+ new: before..before + MultiBufferOffset(1),
+ });
+ }
+ },
+ (),
+ );
+ new_excerpts.push(excerpt.clone(), ());
+ to_insert.next();
+ cursor.next();
+ continue;
+ }
- let new = new_iter.peek();
- // Try to merge the next new range or existing excerpt into the last
- // queued insert.
- if let Some((last_id, last)) = to_insert.last_mut() {
- // Next new range overlaps the last queued insert: absorb it by
- // extending the insert's end.
- if let Some(new) = new
- && last.context.end >= new.context.start
- {
- last.context.end = last.context.end.max(new.context.end);
- excerpt_ids.push(*last_id);
- new_iter.next();
- continue;
- }
- // Next existing excerpt overlaps the last queued insert: absorb
- // it by extending the insert's end, and record the existing
- // excerpt as replaced so anchors in it resolve to the new one.
- if let Some((existing_id, existing_range)) = &existing
- && last.context.end >= existing_range.start
- {
- last.context.end = last.context.end.max(existing_range.end);
- to_remove.push(*existing_id);
- Arc::make_mut(&mut self.snapshot.get_mut().replaced_excerpts)
- .insert(*existing_id, *last_id);
- existing_iter.next();
- continue;
- }
+ if excerpt
+ .range
+ .context
+ .start
+ .cmp(&next_excerpt.context.start, &buffer_snapshot)
+ .is_le()
+ {
+ // remove old excerpt
+ let before = cursor.position.1;
+ cursor.next();
+ let after = cursor.position.1;
+ patch.push(Edit {
+ old: before..after,
+ new: new_excerpts.summary().len()..new_excerpts.summary().len(),
+ });
+ } else {
+ // insert new excerpt
+ let next_excerpt = to_insert.next().unwrap();
+ added_new_excerpt = true;
+ let before = new_excerpts.summary().len();
+ new_excerpts.update_last(
+ |prev_excerpt| {
+ prev_excerpt.has_trailing_newline = true;
+ },
+ (),
+ );
+ new_excerpts.push(
+ Excerpt::new(
+ path_key.clone(),
+ path_key_index,
+ &buffer_snapshot,
+ next_excerpt.clone(),
+ false,
+ ),
+ (),
+ );
+ let after = new_excerpts.summary().len();
+ patch.push_maybe_empty(Edit {
+ old: cursor.position.1..cursor.position.1,
+ new: before..after,
+ });
}
+ }
- match (new, existing) {
- (None, None) => break,
+ // remove any further trailing excerpts
+ let mut before = cursor.position.1;
+ cursor.seek_forward(&path_key, Bias::Right);
+ let after = cursor.position.1;
+ // if we removed the previous last excerpt, remove the trailing newline from the new last excerpt
+ if cursor.item().is_none() && to_insert.peek().is_none() {
+ new_excerpts.update_last(
+ |excerpt| {
+ if excerpt.has_trailing_newline {
+ before.0.0 = before
+ .0
+ .0
+ .checked_sub(1)
+ .expect("should have preceding excerpt");
+ excerpt.has_trailing_newline = false;
+ }
+ },
+ (),
+ );
+ }
+ patch.push(Edit {
+ old: before..after,
+ new: new_excerpts.summary().len()..new_excerpts.summary().len(),
+ });
- // No more new ranges; remove the remaining existing excerpt.
- (None, Some((existing_id, _))) => {
- existing_iter.next();
- to_remove.push(existing_id);
- }
+ while let Some(next_excerpt) = to_insert.next() {
+ added_new_excerpt = true;
+ let before = new_excerpts.summary().len();
+ new_excerpts.update_last(
+ |prev_excerpt| {
+ prev_excerpt.has_trailing_newline = true;
+ },
+ (),
+ );
+ new_excerpts.push(
+ Excerpt::new(
+ path_key.clone(),
+ path_key_index,
+ &buffer_snapshot,
+ next_excerpt.clone(),
+ false,
+ ),
+ (),
+ );
+ let after = new_excerpts.summary().len();
+ patch.push_maybe_empty(Edit {
+ old: cursor.position.1..cursor.position.1,
+ new: before..after,
+ });
+ }
- // No more existing excerpts; queue the new range for insertion.
- (Some(_), None) => {
- added_a_new_excerpt = true;
- let new_id = next_excerpt_id();
- excerpt_ids.push(new_id);
- to_insert.push((new_id, new_iter.next().unwrap()));
- }
+ let suffix_start = cursor.position.1;
+ let suffix = cursor.suffix();
+ let changed_trailing_excerpt = suffix.is_empty();
+ if !suffix.is_empty() {
+ let before = new_excerpts.summary().len();
+ new_excerpts.update_last(
+ |prev_excerpt| {
+ if !prev_excerpt.has_trailing_newline {
+ prev_excerpt.has_trailing_newline = true;
+ patch.push(Edit {
+ old: suffix_start..suffix_start,
+ new: before..before + MultiBufferOffset(1),
+ });
+ }
+ },
+ (),
+ );
+ }
+ new_excerpts.append(suffix, ());
+ drop(cursor);
+
+ snapshot.excerpts = new_excerpts;
+ snapshot.buffers.insert(
+ buffer_id,
+ BufferStateSnapshot {
+ path_key: path_key.clone(),
+ path_key_index,
+ buffer_snapshot: buffer_snapshot.clone(),
+ },
+ );
+
+ self.buffers.entry(buffer_id).or_insert_with(|| {
+ self.buffer_changed_since_sync.replace(true);
+ buffer.update(cx, |buffer, _| {
+ buffer.record_changes(Rc::downgrade(&self.buffer_changed_since_sync));
+ });
+ BufferState {
+ _subscriptions: [
+ cx.observe(&buffer, |_, _, cx| cx.notify()),
+ cx.subscribe(&buffer, Self::on_buffer_event),
+ ],
+ buffer: buffer.clone(),
+ }
+ });
- // Existing excerpt ends before the new range starts, so it
- // has no corresponding new range and must be removed. Flush
- // pending inserts and advance `insert_after` past it so that
- // future inserts receive locators *after* this excerpt's
- // locator, preserving forward ordering.
- (Some(new), Some((_, existing_range)))
- if existing_range.end < new.context.start =>
- {
- self.insert_excerpts_with_ids_after(
- insert_after,
- buffer.clone(),
- mem::take(&mut to_insert),
- cx,
- );
- insert_after = existing_iter.next().unwrap();
- to_remove.push(insert_after);
- }
- // New range ends before the existing excerpt starts, so the
- // new range has no corresponding existing excerpt. Queue it
- // for insertion at the current `insert_after` position
- // (before the existing excerpt), which is the correct
- // spatial ordering.
- (Some(new), Some((_, existing_range)))
- if existing_range.start > new.context.end =>
- {
- let new_id = next_excerpt_id();
- excerpt_ids.push(new_id);
- to_insert.push((new_id, new_iter.next().unwrap()));
- }
- // Exact match: keep the existing excerpt in place, flush
- // any pending inserts before it, and use it as the new
- // `insert_after` anchor.
- (Some(new), Some((_, existing_range)))
- if existing_range.start == new.context.start
- && existing_range.end == new.context.end =>
- {
- self.insert_excerpts_with_ids_after(
- insert_after,
- buffer.clone(),
- mem::take(&mut to_insert),
- cx,
- );
- insert_after = existing_iter.next().unwrap();
- excerpt_ids.push(insert_after);
- new_iter.next();
- }
+ if changed_trailing_excerpt {
+ snapshot.trailing_excerpt_update_count += 1;
+ }
- // Partial overlap: replace the existing excerpt with a new
- // one whose range is the union of both, and record the
- // replacement so that anchors in the old excerpt resolve to
- // the new one.
- (Some(_), Some((_, existing_range))) => {
- let existing_id = existing_iter.next().unwrap();
- let new_id = next_excerpt_id();
- Arc::make_mut(&mut self.snapshot.get_mut().replaced_excerpts)
- .insert(existing_id, new_id);
- to_remove.push(existing_id);
- let mut range = new_iter.next().unwrap();
- range.context.start = range.context.start.min(existing_range.start);
- range.context.end = range.context.end.max(existing_range.end);
- excerpt_ids.push(new_id);
- to_insert.push((new_id, range));
- }
- };
+ let edits = Self::sync_diff_transforms(
+ &mut snapshot,
+ patch.into_inner(),
+ DiffChangeKind::BufferEdited,
+ );
+ if !edits.is_empty() {
+ self.subscriptions.publish(edits);
}
- self.insert_excerpts_with_ids_after(insert_after, buffer, to_insert, cx);
- // todo(lw): There is a logic bug somewhere that causes the to_remove vector to be not ordered correctly
- to_remove.sort_by_cached_key(|&id| snapshot.excerpt_locator_for_id(id));
- self.remove_excerpts(to_remove, cx);
+ cx.emit(Event::Edited {
+ edited_buffer: None,
+ is_local: true,
+ });
+ cx.emit(Event::BufferRangesUpdated {
+ buffer,
+ path_key: path_key.clone(),
+ ranges: new_ranges,
+ });
+ cx.notify();
- if excerpt_ids.is_empty() {
- self.excerpts_by_path.remove(&path);
- } else {
- let snapshot = &*self.snapshot.get_mut();
- let excerpt_ids = excerpt_ids
- .iter()
- .dedup()
- .cloned()
- // todo(lw): There is a logic bug somewhere that causes excerpt_ids to not necessarily be in order by locator
- .sorted_by_cached_key(|&id| snapshot.excerpt_locator_for_id(id))
- .collect();
- for &excerpt_id in &excerpt_ids {
- self.paths_by_excerpt.insert(excerpt_id, path.clone());
- }
- self.excerpts_by_path.insert(path, excerpt_ids);
+ added_new_excerpt
+ }
+
+ 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 {
+ return;
+ };
+ self.remove_excerpts(path, cx);
+ }
+
+ pub fn remove_excerpts(&mut self, path: PathKey, cx: &mut Context<Self>) {
+ assert_eq!(self.history.transaction_depth(), 0);
+ self.sync_mut(cx);
+
+ let mut snapshot = self.snapshot.get_mut();
+ let mut cursor = snapshot
+ .excerpts
+ .cursor::<Dimensions<PathKey, ExcerptOffset>>(());
+ let mut new_excerpts = SumTree::new(());
+ new_excerpts.append(cursor.slice(&path, Bias::Left), ());
+ let mut edit_start = cursor.position.1;
+ let mut buffer_id = None;
+ if let Some(excerpt) = cursor.item()
+ && excerpt.path_key == path
+ {
+ buffer_id = Some(excerpt.buffer_id);
}
+ cursor.seek(&path, Bias::Right);
+ let edit_end = cursor.position.1;
+ let suffix = cursor.suffix();
+ let changed_trailing_excerpt = suffix.is_empty();
+ new_excerpts.append(suffix, ());
+
+ if let Some(buffer_id) = buffer_id {
+ 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: vec![buffer_id],
+ })
+ }
+ drop(cursor);
+ 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;
+ edit_start.0.0 = edit_start
+ .0
+ .0
+ .checked_sub(1)
+ .expect("should have at least one excerpt");
+ }
+ },
+ (),
+ )
+ }
+
+ let edit = Edit {
+ old: edit_start..edit_end,
+ new: edit_start..edit_start,
+ };
+ snapshot.excerpts = new_excerpts;
- PathExcerptInsertResult {
- excerpt_ids,
- added_new_excerpt: added_a_new_excerpt,
+ let edits =
+ Self::sync_diff_transforms(&mut snapshot, vec![edit], DiffChangeKind::BufferEdited);
+ if !edits.is_empty() {
+ self.subscriptions.publish(edits);
}
+
+ cx.emit(Event::Edited {
+ edited_buffer: None,
+ is_local: true,
+ });
+ cx.notify();
}
}
@@ -2,15 +2,15 @@ use gpui::{App, Context, Entity};
use language::{self, Buffer, TransactionId};
use std::{
collections::HashMap,
- ops::{AddAssign, Range, Sub},
+ ops::Range,
time::{Duration, Instant},
};
use sum_tree::Bias;
use text::BufferId;
-use crate::{BufferState, MultiBufferDimension};
+use crate::{Anchor, BufferState, MultiBufferOffset};
-use super::{Event, ExcerptSummary, MultiBuffer};
+use super::{Event, MultiBuffer};
#[derive(Clone)]
pub(super) struct History {
@@ -314,71 +314,50 @@ impl MultiBuffer {
}
}
- pub fn edited_ranges_for_transaction<D>(
+ pub fn edited_ranges_for_transaction(
&self,
transaction_id: TransactionId,
cx: &App,
- ) -> Vec<Range<D>>
- where
- D: MultiBufferDimension
- + Ord
- + Sub<D, Output = D::TextDimension>
- + AddAssign<D::TextDimension>,
- D::TextDimension: PartialOrd + Sub<D::TextDimension, Output = D::TextDimension>,
- {
+ ) -> Vec<Range<MultiBufferOffset>> {
let Some(transaction) = self.history.transaction(transaction_id) else {
return Vec::new();
};
- let mut ranges = Vec::new();
let snapshot = self.read(cx);
- let mut cursor = snapshot.excerpts.cursor::<ExcerptSummary>(());
+ let mut buffer_anchors = Vec::new();
for (buffer_id, buffer_transaction) in &transaction.buffer_transactions {
- let Some(buffer_state) = self.buffers.get(buffer_id) else {
+ let Some(buffer) = self.buffer(*buffer_id) else {
continue;
};
+ let Some(excerpt) = snapshot.first_excerpt_for_buffer(*buffer_id) else {
+ continue;
+ };
+ let buffer_snapshot = buffer.read(cx).snapshot();
- let buffer = buffer_state.buffer.read(cx);
- for range in
- buffer.edited_ranges_for_transaction_id::<D::TextDimension>(*buffer_transaction)
+ for range in buffer
+ .read(cx)
+ .edited_ranges_for_transaction_id::<usize>(*buffer_transaction)
{
- for excerpt_id in &buffer_state.excerpts {
- cursor.seek(excerpt_id, Bias::Left);
- if let Some(excerpt) = cursor.item()
- && excerpt.locator == *excerpt_id
- {
- let excerpt_buffer_start = excerpt
- .range
- .context
- .start
- .summary::<D::TextDimension>(buffer);
- let excerpt_buffer_end = excerpt
- .range
- .context
- .end
- .summary::<D::TextDimension>(buffer);
- let excerpt_range = excerpt_buffer_start..excerpt_buffer_end;
- if excerpt_range.contains(&range.start)
- && excerpt_range.contains(&range.end)
- {
- let excerpt_start = D::from_summary(&cursor.start().text);
-
- let mut start = excerpt_start;
- start += range.start - excerpt_buffer_start;
- let mut end = excerpt_start;
- end += range.end - excerpt_buffer_start;
-
- ranges.push(start..end);
- break;
- }
- }
- }
+ buffer_anchors.push(Anchor::in_buffer(
+ excerpt.path_key_index,
+ buffer_snapshot.anchor_at(range.start, Bias::Left),
+ ));
+ buffer_anchors.push(Anchor::in_buffer(
+ excerpt.path_key_index,
+ buffer_snapshot.anchor_at(range.end, Bias::Right),
+ ));
}
}
+ buffer_anchors.sort_unstable_by(|a, b| a.cmp(b, &snapshot));
- ranges.sort_by_key(|range| range.start);
- ranges
+ snapshot
+ .summaries_for_anchors(buffer_anchors.iter())
+ .as_chunks::<2>()
+ .0
+ .iter()
+ .map(|&[s, e]| s..e)
+ .collect::<Vec<_>>()
}
pub fn merge_transactions(
@@ -79,29 +79,37 @@ fn outline_for_editor(
cx: &mut App,
) -> Option<Task<Vec<OutlineItem<Anchor>>>> {
let multibuffer = editor.read(cx).buffer().read(cx).snapshot(cx);
- let (excerpt_id, _, buffer_snapshot) = multibuffer.as_singleton()?;
+ let buffer_snapshot = multibuffer.as_singleton()?;
let buffer_id = buffer_snapshot.remote_id();
let task = editor.update(cx, |editor, cx| editor.buffer_outline_items(buffer_id, cx));
Some(cx.background_executor().spawn(async move {
task.await
.into_iter()
- .map(|item| OutlineItem {
- depth: item.depth,
- range: Anchor::range_in_buffer(excerpt_id, item.range),
- source_range_for_text: Anchor::range_in_buffer(
- excerpt_id,
- item.source_range_for_text,
- ),
- text: item.text,
- highlight_ranges: item.highlight_ranges,
- name_ranges: item.name_ranges,
- body_range: item
- .body_range
- .map(|r| Anchor::range_in_buffer(excerpt_id, r)),
- annotation_range: item
- .annotation_range
- .map(|r| Anchor::range_in_buffer(excerpt_id, r)),
+ .filter_map(|item| {
+ Some(OutlineItem {
+ depth: item.depth,
+ range: multibuffer.anchor_in_buffer(item.range.start)?
+ ..multibuffer.anchor_in_buffer(item.range.end)?,
+ source_range_for_text: multibuffer
+ .anchor_in_buffer(item.source_range_for_text.start)?
+ ..multibuffer.anchor_in_buffer(item.source_range_for_text.end)?,
+ text: item.text,
+ highlight_ranges: item.highlight_ranges,
+ name_ranges: item.name_ranges,
+ body_range: item.body_range.and_then(|r| {
+ Some(
+ multibuffer.anchor_in_buffer(r.start)?
+ ..multibuffer.anchor_in_buffer(r.end)?,
+ )
+ }),
+ annotation_range: item.annotation_range.and_then(|r| {
+ Some(
+ multibuffer.anchor_in_buffer(r.start)?
+ ..multibuffer.anchor_in_buffer(r.end)?,
+ )
+ }),
+ })
})
.collect()
}))
@@ -1,11 +1,11 @@
mod outline_panel_settings;
use anyhow::Context as _;
-use collections::{BTreeSet, HashMap, HashSet, hash_map};
+use collections::{BTreeSet, HashMap, HashSet};
use db::kvp::KeyValueStore;
use editor::{
- AnchorRangeExt, Bias, DisplayPoint, Editor, EditorEvent, ExcerptId, ExcerptRange,
- MultiBufferSnapshot, RangeToAnchorExt, SelectionEffects,
+ AnchorRangeExt, Bias, DisplayPoint, Editor, EditorEvent, ExcerptRange, MultiBufferSnapshot,
+ RangeToAnchorExt, SelectionEffects,
display_map::ToDisplayPoint,
items::{entry_git_aware_label_color, entry_label_color},
scroll::{Autoscroll, ScrollAnchor},
@@ -129,12 +129,12 @@ pub struct OutlinePanel {
selected_entry: SelectedEntry,
active_item: Option<ActiveItem>,
_subscriptions: Vec<Subscription>,
- new_entries_for_fs_update: HashSet<ExcerptId>,
+ new_entries_for_fs_update: HashSet<BufferId>,
fs_entries_update_task: Task<()>,
cached_entries_update_task: Task<()>,
reveal_selection_task: Task<anyhow::Result<()>>,
outline_fetch_tasks: HashMap<BufferId, Task<()>>,
- excerpts: HashMap<BufferId, HashMap<ExcerptId, Excerpt>>,
+ buffers: HashMap<BufferId, BufferOutlines>,
cached_entries: Vec<CachedEntry>,
filter_editor: Entity<Editor>,
mode: ItemsDisplayMode,
@@ -334,42 +334,41 @@ enum CollapsedEntry {
Dir(WorktreeId, ProjectEntryId),
File(WorktreeId, BufferId),
ExternalFile(BufferId),
- Excerpt(BufferId, ExcerptId),
- Outline(BufferId, ExcerptId, Range<Anchor>),
+ Excerpt(ExcerptRange<Anchor>),
+ Outline(Range<Anchor>),
}
-#[derive(Debug)]
-struct Excerpt {
- range: ExcerptRange<language::Anchor>,
- outlines: ExcerptOutlines,
+struct BufferOutlines {
+ excerpts: Vec<ExcerptRange<Anchor>>,
+ outlines: OutlineState,
}
-impl Excerpt {
+impl BufferOutlines {
fn invalidate_outlines(&mut self) {
- if let ExcerptOutlines::Outlines(valid_outlines) = &mut self.outlines {
- self.outlines = ExcerptOutlines::Invalidated(std::mem::take(valid_outlines));
+ if let OutlineState::Outlines(valid_outlines) = &mut self.outlines {
+ self.outlines = OutlineState::Invalidated(std::mem::take(valid_outlines));
}
}
fn iter_outlines(&self) -> impl Iterator<Item = &Outline> {
match &self.outlines {
- ExcerptOutlines::Outlines(outlines) => outlines.iter(),
- ExcerptOutlines::Invalidated(outlines) => outlines.iter(),
- ExcerptOutlines::NotFetched => [].iter(),
+ OutlineState::Outlines(outlines) => outlines.iter(),
+ OutlineState::Invalidated(outlines) => outlines.iter(),
+ OutlineState::NotFetched => [].iter(),
}
}
fn should_fetch_outlines(&self) -> bool {
match &self.outlines {
- ExcerptOutlines::Outlines(_) => false,
- ExcerptOutlines::Invalidated(_) => true,
- ExcerptOutlines::NotFetched => true,
+ OutlineState::Outlines(_) => false,
+ OutlineState::Invalidated(_) => true,
+ OutlineState::NotFetched => true,
}
}
}
#[derive(Debug)]
-enum ExcerptOutlines {
+enum OutlineState {
Outlines(Vec<Outline>),
Invalidated(Vec<Outline>),
NotFetched,
@@ -536,54 +535,24 @@ impl SearchData {
}
}
-#[derive(Clone, Debug, PartialEq, Eq, Hash)]
-struct OutlineEntryExcerpt {
- id: ExcerptId,
- buffer_id: BufferId,
- range: ExcerptRange<language::Anchor>,
-}
-
-#[derive(Clone, Debug, Eq)]
-struct OutlineEntryOutline {
- buffer_id: BufferId,
- excerpt_id: ExcerptId,
- outline: Outline,
-}
-
-impl PartialEq for OutlineEntryOutline {
- fn eq(&self, other: &Self) -> bool {
- self.buffer_id == other.buffer_id
- && self.excerpt_id == other.excerpt_id
- && self.outline.depth == other.outline.depth
- && self.outline.range == other.outline.range
- && self.outline.text == other.outline.text
- }
-}
-
-impl Hash for OutlineEntryOutline {
- fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
- (
- self.buffer_id,
- self.excerpt_id,
- self.outline.depth,
- &self.outline.range,
- &self.outline.text,
- )
- .hash(state);
- }
-}
-
#[derive(Clone, Debug, PartialEq, Eq)]
enum OutlineEntry {
- Excerpt(OutlineEntryExcerpt),
- Outline(OutlineEntryOutline),
+ Excerpt(ExcerptRange<Anchor>),
+ Outline(Outline),
}
impl OutlineEntry {
- fn ids(&self) -> (BufferId, ExcerptId) {
+ fn buffer_id(&self) -> BufferId {
match self {
- OutlineEntry::Excerpt(excerpt) => (excerpt.buffer_id, excerpt.id),
- OutlineEntry::Outline(outline) => (outline.buffer_id, outline.excerpt_id),
+ OutlineEntry::Excerpt(excerpt) => excerpt.context.start.buffer_id,
+ OutlineEntry::Outline(outline) => outline.range.start.buffer_id,
+ }
+ }
+
+ fn range(&self) -> Range<Anchor> {
+ match self {
+ OutlineEntry::Excerpt(excerpt) => excerpt.context.clone(),
+ OutlineEntry::Outline(outline) => outline.range.clone(),
}
}
}
@@ -593,7 +562,7 @@ struct FsEntryFile {
worktree_id: WorktreeId,
entry: GitEntry,
buffer_id: BufferId,
- excerpts: Vec<ExcerptId>,
+ excerpts: Vec<ExcerptRange<language::Anchor>>,
}
impl PartialEq for FsEntryFile {
@@ -631,7 +600,7 @@ impl Hash for FsEntryDirectory {
#[derive(Debug, Clone, Eq)]
struct FsEntryExternalFile {
buffer_id: BufferId,
- excerpts: Vec<ExcerptId>,
+ excerpts: Vec<ExcerptRange<language::Anchor>>,
}
impl PartialEq for FsEntryExternalFile {
@@ -787,10 +756,8 @@ impl OutlinePanel {
if ¤t_theme != new_theme {
outline_panel_settings = *new_settings;
current_theme = new_theme.clone();
- for excerpts in outline_panel.excerpts.values_mut() {
- for excerpt in excerpts.values_mut() {
- excerpt.invalidate_outlines();
- }
+ for buffer in outline_panel.buffers.values_mut() {
+ buffer.invalidate_outlines();
}
outlines_invalidated = true;
let update_cached_items = outline_panel.update_non_fs_items(window, cx);
@@ -809,30 +776,23 @@ impl OutlinePanel {
let new_depth = new_settings.expand_outlines_with_depth;
- for (buffer_id, excerpts) in &outline_panel.excerpts {
- for (excerpt_id, excerpt) in excerpts {
- if let ExcerptOutlines::Outlines(outlines) = &excerpt.outlines {
- for outline in outlines {
- if outline_panel
- .outline_children_cache
- .get(buffer_id)
- .and_then(|children_map| {
- let key =
- (outline.range.clone(), outline.depth);
- children_map.get(&key)
- })
- .copied()
- .unwrap_or(false)
- && (new_depth == 0 || outline.depth >= new_depth)
- {
- outline_panel.collapsed_entries.insert(
- CollapsedEntry::Outline(
- *buffer_id,
- *excerpt_id,
- outline.range.clone(),
- ),
- );
- }
+ for (buffer_id, buffer) in &outline_panel.buffers {
+ if let OutlineState::Outlines(outlines) = &buffer.outlines {
+ for outline in outlines {
+ if outline_panel
+ .outline_children_cache
+ .get(buffer_id)
+ .and_then(|children_map| {
+ let key = (outline.range.clone(), outline.depth);
+ children_map.get(&key)
+ })
+ .copied()
+ .unwrap_or(false)
+ && (new_depth == 0 || outline.depth >= new_depth)
+ {
+ outline_panel.collapsed_entries.insert(
+ CollapsedEntry::Outline(outline.range.clone()),
+ );
}
}
}
@@ -852,7 +812,7 @@ impl OutlinePanel {
if !outlines_invalidated {
let new_document_symbols = outline_panel
- .excerpts
+ .buffers
.keys()
.filter_map(|buffer_id| {
let buffer = outline_panel
@@ -867,10 +827,8 @@ impl OutlinePanel {
.collect();
if new_document_symbols != document_symbols_by_buffer {
document_symbols_by_buffer = new_document_symbols;
- for excerpts in outline_panel.excerpts.values_mut() {
- for excerpt in excerpts.values_mut() {
- excerpt.invalidate_outlines();
- }
+ for buffer in outline_panel.buffers.values_mut() {
+ buffer.invalidate_outlines();
}
let update_cached_items = outline_panel.update_non_fs_items(window, cx);
if update_cached_items {
@@ -914,7 +872,7 @@ impl OutlinePanel {
cached_entries_update_task: Task::ready(()),
reveal_selection_task: Task::ready(Ok(())),
outline_fetch_tasks: HashMap::default(),
- excerpts: HashMap::default(),
+ buffers: HashMap::default(),
cached_entries: Vec::new(),
_subscriptions: vec![
settings_subscription,
@@ -1110,16 +1068,13 @@ impl OutlinePanel {
PanelEntry::Fs(FsEntry::ExternalFile(file)) => {
change_selection = false;
scroll_to_buffer = Some(file.buffer_id);
- multi_buffer_snapshot.excerpts().find_map(
- |(excerpt_id, buffer_snapshot, excerpt_range)| {
- if buffer_snapshot.remote_id() == file.buffer_id {
- multi_buffer_snapshot
- .anchor_in_excerpt(excerpt_id, excerpt_range.context.start)
- } else {
- None
- }
- },
- )
+ multi_buffer_snapshot.excerpts().find_map(|excerpt_range| {
+ if excerpt_range.context.start.buffer_id == file.buffer_id {
+ multi_buffer_snapshot.anchor_in_excerpt(excerpt_range.context.start)
+ } else {
+ None
+ }
+ })
}
PanelEntry::Fs(FsEntry::File(file)) => {
@@ -1132,26 +1087,20 @@ impl OutlinePanel {
.and_then(|path| project.get_open_buffer(&path, cx))
})
.map(|buffer| {
- active_multi_buffer
- .read(cx)
- .excerpts_for_buffer(buffer.read(cx).remote_id(), cx)
+ multi_buffer_snapshot.excerpts_for_buffer(buffer.read(cx).remote_id())
})
- .and_then(|excerpts| {
- let (excerpt_id, _, excerpt_range) = excerpts.first()?;
- multi_buffer_snapshot
- .anchor_in_excerpt(*excerpt_id, excerpt_range.context.start)
+ .and_then(|mut excerpts| {
+ let excerpt_range = excerpts.next()?;
+ multi_buffer_snapshot.anchor_in_excerpt(excerpt_range.context.start)
})
}
PanelEntry::Outline(OutlineEntry::Outline(outline)) => multi_buffer_snapshot
- .anchor_in_excerpt(outline.excerpt_id, outline.outline.range.start)
- .or_else(|| {
- multi_buffer_snapshot
- .anchor_in_excerpt(outline.excerpt_id, outline.outline.range.end)
- }),
+ .anchor_in_excerpt(outline.range.start)
+ .or_else(|| multi_buffer_snapshot.anchor_in_excerpt(outline.range.end)),
PanelEntry::Outline(OutlineEntry::Excerpt(excerpt)) => {
change_selection = false;
change_focus = false;
- multi_buffer_snapshot.anchor_in_excerpt(excerpt.id, excerpt.range.context.start)
+ multi_buffer_snapshot.anchor_in_excerpt(excerpt.context.start)
}
PanelEntry::Search(search_entry) => Some(search_entry.match_range.start),
};
@@ -1359,12 +1308,12 @@ impl OutlinePanel {
PanelEntry::Outline(OutlineEntry::Excerpt(excerpt)) => {
previous_entries.find(|entry| match entry {
PanelEntry::Fs(FsEntry::File(file)) => {
- file.buffer_id == excerpt.buffer_id
- && file.excerpts.contains(&excerpt.id)
+ file.buffer_id == excerpt.context.start.buffer_id
+ && file.excerpts.contains(&excerpt)
}
PanelEntry::Fs(FsEntry::ExternalFile(external_file)) => {
- external_file.buffer_id == excerpt.buffer_id
- && external_file.excerpts.contains(&excerpt.id)
+ external_file.buffer_id == excerpt.context.start.buffer_id
+ && external_file.excerpts.contains(&excerpt)
}
_ => false,
})
@@ -1372,8 +1321,16 @@ impl OutlinePanel {
PanelEntry::Outline(OutlineEntry::Outline(outline)) => {
previous_entries.find(|entry| {
if let PanelEntry::Outline(OutlineEntry::Excerpt(excerpt)) = entry {
- outline.buffer_id == excerpt.buffer_id
- && outline.excerpt_id == excerpt.id
+ if outline.range.start.buffer_id != excerpt.context.start.buffer_id {
+ return false;
+ }
+ let Some(buffer_snapshot) =
+ self.buffer_snapshot_for_id(outline.range.start.buffer_id, cx)
+ else {
+ return false;
+ };
+ excerpt.contains(&outline.range.start, &buffer_snapshot)
+ || excerpt.contains(&outline.range.end, &buffer_snapshot)
} else {
false
}
@@ -1584,13 +1541,11 @@ impl OutlinePanel {
Some(CollapsedEntry::ExternalFile(external_file.buffer_id))
}
PanelEntry::Outline(OutlineEntry::Excerpt(excerpt)) => {
- Some(CollapsedEntry::Excerpt(excerpt.buffer_id, excerpt.id))
+ Some(CollapsedEntry::Excerpt(excerpt.clone()))
+ }
+ PanelEntry::Outline(OutlineEntry::Outline(outline)) => {
+ Some(CollapsedEntry::Outline(outline.range.clone()))
}
- PanelEntry::Outline(OutlineEntry::Outline(outline)) => Some(CollapsedEntry::Outline(
- outline.buffer_id,
- outline.excerpt_id,
- outline.outline.range.clone(),
- )),
PanelEntry::Search(_) => return,
};
let Some(collapsed_entry) = entry_to_expand else {
@@ -1691,14 +1646,10 @@ impl OutlinePanel {
}
PanelEntry::Outline(OutlineEntry::Excerpt(excerpt)) => self
.collapsed_entries
- .insert(CollapsedEntry::Excerpt(excerpt.buffer_id, excerpt.id)),
- PanelEntry::Outline(OutlineEntry::Outline(outline)) => {
- self.collapsed_entries.insert(CollapsedEntry::Outline(
- outline.buffer_id,
- outline.excerpt_id,
- outline.outline.range.clone(),
- ))
- }
+ .insert(CollapsedEntry::Excerpt(excerpt.clone())),
+ PanelEntry::Outline(OutlineEntry::Outline(outline)) => self
+ .collapsed_entries
+ .insert(CollapsedEntry::Outline(outline.range.clone())),
PanelEntry::Search(_) => false,
};
@@ -1753,31 +1704,26 @@ impl OutlinePanel {
}
}
- for (&buffer_id, excerpts) in &self.excerpts {
- for (&excerpt_id, excerpt) in excerpts {
- match &excerpt.outlines {
- ExcerptOutlines::Outlines(outlines) => {
- for outline in outlines {
- to_uncollapse.insert(CollapsedEntry::Outline(
- buffer_id,
- excerpt_id,
- outline.range.clone(),
- ));
- }
+ for (_buffer_id, buffer) in &self.buffers {
+ match &buffer.outlines {
+ OutlineState::Outlines(outlines) => {
+ for outline in outlines {
+ to_uncollapse.insert(CollapsedEntry::Outline(outline.range.clone()));
}
- ExcerptOutlines::Invalidated(outlines) => {
- for outline in outlines {
- to_uncollapse.insert(CollapsedEntry::Outline(
- buffer_id,
- excerpt_id,
- outline.range.clone(),
- ));
- }
+ }
+ OutlineState::Invalidated(outlines) => {
+ for outline in outlines {
+ to_uncollapse.insert(CollapsedEntry::Outline(outline.range.clone()));
}
- ExcerptOutlines::NotFetched => {}
}
- to_uncollapse.insert(CollapsedEntry::Excerpt(buffer_id, excerpt_id));
+ OutlineState::NotFetched => {}
}
+ to_uncollapse.extend(
+ buffer
+ .excerpts
+ .iter()
+ .map(|excerpt| CollapsedEntry::Excerpt(excerpt.clone())),
+ );
}
for cached in &self.cached_entries {
@@ -1844,14 +1790,10 @@ impl OutlinePanel {
..
}) => Some(CollapsedEntry::Dir(*worktree_id, entries.last()?.id)),
PanelEntry::Outline(OutlineEntry::Excerpt(excerpt)) => {
- Some(CollapsedEntry::Excerpt(excerpt.buffer_id, excerpt.id))
+ Some(CollapsedEntry::Excerpt(excerpt.clone()))
}
PanelEntry::Outline(OutlineEntry::Outline(outline)) => {
- Some(CollapsedEntry::Outline(
- outline.buffer_id,
- outline.excerpt_id,
- outline.outline.range.clone(),
- ))
+ Some(CollapsedEntry::Outline(outline.range.clone()))
}
PanelEntry::Search(_) => None,
},
@@ -1939,17 +1881,13 @@ impl OutlinePanel {
}
}
PanelEntry::Outline(OutlineEntry::Excerpt(excerpt)) => {
- let collapsed_entry = CollapsedEntry::Excerpt(excerpt.buffer_id, excerpt.id);
+ let collapsed_entry = CollapsedEntry::Excerpt(excerpt.clone());
if !self.collapsed_entries.remove(&collapsed_entry) {
self.collapsed_entries.insert(collapsed_entry);
}
}
PanelEntry::Outline(OutlineEntry::Outline(outline)) => {
- let collapsed_entry = CollapsedEntry::Outline(
- outline.buffer_id,
- outline.excerpt_id,
- outline.outline.range.clone(),
- );
+ let collapsed_entry = CollapsedEntry::Outline(outline.range.clone());
if !self.collapsed_entries.remove(&collapsed_entry) {
self.collapsed_entries.insert(collapsed_entry);
}
@@ -2103,6 +2041,8 @@ impl OutlinePanel {
let project = self.project.clone();
self.reveal_selection_task = cx.spawn_in(window, async move |outline_panel, cx| {
cx.background_executor().timer(UPDATE_DEBOUNCE).await;
+ let multibuffer_snapshot =
+ editor.read_with(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx));
let entry_with_selection =
outline_panel.update_in(cx, |outline_panel, window, cx| {
outline_panel.location_for_editor_selection(&editor, window, cx)
@@ -2132,14 +2072,28 @@ impl OutlinePanel {
})
}),
PanelEntry::Outline(outline_entry) => {
- let (buffer_id, excerpt_id) = outline_entry.ids();
+ let buffer_id = outline_entry.buffer_id();
+ let outline_range = outline_entry.range();
outline_panel.update(cx, |outline_panel, cx| {
outline_panel
.collapsed_entries
.remove(&CollapsedEntry::ExternalFile(buffer_id));
- outline_panel
- .collapsed_entries
- .remove(&CollapsedEntry::Excerpt(buffer_id, excerpt_id));
+ if let Some(buffer_snapshot) =
+ outline_panel.buffer_snapshot_for_id(buffer_id, cx)
+ {
+ outline_panel.collapsed_entries.retain(|entry| match entry {
+ CollapsedEntry::Excerpt(excerpt_range) => {
+ let intersects = excerpt_range.context.start.buffer_id
+ == buffer_id
+ && (excerpt_range
+ .contains(&outline_range.start, &buffer_snapshot)
+ || excerpt_range
+ .contains(&outline_range.end, &buffer_snapshot));
+ !intersects
+ }
+ _ => true,
+ });
+ }
let project = outline_panel.project.read(cx);
let entry_id = project
.buffer_for_id(buffer_id, cx)
@@ -2160,11 +2114,9 @@ impl OutlinePanel {
})?
}
PanelEntry::Fs(FsEntry::ExternalFile(..)) => None,
- PanelEntry::Search(SearchEntry { match_range, .. }) => match_range
- .start
- .text_anchor
- .buffer_id
- .or(match_range.end.text_anchor.buffer_id)
+ PanelEntry::Search(SearchEntry { match_range, .. }) => multibuffer_snapshot
+ .anchor_to_buffer_anchor(match_range.start)
+ .map(|(anchor, _)| anchor.buffer_id)
.map(|buffer_id| {
outline_panel.update(cx, |outline_panel, cx| {
outline_panel
@@ -2246,30 +2198,30 @@ impl OutlinePanel {
fn render_excerpt(
&self,
- excerpt: &OutlineEntryExcerpt,
+ excerpt: &ExcerptRange<Anchor>,
depth: usize,
window: &mut Window,
cx: &mut Context<OutlinePanel>,
) -> Option<Stateful<Div>> {
- let item_id = ElementId::from(excerpt.id.to_proto() as usize);
+ let item_id = ElementId::from(format!("{excerpt:?}"));
let is_active = match self.selected_entry() {
Some(PanelEntry::Outline(OutlineEntry::Excerpt(selected_excerpt))) => {
- selected_excerpt.buffer_id == excerpt.buffer_id && selected_excerpt.id == excerpt.id
+ selected_excerpt == excerpt
}
_ => false,
};
let has_outlines = self
- .excerpts
- .get(&excerpt.buffer_id)
- .and_then(|excerpts| match &excerpts.get(&excerpt.id)?.outlines {
- ExcerptOutlines::Outlines(outlines) => Some(outlines),
- ExcerptOutlines::Invalidated(outlines) => Some(outlines),
- ExcerptOutlines::NotFetched => None,
+ .buffers
+ .get(&excerpt.context.start.buffer_id)
+ .and_then(|buffer| match &buffer.outlines {
+ OutlineState::Outlines(outlines) => Some(outlines),
+ OutlineState::Invalidated(outlines) => Some(outlines),
+ OutlineState::NotFetched => None,
})
.is_some_and(|outlines| !outlines.is_empty());
let is_expanded = !self
.collapsed_entries
- .contains(&CollapsedEntry::Excerpt(excerpt.buffer_id, excerpt.id));
+ .contains(&CollapsedEntry::Excerpt(excerpt.clone()));
let color = entry_label_color(is_active);
let icon = if has_outlines {
FileIcons::get_chevron_icon(is_expanded, cx)
@@ -2279,7 +2231,7 @@ impl OutlinePanel {
}
.unwrap_or_else(empty_icon);
- let label = self.excerpt_label(excerpt.buffer_id, &excerpt.range, cx)?;
+ let label = self.excerpt_label(&excerpt, cx)?;
let label_element = Label::new(label)
.single_line()
.color(color)
@@ -2297,13 +2249,8 @@ impl OutlinePanel {
))
}
- fn excerpt_label(
- &self,
- buffer_id: BufferId,
- range: &ExcerptRange<language::Anchor>,
- cx: &App,
- ) -> Option<String> {
- let buffer_snapshot = self.buffer_snapshot_for_id(buffer_id, cx)?;
+ fn excerpt_label(&self, range: &ExcerptRange<language::Anchor>, cx: &App) -> Option<String> {
+ let buffer_snapshot = self.buffer_snapshot_for_id(range.context.start.buffer_id, cx)?;
let excerpt_range = range.context.to_point(&buffer_snapshot);
Some(format!(
"Lines {}- {}",
@@ -2314,19 +2261,19 @@ impl OutlinePanel {
fn render_outline(
&self,
- outline: &OutlineEntryOutline,
+ outline: &Outline,
depth: usize,
string_match: Option<&StringMatch>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Stateful<Div> {
let item_id = ElementId::from(SharedString::from(format!(
- "{:?}|{:?}{:?}|{:?}",
- outline.buffer_id, outline.excerpt_id, outline.outline.range, &outline.outline.text,
+ "{:?}|{:?}",
+ outline.range, &outline.text,
)));
let label_element = outline::render_item(
- &outline.outline,
+ &outline,
string_match
.map(|string_match| string_match.ranges().collect::<Vec<_>>())
.unwrap_or_default(),
@@ -2335,26 +2282,22 @@ impl OutlinePanel {
.into_any_element();
let is_active = match self.selected_entry() {
- Some(PanelEntry::Outline(OutlineEntry::Outline(selected))) => {
- outline == selected && outline.outline == selected.outline
- }
+ Some(PanelEntry::Outline(OutlineEntry::Outline(selected))) => outline == selected,
_ => false,
};
let has_children = self
.outline_children_cache
- .get(&outline.buffer_id)
+ .get(&outline.range.start.buffer_id)
.and_then(|children_map| {
- let key = (outline.outline.range.clone(), outline.outline.depth);
+ let key = (outline.range.clone(), outline.depth);
children_map.get(&key)
})
.copied()
.unwrap_or(false);
- let is_expanded = !self.collapsed_entries.contains(&CollapsedEntry::Outline(
- outline.buffer_id,
- outline.excerpt_id,
- outline.outline.range.clone(),
- ));
+ let is_expanded = !self
+ .collapsed_entries
+ .contains(&CollapsedEntry::Outline(outline.range.clone()));
let icon = if has_children {
FileIcons::get_chevron_icon(is_expanded, cx)
@@ -2784,7 +2727,7 @@ impl OutlinePanel {
let mut new_collapsed_entries = HashSet::default();
let mut new_unfolded_dirs = HashMap::default();
let mut root_entries = HashSet::default();
- let mut new_excerpts = HashMap::<BufferId, HashMap<ExcerptId, Excerpt>>::default();
+ let mut new_buffers = HashMap::<BufferId, BufferOutlines>::default();
let Ok(buffer_excerpts) = outline_panel.update(cx, |outline_panel, cx| {
let git_store = outline_panel.project.read(cx).git_store().clone();
new_collapsed_entries = outline_panel.collapsed_entries.clone();
@@ -2793,13 +2736,18 @@ impl OutlinePanel {
multi_buffer_snapshot.excerpts().fold(
HashMap::default(),
- |mut buffer_excerpts, (excerpt_id, buffer_snapshot, excerpt_range)| {
+ |mut buffer_excerpts, excerpt_range| {
+ let Some(buffer_snapshot) = multi_buffer_snapshot
+ .buffer_for_id(excerpt_range.context.start.buffer_id)
+ else {
+ return buffer_excerpts;
+ };
let buffer_id = buffer_snapshot.remote_id();
let file = File::from_dyn(buffer_snapshot.file());
let entry_id = file.and_then(|file| file.project_entry_id());
let worktree = file.map(|file| file.worktree.read(cx).snapshot());
- let is_new = new_entries.contains(&excerpt_id)
- || !outline_panel.excerpts.contains_key(&buffer_id);
+ let is_new = new_entries.contains(&buffer_id)
+ || !outline_panel.buffers.contains_key(&buffer_id);
let is_folded = active_editor.read(cx).is_buffer_folded(buffer_id, cx);
let status = git_store
.read(cx)
@@ -2813,29 +2761,28 @@ impl OutlinePanel {
(is_new, is_folded, Vec::new(), entry_id, worktree, status)
})
.2
- .push(excerpt_id);
+ .push(excerpt_range.clone());
- let outlines = match outline_panel
- .excerpts
- .get(&buffer_id)
- .and_then(|excerpts| excerpts.get(&excerpt_id))
- {
- Some(old_excerpt) => match &old_excerpt.outlines {
- ExcerptOutlines::Outlines(outlines) => {
- ExcerptOutlines::Outlines(outlines.clone())
+ new_buffers
+ .entry(buffer_id)
+ .or_insert_with(|| {
+ let outlines = match outline_panel.buffers.get(&buffer_id) {
+ Some(old_buffer) => match &old_buffer.outlines {
+ OutlineState::Outlines(outlines) => {
+ OutlineState::Outlines(outlines.clone())
+ }
+ OutlineState::Invalidated(_) => OutlineState::NotFetched,
+ OutlineState::NotFetched => OutlineState::NotFetched,
+ },
+ None => OutlineState::NotFetched,
+ };
+ BufferOutlines {
+ outlines,
+ excerpts: Vec::new(),
}
- ExcerptOutlines::Invalidated(_) => ExcerptOutlines::NotFetched,
- ExcerptOutlines::NotFetched => ExcerptOutlines::NotFetched,
- },
- None => ExcerptOutlines::NotFetched,
- };
- new_excerpts.entry(buffer_id).or_default().insert(
- excerpt_id,
- Excerpt {
- range: excerpt_range,
- outlines,
- },
- );
+ })
+ .excerpts
+ .push(excerpt_range);
buffer_excerpts
},
)
@@ -2856,7 +2803,7 @@ impl OutlinePanel {
BTreeMap::<WorktreeId, HashMap<ProjectEntryId, GitEntry>>::default();
let mut worktree_excerpts = HashMap::<
WorktreeId,
- HashMap<ProjectEntryId, (BufferId, Vec<ExcerptId>)>,
+ HashMap<ProjectEntryId, (BufferId, Vec<ExcerptRange<Anchor>>)>,
>::default();
let mut external_excerpts = HashMap::default();
@@ -3134,7 +3081,7 @@ impl OutlinePanel {
outline_panel
.update_in(cx, |outline_panel, window, cx| {
outline_panel.new_entries_for_fs_update.clear();
- outline_panel.excerpts = new_excerpts;
+ outline_panel.buffers = new_buffers;
outline_panel.collapsed_entries = new_collapsed_entries;
outline_panel.unfolded_dirs = new_unfolded_dirs;
outline_panel.fs_entries = new_fs_entries;
@@ -3144,7 +3091,7 @@ impl OutlinePanel {
// Only update cached entries if we don't have outlines to fetch
// If we do have outlines to fetch, let fetch_outdated_outlines handle the update
- if outline_panel.excerpt_fetch_ranges(cx).is_empty() {
+ if outline_panel.buffers_to_fetch().is_empty() {
outline_panel.update_cached_entries(debounce, window, cx);
}
@@ -3192,8 +3139,15 @@ impl OutlinePanel {
item_handle: new_active_item.downgrade_item(),
active_editor: new_active_editor.downgrade(),
});
- self.new_entries_for_fs_update
- .extend(new_active_editor.read(cx).buffer().read(cx).excerpt_ids());
+ self.new_entries_for_fs_update.extend(
+ new_active_editor
+ .read(cx)
+ .buffer()
+ .read(cx)
+ .snapshot(cx)
+ .excerpts()
+ .map(|excerpt| excerpt.context.start.buffer_id),
+ );
self.selected_entry.invalidate();
self.update_fs_entries(new_active_editor, None, window, cx);
}
@@ -3211,7 +3165,7 @@ impl OutlinePanel {
self.fs_entries.clear();
self.fs_entries_depth.clear();
self.fs_children_count.clear();
- self.excerpts.clear();
+ self.buffers.clear();
self.cached_entries = Vec::new();
self.selected_entry = SelectedEntry::None;
self.pinned = false;
@@ -3225,23 +3179,14 @@ impl OutlinePanel {
window: &mut Window,
cx: &mut Context<Self>,
) -> Option<PanelEntry> {
- let selection = editor.update(cx, |editor, cx| {
- editor
- .selections
- .newest::<language::Point>(&editor.display_snapshot(cx))
- .head()
- });
let editor_snapshot = editor.update(cx, |editor, cx| editor.snapshot(window, cx));
let multi_buffer = editor.read(cx).buffer();
let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
- let (excerpt_id, buffer, _) = editor
- .read(cx)
- .buffer()
- .read(cx)
- .excerpt_containing(selection, cx)?;
- let buffer_id = buffer.read(cx).remote_id();
+ let anchor = editor.update(cx, |editor, _| editor.selections.newest_anchor().head());
+ let selection_display_point = anchor.to_display_point(&editor_snapshot);
+ let (anchor, _) = multi_buffer_snapshot.anchor_to_buffer_anchor(anchor)?;
- if editor.read(cx).is_buffer_folded(buffer_id, cx) {
+ if editor.read(cx).is_buffer_folded(anchor.buffer_id, cx) {
return self
.fs_entries
.iter()
@@ -3254,14 +3199,12 @@ impl OutlinePanel {
| FsEntry::ExternalFile(FsEntryExternalFile {
buffer_id: other_buffer_id,
..
- }) => buffer_id == *other_buffer_id,
+ }) => anchor.buffer_id == *other_buffer_id,
})
.cloned()
.map(PanelEntry::Fs);
}
- let selection_display_point = selection.to_display_point(&editor_snapshot);
-
match &self.mode {
ItemsDisplayMode::Search(search_state) => search_state
.matches
@@ -3298,32 +3241,31 @@ impl OutlinePanel {
})
}),
ItemsDisplayMode::Outline => self.outline_location(
- buffer_id,
- excerpt_id,
+ anchor,
multi_buffer_snapshot,
editor_snapshot,
selection_display_point,
+ cx,
),
}
}
fn outline_location(
&self,
- buffer_id: BufferId,
- excerpt_id: ExcerptId,
+ selection_anchor: Anchor,
multi_buffer_snapshot: editor::MultiBufferSnapshot,
editor_snapshot: editor::EditorSnapshot,
selection_display_point: DisplayPoint,
+ cx: &App,
) -> Option<PanelEntry> {
let excerpt_outlines = self
- .excerpts
- .get(&buffer_id)
- .and_then(|excerpts| excerpts.get(&excerpt_id))
+ .buffers
+ .get(&selection_anchor.buffer_id)
.into_iter()
- .flat_map(|excerpt| excerpt.iter_outlines())
+ .flat_map(|buffer| buffer.iter_outlines())
.flat_map(|outline| {
let range = multi_buffer_snapshot
- .anchor_range_in_excerpt(excerpt_id, outline.range.clone())?;
+ .buffer_anchor_range_to_anchor_range(outline.range.clone())?;
Some((
range.start.to_display_point(&editor_snapshot)
..range.end.to_display_point(&editor_snapshot),
@@ -3411,16 +3353,16 @@ impl OutlinePanel {
.cloned();
let closest_container = match outline_item {
- Some(outline) => PanelEntry::Outline(OutlineEntry::Outline(OutlineEntryOutline {
- buffer_id,
- excerpt_id,
- outline,
- })),
+ Some(outline) => PanelEntry::Outline(OutlineEntry::Outline(outline)),
None => {
self.cached_entries.iter().rev().find_map(|cached_entry| {
match &cached_entry.entry {
PanelEntry::Outline(OutlineEntry::Excerpt(excerpt)) => {
- if excerpt.buffer_id == buffer_id && excerpt.id == excerpt_id {
+ if excerpt.context.start.buffer_id == selection_anchor.buffer_id
+ && let Some(buffer_snapshot) =
+ self.buffer_snapshot_for_id(excerpt.context.start.buffer_id, cx)
+ && excerpt.contains(&selection_anchor, &buffer_snapshot)
+ {
Some(cached_entry.entry.clone())
} else {
None
@@ -3430,6 +3372,7 @@ impl OutlinePanel {
FsEntry::ExternalFile(FsEntryExternalFile {
buffer_id: file_buffer_id,
excerpts: file_excerpts,
+ ..
})
| FsEntry::File(FsEntryFile {
buffer_id: file_buffer_id,
@@ -3437,7 +3380,13 @@ impl OutlinePanel {
..
}),
) => {
- if file_buffer_id == &buffer_id && file_excerpts.contains(&excerpt_id) {
+ if *file_buffer_id == selection_anchor.buffer_id
+ && let Some(buffer_snapshot) =
+ self.buffer_snapshot_for_id(*file_buffer_id, cx)
+ && file_excerpts.iter().any(|excerpt| {
+ excerpt.contains(&selection_anchor, &buffer_snapshot)
+ })
+ {
Some(cached_entry.entry.clone())
} else {
None
@@ -3452,18 +3401,17 @@ impl OutlinePanel {
}
fn fetch_outdated_outlines(&mut self, window: &mut Window, cx: &mut Context<Self>) {
- let excerpt_fetch_ranges = self.excerpt_fetch_ranges(cx);
- if excerpt_fetch_ranges.is_empty() {
+ let buffers_to_fetch = self.buffers_to_fetch();
+ if buffers_to_fetch.is_empty() {
return;
}
let first_update = Arc::new(AtomicBool::new(true));
- for (buffer_id, (_buffer_snapshot, excerpt_ranges)) in excerpt_fetch_ranges {
+ for buffer_id in buffers_to_fetch {
let outline_task = self.active_editor().map(|editor| {
editor.update(cx, |editor, cx| editor.buffer_outline_items(buffer_id, cx))
});
- let excerpt_ids = excerpt_ranges.keys().copied().collect::<Vec<_>>();
let first_update = first_update.clone();
self.outline_fetch_tasks.insert(
@@ -3498,40 +3446,26 @@ impl OutlinePanel {
Some(UPDATE_DEBOUNCE)
};
- for excerpt_id in &excerpt_ids {
- if let Some(excerpt) = outline_panel
- .excerpts
- .entry(buffer_id)
- .or_default()
- .get_mut(excerpt_id)
+ if let Some(buffer) = outline_panel.buffers.get_mut(&buffer_id) {
+ buffer.outlines = OutlineState::Outlines(fetched_outlines.clone());
+
+ if let Some(default_depth) = pending_default_depth
+ && let OutlineState::Outlines(outlines) = &buffer.outlines
{
- excerpt.outlines =
- ExcerptOutlines::Outlines(fetched_outlines.clone());
-
- if let Some(default_depth) = pending_default_depth
- && let ExcerptOutlines::Outlines(outlines) =
- &excerpt.outlines
- {
- outlines
- .iter()
- .filter(|outline| {
- (default_depth == 0
- || outline.depth >= default_depth)
- && outlines_with_children.contains(&(
- outline.range.clone(),
- outline.depth,
- ))
- })
- .for_each(|outline| {
- outline_panel.collapsed_entries.insert(
- CollapsedEntry::Outline(
- buffer_id,
- *excerpt_id,
- outline.range.clone(),
- ),
- );
- });
- }
+ outlines
+ .iter()
+ .filter(|outline| {
+ (default_depth == 0 || outline.depth >= default_depth)
+ && outlines_with_children.contains(&(
+ outline.range.clone(),
+ outline.depth,
+ ))
+ })
+ .for_each(|outline| {
+ outline_panel.collapsed_entries.insert(
+ CollapsedEntry::Outline(outline.range.clone()),
+ );
+ });
}
}
@@ -585,7 +585,7 @@ async fn raw_to_buffer_semantic_tokens(
}
Some(BufferSemanticToken {
- range: buffer_snapshot.anchor_range_around(start..end),
+ range: buffer_snapshot.anchor_range_inside(start..end),
token_type: token.token_type,
token_modifiers: token.token_modifiers,
})
@@ -1032,6 +1032,8 @@ impl DirectoryLister {
}
}
+pub const CURRENT_PROJECT_FEATURES: &[&str] = &["new-style-anchors"];
+
#[cfg(feature = "test-support")]
pub const DEFAULT_COMPLETION_CONTEXT: CompletionContext = CompletionContext {
trigger_kind: lsp::CompletionTriggerKind::INVOKED,
@@ -1644,6 +1646,10 @@ impl Project {
project_id: remote_id,
committer_email: committer.email,
committer_name: committer.name,
+ features: CURRENT_PROJECT_FEATURES
+ .iter()
+ .map(|s| s.to_string())
+ .collect(),
})
.await?;
Self::from_join_project_response(
@@ -1771,7 +1771,7 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
DiagnosticSet::from_sorted_entries(
vec![DiagnosticEntry {
diagnostic: Default::default(),
- range: Anchor::MIN..Anchor::MAX,
+ range: Anchor::min_max_range_for_buffer(buffer.remote_id()),
}],
&buffer.snapshot(),
),
@@ -8525,9 +8525,10 @@ async fn test_unstaged_diff_for_buffer(cx: &mut gpui::TestAppContext) {
unstaged_diff.update(cx, |unstaged_diff, cx| {
let snapshot = buffer.read(cx).snapshot();
assert_hunks(
- unstaged_diff
- .snapshot(cx)
- .hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot),
+ unstaged_diff.snapshot(cx).hunks_intersecting_range(
+ Anchor::min_max_range_for_buffer(snapshot.remote_id()),
+ &snapshot,
+ ),
&snapshot,
&unstaged_diff.base_text(cx).text(),
&[(
@@ -8616,8 +8617,10 @@ async fn test_uncommitted_diff_for_buffer(cx: &mut gpui::TestAppContext) {
diff_1.update(cx, |diff, cx| {
let snapshot = buffer_1.read(cx).snapshot();
assert_hunks(
- diff.snapshot(cx)
- .hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot),
+ diff.snapshot(cx).hunks_intersecting_range(
+ Anchor::min_max_range_for_buffer(snapshot.remote_id()),
+ &snapshot,
+ ),
&snapshot,
&diff.base_text_string(cx).unwrap(),
&[
@@ -8658,8 +8661,10 @@ async fn test_uncommitted_diff_for_buffer(cx: &mut gpui::TestAppContext) {
diff_1.update(cx, |diff, cx| {
let snapshot = buffer_1.read(cx).snapshot();
assert_hunks(
- diff.snapshot(cx)
- .hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot),
+ diff.snapshot(cx).hunks_intersecting_range(
+ Anchor::min_max_range_for_buffer(snapshot.remote_id()),
+ &snapshot,
+ ),
&snapshot,
&diff.base_text(cx).text(),
&[(
@@ -8688,8 +8693,10 @@ async fn test_uncommitted_diff_for_buffer(cx: &mut gpui::TestAppContext) {
diff_2.update(cx, |diff, cx| {
let snapshot = buffer_2.read(cx).snapshot();
assert_hunks(
- diff.snapshot(cx)
- .hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot),
+ diff.snapshot(cx).hunks_intersecting_range(
+ Anchor::min_max_range_for_buffer(snapshot.remote_id()),
+ &snapshot,
+ ),
&snapshot,
&diff.base_text_string(cx).unwrap(),
&[(
@@ -8710,8 +8717,10 @@ async fn test_uncommitted_diff_for_buffer(cx: &mut gpui::TestAppContext) {
diff_2.update(cx, |diff, cx| {
let snapshot = buffer_2.read(cx).snapshot();
assert_hunks(
- diff.snapshot(cx)
- .hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot),
+ diff.snapshot(cx).hunks_intersecting_range(
+ Anchor::min_max_range_for_buffer(snapshot.remote_id()),
+ &snapshot,
+ ),
&snapshot,
&diff.base_text_string(cx).unwrap(),
&[(
@@ -212,10 +212,15 @@ message Selection {
}
message EditorAnchor {
- uint64 excerpt_id = 1;
+ optional uint64 excerpt_id = 1;
Anchor anchor = 2;
}
+message PathKey {
+ optional uint64 sort_prefix = 1;
+ string path = 2;
+}
+
enum CursorShape {
CursorBar = 0;
CursorBlock = 1;
@@ -174,6 +174,7 @@ message ShareProject {
reserved 3;
bool is_ssh_project = 4;
optional bool windows_paths = 5;
+ repeated string features = 6;
}
message ShareProjectResponse {
@@ -193,6 +194,7 @@ message JoinProject {
uint64 project_id = 1;
optional string committer_email = 2;
optional string committer_name = 3;
+ repeated string features = 4;
}
message JoinProjectResponse {
@@ -204,6 +206,7 @@ message JoinProjectResponse {
repeated string language_server_capabilities = 8;
ChannelRole role = 6;
bool windows_paths = 9;
+ repeated string features = 10;
reserved 7;
}
@@ -359,6 +362,8 @@ message UpdateView {
reserved 7;
double scroll_x = 8;
double scroll_y = 9;
+ repeated PathExcerpts updated_paths = 10;
+ repeated uint64 deleted_buffers = 11;
}
}
@@ -385,6 +390,7 @@ message View {
reserved 8;
double scroll_x = 9;
double scroll_y = 10;
+ repeated PathExcerpts path_excerpts = 11;
}
message ChannelView {
@@ -407,6 +413,19 @@ message Excerpt {
Anchor primary_end = 6;
}
+message ExcerptRange {
+ Anchor context_start = 1;
+ Anchor context_end = 2;
+ Anchor primary_start = 3;
+ Anchor primary_end = 4;
+}
+
+message PathExcerpts {
+ PathKey path_key = 1;
+ uint64 buffer_id = 2;
+ repeated ExcerptRange ranges = 3;
+}
+
message Contact {
uint64 user_id = 1;
bool online = 2;
@@ -3550,7 +3550,16 @@ mod tests {
// Manually unfold one buffer (simulating a chevron click)
let first_buffer_id = editor.read_with(cx, |editor, cx| {
- editor.buffer().read(cx).excerpt_buffer_ids()[0]
+ editor
+ .buffer()
+ .read(cx)
+ .snapshot(cx)
+ .excerpts()
+ .nth(0)
+ .unwrap()
+ .context
+ .start
+ .buffer_id
});
editor.update_in(cx, |editor, _window, cx| {
editor.unfold_buffer(first_buffer_id, cx);
@@ -3564,7 +3573,16 @@ mod tests {
// Manually unfold the second buffer too
let second_buffer_id = editor.read_with(cx, |editor, cx| {
- editor.buffer().read(cx).excerpt_buffer_ids()[1]
+ editor
+ .buffer()
+ .read(cx)
+ .snapshot(cx)
+ .excerpts()
+ .nth(1)
+ .unwrap()
+ .context
+ .start
+ .buffer_id
});
editor.update_in(cx, |editor, _window, cx| {
editor.unfold_buffer(second_buffer_id, cx);
@@ -11,8 +11,8 @@ use crate::{
use anyhow::Context as _;
use collections::HashMap;
use editor::{
- Anchor, Editor, EditorEvent, EditorSettings, ExcerptId, MAX_TAB_TITLE_LEN, MultiBuffer,
- PathKey, SelectionEffects,
+ Anchor, Editor, EditorEvent, EditorSettings, MAX_TAB_TITLE_LEN, MultiBuffer, PathKey,
+ SelectionEffects,
actions::{Backtab, FoldAll, SelectAll, Tab, UnfoldAll},
items::active_match_index,
multibuffer_context_lines,
@@ -342,41 +342,32 @@ impl ProjectSearch {
}
fn remove_deleted_buffers(&mut self, cx: &mut Context<Self>) {
- let (deleted_paths, removed_excerpt_ids) = {
- let excerpts = self.excerpts.read(cx);
- let deleted_paths: Vec<PathKey> = excerpts
- .paths()
- .filter(|path| {
- excerpts.buffer_for_path(path, cx).is_some_and(|buffer| {
- buffer
- .read(cx)
- .file()
- .is_some_and(|file| file.disk_state().is_deleted())
- })
- })
- .cloned()
- .collect();
-
- let removed_excerpt_ids: collections::HashSet<ExcerptId> = deleted_paths
- .iter()
- .flat_map(|path| excerpts.excerpts_for_path(path))
- .collect();
-
- (deleted_paths, removed_excerpt_ids)
- };
+ let deleted_buffer_ids = self
+ .excerpts
+ .read(cx)
+ .all_buffers_iter()
+ .filter(|buffer| {
+ buffer
+ .read(cx)
+ .file()
+ .is_some_and(|file| file.disk_state().is_deleted())
+ })
+ .map(|buffer| buffer.read(cx).remote_id())
+ .collect::<Vec<_>>();
- if deleted_paths.is_empty() {
+ if deleted_buffer_ids.is_empty() {
return;
}
- self.excerpts.update(cx, |excerpts, cx| {
- for path in deleted_paths {
- excerpts.remove_excerpts_for_path(path, cx);
+ let snapshot = self.excerpts.update(cx, |excerpts, cx| {
+ for buffer_id in deleted_buffer_ids {
+ excerpts.remove_excerpts_for_buffer(buffer_id, cx);
}
+ excerpts.snapshot(cx)
});
self.match_ranges
- .retain(|range| !removed_excerpt_ids.contains(&range.start.excerpt_id));
+ .retain(|range| snapshot.anchor_to_buffer_anchor(range.start).is_some());
cx.notify();
}
@@ -2990,7 +2981,13 @@ pub mod tests {
.read(cx)
.buffer()
.read(cx)
- .excerpt_buffer_ids()[0]
+ .snapshot(cx)
+ .excerpts()
+ .next()
+ .unwrap()
+ .context
+ .start
+ .buffer_id
})
.expect("should read buffer ids");
@@ -321,13 +321,11 @@ pub fn task_contexts(
})
.unwrap_or_default();
- let latest_selection = active_editor.as_ref().map(|active_editor| {
- active_editor
- .read(cx)
- .selections
- .newest_anchor()
- .head()
- .text_anchor
+ let latest_selection = active_editor.as_ref().and_then(|active_editor| {
+ let snapshot = active_editor.read(cx).buffer().read(cx).snapshot(cx);
+ snapshot
+ .anchor_to_buffer_anchor(active_editor.read(cx).selections.newest_anchor().head())
+ .map(|(anchor, _)| anchor)
});
let mut worktree_abs_paths = workspace
@@ -24,7 +24,7 @@ pub struct Anchor {
/// Whether this anchor stays attached to the character *before* or *after*
/// the offset.
pub bias: Bias,
- pub buffer_id: Option<BufferId>,
+ pub buffer_id: BufferId,
}
impl Debug for Anchor {
@@ -46,28 +46,7 @@ impl Debug for Anchor {
}
impl Anchor {
- pub const MIN: Self = Self {
- timestamp_replica_id: clock::Lamport::MIN.replica_id,
- timestamp_value: clock::Lamport::MIN.value,
- offset: u32::MIN,
- bias: Bias::Left,
- buffer_id: None,
- };
-
- pub const MAX: Self = Self {
- timestamp_replica_id: clock::Lamport::MAX.replica_id,
- timestamp_value: clock::Lamport::MAX.value,
- offset: u32::MAX,
- bias: Bias::Right,
- buffer_id: None,
- };
-
- pub fn new(
- timestamp: clock::Lamport,
- offset: u32,
- bias: Bias,
- buffer_id: Option<BufferId>,
- ) -> Self {
+ pub fn new(timestamp: clock::Lamport, offset: u32, bias: Bias, buffer_id: BufferId) -> Self {
Self {
timestamp_replica_id: timestamp.replica_id,
timestamp_value: timestamp.value,
@@ -83,7 +62,7 @@ impl Anchor {
timestamp_value: clock::Lamport::MIN.value,
offset: u32::MIN,
bias: Bias::Left,
- buffer_id: Some(buffer_id),
+ buffer_id,
}
}
@@ -93,7 +72,7 @@ impl Anchor {
timestamp_value: clock::Lamport::MAX.value,
offset: u32::MAX,
bias: Bias::Right,
- buffer_id: Some(buffer_id),
+ buffer_id,
}
}
@@ -171,7 +150,7 @@ impl Anchor {
pub fn is_valid(&self, buffer: &BufferSnapshot) -> bool {
if self.is_min() || self.is_max() {
true
- } else if self.buffer_id.is_none_or(|id| id != buffer.remote_id) {
+ } else if self.buffer_id != buffer.remote_id {
false
} else {
let Some(fragment_id) = buffer.try_fragment_id_for_anchor(self) else {
@@ -207,6 +186,18 @@ impl Anchor {
value: self.timestamp_value,
}
}
+
+ pub fn opaque_id(&self) -> [u8; 20] {
+ let mut bytes = [0u8; 20];
+ let buffer_id: u64 = self.buffer_id.into();
+ bytes[0..8].copy_from_slice(&buffer_id.to_le_bytes());
+ bytes[8..12].copy_from_slice(&self.offset.to_le_bytes());
+ bytes[12..16].copy_from_slice(&self.timestamp_value.to_le_bytes());
+ let replica_id = self.timestamp_replica_id.as_u16();
+ bytes[16..18].copy_from_slice(&replica_id.to_le_bytes());
+ bytes[18] = self.bias as u8;
+ bytes
+ }
}
pub trait OffsetRangeExt {
@@ -237,6 +228,7 @@ where
pub trait AnchorRangeExt {
fn cmp(&self, b: &Range<Anchor>, buffer: &BufferSnapshot) -> Ordering;
fn overlaps(&self, b: &Range<Anchor>, buffer: &BufferSnapshot) -> bool;
+ fn contains_anchor(&self, b: Anchor, buffer: &BufferSnapshot) -> bool;
}
impl AnchorRangeExt for Range<Anchor> {
@@ -250,4 +242,8 @@ impl AnchorRangeExt for Range<Anchor> {
fn overlaps(&self, other: &Range<Anchor>, buffer: &BufferSnapshot) -> bool {
self.start.cmp(&other.end, buffer).is_lt() && other.start.cmp(&self.end, buffer).is_lt()
}
+
+ fn contains_anchor(&self, other: Anchor, buffer: &BufferSnapshot) -> bool {
+ self.start.cmp(&other, buffer).is_le() && self.end.cmp(&other, buffer).is_ge()
+ }
}
@@ -56,7 +56,10 @@ where
if edit.is_empty() {
return;
}
+ self.push_maybe_empty(edit);
+ }
+ pub fn push_maybe_empty(&mut self, edit: Edit<T>) {
if let Some(last) = self.0.last_mut() {
if last.old.end >= edit.old.start {
last.old.end = edit.old.end;
@@ -2377,7 +2377,7 @@ impl BufferSnapshot {
pub fn summaries_for_anchors<'a, D, A>(&'a self, anchors: A) -> impl 'a + Iterator<Item = D>
where
D: 'a + TextDimension,
- A: 'a + IntoIterator<Item = &'a Anchor>,
+ A: 'a + IntoIterator<Item = Anchor>,
{
let anchors = anchors.into_iter();
self.summaries_for_anchors_with_payload::<D, _, ()>(anchors.map(|a| (a, ())))
@@ -2390,7 +2390,7 @@ impl BufferSnapshot {
) -> impl 'a + Iterator<Item = (D, T)>
where
D: 'a + TextDimension,
- A: 'a + IntoIterator<Item = (&'a Anchor, T)>,
+ A: 'a + IntoIterator<Item = (Anchor, T)>,
{
let anchors = anchors.into_iter();
let mut fragment_cursor = self
@@ -2406,7 +2406,7 @@ impl BufferSnapshot {
return (D::from_text_summary(&self.visible_text.summary()), payload);
}
- let Some(insertion) = self.try_find_fragment(anchor) else {
+ let Some(insertion) = self.try_find_fragment(&anchor) else {
panic!(
"invalid insertion for buffer {}@{:?} with anchor {:?}",
self.remote_id(),
@@ -2457,7 +2457,7 @@ impl BufferSnapshot {
} else if anchor.is_max() {
self.visible_text.len()
} else {
- debug_assert_eq!(anchor.buffer_id, Some(self.remote_id));
+ debug_assert_eq!(anchor.buffer_id, self.remote_id);
debug_assert!(
self.version.observed(anchor.timestamp()),
"Anchor timestamp {:?} not observed by buffer {:?}",
@@ -2489,7 +2489,7 @@ impl BufferSnapshot {
#[cold]
fn panic_bad_anchor(&self, anchor: &Anchor) -> ! {
- if anchor.buffer_id.is_some_and(|id| id != self.remote_id) {
+ if anchor.buffer_id != self.remote_id {
panic!(
"invalid anchor - buffer id does not match: anchor {anchor:?}; buffer id: {}, version: {:?}",
self.remote_id, self.version
@@ -2553,12 +2553,12 @@ impl BufferSnapshot {
}
/// Returns an anchor range for the given input position range that is anchored to the text in the range.
- pub fn anchor_range_around<T: ToOffset>(&self, position: Range<T>) -> Range<Anchor> {
+ pub fn anchor_range_inside<T: ToOffset>(&self, position: Range<T>) -> Range<Anchor> {
self.anchor_after(position.start)..self.anchor_before(position.end)
}
/// Returns an anchor range for the given input position range that is anchored to the text before and after.
- pub fn anchor_range_between<T: ToOffset>(&self, position: Range<T>) -> Range<Anchor> {
+ pub fn anchor_range_outside<T: ToOffset>(&self, position: Range<T>) -> Range<Anchor> {
self.anchor_before(position.start)..self.anchor_after(position.end)
}
@@ -2608,7 +2608,7 @@ impl BufferSnapshot {
fragment.timestamp,
fragment.insertion_offset + overshoot as u32,
bias,
- Some(self.remote_id),
+ self.remote_id,
)
}
}
@@ -2616,8 +2616,7 @@ impl BufferSnapshot {
pub fn can_resolve(&self, anchor: &Anchor) -> bool {
anchor.is_min()
|| anchor.is_max()
- || (Some(self.remote_id) == anchor.buffer_id
- && self.version.observed(anchor.timestamp()))
+ || (self.remote_id == anchor.buffer_id && self.version.observed(anchor.timestamp()))
}
pub fn clip_offset(&self, offset: usize, bias: Bias) -> usize {
@@ -2643,7 +2642,10 @@ impl BufferSnapshot {
where
D: TextDimension + Ord,
{
- self.edits_since_in_range(since, Anchor::MIN..Anchor::MAX)
+ self.edits_since_in_range(
+ since,
+ Anchor::min_for_buffer(self.remote_id)..Anchor::max_for_buffer(self.remote_id),
+ )
}
pub fn anchored_edits_since<'a, D>(
@@ -2653,7 +2655,10 @@ impl BufferSnapshot {
where
D: TextDimension + Ord,
{
- self.anchored_edits_since_in_range(since, Anchor::MIN..Anchor::MAX)
+ self.anchored_edits_since_in_range(
+ since,
+ Anchor::min_for_buffer(self.remote_id)..Anchor::max_for_buffer(self.remote_id),
+ )
}
pub fn edits_since_in_range<'a, D>(
@@ -2916,13 +2921,13 @@ impl<D: TextDimension + Ord, F: FnMut(&FragmentSummary) -> bool> Iterator for Ed
fragment.timestamp,
fragment.insertion_offset,
Bias::Right,
- Some(self.buffer_id),
+ self.buffer_id,
);
let end_anchor = Anchor::new(
fragment.timestamp,
fragment.insertion_offset + fragment.len,
Bias::Left,
- Some(self.buffer_id),
+ self.buffer_id,
);
if !fragment.was_visible(self.since, self.undos) && fragment.visible {
@@ -117,7 +117,7 @@ impl ActiveToolchain {
cx: &mut Context<Self>,
) {
let editor = editor.read(cx);
- if let Some((_, buffer, _)) = editor.active_excerpt(cx)
+ if let Some(buffer) = editor.active_buffer(cx)
&& let Some(worktree_id) = buffer.read(cx).file().map(|file| file.worktree_id(cx))
{
let subscription = cx.subscribe_in(
@@ -584,11 +584,11 @@ impl ToolchainSelector {
window: &mut Window,
cx: &mut Context<Workspace>,
) -> Option<()> {
- let (_, buffer, _) = workspace
+ let buffer = workspace
.active_item(cx)?
.act_as::<Editor>(cx)?
.read(cx)
- .active_excerpt(cx)?;
+ .active_buffer(cx)?;
let project = workspace.project().clone();
let language_name = buffer.read(cx).language()?.name();
@@ -1348,7 +1348,7 @@ impl Position {
let snapshot = editor.snapshot(window, cx);
let target = match self {
Position::Line { row, offset } => {
- if let Some(anchor) = editor.active_excerpt(cx).and_then(|(_, buffer, _)| {
+ if let Some(anchor) = editor.active_buffer(cx).and_then(|buffer| {
editor.buffer().read(cx).buffer_point_to_anchor(
&buffer,
Point::new(row.saturating_sub(1), 0),
@@ -2336,7 +2336,7 @@ impl Vim {
match c {
'%' => {
self.update_editor(cx, |_, editor, cx| {
- if let Some((_, buffer, _)) = editor.active_excerpt(cx)
+ if let Some(buffer) = editor.active_buffer(cx)
&& let Some(file) = buffer.read(cx).file()
&& let Some(local) = file.as_local()
{
@@ -1,5 +1,6 @@
use editor::{
Anchor, Bias, BufferOffset, DisplayPoint, Editor, MultiBufferOffset, RowExt, ToOffset,
+ ToPoint as _,
display_map::{DisplayRow, DisplaySnapshot, FoldPoint, ToDisplayPoint},
movement::{
self, FindRange, TextLayoutDetails, find_boundary, find_preceding_boundary_display_point,
@@ -11,6 +12,7 @@ use multi_buffer::MultiBufferRow;
use schemars::JsonSchema;
use serde::Deserialize;
use std::{f64, ops::Range};
+
use workspace::searchable::Direction;
use crate::{
@@ -2340,39 +2342,19 @@ fn start_of_next_sentence(
fn go_to_line(map: &DisplaySnapshot, display_point: DisplayPoint, line: usize) -> DisplayPoint {
let point = map.display_point_to_point(display_point, Bias::Left);
- let Some(mut excerpt) = map.buffer_snapshot().excerpt_containing(point..point) else {
+ let snapshot = map.buffer_snapshot();
+ let Some((buffer_snapshot, _)) = snapshot.point_to_buffer_point(point) else {
+ return display_point;
+ };
+
+ let Some(anchor) = snapshot.anchor_in_excerpt(buffer_snapshot.anchor_after(
+ buffer_snapshot.clip_point(Point::new((line - 1) as u32, point.column), Bias::Left),
+ )) else {
return display_point;
};
- let offset = excerpt.buffer().point_to_offset(
- excerpt
- .buffer()
- .clip_point(Point::new((line - 1) as u32, point.column), Bias::Left),
- );
- let buffer_range = excerpt.buffer_range();
- if offset >= buffer_range.start.0 && offset <= buffer_range.end.0 {
- let point = map
- .buffer_snapshot()
- .offset_to_point(excerpt.map_offset_from_buffer(BufferOffset(offset)));
- return map.clip_point(map.point_to_display_point(point, Bias::Left), Bias::Left);
- }
- for (excerpt, buffer, range) in map.buffer_snapshot().excerpts() {
- let excerpt_range = language::ToOffset::to_offset(&range.context.start, buffer)
- ..language::ToOffset::to_offset(&range.context.end, buffer);
- if offset >= excerpt_range.start && offset <= excerpt_range.end {
- let text_anchor = buffer.anchor_after(offset);
- let anchor = Anchor::in_buffer(excerpt, text_anchor);
- return anchor.to_display_point(map);
- } else if offset <= excerpt_range.start {
- let anchor = Anchor::in_buffer(excerpt, range.context.start);
- return anchor.to_display_point(map);
- }
- }
map.clip_point(
- map.point_to_display_point(
- map.buffer_snapshot().clip_point(point, Bias::Left),
- Bias::Left,
- ),
+ map.point_to_display_point(anchor.to_point(snapshot), Bias::Left),
Bias::Left,
)
}
@@ -932,7 +932,7 @@ impl Vim {
Vim::take_forced_motion(cx);
self.update_editor(cx, |vim, editor, cx| {
let selection = editor.selections.newest_anchor();
- let Some((buffer, point, _)) = editor
+ let Some((buffer, point)) = editor
.buffer()
.read(cx)
.point_to_buffer_point(selection.head(), cx)
@@ -203,33 +203,24 @@ fn find_mini_delimiters(
is_valid_delimiter: &DelimiterPredicate,
) -> Option<Range<DisplayPoint>> {
let point = map.clip_at_line_end(display_point).to_point(map);
- let offset = point.to_offset(&map.buffer_snapshot());
+ let offset = map.buffer_snapshot().point_to_offset(point);
let line_range = get_line_range(map, point);
let visible_line_range = get_visible_line_range(&line_range);
let snapshot = &map.buffer_snapshot();
- let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
- let buffer = excerpt.buffer();
- let buffer_offset = excerpt.map_offset_to_buffer(offset);
- let bracket_filter = |open: Range<usize>, close: Range<usize>| {
- is_valid_delimiter(buffer, open.start, close.start)
- };
-
- // Try to find delimiters in visible range first
let ranges = map
.buffer_snapshot()
.bracket_ranges(visible_line_range)
.map(|ranges| {
ranges.filter_map(|(open, close)| {
- // Convert the ranges from multibuffer space to buffer space as
- // that is what `is_valid_delimiter` expects, otherwise it might
- // panic as the values might be out of bounds.
- let buffer_open = excerpt.map_range_to_buffer(open.clone());
- let buffer_close = excerpt.map_range_to_buffer(close.clone());
+ let (buffer, buffer_open) =
+ snapshot.range_to_buffer_range::<MultiBufferOffset>(open.clone())?;
+ let (_, buffer_close) =
+ snapshot.range_to_buffer_range::<MultiBufferOffset>(close.clone())?;
- if is_valid_delimiter(buffer, buffer_open.start.0, buffer_close.start.0) {
+ if is_valid_delimiter(buffer, buffer_open.start, buffer_close.start) {
Some((open, close))
} else {
None
@@ -247,18 +238,31 @@ fn find_mini_delimiters(
);
}
- // Fall back to innermost enclosing brackets
- let (open_bracket, close_bracket) = buffer
- .innermost_enclosing_bracket_ranges(buffer_offset..buffer_offset, Some(&bracket_filter))?;
+ let results = snapshot.map_excerpt_ranges(offset..offset, |buffer, _, input_range| {
+ let buffer_offset = input_range.start.0;
+ let bracket_filter = |open: Range<usize>, close: Range<usize>| {
+ is_valid_delimiter(buffer, open.start, close.start)
+ };
+ let Some((open, close)) = buffer.innermost_enclosing_bracket_ranges(
+ buffer_offset..buffer_offset,
+ Some(&bracket_filter),
+ ) else {
+ return vec![];
+ };
+ vec![
+ (BufferOffset(open.start)..BufferOffset(open.end), ()),
+ (BufferOffset(close.start)..BufferOffset(close.end), ()),
+ ]
+ })?;
+
+ if results.len() < 2 {
+ return None;
+ }
Some(
DelimiterRange {
- open: excerpt.map_range_from_buffer(
- BufferOffset(open_bracket.start)..BufferOffset(open_bracket.end),
- ),
- close: excerpt.map_range_from_buffer(
- BufferOffset(close_bracket.start)..BufferOffset(close_bracket.end),
- ),
+ open: results[0].0.clone(),
+ close: results[1].0.clone(),
}
.to_display_range(map, around),
)
@@ -935,61 +939,64 @@ pub fn surrounding_html_tag(
}
let snapshot = &map.buffer_snapshot();
- let offset = head.to_offset(map, Bias::Left);
- let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
- let buffer = excerpt.buffer();
- let offset = excerpt.map_offset_to_buffer(offset);
-
- // Find the most closest to current offset
- let mut cursor = buffer.syntax_layer_at(offset)?.node().walk();
- let mut last_child_node = cursor.node();
- while cursor.goto_first_child_for_byte(offset.0).is_some() {
- last_child_node = cursor.node();
- }
-
- let mut last_child_node = Some(last_child_node);
- while let Some(cur_node) = last_child_node {
- if cur_node.child_count() >= 2 {
- let first_child = cur_node.child(0);
- let last_child = cur_node.child(cur_node.child_count() as u32 - 1);
- if let (Some(first_child), Some(last_child)) = (first_child, last_child) {
- let open_tag = open_tag(buffer.chars_for_range(first_child.byte_range()));
- let close_tag = close_tag(buffer.chars_for_range(last_child.byte_range()));
- // It needs to be handled differently according to the selection length
- let is_valid = if range.end.to_offset(map, Bias::Left)
- - range.start.to_offset(map, Bias::Left)
- <= 1
- {
- offset.0 <= last_child.end_byte()
- } else {
- excerpt
- .map_offset_to_buffer(range.start.to_offset(map, Bias::Left))
- .0
- >= first_child.start_byte()
- && excerpt
- .map_offset_to_buffer(range.end.to_offset(map, Bias::Left))
- .0
- <= last_child.start_byte() + 1
- };
- if open_tag.is_some() && open_tag == close_tag && is_valid {
- let range = if around {
- first_child.byte_range().start..last_child.byte_range().end
- } else {
- first_child.byte_range().end..last_child.byte_range().start
- };
- let range = BufferOffset(range.start)..BufferOffset(range.end);
- if excerpt.contains_buffer_range(range.clone()) {
- let result = excerpt.map_range_from_buffer(range);
- return Some(
- result.start.to_display_point(map)..result.end.to_display_point(map),
- );
+ let head_offset = head.to_offset(map, Bias::Left);
+ let range_start = range.start.to_offset(map, Bias::Left);
+ let range_end = range.end.to_offset(map, Bias::Left);
+ let head_is_start = head_offset <= range_start;
+
+ let results = snapshot.map_excerpt_ranges(
+ range_start..range_end,
+ |buffer, _excerpt_range, input_buffer_range| {
+ let buffer_offset = if head_is_start {
+ input_buffer_range.start
+ } else {
+ input_buffer_range.end
+ };
+
+ let Some(layer) = buffer.syntax_layer_at(buffer_offset) else {
+ return Vec::new();
+ };
+ let mut cursor = layer.node().walk();
+ let mut last_child_node = cursor.node();
+ while cursor.goto_first_child_for_byte(buffer_offset.0).is_some() {
+ last_child_node = cursor.node();
+ }
+
+ let mut last_child_node = Some(last_child_node);
+ while let Some(cur_node) = last_child_node {
+ if cur_node.child_count() >= 2 {
+ let first_child = cur_node.child(0);
+ let last_child = cur_node.child(cur_node.child_count() as u32 - 1);
+ if let (Some(first_child), Some(last_child)) = (first_child, last_child) {
+ let open_tag = open_tag(buffer.chars_for_range(first_child.byte_range()));
+ let close_tag = close_tag(buffer.chars_for_range(last_child.byte_range()));
+ let is_valid = if range_end.saturating_sub(range_start) <= 1 {
+ buffer_offset.0 <= last_child.end_byte()
+ } else {
+ input_buffer_range.start.0 >= first_child.start_byte()
+ && input_buffer_range.end.0 <= last_child.start_byte() + 1
+ };
+ if open_tag.is_some() && open_tag == close_tag && is_valid {
+ let buffer_range = if around {
+ first_child.byte_range().start..last_child.byte_range().end
+ } else {
+ first_child.byte_range().end..last_child.byte_range().start
+ };
+ return vec![(
+ BufferOffset(buffer_range.start)..BufferOffset(buffer_range.end),
+ (),
+ )];
+ }
}
}
+ last_child_node = cur_node.parent();
}
- }
- last_child_node = cur_node.parent();
- }
- None
+ Vec::new()
+ },
+ )?;
+
+ let (result, ()) = results.into_iter().next()?;
+ Some(result.start.to_display_point(map)..result.end.to_display_point(map))
}
/// Returns a range that surrounds the word and following whitespace
@@ -1163,44 +1170,55 @@ fn text_object(
let snapshot = &map.buffer_snapshot();
let offset = relative_to.to_offset(map, Bias::Left);
- let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
- let buffer = excerpt.buffer();
- let offset = excerpt.map_offset_to_buffer(offset);
-
- let mut matches: Vec<Range<usize>> = buffer
- .text_object_ranges(offset..offset, TreeSitterOptions::default())
- .filter_map(|(r, m)| if m == target { Some(r) } else { None })
- .collect();
- matches.sort_by_key(|r| r.end - r.start);
- if let Some(buffer_range) = matches.first() {
- let buffer_range = BufferOffset(buffer_range.start)..BufferOffset(buffer_range.end);
- let range = excerpt.map_range_from_buffer(buffer_range);
- return Some(range.start.to_display_point(map)..range.end.to_display_point(map));
- }
-
- let around = target.around()?;
- let mut matches: Vec<Range<usize>> = buffer
- .text_object_ranges(offset..offset, TreeSitterOptions::default())
- .filter_map(|(r, m)| if m == around { Some(r) } else { None })
- .collect();
- matches.sort_by_key(|r| r.end - r.start);
- let around_range = matches.first()?;
-
- let mut matches: Vec<Range<usize>> = buffer
- .text_object_ranges(around_range.clone(), TreeSitterOptions::default())
- .filter_map(|(r, m)| if m == target { Some(r) } else { None })
- .collect();
- matches.sort_by_key(|r| r.start);
- if let Some(buffer_range) = matches.first()
- && !buffer_range.is_empty()
- {
- let buffer_range = BufferOffset(buffer_range.start)..BufferOffset(buffer_range.end);
- let range = excerpt.map_range_from_buffer(buffer_range);
- return Some(range.start.to_display_point(map)..range.end.to_display_point(map));
- }
- let around_range = BufferOffset(around_range.start)..BufferOffset(around_range.end);
- let buffer_range = excerpt.map_range_from_buffer(around_range);
- return Some(buffer_range.start.to_display_point(map)..buffer_range.end.to_display_point(map));
+ let results =
+ snapshot.map_excerpt_ranges(offset..offset, |buffer, _excerpt_range, buffer_range| {
+ let buffer_offset = buffer_range.start;
+
+ let mut matches: Vec<Range<usize>> = buffer
+ .text_object_ranges(buffer_offset..buffer_offset, TreeSitterOptions::default())
+ .filter_map(|(r, m)| if m == target { Some(r) } else { None })
+ .collect();
+ matches.sort_by_key(|r| r.end - r.start);
+ if let Some(buffer_range) = matches.first() {
+ return vec![(
+ BufferOffset(buffer_range.start)..BufferOffset(buffer_range.end),
+ (),
+ )];
+ }
+
+ let Some(around) = target.around() else {
+ return vec![];
+ };
+ let mut matches: Vec<Range<usize>> = buffer
+ .text_object_ranges(buffer_offset..buffer_offset, TreeSitterOptions::default())
+ .filter_map(|(r, m)| if m == around { Some(r) } else { None })
+ .collect();
+ matches.sort_by_key(|r| r.end - r.start);
+ let Some(around_range) = matches.first() else {
+ return vec![];
+ };
+
+ let mut matches: Vec<Range<usize>> = buffer
+ .text_object_ranges(around_range.clone(), TreeSitterOptions::default())
+ .filter_map(|(r, m)| if m == target { Some(r) } else { None })
+ .collect();
+ matches.sort_by_key(|r| r.start);
+ if let Some(buffer_range) = matches.first()
+ && !buffer_range.is_empty()
+ {
+ return vec![(
+ BufferOffset(buffer_range.start)..BufferOffset(buffer_range.end),
+ (),
+ )];
+ }
+ vec![(
+ BufferOffset(around_range.start)..BufferOffset(around_range.end),
+ (),
+ )]
+ })?;
+
+ let (range, ()) = results.into_iter().next()?;
+ Some(range.start.to_display_point(map)..range.end.to_display_point(map))
}
fn argument(
@@ -1211,16 +1229,11 @@ fn argument(
let snapshot = &map.buffer_snapshot();
let offset = relative_to.to_offset(map, Bias::Left);
- // The `argument` vim text object uses the syntax tree, so we operate at the buffer level and map back to the display level
- let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
- let buffer = excerpt.buffer();
-
fn comma_delimited_range_at(
buffer: &BufferSnapshot,
mut offset: BufferOffset,
include_comma: bool,
) -> Option<Range<BufferOffset>> {
- // Seek to the first non-whitespace character
offset += buffer
.chars_at(offset)
.take_while(|c| c.is_whitespace())
@@ -1228,25 +1241,20 @@ fn argument(
.sum::<usize>();
let bracket_filter = |open: Range<usize>, close: Range<usize>| {
- // Filter out empty ranges
if open.end == close.start {
return false;
}
- // If the cursor is outside the brackets, ignore them
if open.start == offset.0 || close.end == offset.0 {
return false;
}
- // TODO: Is there any better way to filter out string brackets?
- // Used to filter out string brackets
matches!(
buffer.chars_at(open.start).next(),
Some('(' | '[' | '{' | '<' | '|')
)
};
- // Find the brackets containing the cursor
let (open_bracket, close_bracket) =
buffer.innermost_enclosing_bracket_ranges(offset..offset, Some(&bracket_filter))?;
@@ -1256,7 +1264,6 @@ fn argument(
let node = layer.node();
let mut cursor = node.walk();
- // Loop until we find the smallest node whose parent covers the bracket range. This node is the argument in the parent argument list
let mut parent_covers_bracket_range = false;
loop {
let node = cursor.node();
@@ -1268,20 +1275,17 @@ fn argument(
}
parent_covers_bracket_range = covers_bracket_range;
- // Unable to find a child node with a parent that covers the bracket range, so no argument to select
cursor.goto_first_child_for_byte(offset.0)?;
}
let mut argument_node = cursor.node();
- // If the child node is the open bracket, move to the next sibling.
if argument_node.byte_range() == open_bracket {
if !cursor.goto_next_sibling() {
return Some(inner_bracket_range);
}
argument_node = cursor.node();
}
- // While the child node is the close bracket or a comma, move to the previous sibling
while argument_node.byte_range() == close_bracket || argument_node.kind() == "," {
if !cursor.goto_previous_sibling() {
return Some(inner_bracket_range);
@@ -1292,14 +1296,11 @@ fn argument(
}
}
- // The start and end of the argument range, defaulting to the start and end of the argument node
let mut start = argument_node.start_byte();
let mut end = argument_node.end_byte();
let mut needs_surrounding_comma = include_comma;
- // Seek backwards to find the start of the argument - either the previous comma or the opening bracket.
- // We do this because multiple nodes can represent a single argument, such as with rust `vec![a.b.c, d.e.f]`
while cursor.goto_previous_sibling() {
let prev = cursor.node();
@@ -1317,7 +1318,6 @@ fn argument(
}
}
- // Do the same for the end of the argument, extending to next comma or the end of the argument list
while cursor.goto_next_sibling() {
let next = cursor.node();
@@ -1326,7 +1326,6 @@ fn argument(
break;
} else if next.kind() == "," {
if needs_surrounding_comma {
- // Select up to the beginning of the next argument if there is one, otherwise to the end of the comma
if let Some(next_arg) = next.next_sibling() {
end = next_arg.start_byte();
} else {
@@ -1342,14 +1341,17 @@ fn argument(
Some(BufferOffset(start)..BufferOffset(end))
}
- let result = comma_delimited_range_at(buffer, excerpt.map_offset_to_buffer(offset), around)?;
+ let results =
+ snapshot.map_excerpt_ranges(offset..offset, |buffer, _excerpt_range, buffer_range| {
+ let buffer_offset = buffer_range.start;
+ match comma_delimited_range_at(buffer, buffer_offset, around) {
+ Some(result) => vec![(result, ())],
+ None => vec![],
+ }
+ })?;
- if excerpt.contains_buffer_range(result.clone()) {
- let result = excerpt.map_range_from_buffer(result);
- Some(result.start.to_display_point(map)..result.end.to_display_point(map))
- } else {
- None
- }
+ let (range, ()) = results.into_iter().next()?;
+ Some(range.start.to_display_point(map)..range.end.to_display_point(map))
}
fn indent(
@@ -3369,7 +3371,12 @@ mod test {
// but, since this is being set manually, the language isn't
// automatically set.
let editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
- let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
+ let buffer_ids = multi_buffer
+ .read(cx)
+ .snapshot(cx)
+ .excerpts()
+ .map(|excerpt| excerpt.context.start.buffer_id)
+ .collect::<Vec<_>>();
if let Some(buffer) = multi_buffer.read(cx).buffer(buffer_ids[1]) {
buffer.update(cx, |buffer, cx| {
buffer.set_language(Some(language::rust_lang()), cx);
@@ -426,7 +426,7 @@ impl MarksState {
name.clone(),
buffer
.read(cx)
- .summaries_for_anchors::<Point, _>(anchors)
+ .summaries_for_anchors::<Point, _>(anchors.iter().copied())
.collect(),
)
})
@@ -492,7 +492,14 @@ impl MarksState {
{
let buffer_marks = old_marks
.into_iter()
- .map(|(k, v)| (k, v.into_iter().map(|anchor| anchor.text_anchor).collect()))
+ .map(|(k, v)| {
+ (
+ k,
+ v.into_iter()
+ .filter_map(|anchor| anchor.raw_text_anchor())
+ .collect(),
+ )
+ })
.collect();
self.buffer_marks
.insert(buffer.read(cx).remote_id(), buffer_marks);
@@ -569,6 +576,7 @@ impl MarksState {
anchors: Vec<Anchor>,
cx: &mut Context<Self>,
) {
+ let multibuffer_snapshot = multibuffer.read(cx).snapshot(cx);
let buffer = multibuffer.read(cx).as_singleton();
let abs_path = buffer.as_ref().and_then(|b| self.path_for_buffer(b, cx));
@@ -602,7 +610,7 @@ impl MarksState {
name.clone(),
anchors
.into_iter()
- .map(|anchor| anchor.text_anchor)
+ .filter_map(|anchor| Some(multibuffer_snapshot.anchor_to_buffer_anchor(anchor)?.0))
.collect(),
);
if !self.watched_buffers.contains_key(&buffer_id) {
@@ -629,12 +637,13 @@ impl MarksState {
return Some(Mark::Local(anchors.get(name)?.clone()));
}
- let (excerpt_id, buffer_id, _) = multi_buffer.read(cx).read(cx).as_singleton()?;
- if let Some(anchors) = self.buffer_marks.get(&buffer_id) {
+ let multibuffer_snapshot = multi_buffer.read(cx).snapshot(cx);
+ let buffer_snapshot = multibuffer_snapshot.as_singleton()?;
+ if let Some(anchors) = self.buffer_marks.get(&buffer_snapshot.remote_id()) {
let text_anchors = anchors.get(name)?;
let anchors = text_anchors
.iter()
- .map(|anchor| Anchor::in_buffer(excerpt_id, *anchor))
+ .filter_map(|anchor| multibuffer_snapshot.anchor_in_excerpt(*anchor))
.collect();
return Some(Mark::Local(anchors));
}
@@ -895,14 +904,13 @@ impl VimGlobals {
}
}
'%' => editor.and_then(|editor| {
- let selection = editor
- .selections
- .newest::<Point>(&editor.display_snapshot(cx));
- if let Some((_, buffer, _)) = editor
- .buffer()
- .read(cx)
- .excerpt_containing(selection.head(), cx)
- {
+ let multibuffer = editor.buffer().read(cx);
+ let snapshot = multibuffer.snapshot(cx);
+ let selection = editor.selections.newest_anchor();
+ let buffer = snapshot
+ .anchor_to_buffer_anchor(selection.head())
+ .and_then(|(text_anchor, _)| multibuffer.buffer(text_anchor.buffer_id));
+ if let Some(buffer) = buffer {
buffer
.read(cx)
.file()
@@ -2117,7 +2117,12 @@ async fn test_folded_multibuffer_excerpts(cx: &mut gpui::TestAppContext) {
);
let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
- let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
+ let buffer_ids = multi_buffer
+ .read(cx)
+ .snapshot(cx)
+ .excerpts()
+ .map(|excerpt| excerpt.context.start.buffer_id)
+ .collect::<Vec<_>>();
// fold all but the second buffer, so that we test navigating between two
// adjacent folded buffers, as well as folded buffers at the start and
// end the multibuffer
@@ -2262,7 +2267,13 @@ async fn test_folded_multibuffer_excerpts(cx: &mut gpui::TestAppContext) {
"
});
cx.update_editor(|editor, _, cx| {
- let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
+ let buffer_ids = editor
+ .buffer()
+ .read(cx)
+ .snapshot(cx)
+ .excerpts()
+ .map(|excerpt| excerpt.context.start.buffer_id)
+ .collect::<Vec<_>>();
editor.fold_buffer(buffer_ids[1], cx);
});
@@ -1226,10 +1226,8 @@ where
let mut display = format!("{err:#}");
if !display.ends_with('\n') {
display.push('.');
- display.push(' ')
}
- let detail =
- f(err, window, cx).unwrap_or_else(|| format!("{display}Please try again."));
+ let detail = f(err, window, cx).unwrap_or(display);
window.prompt(PromptLevel::Critical, &msg, Some(&detail), &["Ok"], cx)
}) {
prompt.await.ok();
@@ -1,6 +1,7 @@
use crate::{
AnyActiveCall, AppState, CollaboratorId, FollowerState, Pane, ParticipantLocation, Workspace,
WorkspaceSettings,
+ notifications::DetachAndPromptErr,
pane_group::element::pane_axis,
workspace_settings::{PaneSplitDirectionHorizontal, PaneSplitDirectionVertical},
};
@@ -438,14 +439,19 @@ impl PaneLeaderDecorator for PaneRenderContext<'_> {
let app_state = self.app_state.clone();
this.cursor_pointer().on_mouse_down(
MouseButton::Left,
- move |_, _, cx| {
+ move |_, window, cx| {
crate::join_in_room_project(
leader_project_id,
leader_user_id,
app_state.clone(),
cx,
)
- .detach_and_log_err(cx);
+ .detach_and_prompt_err(
+ "Failed to join project",
+ window,
+ cx,
+ |error, _, _| Some(format!("{error:#}")),
+ );
},
)
},
@@ -5528,7 +5528,9 @@ impl Workspace {
if let Some(project_id) = other_project_id {
let app_state = self.app_state.clone();
crate::join_in_room_project(project_id, remote_participant.user.id, app_state, cx)
- .detach_and_log_err(cx);
+ .detach_and_prompt_err("Failed to join project", window, cx, |error, _, _| {
+ Some(format!("{error:#}"))
+ });
}
}