From 2a15bf630d60916bbd3724b158fdd36d31b7c5b7 Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Wed, 1 Apr 2026 13:25:32 -0400 Subject: [PATCH] Require multibuffer excerpts to be ordered and nonoverlapping (#52364) TODO: - [x] merge main - [x] nonshrinking `set_excerpts_for_path` - [x] Test-drive potential problem areas in the app - [x] prepare cloud side - [x] test collaboration - [ ] docstrings - [ ] ??? ## Context ### Background Currently, a multibuffer consists of an arbitrary list of anchor-delimited excerpts from individual buffers. Excerpt ranges for a fixed buffer are permitted to overlap, and can appear in any order in the multibuffer, possibly separated by excerpts from other buffers. However, in practice all code that constructs multibuffers does so using the APIs defined in the `path_key` submodule of the `multi_buffer` crate (`set_excerpts_for_path` etc.) If you only use these APIs, the resulting multibuffer will maintain the following invariants: - All excerpts for the same buffer appear contiguously in the multibuffer - Excerpts for the same buffer cannot overlap - Excerpts for the same buffer appear in order - The placement of the excerpts for a specific buffer in the multibuffer are determined by the `PathKey` passed to `set_excerpts_for_path`. There is exactly one `PathKey` per buffer in the multibuffer ### Purpose of this PR This PR changes the multibuffer so that the invariants maintained by the `path_key` APIs *always* hold. It's no longer possible to construct a multibuffer with overlapping excerpts, etc. The APIs that permitted this, like `insert_excerpts_with_ids_after`, have been removed in favor of the `path_key` suite. The main upshot of this is that given a `text::Anchor` and a multibuffer, it's possible to efficiently figure out the unique excerpt that includes that anchor, if any: ``` impl MultiBufferSnapshot { fn buffer_anchor_to_anchor(&self, anchor: text::Anchor) -> Option; } ``` And in the other direction, given a `multi_buffer::Anchor`, we can look at its `text::Anchor` to locate the excerpt that contains it. That means we don't need an `ExcerptId` to create or resolve `multi_buffer::Anchor`, and in fact we can delete `ExcerptId` entirely, so that excerpts no longer have any identity outside their `Range`. There are a large number of changes to `editor` and other downstream crates as a result of removing `ExcerptId` and multibuffer APIs that assumed it. ### Other changes There are some other improvements that are not immediate consequences of that big change, but helped make it smoother. Notably: - The `buffer_id` field of `text::Anchor` is no longer optional. `text::Anchor::{MIN, MAX}` have been removed in favor of `min_for_buffer`, etc. - `multi_buffer::Anchor` is now a three-variant enum (inlined slightly): ``` enum Anchor { Min, Excerpt { text_anchor: text::Anchor, path_key_index: PathKeyIndex, diff_base_anchor: Option, }, Max, } ``` That means it's no longer possible to unconditionally access the `text_anchor` field, which is good because most of the places that were doing that were buggy for min/max! Instead, we have a new API that correctly resolves min/max to the start of the first excerpt or the end of the last excerpt: ``` impl MultiBufferSnapshot { fn anchor_to_buffer_anchor(&self, anchor: multi_buffer::Anchor) -> Option; } ``` - `MultiBufferExcerpt` has been removed in favor of a new `map_excerpt_ranges` API directly on `MultiBufferSnapshot`. ## Self-Review Checklist - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - N/A --------- Co-authored-by: Conrad Irwin Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Co-authored-by: Jakub Konka Co-authored-by: Conrad --- crates/acp_thread/src/acp_thread.rs | 2 +- crates/acp_thread/src/diff.rs | 2 +- crates/action_log/src/action_log.rs | 10 +- crates/agent/src/edit_agent.rs | 4 +- .../src/tools/streaming_edit_file_tool.rs | 6 +- crates/agent_ui/src/agent_diff.rs | 41 +- crates/agent_ui/src/buffer_codegen.rs | 4 +- crates/agent_ui/src/completion_provider.rs | 7 +- .../src/conversation_view/thread_view.rs | 13 +- crates/agent_ui/src/inline_assistant.rs | 61 +- crates/agent_ui/src/mention_set.rs | 29 +- crates/agent_ui/src/message_editor.rs | 92 +- crates/buffer_diff/src/buffer_diff.rs | 14 +- crates/call/src/call_impl/room.rs | 6 +- crates/client/src/client.rs | 2 + .../20221109000000_test_schema.sql | 3 +- .../migrations/20251208000000_test_schema.sql | 3 +- crates/collab/src/db.rs | 1 + crates/collab/src/db/queries/projects.rs | 4 + crates/collab/src/db/tables/project.rs | 1 + crates/collab/src/rpc.rs | 25 + .../tests/integration/channel_buffer_tests.rs | 2 +- .../tests/integration/db_tests/db_tests.rs | 39 +- .../tests/integration/following_tests.rs | 17 +- crates/collab_ui/src/collab_panel.rs | 4 +- crates/csv_preview/src/csv_preview.rs | 4 +- crates/debugger_ui/src/debugger_ui.rs | 2 +- .../src/session/running/console.rs | 5 +- crates/diagnostics/src/buffer_diagnostics.rs | 49 +- crates/diagnostics/src/diagnostic_renderer.rs | 23 +- crates/diagnostics/src/diagnostics.rs | 50 +- crates/edit_prediction/src/capture_example.rs | 2 +- crates/edit_prediction/src/edit_prediction.rs | 14 +- crates/edit_prediction/src/udiff.rs | 3 +- .../src/edit_prediction_context_view.rs | 12 +- .../src/rate_prediction_modal.rs | 40 +- crates/editor/src/bracket_colorization.rs | 214 +- crates/editor/src/code_completion_tests.rs | 4 +- crates/editor/src/code_context_menus.rs | 13 +- crates/editor/src/display_map.rs | 123 +- crates/editor/src/display_map/block_map.rs | 151 +- crates/editor/src/display_map/crease_map.rs | 2 +- crates/editor/src/display_map/fold_map.rs | 22 +- crates/editor/src/display_map/inlay_map.rs | 22 +- crates/editor/src/document_colors.rs | 105 +- crates/editor/src/document_symbols.rs | 84 +- crates/editor/src/edit_prediction_tests.rs | 9 +- crates/editor/src/editor.rs | 1315 +++--- crates/editor/src/editor_tests.rs | 281 +- crates/editor/src/element.rs | 201 +- crates/editor/src/folding_ranges.rs | 6 +- crates/editor/src/git/blame.rs | 19 +- crates/editor/src/hover_links.rs | 40 +- crates/editor/src/hover_popover.rs | 10 +- crates/editor/src/inlays.rs | 1 + crates/editor/src/inlays/inlay_hints.rs | 85 +- crates/editor/src/items.rs | 378 +- crates/editor/src/jsx_tag_auto_close.rs | 11 +- crates/editor/src/linked_editing_ranges.rs | 25 +- crates/editor/src/lsp_ext.rs | 28 +- crates/editor/src/mouse_context_menu.rs | 21 +- crates/editor/src/movement.rs | 36 +- crates/editor/src/runnables.rs | 73 +- crates/editor/src/rust_analyzer_ext.rs | 58 +- crates/editor/src/scroll.rs | 4 +- crates/editor/src/scroll/actions.rs | 2 +- crates/editor/src/selections_collection.rs | 109 +- crates/editor/src/semantic_tokens.rs | 20 +- crates/editor/src/split.rs | 786 ++-- crates/editor/src/split_editor_view.rs | 17 +- crates/editor/src/tasks.rs | 101 + crates/editor/src/test.rs | 4 +- crates/editor/src/test/editor_test_context.rs | 106 +- .../src/active_buffer_encoding.rs | 2 +- .../src/encoding_selector.rs | 4 +- crates/git_ui/src/commit_view.rs | 7 +- crates/git_ui/src/conflict_view.rs | 123 +- crates/git_ui/src/git_panel.rs | 7 +- crates/git_ui/src/project_diff.rs | 70 +- crates/git_ui/src/text_diff_view.rs | 36 +- crates/go_to_line/src/cursor_position.rs | 31 +- crates/go_to_line/src/go_to_line.rs | 12 +- crates/gpui/src/window.rs | 9 + crates/inspector_ui/src/div_inspector.rs | 4 +- .../src/action_completion_provider.rs | 1 - crates/keymap_editor/src/keymap_editor.rs | 1 - crates/language/src/diagnostic_set.rs | 22 +- crates/language/src/proto.rs | 8 +- crates/language/src/syntax_map.rs | 40 +- .../src/active_buffer_language.rs | 2 +- .../src/language_selector.rs | 23 +- .../src/highlights_tree_view.rs | 176 +- crates/language_tools/src/lsp_button.rs | 17 +- crates/language_tools/src/syntax_tree_view.rs | 19 +- .../src/line_ending_indicator.rs | 2 +- .../src/line_ending_selector.rs | 2 +- .../src/markdown_preview_view.rs | 2 +- crates/multi_buffer/src/anchor.rs | 516 ++- crates/multi_buffer/src/multi_buffer.rs | 3912 +++++++---------- crates/multi_buffer/src/multi_buffer_tests.rs | 1715 +++++--- crates/multi_buffer/src/path_key.rs | 849 ++-- crates/multi_buffer/src/transaction.rs | 81 +- crates/outline/src/outline.rs | 42 +- crates/outline_panel/src/outline_panel.rs | 968 ++-- .../project/src/lsp_store/semantic_tokens.rs | 2 +- crates/project/src/project.rs | 6 + .../tests/integration/project_tests.rs | 33 +- crates/proto/proto/buffer.proto | 7 +- crates/proto/proto/call.proto | 19 + crates/search/src/buffer_search.rs | 22 +- crates/search/src/project_search.rs | 57 +- crates/tasks_ui/src/tasks_ui.rs | 12 +- crates/text/src/anchor.rs | 48 +- crates/text/src/patch.rs | 3 + crates/text/src/text.rs | 33 +- .../src/active_toolchain.rs | 2 +- .../src/toolchain_selector.rs | 4 +- crates/vim/src/command.rs | 4 +- crates/vim/src/motion.rs | 40 +- crates/vim/src/normal.rs | 2 +- crates/vim/src/object.rs | 289 +- crates/vim/src/state.rs | 36 +- crates/vim/src/test.rs | 15 +- crates/workspace/src/notifications.rs | 4 +- crates/workspace/src/pane_group.rs | 10 +- crates/workspace/src/workspace.rs | 4 +- 126 files changed, 7262 insertions(+), 7175 deletions(-) create mode 100644 crates/editor/src/tasks.rs diff --git a/crates/acp_thread/src/acp_thread.rs b/crates/acp_thread/src/acp_thread.rs index 937592b8a94df00ca1c7565d43893b99693f8892..0bcb8254c8b8123eef3faaa913bb360de8dcc76d 100644 --- a/crates/acp_thread/src/acp_thread.rs +++ b/crates/acp_thread/src/acp_thread.rs @@ -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::>() }) diff --git a/crates/acp_thread/src/diff.rs b/crates/acp_thread/src/diff.rs index 08b1b9bdf24d1ff9980164c1af8b3e60bd2f3339..a6d3b86db7c980bb5e4e5a8cacee95abeaabc3f1 100644 --- a/crates/acp_thread/src/diff.rs +++ b/crates/acp_thread/src/diff.rs @@ -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 { diff --git a/crates/action_log/src/action_log.rs b/crates/action_log/src/action_log.rs index 3faf767c7020763eadc7db6c93af42f650a07434..1f17d38f7d2a2770350026f2f145a53723ef7481 100644 --- a/crates/action_log/src/action_log.rs +++ b/crates/action_log/src/action_log.rs @@ -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)); } diff --git a/crates/agent/src/edit_agent.rs b/crates/agent/src/edit_agent.rs index 6e6cf9735a922695bf089bdcc78798fb086ad364..f0dae2a7b39dcad0fea280a2354f2f3c5c61600b 100644 --- a/crates/agent/src/edit_agent.rs +++ b/crates/agent/src/edit_agent.rs @@ -374,13 +374,13 @@ impl EditAgent { buffer.edit(edits.iter().cloned(), None, cx); let max_edit_end = buffer .summaries_for_anchors::( - edits.iter().map(|(range, _)| &range.end), + edits.iter().map(|(range, _)| range.end), ) .max() .unwrap(); let min_edit_start = buffer .summaries_for_anchors::( - edits.iter().map(|(range, _)| &range.start), + edits.iter().map(|(range, _)| range.start), ) .min() .unwrap(); diff --git a/crates/agent/src/tools/streaming_edit_file_tool.rs b/crates/agent/src/tools/streaming_edit_file_tool.rs index 88ec1e67787ad6efbeaa46b83b9034a24b10d3db..c0c29bfc43d9c58ac011b3170edf81210ba8ee66 100644 --- a/crates/agent/src/tools/streaming_edit_file_tool.rs +++ b/crates/agent/src/tools/streaming_edit_file_tool.rs @@ -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; } diff --git a/crates/agent_ui/src/agent_diff.rs b/crates/agent_ui/src/agent_diff.rs index 2e709c0be3297e270119c048c7b8e25e7958ee69..d5cf63f6cdde9a85a54daaa29f8fc2c6833bdd77 100644 --- a/crates/agent_ui/src/agent_diff.rs +++ b/crates/agent_ui/src/agent_diff.rs @@ -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::>(); 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, diff --git a/crates/agent_ui/src/buffer_codegen.rs b/crates/agent_ui/src/buffer_codegen.rs index 420f8665e349c4e79222cdfa034de44971fab538..d5288c564d7211a986fa6347e2b74782c58d9c75 100644 --- a/crates/agent_ui/src/buffer_codegen.rs +++ b/crates/agent_ui/src/buffer_codegen.rs @@ -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()) diff --git a/crates/agent_ui/src/completion_provider.rs b/crates/agent_ui/src/completion_provider.rs index 6259269834b0add5b87fd9d397e17671d30adb9f..a72b352375ef9b219729172f0d19854287e0e7fc 100644 --- a/crates/agent_ui/src/completion_provider.rs +++ b/crates/agent_ui/src/completion_provider.rs @@ -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 PromptCompletionProvider { 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 PromptCompletionProvider { impl CompletionProvider for PromptCompletionProvider { fn completions( &self, - _excerpt_id: ExcerptId, buffer: &Entity, buffer_position: Anchor, _trigger: CompletionContext, diff --git a/crates/agent_ui/src/conversation_view/thread_view.rs b/crates/agent_ui/src/conversation_view/thread_view.rs index b25769eadbe31c35a6261cc9433349a2943617be..1782f7230e1d05a06abe4788b7883eddd402796a 100644 --- a/crates/agent_ui/src/conversation_view/thread_view.rs +++ b/crates/agent_ui/src/conversation_view/thread_view.rs @@ -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]); }) diff --git a/crates/agent_ui/src/inline_assistant.rs b/crates/agent_ui/src/inline_assistant.rs index 01543b657fc2d00fbf8c68cd96c6329d2f4952d6..20e0b702978b7e72a8526b03570854965335310c 100644 --- a/crates/agent_ui/src/inline_assistant.rs +++ b/crates/agent_ui/src/inline_assistant.rs @@ -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::(*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::( - 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: Entity, 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")?; diff --git a/crates/agent_ui/src/mention_set.rs b/crates/agent_ui/src/mention_set.rs index 2559edc566d4467eaaab180e0a16f4af5fae7ab9..4db856f9dd1e512a7b8b43eadcefccc22fe50188 100644 --- a/crates/agent_ui/src/mention_set.rs +++ b/crates/agent_ui/src/mention_set.rs @@ -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); diff --git a/crates/agent_ui/src/message_editor.rs b/crates/agent_ui/src/message_editor.rs index df36f38899c9abea165d0ff5a01834a2bb84c82f..8660e792cd23bc418b1d2c204bfafb2a81ba48df 100644 --- a/crates/agent_ui/src/message_editor.rs +++ b/crates/agent_ui/src/message_editor.rs @@ -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); diff --git a/crates/buffer_diff/src/buffer_diff.rs b/crates/buffer_diff/src/buffer_diff.rs index 1cb1e801c2cd68d442321da76c0abb848f9fa0d8..c168bd2956e0687eca5e5adeb16edbe70e9edd54 100644 --- a/crates/buffer_diff/src/buffer_diff.rs +++ b/crates/buffer_diff/src/buffer_diff.rs @@ -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 { .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 { ), ), ( - &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::>(); let Some(secondary) = self.secondary_diff.clone() else { return; diff --git a/crates/call/src/call_impl/room.rs b/crates/call/src/call_impl/room.rs index f92a8163d54de0c21c7318c4baab5aad5ce49b75..37a3fd823ec03d3b1d94419ac47662431d718708 100644 --- a/crates/call/src/call_impl/room.rs +++ b/crates/call/src/call_impl/room.rs @@ -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| { diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index f40d90a983978e8928477b5a2973dfa05e05b907..6a11a6b924eed3dfd79ff379638ed4085e2b7bcb 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -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(); diff --git a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql index 75d7dbf194068f78b3d566e54bb0fa18f66a9878..2a87d617ebb19117ca87c00cc0887b07e416c8bd 100644 --- a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql +++ b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql @@ -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"); diff --git a/crates/collab/migrations/20251208000000_test_schema.sql b/crates/collab/migrations/20251208000000_test_schema.sql index 394deaf2c0d6a80a2ab6ab1b95a333081c816e23..8a56b9ce982f9a39a14bfc55fe8a34870ddea1c6 100644 --- a/crates/collab/migrations/20251208000000_test_schema.sql +++ b/crates/collab/migrations/20251208000000_test_schema.sql @@ -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 diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index d8803c253f5feef8ef5e040f3ea112abcc688f52..3e4c36631b29d35871cac101542bcc6904fbb271 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -589,6 +589,7 @@ pub struct Project { pub repositories: Vec, pub language_servers: Vec, pub path_style: PathStyle, + pub features: Vec, } pub struct ProjectCollaborator { diff --git a/crates/collab/src/db/queries/projects.rs b/crates/collab/src/db/queries/projects.rs index 71365fb3846c1dccbf527d76779ed8816bde243b..3fc59f96332180d7d7bca4b6f71a345d9699e9e2 100644 --- a/crates/collab/src/db/queries/projects.rs +++ b/crates/collab/src/db/queries/projects.rs @@ -34,6 +34,7 @@ impl Database { worktrees: &[proto::WorktreeMetadata], is_ssh_project: bool, windows_paths: bool, + features: &[String], ) -> Result> { 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 = 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)) } diff --git a/crates/collab/src/db/tables/project.rs b/crates/collab/src/db/tables/project.rs index 11a9b972ebcd7af29d6e6c234096384ce9ff7701..76d399cfc6445ca7c2516cc4cd76e885230868af 100644 --- a/crates/collab/src/db/tables/project.rs +++ b/crates/collab/src/db/tables/project.rs @@ -13,6 +13,7 @@ pub struct Model { pub host_connection_id: Option, pub host_connection_server_id: Option, pub windows_paths: bool, + pub features: String, } impl Model { diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 3c4efe0580c18c938f8245de9f40bf216bab9c81..e05df1909db1e8afed0c06425d84799ff985f3c5 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -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 = + 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) { diff --git a/crates/collab/tests/integration/channel_buffer_tests.rs b/crates/collab/tests/integration/channel_buffer_tests.rs index a5aca7dd82ca23b1c348bea1fff5d2da2870c654..dd8ae9a2c02cfae6c6c7e8c369308c5092be113e 100644 --- a/crates/collab/tests/integration/channel_buffer_tests.rs +++ b/crates/collab/tests/integration/channel_buffer_tests.rs @@ -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| { diff --git a/crates/collab/tests/integration/db_tests/db_tests.rs b/crates/collab/tests/integration/db_tests/db_tests.rs index e2006b7fb9984c4bd0cf16a62e9321b2f7007e9e..710f95dbf7d82e05a541b844b093a04ca88565f7 100644 --- a/crates/collab/tests/integration/db_tests/db_tests.rs +++ b/crates/collab/tests/integration/db_tests/db_tests.rs @@ -350,20 +350,41 @@ async fn test_project_count(db: &Arc) { .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 }) diff --git a/crates/collab/tests/integration/following_tests.rs b/crates/collab/tests/integration/following_tests.rs index c4031788c87f747c3125f4dbc509d68ea3720b43..7109b0f31452d2573426aa2300e7967b8f5a6601 100644 --- a/crates/collab/tests/integration/following_tests.rs +++ b/crates/collab/tests/integration/following_tests.rs @@ -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 = 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::>() }); 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::>() }); assert_eq!(positions, new_positions); diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 6a53e590586ec2353feafe267501619e8bbfcc71..91385b298dc661c4a79e4fb52d5be0f38672bff5 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -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:#}")), ); } } diff --git a/crates/csv_preview/src/csv_preview.rs b/crates/csv_preview/src/csv_preview.rs index c38cefb2456b3f44e3cac61b02294ab1ed1e79f4..1b99139b004a940dfa0902e185f67fb4b77ed6a1 100644 --- a/crates/csv_preview/src/csv_preview.rs +++ b/crates/csv_preview/src/csv_preview.rs @@ -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); } _ => {} diff --git a/crates/debugger_ui/src/debugger_ui.rs b/crates/debugger_ui/src/debugger_ui.rs index 124967650b31cd88e72b2867838fb3a4ecbcf920..f5947a4393b2eeb8ca6ad3f844962500aa4ecf2d 100644 --- a/crates/debugger_ui/src/debugger_ui.rs +++ b/crates/debugger_ui/src/debugger_ui.rs @@ -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 diff --git a/crates/debugger_ui/src/session/running/console.rs b/crates/debugger_ui/src/session/running/console.rs index c488e88d74e7f282bd0424a2213e08e2c9bec15f..65bc949b2b6ddb1a707abf2e001ffde151fb70b8 100644 --- a/crates/debugger_ui/src/session/running/console.rs +++ b/crates/debugger_ui/src/session/running/console.rs @@ -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); impl CompletionProvider for ConsoleQueryBarCompletionProvider { fn completions( &self, - _excerpt_id: ExcerptId, buffer: &Entity, buffer_position: language::Anchor, _trigger: editor::CompletionContext, diff --git a/crates/diagnostics/src/buffer_diagnostics.rs b/crates/diagnostics/src/buffer_diagnostics.rs index 56924585011921ddebc96b971fd15c3abd151a85..040aeae4742e18449523cbc255b4370814c1f8d7 100644 --- a/crates/diagnostics/src/buffer_diagnostics.rs +++ b/crates/diagnostics/src/buffer_diagnostics.rs @@ -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> = 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() { diff --git a/crates/diagnostics/src/diagnostic_renderer.rs b/crates/diagnostics/src/diagnostic_renderer.rs index 62b7f4eadf322da1c57a9f1da60b412d7b0dcd68..eaf414560845ea326fc508fe19d71fb01ebc1f32 100644 --- a/crates/diagnostics/src/diagnostic_renderer.rs +++ b/crates/diagnostics/src/diagnostic_renderer.rs @@ -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 diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index b200d01669a90c1e439338b9b01118cce8b8bb0c..9bedc2db4a138eec468857013f9f1a010923bbe6 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -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::>() }); - 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> = 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(); diff --git a/crates/edit_prediction/src/capture_example.rs b/crates/edit_prediction/src/capture_example.rs index d21df7868162d279cb18aeea3ef04d4ea9d7be7f..5eb422246775c4409f7f15e3a672a2d407386acc 100644 --- a/crates/edit_prediction/src/capture_example.rs +++ b/crates/edit_prediction/src/capture_example.rs @@ -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, diff --git a/crates/edit_prediction/src/edit_prediction.rs b/crates/edit_prediction/src/edit_prediction.rs index 3a66f712e31d7853bede21ab96ca6c7e92bea967..61690c470829ca4bb16a6af9f1df2ea6e7cc6023 100644 --- a/crates/edit_prediction/src/edit_prediction.rs +++ b/crates/edit_prediction/src/edit_prediction.rs @@ -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, language::Anchor)>> { let collaborator_cursor_rows: Vec = 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) } diff --git a/crates/edit_prediction/src/udiff.rs b/crates/edit_prediction/src/udiff.rs index 14be1991d34e985067f5ad8729fd7ac8485211db..407dc4fc7239fb1974ef8bc5be4b3a99cd31f187 100644 --- a/crates/edit_prediction/src/udiff.rs +++ b/crates/edit_prediction/src/udiff.rs @@ -54,7 +54,6 @@ pub async fn apply_diff( let mut included_files: HashMap> = 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, Arc)> = 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(()) diff --git a/crates/edit_prediction_ui/src/edit_prediction_context_view.rs b/crates/edit_prediction_ui/src/edit_prediction_context_view.rs index 48e74dcdcc102f9ed7844f1b8829e0182fe2c97b..1407ffc73d82c6e564fe46e688b6d6d16a307c01 100644 --- a/crates/edit_prediction_ui/src/edit_prediction_context_view.rs +++ b/crates/edit_prediction_ui/src/edit_prediction_context_view.rs @@ -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)); + } } } }); diff --git a/crates/edit_prediction_ui/src/rate_prediction_modal.rs b/crates/edit_prediction_ui/src/rate_prediction_modal.rs index 1fb6c36bc9503e0a2fea7b3f77d1515747d1363c..eb071bf955cede173e74993c93ab5cd294338474 100644 --- a/crates/edit_prediction_ui/src/rate_prediction_modal.rs +++ b/crates/edit_prediction_ui/src/rate_prediction_modal.rs @@ -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_position: language::Anchor, _trigger: editor::CompletionContext, diff --git a/crates/editor/src/bracket_colorization.rs b/crates/editor/src/bracket_colorization.rs index 0c9fa29ae6a19ad81ec265cc832a5d3ec15cec51..8c8c3a36e9a73a0b3960f1239f49270647dabea7 100644 --- a/crates/editor/src/bracket_colorization.rs +++ b/crates/editor/src/bracket_colorization.rs @@ -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)> = visible_excerpts + let visible_excerpts = self.visible_buffer_ranges(cx); + let excerpt_data: Vec<( + BufferSnapshot, + Range, + ExcerptRange, + )> = 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::>>>(); + .collect::, HashSet>>>(); 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>> = 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, + buffer_range: Range, + excerpt_range: ExcerptRange, fetched_chunks: &mut HashSet>, - excerpt_id: ExcerptId, accents_count: usize, - anchors_in_multi_buffer: &impl Fn(ExcerptId, [text::Anchor; 4]) -> Option<[Option; 4]>, ) -> Vec<(usize, Vec>)> { + 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> = None; + } + "#}, + indoc! {r#" + fn process_data_2() { + let other_map: Option> = 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}23\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}23\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) { diff --git a/crates/editor/src/code_completion_tests.rs b/crates/editor/src/code_completion_tests.rs index 4602824486ebb88f78ed529abb91ddcc1c34646f..3211f0b818eb3079007db4bf268e84bd53d3cbf1 100644 --- a/crates/editor/src/code_completion_tests.rs +++ b/crates/editor/src/code_completion_tests.rs @@ -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, ) -> 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, diff --git a/crates/editor/src/code_context_menus.rs b/crates/editor/src/code_context_menus.rs index 5d6c037d9b67034423dda9f119a1e78fb1e5b9b2..2db2086eef422a87a0825c4a4ad820d422b160e9 100644 --- a/crates/editor/src/code_context_menus.rs +++ b/crates/editor/src/code_context_menus.rs @@ -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, - selection: Range, + initial_position: Anchor, + selection: Range, buffer: Entity, scroll_handle: Option, 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, } @@ -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, }, diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 933f0e6e18e57c38b6bcc3636f60bd1ae671d3a6..916391b32d580dc0cc86670056a42bd5a0861aab 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -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, &MultiBufferSnapshot, &MultiBufferSnapshot, - (Bound, Bound), + Range, ) -> Vec; /// 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, lhs_buffer_to_rhs_buffer: HashMap, - rhs_excerpt_to_lhs_excerpt: HashMap, - lhs_excerpt_to_rhs_excerpt: HashMap, rhs_rows_to_lhs_rows: ConvertMultiBufferRows, lhs_rows_to_rhs_rows: ConvertMultiBufferRows, rhs_custom_block_to_balancing_block: RefCell>, @@ -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, Bound), + bounds: Range, ) -> Vec { - 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 { - 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 { - 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 { - 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, - &HashMap, - ) { - ( - &self.lhs_excerpt_to_rhs_excerpt, - &self.rhs_excerpt_to_lhs_excerpt, - ) - } - fn buffer_to_companion_buffer(&self, display_map_id: EntityId) -> &HashMap { 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, - rhs_ids: impl IntoIterator, - ) { - 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 { 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 { - 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::>(); - 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({ diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 531de6da49e375a4f7ba2833106e1716de551ff2..25874457a8e3d4787de22e3e8c0e2c61a49708f8 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -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 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), 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::>() + }); 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::>(); 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::>(); 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::>(); + 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::(), )) } 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::>(); 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::>(); 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 { diff --git a/crates/editor/src/display_map/crease_map.rs b/crates/editor/src/display_map/crease_map.rs index 7c81562b7448bdb53bd0dd641eada92dff527aac..1664012b5eb43fb82c7c0fce38844d98ab0f7226 100644 --- a/crates/editor/src/display_map/crease_map.rs +++ b/crates/editor/src/display_map/crease_map.rs @@ -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, } } } diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 95479e297cb82adcf8c3eb1f73e95f8b557eef43..1554bb96dab0e2f76a17df1396bd945f332af208 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -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::(&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, } } diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 9c05a182ef56eb803ff545a1c9d3914b505767aa..47ca295ccb1a08768ce129b92d10506294a9cf78 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -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, diff --git a/crates/editor/src/document_colors.rs b/crates/editor/src/document_colors.rs index a38a0527f0641ef2d622b2f33fa1e932080ad7b5..8f8b70128ffc2bb66b2147baaa53d77e40c03c25 100644 --- a/crates/editor/src/document_colors.rs +++ b/crates/editor/src/document_colors.rs @@ -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::>() }) @@ -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, 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)); } } } diff --git a/crates/editor/src/document_symbols.rs b/crates/editor/src/document_symbols.rs index 0668a034c8755a8702e31ec3a060b7f3b79c6829..ef9159788a7a5c2b2c317015219090fdae6a4944 100644 --- a/crates/editor/src/document_symbols.rs +++ b/crates/editor/src/document_symbols.rs @@ -62,10 +62,10 @@ impl Editor { multi_buffer_snapshot: &MultiBufferSnapshot, cx: &Context, ) -> 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, + _cx: &Context, ) -> Option<(BufferId, Vec>)> { - 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::>(); @@ -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) diff --git a/crates/editor/src/edit_prediction_tests.rs b/crates/editor/src/edit_prediction_tests.rs index 52939a9e5a8fd1a35a3a3c0bcd2a04b893bd6628..d1e326bc93b8052f3ae089c211e65eb3ef020fdf 100644 --- a/crates/editor/src/edit_prediction_tests.rs +++ b/crates/editor/src/edit_prediction_tests.rs @@ -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: &Entity, _buffer_position: text::Anchor, _trigger: CompletionContext, _window: &mut Window, - _cx: &mut Context, + cx: &mut Context, ) -> Task>> { 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, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 76ec95928dc729e12060e75f8ec7d61197624c5f..e4cccf3fc5607937a2a82b2ab1089e00bbda6fa7 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -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, Arc)>, /// 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 { @@ -1340,7 +1343,7 @@ pub struct Editor { suppress_selection_callback: bool, applicable_language_settings: HashMap, LanguageSettings>, accent_data: Option, - bracket_fetched_tree_sitter_chunks: HashMap>>, + bracket_fetched_tree_sitter_chunks: HashMap, HashSet>>, semantic_token_state: SemanticTokenState, pub(crate) refresh_matching_bracket_highlights_task: Task<()>, refresh_document_symbols_task: Shared>, @@ -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 = 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, Range)> { - self.buffer - .read(cx) - .excerpt_containing(self.selections.newest_anchor().head(), cx) + pub fn active_buffer(&self, cx: &App) -> Option> { + 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) { 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, + query_range: Range, cx: &App, ) -> Option, Vec>>> { + 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, 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, - ) -> HashMap, clock::Global, Range)> { - 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>, 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) -> Vec> { + 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, + ) -> Vec<( + BufferSnapshot, + Range, + ExcerptRange, + )> { + 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> = - 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::(); - 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 = 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::>(); 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::(transaction) .all(|range| { @@ -7207,15 +7247,21 @@ impl Editor { .read(cx) .edited_ranges_for_transaction::(transaction) .collect::>(); - 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::(&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 @@ -8728,11 +8764,8 @@ impl Editor { return None; }; - let (buffer, cursor_buffer_position) = - self.buffer.read(cx).text_anchor_for_position(cursor, cx)?; - self.edit_prediction_settings = - self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx); + self.edit_prediction_settings_at_position(&buffer, cursor_text_anchor, cx); self.in_leading_whitespace = multibuffer.is_line_whitespace_upto(cursor); @@ -8755,7 +8788,7 @@ impl Editor { } } - let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?; + let edit_prediction = provider.suggest(&buffer, cursor_text_anchor, cx)?; let (completion_id, edits, predicted_cursor_position, edit_preview) = match edit_prediction { @@ -8789,7 +8822,7 @@ impl Editor { .into_iter() .flat_map(|(range, new_text)| { Some(( - multibuffer.anchor_range_in_excerpt(excerpt_id, range)?, + multibuffer.buffer_anchor_range_to_anchor_range(range)?, new_text, )) }) @@ -8799,7 +8832,7 @@ impl Editor { } let cursor_position = predicted_cursor_position.and_then(|predicted| { - let anchor = multibuffer.anchor_in_excerpt(excerpt_id, predicted.anchor)?; + let anchor = multibuffer.anchor_in_excerpt(predicted.anchor)?; Some((anchor, predicted.offset)) }); @@ -8813,7 +8846,9 @@ impl Editor { let cursor_row = cursor.to_point(&multibuffer).row; - let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?; + let snapshot = multibuffer + .buffer_for_id(cursor_text_anchor.buffer_id) + .cloned()?; let mut inlay_ids = Vec::new(); let invalidation_row_range; @@ -8960,20 +8995,14 @@ impl Editor { let snapshot = self.snapshot(window, cx); let multi_buffer_snapshot = snapshot.buffer_snapshot(); - let Some(project) = self.project() else { - return breakpoint_display_points; - }; let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left) ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right); - for (buffer_snapshot, range, excerpt_id) in - multi_buffer_snapshot.range_to_buffer_ranges(range.start..=range.end) + for (buffer_snapshot, range, _) in + multi_buffer_snapshot.range_to_buffer_ranges(range.start..range.end) { - let Some(buffer) = project - .read(cx) - .buffer_for_id(buffer_snapshot.remote_id(), cx) - else { + let Some(buffer) = self.buffer().read(cx).buffer(buffer_snapshot.remote_id()) else { continue; }; let breakpoints = breakpoint_store.read(cx).breakpoints( @@ -8982,11 +9011,15 @@ impl Editor { buffer_snapshot.anchor_before(range.start) ..buffer_snapshot.anchor_after(range.end), ), - buffer_snapshot, + &buffer_snapshot, cx, ); for (breakpoint, state) in breakpoints { - let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position); + let Some(multi_buffer_anchor) = + multi_buffer_snapshot.anchor_in_excerpt(breakpoint.position) + else { + continue; + }; let position = multi_buffer_anchor .to_point(&multi_buffer_snapshot) .to_display_point(&snapshot); @@ -9764,7 +9797,14 @@ impl Editor { } let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() { - crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx) + crate::edit_prediction_edit_text( + snapshot, + edits, + edit_preview, + false, + editor_snapshot.buffer_snapshot(), + cx, + ) } else { // Fallback for providers without edit_preview crate::edit_prediction_fallback_text(edits, cx) @@ -10204,7 +10244,8 @@ impl Editor { .child(div().px_1p5().child(match &prediction.completion { EditPrediction::MoveWithin { target, snapshot } => { use text::ToPoint as _; - if target.text_anchor.to_point(snapshot).row > cursor_point.row + if target.text_anchor_in(&snapshot).to_point(snapshot).row + > cursor_point.row { Icon::new(icons.down) } else { @@ -10418,19 +10459,18 @@ impl Editor { if !supports_jump { return None; } + let (target, _) = self.display_snapshot(cx).anchor_to_buffer_anchor(*target)?; Some( h_flex() .px_2() .gap_2() .flex_1() - .child( - if target.text_anchor.to_point(snapshot).row > cursor_point.row { - Icon::new(icons.down) - } else { - Icon::new(icons.up) - }, - ) + .child(if target.to_point(snapshot).row > cursor_point.row { + Icon::new(icons.down) + } else { + Icon::new(icons.up) + }) .child(Label::new("Jump to Edit")), ) } @@ -10454,12 +10494,24 @@ impl Editor { snapshot, .. } => { - let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row; + let first_edit_row = self + .display_snapshot(cx) + .anchor_to_buffer_anchor(edits.first()?.0.start)? + .0 + .to_point(snapshot) + .row; let (highlighted_edits, has_more_lines) = if let Some(edit_preview) = edit_preview.as_ref() { - crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx) - .first_line_preview() + crate::edit_prediction_edit_text( + snapshot, + edits, + edit_preview, + true, + &self.display_snapshot(cx), + cx, + ) + .first_line_preview() } else { crate::edit_prediction_fallback_text(edits, cx).first_line_preview() }; @@ -10554,21 +10606,15 @@ impl Editor { selection: Range, cx: &mut Context, ) { - let Some((_, buffer, _)) = self - .buffer() - .read(cx) - .excerpt_containing(selection.start, cx) + let buffer_snapshot = self.buffer.read(cx).snapshot(cx); + let Some((buffer_snapshot, range)) = + buffer_snapshot.anchor_range_to_buffer_anchor_range(selection.clone()) else { return; }; - let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx) - else { + let Some(buffer) = self.buffer.read(cx).buffer(buffer_snapshot.remote_id()) else { return; }; - if buffer != end_buffer { - log::error!("expected anchor range to have matching buffer IDs"); - return; - } let id = post_inc(&mut self.next_completion_id); let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order; @@ -10579,7 +10625,8 @@ impl Editor { id, true, choices, - selection, + selection.start, + range, buffer, old_menu.map(|menu| menu.primary_scroll_handle()), snippet_sort_order, @@ -11697,7 +11744,7 @@ impl Editor { let buffer = self.buffer().read(cx); let ranges = buffer_ids .into_iter() - .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx)) + .flat_map(|buffer_id| buffer.range_for_buffer(buffer_id, cx)) .collect::>(); self.restore_hunks_in_ranges(ranges, window, cx); @@ -11767,8 +11814,11 @@ impl Editor { let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges); self.transact(window, cx, |editor, window, cx| { editor.restore_diff_hunks(hunks, cx); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { - selections.refresh() + let selections = editor + .selections + .all::(&editor.display_snapshot(cx)); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select(selections); }); }); } @@ -11822,7 +11872,7 @@ impl Editor { window: &mut Window, cx: &mut Context, ) { - if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| { + if let Some(working_directory) = self.active_buffer(cx).and_then(|buffer| { let project_path = buffer.read(cx).project_path(cx)?; let project = self.project()?.read(cx); let entry = project.entry_for_path(&project_path, cx)?; @@ -11934,22 +11984,19 @@ impl Editor { snapshot: &EditorSnapshot, cx: &mut Context, ) -> Option<(Anchor, Breakpoint)> { - let buffer = self - .buffer - .read(cx) - .buffer_for_anchor(breakpoint_position, cx)?; + let (breakpoint_position, _) = snapshot + .buffer_snapshot() + .anchor_to_buffer_anchor(breakpoint_position)?; + let buffer = self.buffer.read(cx).buffer(breakpoint_position.buffer_id)?; - let enclosing_excerpt = breakpoint_position.excerpt_id; let buffer_snapshot = buffer.read(cx).snapshot(); let row = buffer_snapshot - .summary_for_anchor::(&breakpoint_position.text_anchor) + .summary_for_anchor::(&breakpoint_position) .row; - let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row)); - let anchor_end = snapshot - .buffer_snapshot() - .anchor_after(Point::new(row, line_len)); + let line_len = buffer_snapshot.line_len(row); + let anchor_end = buffer_snapshot.anchor_after(Point::new(row, line_len)); self.breakpoint_store .as_ref()? @@ -11957,7 +12004,7 @@ impl Editor { breakpoint_store .breakpoints( &buffer, - Some(breakpoint_position.text_anchor..anchor_end.text_anchor), + Some(breakpoint_position..anchor_end), &buffer_snapshot, cx, ) @@ -11970,7 +12017,7 @@ impl Editor { if breakpoint_row == row { snapshot .buffer_snapshot() - .anchor_in_excerpt(enclosing_excerpt, bp.position) + .anchor_in_excerpt(bp.position) .map(|position| (position, bp.bp.clone())) } else { None @@ -12246,20 +12293,20 @@ impl Editor { let Some(breakpoint_store) = &self.breakpoint_store else { return; }; - - let Some(buffer) = self - .buffer - .read(cx) - .buffer_for_anchor(breakpoint_position, cx) + let buffer_snapshot = self.buffer.read(cx).snapshot(cx); + let Some((position, _)) = buffer_snapshot.anchor_to_buffer_anchor(breakpoint_position) else { return; }; + let Some(buffer) = self.buffer.read(cx).buffer(position.buffer_id) else { + return; + }; breakpoint_store.update(cx, |breakpoint_store, cx| { breakpoint_store.toggle_breakpoint( buffer, BreakpointWithPosition { - position: breakpoint_position.text_anchor, + position, bp: breakpoint, }, edit_action, @@ -15484,7 +15531,7 @@ impl Editor { } self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); self.change_selections(Default::default(), window, cx, |s| { - s.select_ranges(vec![Anchor::min()..Anchor::min()]); + s.select_ranges(vec![Anchor::Min..Anchor::Min]); }); } @@ -15601,7 +15648,7 @@ impl Editor { pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - s.select_ranges([Anchor::min()..Anchor::max()]); + s.select_ranges(vec![Anchor::Min..Anchor::Max]); }); } @@ -17026,10 +17073,7 @@ impl Editor { window: &mut Window, cx: &mut Context, ) { - let old_selections: Box<[_]> = self - .selections - .all::(&self.display_snapshot(cx)) - .into(); + let old_selections = self.selections.all_anchors(&self.display_snapshot(cx)); if old_selections.is_empty() { return; } @@ -17042,21 +17086,25 @@ impl Editor { let new_selections = old_selections .iter() .map(|selection| { - let old_range = selection.start..selection.end; - let old_range = - old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer); - let excerpt = buffer.excerpt_containing(old_range.clone()); - - if let Some(mut excerpt) = excerpt - && let Some(node) = excerpt - .buffer() - .syntax_next_sibling(excerpt.map_range_to_buffer(old_range)) + selection.start.to_offset(&buffer)..selection.end.to_offset(&buffer); + if let Some(results) = buffer.map_excerpt_ranges( + old_range, + |buf, _excerpt_range, input_buffer_range| { + let Some(node) = buf.syntax_next_sibling(input_buffer_range) else { + return Vec::new(); + }; + vec![( + BufferOffset(node.byte_range().start) + ..BufferOffset(node.byte_range().end), + (), + )] + }, + ) && let [(new_range, _)] = results.as_slice() { - let new_range = excerpt.map_range_from_buffer( - BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end), - ); selected_sibling = true; + let new_range = + buffer.anchor_after(new_range.start)..buffer.anchor_before(new_range.end); Selection { id: selection.id, start: new_range.start, @@ -17088,36 +17136,35 @@ impl Editor { window: &mut Window, cx: &mut Context, ) { - let old_selections: Box<[_]> = self - .selections - .all::(&self.display_snapshot(cx)) - .into(); - if old_selections.is_empty() { - return; - } + let old_selections: Arc<[_]> = self.selections.all_anchors(&self.display_snapshot(cx)); self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - let buffer = self.buffer.read(cx).snapshot(cx); + let multibuffer_snapshot = self.buffer.read(cx).snapshot(cx); let mut selected_sibling = false; let new_selections = old_selections .iter() .map(|selection| { - let old_range = selection.start..selection.end; - let old_range = - old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer); - let excerpt = buffer.excerpt_containing(old_range.clone()); - - if let Some(mut excerpt) = excerpt - && let Some(node) = excerpt - .buffer() - .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range)) + let old_range = selection.start.to_offset(&multibuffer_snapshot) + ..selection.end.to_offset(&multibuffer_snapshot); + if let Some(results) = multibuffer_snapshot.map_excerpt_ranges( + old_range, + |buf, _excerpt_range, input_buffer_range| { + let Some(node) = buf.syntax_prev_sibling(input_buffer_range) else { + return Vec::new(); + }; + vec![( + BufferOffset(node.byte_range().start) + ..BufferOffset(node.byte_range().end), + (), + )] + }, + ) && let [(new_range, _)] = results.as_slice() { - let new_range = excerpt.map_range_from_buffer( - BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end), - ); selected_sibling = true; + let new_range = multibuffer_snapshot.anchor_after(new_range.start) + ..multibuffer_snapshot.anchor_before(new_range.end); Selection { id: selection.id, start: new_range.start, @@ -17474,16 +17521,21 @@ impl Editor { }; let snapshot = self.buffer.read(cx).snapshot(cx); - let excerpt_ids = selections + let excerpt_anchors = selections .iter() - .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range())) - .unique() - .sorted() + .flat_map(|selection| { + snapshot + .range_to_buffer_ranges(selection.range()) + .into_iter() + .filter_map(|(buffer_snapshot, range, _)| { + snapshot.anchor_in_excerpt(buffer_snapshot.anchor_after(range.start)) + }) + }) .collect::>(); if self.delegate_expand_excerpts { cx.emit(EditorEvent::ExpandExcerptsRequested { - excerpt_ids, + excerpt_anchors, lines, direction, }); @@ -17491,13 +17543,13 @@ impl Editor { } self.buffer.update(cx, |buffer, cx| { - buffer.expand_excerpts(excerpt_ids, lines, direction, cx) + buffer.expand_excerpts(excerpt_anchors, lines, direction, cx) }) } - pub fn expand_excerpt( + pub(crate) fn expand_excerpt( &mut self, - excerpt: ExcerptId, + excerpt_anchor: Anchor, direction: ExpandExcerptDirection, window: &mut Window, cx: &mut Context, @@ -17506,7 +17558,7 @@ impl Editor { if self.delegate_expand_excerpts { cx.emit(EditorEvent::ExpandExcerptsRequested { - excerpt_ids: vec![excerpt], + excerpt_anchors: vec![excerpt_anchor], lines: lines_to_expand, direction, }); @@ -17519,12 +17571,11 @@ impl Editor { if direction == ExpandExcerptDirection::Down { let multi_buffer = self.buffer.read(cx); let snapshot = multi_buffer.snapshot(cx); - if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) - && let Some(buffer) = multi_buffer.buffer(buffer_id) - && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt) + if let Some((buffer_snapshot, excerpt_range)) = + snapshot.excerpt_containing(excerpt_anchor..excerpt_anchor) { - let buffer_snapshot = buffer.read(cx).snapshot(); - let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row; + let excerpt_end_row = + Point::from_anchor(&excerpt_range.context.end, &buffer_snapshot).row; let last_row = buffer_snapshot.max_point().row; let lines_below = last_row.saturating_sub(excerpt_end_row); if lines_below >= lines_to_expand { @@ -17540,14 +17591,14 @@ impl Editor { .buffer .read(cx) .snapshot(cx) - .excerpt_before(excerpt) + .excerpt_before(excerpt_anchor) .is_none() { scroll = Some(current_scroll_position); } self.buffer.update(cx, |buffer, cx| { - buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx) + buffer.expand_excerpts([excerpt_anchor], lines_to_expand, direction, cx) }); if let Some(new_scroll_position) = scroll { @@ -17571,20 +17622,15 @@ impl Editor { cx: &mut Context, ) { let multibuffer = self.buffer().read(cx); - let Some(buffer) = multibuffer.as_singleton() else { - return; - }; - let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else { - return; - }; - let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else { + if !multibuffer.is_singleton() { return; }; + let anchor_range = range.to_anchors(&multibuffer.snapshot(cx)); self.change_selections( SelectionEffects::default().nav_history(true), window, cx, - |s| s.select_anchor_ranges([start..end]), + |s| s.select_anchor_ranges([anchor_range]), ); } @@ -17685,9 +17731,10 @@ impl Editor { }; let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start); - let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else { + let Some((buffer_anchor, _)) = buffer.anchor_to_buffer_anchor(next_diagnostic_start) else { return; }; + let buffer_id = buffer_anchor.buffer_id; let snapshot = self.snapshot(window, cx); if snapshot.intersects_fold(next_diagnostic.range.start) { self.unfold_ranges( @@ -18560,9 +18607,9 @@ impl Editor { let editor_snapshot = self.snapshot(window, cx); // We don't care about multi-buffer symbols - let Some((excerpt_id, _, _)) = editor_snapshot.as_singleton() else { + if !editor_snapshot.is_singleton() { return Task::ready(Ok(())); - }; + } let cursor_offset = self .selections @@ -18582,7 +18629,11 @@ impl Editor { let multi_snapshot = editor_snapshot.buffer(); let buffer_range = |range: &Range<_>| { - Anchor::range_in_buffer(excerpt_id, range.clone()).to_offset(multi_snapshot) + Some( + multi_snapshot + .buffer_anchor_range_to_anchor_range(range.clone())? + .to_offset(multi_snapshot), + ) }; wcx.update_window(wcx.window_handle(), |_, window, acx| { @@ -18591,7 +18642,7 @@ impl Editor { .enumerate() .filter_map(|(idx, item)| { // Find the closest outline item by distance between outline text and cursor location - let source_range = buffer_range(&item.source_range_for_text); + let source_range = buffer_range(&item.source_range_for_text)?; let distance_to_closest_endpoint = cmp::min( (source_range.start.0 as isize - cursor_offset.0 as isize).abs(), (source_range.end.0 as isize - cursor_offset.0 as isize).abs(), @@ -18616,7 +18667,9 @@ impl Editor { return; }; - let range = buffer_range(&outline_items[idx].source_range_for_text); + let Some(range) = buffer_range(&outline_items[idx].source_range_for_text) else { + return; + }; let selection = [range.start..range.start]; let _ = editor @@ -18686,24 +18739,15 @@ impl Editor { let (locations, current_location_index) = multi_buffer.update(cx, |multi_buffer, cx| { + let multi_buffer_snapshot = multi_buffer.snapshot(cx); let mut locations = locations .into_iter() .filter_map(|loc| { - let start = multi_buffer.buffer_anchor_to_anchor( - &loc.buffer, - loc.range.start, - cx, - )?; - let end = multi_buffer.buffer_anchor_to_anchor( - &loc.buffer, - loc.range.end, - cx, - )?; + let start = multi_buffer_snapshot.anchor_in_excerpt(loc.range.start)?; + let end = multi_buffer_snapshot.anchor_in_excerpt(loc.range.end)?; Some(start..end) }) .collect::>(); - - let multi_buffer_snapshot = multi_buffer.snapshot(cx); // There is an O(n) implementation, but given this list will be // small (usually <100 items), the extra O(log(n)) factor isn't // worth the (surprisingly large amount of) extra complexity. @@ -18959,14 +19003,21 @@ impl Editor { for (buffer, mut ranges_for_buffer) in locations { ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end))); key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone())); - let (new_ranges, _) = multibuffer.set_excerpts_for_path( + multibuffer.set_excerpts_for_path( PathKey::for_buffer(&buffer, cx), buffer.clone(), - ranges_for_buffer, + ranges_for_buffer.clone(), multibuffer_context_lines(cx), cx, ); - ranges.extend(new_ranges) + let snapshot = multibuffer.snapshot(cx); + let buffer_snapshot = buffer.read(cx).snapshot(); + ranges.extend(ranges_for_buffer.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.with_title(title) @@ -19074,28 +19125,11 @@ impl Editor { let snapshot = cursor_buffer.read(cx).snapshot(); let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot); let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot); - let prepare_rename = provider - .range_for_rename(&cursor_buffer, cursor_buffer_position, cx) - .unwrap_or_else(|| Task::ready(Ok(None))); + let prepare_rename = provider.range_for_rename(&cursor_buffer, cursor_buffer_position, cx); drop(snapshot); Some(cx.spawn_in(window, async move |this, cx| { - let rename_range = if let Some(range) = prepare_rename.await? { - Some(range) - } else { - this.update(cx, |this, cx| { - let buffer = this.buffer.read(cx).snapshot(cx); - let mut buffer_highlights = this - .document_highlights_for_position(selection.head(), &buffer) - .filter(|highlight| { - highlight.start.excerpt_id == selection.head().excerpt_id - && highlight.end.excerpt_id == selection.head().excerpt_id - }); - buffer_highlights - .next() - .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor) - })? - }; + let rename_range = prepare_rename.await?; if let Some(rename_range) = rename_range { this.update_in(cx, |this, window, cx| { let snapshot = cursor_buffer.read(cx).snapshot(); @@ -19417,12 +19451,12 @@ impl Editor { let mut buffer_id_to_ranges: BTreeMap>> = BTreeMap::new(); for selection_range in selection_ranges { - for (buffer, buffer_range, _) in - snapshot.range_to_buffer_ranges(selection_range.start..=selection_range.end) + for (buffer_snapshot, buffer_range, _) in + snapshot.range_to_buffer_ranges(selection_range.start..selection_range.end) { - let buffer_id = buffer.remote_id(); - let start = buffer.anchor_before(buffer_range.start); - let end = buffer.anchor_after(buffer_range.end); + let buffer_id = buffer_snapshot.remote_id(); + let start = buffer_snapshot.anchor_before(buffer_range.start); + let end = buffer_snapshot.anchor_after(buffer_range.end); buffers.insert(multi_buffer.buffer(buffer_id).unwrap()); buffer_id_to_ranges .entry(buffer_id) @@ -20200,10 +20234,10 @@ impl Editor { .is_some(); has_folds } else { - let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids(); - let has_folds = buffer_ids - .iter() - .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx)); + let snapshot = self.buffer.read(cx).snapshot(cx); + let has_folds = snapshot + .all_buffer_ids() + .any(|buffer_id| self.is_buffer_folded(buffer_id, cx)); has_folds }; @@ -20368,7 +20402,8 @@ impl Editor { self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| { editor .update_in(cx, |editor, _, cx| { - for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() { + let snapshot = editor.buffer.read(cx).snapshot(cx); + for buffer_id in snapshot.all_buffer_ids() { editor.fold_buffer(buffer_id, cx); } }) @@ -20556,7 +20591,8 @@ impl Editor { self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| { editor .update(cx, |editor, cx| { - for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() { + let snapshot = editor.buffer.read(cx).snapshot(cx); + for buffer_id in snapshot.all_buffer_ids() { editor.unfold_buffer(buffer_id, cx); } }) @@ -20655,25 +20691,19 @@ impl Editor { return; } - let mut all_folded_excerpt_ids = Vec::new(); - for buffer_id in &ids_to_fold { - let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(*buffer_id, cx); - all_folded_excerpt_ids.extend(folded_excerpts.into_iter().map(|(id, _, _)| id)); - } - self.display_map.update(cx, |display_map, cx| { display_map.fold_buffers(ids_to_fold.clone(), cx) }); let snapshot = self.display_snapshot(cx); self.selections.change_with(&snapshot, |selections| { - for buffer_id in ids_to_fold { + for buffer_id in ids_to_fold.iter().copied() { selections.remove_selections_from_buffer(buffer_id); } }); cx.emit(EditorEvent::BufferFoldToggled { - ids: all_folded_excerpt_ids, + ids: ids_to_fold, folded: true, }); cx.notify(); @@ -20683,12 +20713,11 @@ impl Editor { if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) { return; } - let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx); self.display_map.update(cx, |display_map, cx| { display_map.unfold_buffers([buffer_id], cx); }); cx.emit(EditorEvent::BufferFoldToggled { - ids: unfolded_excerpts.iter().map(|&(id, _, _)| id).collect(), + ids: vec![buffer_id], folded: false, }); cx.notify(); @@ -20741,14 +20770,6 @@ impl Editor { return; } - let mut buffers_affected = HashSet::default(); - let multi_buffer = self.buffer().read(cx); - for range in ranges { - if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) { - buffers_affected.insert(buffer.read(cx).remote_id()); - }; - } - self.display_map.update(cx, update); if auto_scroll { @@ -20786,7 +20807,7 @@ impl Editor { cx: &mut Context, ) { self.buffer.update(cx, |buffer, cx| { - buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx) + buffer.expand_diff_hunks(vec![Anchor::Min..Anchor::Max], cx) }); } @@ -20797,7 +20818,7 @@ impl Editor { cx: &mut Context, ) { self.buffer.update(cx, |buffer, cx| { - buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx) + buffer.collapse_diff_hunks(vec![Anchor::Min..Anchor::Max], cx) }); } @@ -20822,7 +20843,7 @@ impl Editor { buffer: &'a MultiBufferSnapshot, ) -> impl 'a + Iterator { ranges.iter().flat_map(move |range| { - let end_excerpt_id = range.end.excerpt_id; + let end_excerpt = buffer.excerpt_containing(range.end..range.end); let range = range.to_point(buffer); let mut peek_end = range.end; if range.end.row < buffer.max_row().0 { @@ -20830,7 +20851,19 @@ impl Editor { } buffer .diff_hunks_in_range(range.start..peek_end) - .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le()) + .filter(move |hunk| { + if let Some((_, excerpt_range)) = &end_excerpt + && let Some(end_anchor) = + buffer.anchor_in_excerpt(excerpt_range.context.end) + && let Some(hunk_end_anchor) = + buffer.anchor_in_excerpt(hunk.excerpt_range.context.end) + && hunk_end_anchor.cmp(&end_anchor, buffer).is_gt() + { + false + } else { + true + } + }) }) } @@ -21032,7 +21065,7 @@ impl Editor { pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context) -> bool { self.buffer.update(cx, |buffer, cx| { - let ranges = vec![Anchor::min()..Anchor::max()]; + let ranges = vec![Anchor::Min..Anchor::Max]; if !buffer.all_diff_hunks_expanded() && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx) { @@ -21048,7 +21081,7 @@ impl Editor { if self.buffer.read(cx).all_diff_hunks_expanded() { return true; } - let ranges = vec![Anchor::min()..Anchor::max()]; + let ranges = vec![Anchor::Min..Anchor::Max]; self.buffer .read(cx) .has_expanded_diff_hunks_in_ranges(&ranges, cx) @@ -22103,11 +22136,11 @@ impl Editor { let end_point = overlay.anchor_range.end.to_point(&snapshot); let start_row = snapshot .point_to_buffer_point(start_point) - .map(|(_, p, _)| p.row) + .map(|(_, p)| p.row) .unwrap_or(start_point.row); let end_row = snapshot .point_to_buffer_point(end_point) - .map(|(_, p, _)| p.row) + .map(|(_, p)| p.row) .unwrap_or(end_point.row); Some((start_row, end_row)) } @@ -22607,9 +22640,9 @@ impl Editor { snapshot.range_to_buffer_ranges(start_point..end_point); let ranges: Vec<(u32, u32)> = buffer_ranges .iter() - .map(|(buffer, range, _)| { - let start = buffer.offset_to_point(range.start.0).row; - let end = buffer.offset_to_point(range.end.0).row; + .map(|(buffer_snapshot, range, _)| { + let start = buffer_snapshot.offset_to_point(range.start.0).row; + let end = buffer_snapshot.offset_to_point(range.end.0).row; (start, end) }) .collect(); @@ -22935,15 +22968,14 @@ impl Editor { } fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> { - self.active_excerpt(cx)? - .1 + self.active_buffer(cx)? .read(cx) .file() .and_then(|f| f.as_local()) } pub fn target_file_abs_path(&self, cx: &mut Context) -> Option { - self.active_excerpt(cx).and_then(|(_, buffer, _)| { + self.active_buffer(cx).and_then(|buffer| { let buffer = buffer.read(cx); if let Some(project_path) = buffer.project_path(cx) { let project = self.project()?.read(cx); @@ -22992,7 +23024,7 @@ impl Editor { _window: &mut Window, cx: &mut Context, ) { - if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| { + if let Some(path) = self.active_buffer(cx).and_then(|buffer| { let project = self.project()?.read(cx); let path = buffer.read(cx).file()?.path(); let path = path.display(project.path_style(cx)); @@ -23050,41 +23082,22 @@ impl Editor { } let position = active_stack_frame.position; - let buffer_id = position.buffer_id?; - let snapshot = self - .project - .as_ref()? - .read(cx) - .buffer_for_id(buffer_id, cx)? - .read(cx) - .snapshot(); - let mut handled = false; - for (id, _, ExcerptRange { context, .. }) in - self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx) - { - if context.start.cmp(&position, &snapshot).is_ge() - || context.end.cmp(&position, &snapshot).is_lt() - { - continue; - } - let snapshot = self.buffer.read(cx).snapshot(cx); - let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?; + let snapshot = self.buffer.read(cx).snapshot(cx); + let multibuffer_anchor = snapshot.anchor_in_excerpt(position)?; - handled = true; - self.clear_row_highlights::(); + self.clear_row_highlights::(); - self.go_to_line::( - multibuffer_anchor, - Some(cx.theme().colors().editor_debugger_active_line_background), - window, - cx, - ); + self.go_to_line::( + multibuffer_anchor, + Some(cx.theme().colors().editor_debugger_active_line_background), + window, + cx, + ); - cx.notify(); - } + cx.notify(); - handled.then_some(()) + Some(()) }) .is_some() } @@ -23095,7 +23108,7 @@ impl Editor { _: &mut Window, cx: &mut Context, ) { - if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| { + if let Some(file_stem) = self.active_buffer(cx).and_then(|buffer| { let file = buffer.read(cx).file()?; file.path().file_stem() }) { @@ -23104,7 +23117,7 @@ impl Editor { } pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context) { - if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| { + if let Some(file_name) = self.active_buffer(cx).and_then(|buffer| { let file = buffer.read(cx).file()?; Some(file.file_name(cx)) }) { @@ -23157,7 +23170,7 @@ impl Editor { .selections .newest::(&snapshot.display_snapshot) .head(); - let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?; + let (buffer, point) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?; let (_, blame_entry) = blame .update(cx, |blame, cx| { blame @@ -23304,33 +23317,28 @@ impl Editor { let multi_buffer = self.buffer().read(cx); let multi_buffer_snapshot = multi_buffer.snapshot(cx); let buffer_ranges = multi_buffer_snapshot - .range_to_buffer_ranges(selection_range.start..=selection_range.end); + .range_to_buffer_ranges(selection_range.start..selection_range.end); - let (buffer, range, _) = if selection.reversed { + let (buffer_snapshot, range, _) = if selection.reversed { buffer_ranges.first() } else { buffer_ranges.last() }?; - let buffer_range = range.to_point(buffer); + let buffer_range = range.to_point(buffer_snapshot); + let buffer = multi_buffer.buffer(buffer_snapshot.remote_id()).unwrap(); - let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else { - return Some(( - multi_buffer.buffer(buffer.remote_id()).unwrap(), - buffer_range.start.row..buffer_range.end.row, - )); + let Some(buffer_diff) = multi_buffer.diff_for(buffer_snapshot.remote_id()) else { + return Some((buffer, buffer_range.start.row..buffer_range.end.row)); }; let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx); - let start = - buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.start, buffer); - let end = - buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.end, buffer); + let start = buffer_diff_snapshot + .buffer_point_to_base_text_point(buffer_range.start, &buffer_snapshot); + let end = buffer_diff_snapshot + .buffer_point_to_base_text_point(buffer_range.end, &buffer_snapshot); - Some(( - multi_buffer.buffer(buffer.remote_id()).unwrap(), - start.row..end.row, - )) + Some((buffer, start.row..end.row)) }); let Some((buffer, selection)) = buffer_and_selection else { @@ -23404,7 +23412,7 @@ impl Editor { end_line }; - if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| { + if let Some(file_location) = self.active_buffer(cx).and_then(|buffer| { let project = self.project()?.read(cx); let file = buffer.read(cx).file()?; let path = file.path().display(project.path_style(cx)); @@ -23505,6 +23513,7 @@ impl Editor { let Some(buffer) = multibuffer.as_singleton() else { return; }; + let buffer_snapshot = buffer.read(cx).snapshot(); let Some(workspace) = self.workspace() else { return; @@ -23519,7 +23528,8 @@ impl Editor { .map(|selection| { ( buffer.clone(), - (selection.start.text_anchor..selection.end.text_anchor) + (selection.start.text_anchor_in(&buffer_snapshot) + ..selection.end.text_anchor_in(&buffer_snapshot)) .to_point(buffer.read(cx)), ) }) @@ -23688,8 +23698,7 @@ impl Editor { let start = highlight.range.start.to_display_point(&snapshot); let end = highlight.range.end.to_display_point(&snapshot); let start_row = start.row().0; - let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0 - { + let end_row = if !highlight.range.end.is_max() && end.column() == 0 { end.row().0.saturating_sub(1) } else { end.row().0 @@ -23925,42 +23934,6 @@ impl Editor { } } - fn document_highlights_for_position<'a>( - &'a self, - position: Anchor, - buffer: &'a MultiBufferSnapshot, - ) -> impl 'a + Iterator> { - let read_highlights = self - .background_highlights - .get(&HighlightKey::DocumentHighlightRead) - .map(|h| &h.1); - let write_highlights = self - .background_highlights - .get(&HighlightKey::DocumentHighlightWrite) - .map(|h| &h.1); - let left_position = position.bias_left(buffer); - let right_position = position.bias_right(buffer); - read_highlights - .into_iter() - .chain(write_highlights) - .flat_map(move |ranges| { - let start_ix = match ranges.binary_search_by(|probe| { - let cmp = probe.end.cmp(&left_position, buffer); - if cmp.is_ge() { - Ordering::Greater - } else { - Ordering::Less - } - }) { - Ok(i) | Err(i) => i, - }; - - ranges[start_ix..] - .iter() - .take_while(move |range| range.start.cmp(&right_position, buffer).is_le()) - }) - } - pub fn has_background_highlights(&self, key: HighlightKey) -> bool { self.background_highlights .get(&key) @@ -24182,26 +24155,16 @@ impl Editor { return Some(Task::ready(Ok(Vec::new()))); }; - let buffer = editor.buffer.read_with(cx, |buffer, cx| { - let snapshot = buffer.snapshot(cx); - - let excerpt = snapshot.excerpt_containing( - current_execution_position..current_execution_position, - )?; - - editor.buffer.read(cx).buffer(excerpt.buffer_id()) - })?; - - if current_execution_position - .text_anchor - .buffer_id - .is_some_and(|id| id != buffer.read(cx).remote_id()) - { - return Some(Task::ready(Ok(Vec::new()))); - } + let (buffer, buffer_anchor) = + editor.buffer.read_with(cx, |multibuffer, cx| { + let multibuffer_snapshot = multibuffer.snapshot(cx); + let (buffer_anchor, _) = multibuffer_snapshot + .anchor_to_buffer_anchor(current_execution_position)?; + let buffer = multibuffer.buffer(buffer_anchor.buffer_id)?; + Some((buffer, buffer_anchor)) + })?; - let range = - buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor; + let range = buffer.read(cx).anchor_before(0)..buffer_anchor; semantics.inline_values(buffer, range, cx) }) @@ -24215,7 +24178,7 @@ impl Editor { for (buffer_id, inline_value) in inline_values .into_iter() - .filter_map(|hint| Some((hint.position.buffer_id?, hint))) + .map(|hint| (hint.position.buffer_id, hint)) { buffer_inline_values .entry(buffer_id) @@ -24228,22 +24191,20 @@ impl Editor { let snapshot = editor.buffer.read(cx).snapshot(cx); let mut new_inlays = Vec::default(); - for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() { - let buffer_id = buffer_snapshot.remote_id(); - buffer_inline_values - .get(&buffer_id) - .into_iter() - .flatten() - .for_each(|hint| { - let inlay = Inlay::debugger( - post_inc(&mut editor.next_inlay_id), - Anchor::in_buffer(excerpt_id, hint.position), - hint.text(), - ); - if !inlay.text().chars().contains(&'\n') { - new_inlays.push(inlay); - } - }); + for (_buffer_id, inline_values) in buffer_inline_values { + for hint in inline_values { + let Some(anchor) = snapshot.anchor_in_excerpt(hint.position) else { + continue; + }; + let inlay = Inlay::debugger( + post_inc(&mut editor.next_inlay_id), + anchor, + hint.text(), + ); + if !inlay.text().chars().contains(&'\n') { + new_inlays.push(inlay); + } + } } let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect(); @@ -24312,11 +24273,12 @@ impl Editor { }; telemetry.log_edit_event("editor", is_via_ssh); } - multi_buffer::Event::ExcerptsAdded { + multi_buffer::Event::BufferRangesUpdated { buffer, - predecessor, - excerpts, + ranges, + path_key, } => { + self.refresh_document_highlights(cx); let buffer_id = buffer.read(cx).remote_id(); if self.buffer.read(cx).diff_for(buffer_id).is_none() && let Some(project) = &self.project @@ -24330,27 +24292,29 @@ impl Editor { ) .detach(); } - self.semantic_token_state - .invalidate_buffer(&buffer.read(cx).remote_id()); + self.register_visible_buffers(cx); self.update_lsp_data(Some(buffer_id), window, cx); self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx); self.refresh_runnables(None, window, cx); + self.bracket_fetched_tree_sitter_chunks + .retain(|range, _| range.start.buffer_id != buffer_id); self.colorize_brackets(false, cx); self.refresh_selected_text_highlights(&self.display_snapshot(cx), true, window, cx); - cx.emit(EditorEvent::ExcerptsAdded { + self.semantic_token_state.invalidate_buffer(&buffer_id); + cx.emit(EditorEvent::BufferRangesUpdated { buffer: buffer.clone(), - predecessor: *predecessor, - excerpts: excerpts.clone(), + ranges: ranges.clone(), + path_key: path_key.clone(), }); } - multi_buffer::Event::ExcerptsRemoved { - ids, - removed_buffer_ids, - } => { + multi_buffer::Event::BuffersRemoved { removed_buffer_ids } => { if let Some(inlay_hints) = &mut self.inlay_hints { inlay_hints.remove_inlay_chunk_data(removed_buffer_ids); } - self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx); + self.refresh_inlay_hints( + InlayHintRefreshReason::BuffersRemoved(removed_buffer_ids.clone()), + cx, + ); for buffer_id in removed_buffer_ids { self.registered_buffers.remove(buffer_id); self.clear_runnables(Some(*buffer_id)); @@ -24366,38 +24330,18 @@ impl Editor { }); jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx); - cx.emit(EditorEvent::ExcerptsRemoved { - ids: ids.clone(), + cx.emit(EditorEvent::BuffersRemoved { removed_buffer_ids: removed_buffer_ids.clone(), }); } - multi_buffer::Event::ExcerptsEdited { - excerpt_ids, - buffer_ids, - } => { + multi_buffer::Event::BuffersEdited { buffer_ids } => { self.display_map.update(cx, |map, cx| { map.unfold_buffers(buffer_ids.iter().copied(), cx) }); - cx.emit(EditorEvent::ExcerptsEdited { - ids: excerpt_ids.clone(), + cx.emit(EditorEvent::BuffersEdited { + buffer_ids: buffer_ids.clone(), }); } - multi_buffer::Event::ExcerptsExpanded { ids } => { - self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx); - self.refresh_document_highlights(cx); - let snapshot = multibuffer.read(cx).snapshot(cx); - for id in ids { - self.bracket_fetched_tree_sitter_chunks.remove(id); - if let Some(buffer) = snapshot.buffer_for_excerpt(*id) { - self.semantic_token_state - .invalidate_buffer(&buffer.remote_id()); - } - } - self.colorize_brackets(false, cx); - self.update_lsp_data(None, window, cx); - self.refresh_runnables(None, window, cx); - cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() }) - } multi_buffer::Event::Reparsed(buffer_id) => { self.refresh_runnables(Some(*buffer_id), window, cx); self.refresh_selected_text_highlights(&self.display_snapshot(cx), true, window, cx); @@ -24700,18 +24644,13 @@ impl Editor { let mut new_selections_by_buffer = HashMap::default(); match &jump_data { Some(JumpData::MultiBufferPoint { - excerpt_id, - position, anchor, + position, line_offset_from_top, }) => { - let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx); - if let Some(buffer) = multi_buffer_snapshot - .buffer_id_for_excerpt(*excerpt_id) - .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id)) - { + if let Some(buffer) = self.buffer.read(cx).buffer(anchor.buffer_id) { let buffer_snapshot = buffer.read(cx).snapshot(); - let jump_to_point = if buffer_snapshot.can_resolve(anchor) { + let jump_to_point = if buffer_snapshot.can_resolve(&anchor) { language::ToPoint::to_point(anchor, &buffer_snapshot) } else { buffer_snapshot.clip_point(*position, Bias::Left) @@ -24731,7 +24670,7 @@ impl Editor { line_offset_from_top, }) => { let point = MultiBufferPoint::new(row.0, 0); - if let Some((buffer, buffer_point, _)) = + if let Some((buffer, buffer_point)) = self.buffer.read(cx).point_to_buffer_point(point, cx) { let buffer_offset = buffer.read(cx).point_to_offset(buffer_point); @@ -24747,18 +24686,20 @@ impl Editor { .selections .all::(&self.display_snapshot(cx)); let multi_buffer = self.buffer.read(cx); + let multi_buffer_snapshot = multi_buffer.snapshot(cx); for selection in selections { - for (snapshot, range, _, anchor) in multi_buffer - .snapshot(cx) + for (snapshot, range, anchor) in multi_buffer_snapshot .range_to_buffer_ranges_with_deleted_hunks(selection.range()) { - if let Some(anchor) = anchor { - let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx) + if let Some((text_anchor, _)) = anchor.and_then(|anchor| { + multi_buffer_snapshot.anchor_to_buffer_anchor(anchor) + }) { + let Some(buffer_handle) = multi_buffer.buffer(text_anchor.buffer_id) else { continue; }; let offset = text::ToOffset::to_offset( - &anchor.text_anchor, + &text_anchor, &buffer_handle.read(cx).snapshot(), ); let range = BufferOffset(offset)..BufferOffset(offset); @@ -24907,9 +24848,7 @@ impl Editor { }; let nav_history = editor.nav_history.take(); let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx); - let Some((excerpt_id, _, buffer_snapshot)) = - multibuffer_snapshot.as_singleton() - else { + let Some(buffer_snapshot) = multibuffer_snapshot.as_singleton() else { return; }; editor.change_selections( @@ -24921,7 +24860,7 @@ impl Editor { let range = buffer_snapshot.anchor_before(range.start) ..buffer_snapshot.anchor_after(range.end); multibuffer_snapshot - .anchor_range_in_excerpt(excerpt_id, range) + .buffer_anchor_range_to_anchor_range(range) .unwrap() })); }, @@ -25415,8 +25354,11 @@ impl Editor { } } }); - self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { - selections.refresh() + let selections = self + .selections + .all::(&self.display_snapshot(cx)); + self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select(selections); }); } @@ -25798,7 +25740,12 @@ impl Editor { if !self.lsp_data_enabled() { return; } - for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) { + let visible_buffers: Vec<_> = self + .visible_buffers(cx) + .into_iter() + .filter(|buffer| self.is_lsp_relevant(buffer.read(cx).file(), cx)) + .collect(); + for visible_buffer in visible_buffers { self.register_buffer(visible_buffer.read(cx).remote_id(), cx); } } @@ -26090,17 +26037,16 @@ fn process_completion_for_edit( range_to_replace.end = *cursor_position; } - let replace_range = range_to_replace.to_offset(buffer); CompletionEdit { new_text, - replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end), + replace_range: range_to_replace, snippet, } } struct CompletionEdit { new_text: String, - replace_range: Range, + replace_range: Range, snippet: Option, } @@ -26542,10 +26488,10 @@ impl NewlineConfig { range: Range, ) -> bool { let (buffer, range) = match buffer - .range_to_buffer_ranges(range.start..=range.end) + .range_to_buffer_ranges(range.start..range.end) .as_slice() { - [(buffer, range, _)] => (*buffer, range.clone()), + [(buffer_snapshot, range, _)] => (buffer_snapshot.clone(), range.clone()), _ => return false, }; let pair = { @@ -27084,7 +27030,7 @@ pub trait SemanticsProvider { buffer: &Entity, position: text::Anchor, cx: &mut App, - ) -> Option>>>>; + ) -> Task>>>; fn perform_rename( &self, @@ -27098,7 +27044,6 @@ pub trait SemanticsProvider { pub trait CompletionProvider { fn completions( &self, - excerpt_id: ExcerptId, buffer: &Entity, buffer_position: text::Anchor, trigger: CompletionContext, @@ -27167,7 +27112,6 @@ pub trait CodeActionProvider { &self, buffer_handle: Entity, action: CodeAction, - excerpt_id: ExcerptId, push_to_history: bool, window: &mut Window, cx: &mut App, @@ -27210,7 +27154,6 @@ impl CodeActionProvider for Entity { &self, buffer_handle: Entity, action: CodeAction, - _excerpt_id: ExcerptId, push_to_history: bool, _window: &mut Window, cx: &mut App, @@ -27458,7 +27401,6 @@ fn snippet_completions( impl CompletionProvider for Entity { fn completions( &self, - _excerpt_id: ExcerptId, buffer: &Entity, buffer_position: text::Anchor, options: CompletionContext, @@ -27680,8 +27622,12 @@ impl SemanticsProvider for WeakEntity { buffer: &Entity, position: text::Anchor, cx: &mut App, - ) -> Option>>>> { - self.update(cx, |project, cx| { + ) -> Task>>> { + let Some(this) = self.upgrade() else { + return Task::ready(Ok(None)); + }; + + this.update(cx, |project, cx| { let buffer = buffer.clone(); let task = project.prepare_rename(buffer.clone(), position, cx); cx.spawn(async move |_, cx| { @@ -27705,7 +27651,6 @@ impl SemanticsProvider for WeakEntity { }) }) }) - .ok() } fn perform_rename( @@ -27882,6 +27827,7 @@ impl EditorSnapshot { end_row.0 += 1; } let is_created_file = hunk.is_created_file(); + let multi_buffer_range = hunk.multi_buffer_range.clone(); DisplayDiffHunk::Unfolded { status: hunk.status(), @@ -27889,10 +27835,7 @@ impl EditorSnapshot { ..hunk.diff_base_byte_range.end.0, word_diffs: hunk.word_diffs, display_row_range: hunk_display_start.row()..end_row, - multi_buffer_range: Anchor::range_in_buffer( - hunk.excerpt_id, - hunk.buffer_range, - ), + multi_buffer_range, is_created_file, } }; @@ -28213,27 +28156,23 @@ pub enum EditorEvent { utf16_range_to_replace: Option>, text: Arc, }, - ExcerptsAdded { + BufferRangesUpdated { buffer: Entity, - predecessor: ExcerptId, - excerpts: Vec<(ExcerptId, ExcerptRange)>, + path_key: PathKey, + ranges: Vec>, }, - ExcerptsRemoved { - ids: Vec, + BuffersRemoved { removed_buffer_ids: Vec, }, + BuffersEdited { + buffer_ids: Vec, + }, BufferFoldToggled { - ids: Vec, + ids: Vec, folded: bool, }, - ExcerptsEdited { - ids: Vec, - }, - ExcerptsExpanded { - ids: Vec, - }, ExpandExcerptsRequested { - excerpt_ids: Vec, + excerpt_anchors: Vec, lines: u32, direction: ExpandExcerptDirection, }, @@ -28834,11 +28773,19 @@ fn edit_prediction_edit_text( edits: &[(Range, impl AsRef)], edit_preview: &EditPreview, include_deletions: bool, + multibuffer_snapshot: &MultiBufferSnapshot, cx: &App, ) -> HighlightedText { let edits = edits .iter() - .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text)) + .filter_map(|(anchor, text)| { + Some(( + multibuffer_snapshot + .anchor_range_to_buffer_anchor_range(anchor.clone())? + .1, + text, + )) + }) .collect::>(); edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx) diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 7e397507eda0d800ee9ed6b204ed95e71d50234b..2afd724f5e4a7332b713e14f1e4da5ad32517f13 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -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::>() }); @@ -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::>(); - 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::>() }); 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::>(); - 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::>() }); 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::>(); - 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::>() }); 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::>(); 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::>(); 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::>(); // 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::>(); 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::>() + }); 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, ) -> 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] diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 2fdb2686ee00ea2fc27881b0c18a54fa85466d9a..7a532dc7a75ea3583456be6611ef072cd7692bc7 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -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::(&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, latest_selection_anchors: &HashMap, is_row_soft_wrapped: impl Copy + Fn(usize) -> bool, - sticky_header_excerpt_id: Option, + sticky_header_excerpt_id: Option, indent_guides: &Option>, 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, latest_selection_anchors: &HashMap, is_row_soft_wrapped: impl Copy + Fn(usize) -> bool, - sticky_header_excerpt_id: Option, + sticky_header_excerpt_id: Option, indent_guides: &Option>, 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, ) -> 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, - 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, diff --git a/crates/editor/src/folding_ranges.rs b/crates/editor/src/folding_ranges.rs index de32f481d52e501eea8f7814f4b114fbdbbd0458..c59a3e004a8b4f791af2d44be19878239ece1d42 100644 --- a/crates/editor/src/folding_ranges.rs +++ b/crates/editor/src/folding_ranges.rs @@ -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(); diff --git a/crates/editor/src/git/blame.rs b/crates/editor/src/git/blame.rs index 827d182a0f11508ae301691f832e7ec04a728364..9ba5c4aa19cd66c454bf633a04636cd63bd180b8 100644 --- a/crates/editor/src/git/blame.rs +++ b/crates/editor/src/git/blame.rs @@ -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::>() }) diff --git a/crates/editor/src/hover_links.rs b/crates/editor/src/hover_links.rs index 3a6ff4ec0e4fc53d19bfb51a10b1f7790933b175..7f05f4355bfaa218dbc26aab77d949b2146816d7 100644 --- a/crates/editor/src/hover_links.rs +++ b/crates/editor/src/hover_links.rs @@ -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() diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 3bad6c97b6bcba4015331257a5b9a476dd0d3fd3..55350a9c679a10ea8597ae8c923c33af34d71360 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -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(|| { diff --git a/crates/editor/src/inlays.rs b/crates/editor/src/inlays.rs index 8c46e797cada703c9101fd91e670cbdd4ea713ac..689e273ce28310cb5051b0eae108b74de48d3ac1 100644 --- a/crates/editor/src/inlays.rs +++ b/crates/editor/src/inlays.rs @@ -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, } diff --git a/crates/editor/src/inlays/inlay_hints.rs b/crates/editor/src/inlays/inlay_hints.rs index 8422937ab81a392ad7d1187adcab765cc7f6875f..ac3133ea89c5da7cd861d608bcbd61975ee9535c 100644 --- a/crates/editor/src/inlays/inlay_hints.rs +++ b/crates/editor/src/inlays/inlay_hints.rs @@ -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, current_hints: impl IntoIterator, + 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, }, - ExcerptsRemoved(Vec), + BuffersRemoved(Vec), } 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, anyhow::Result)>, cx: &mut Context, ) { + 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::>(); 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 { 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(); diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index d14078e79abdbfe40879da09221bad7bef47475a..28e920c28bd9854a38a5019622248fa79cd0a8e1 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -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::>(); + 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::>(); + 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 = 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::>(); + 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::>(); 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::>(); - 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::>(); + 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::>(); - 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, -) -> Option { - 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, - buffer: &MultiBufferSnapshot, -) -> proto::Selection { +fn serialize_selection(selection: &Selection) -> 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) -> 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)> { + excerpt_range: proto::ExcerptRange, +) -> Option> { 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> { +fn deserialize_selection( + selection: proto::Selection, + buffer: &MultiBufferSnapshot, +) -> Option> { 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 { - 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 { + 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 { 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 { + 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; diff --git a/crates/editor/src/jsx_tag_auto_close.rs b/crates/editor/src/jsx_tag_auto_close.rs index b91f039aff7cfb8bc7997cfbf63abb8dbe4662e5..d57941f6d082a929f6547c38ddbc21908304d76c 100644 --- a/crates/editor/src/jsx_tag_auto_close.rs +++ b/crates/editor/src/jsx_tag_auto_close.rs @@ -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())); diff --git a/crates/editor/src/linked_editing_ranges.rs b/crates/editor/src/linked_editing_ranges.rs index ccd0e64bd850f6ce84e225fe77f1c0a0d5385dc1..148bb27addecfb4982625a2d6129e7d3827d7883 100644 --- a/crates/editor/src/linked_editing_ranges.rs +++ b/crates/editor/src/linked_editing_ranges.rs @@ -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::(&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)); } } }) diff --git a/crates/editor/src/lsp_ext.rs b/crates/editor/src/lsp_ext.rs index ef0f92de79b0fe7a7e4a495dc29c1305b2f5eefa..6f9f94bc72227f7f30bdca1c9ae1ce436f3d5aa4 100644 --- a/crates/editor/src/lsp_ext.rs +++ b/crates/editor/src/lsp_ext.rs @@ -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( cx: &mut App, filter_language: F, language_server_name: LanguageServerName, -) -> Option<(Anchor, Arc, LanguageServerId, Entity)> +) -> Option<( + text::Anchor, + Arc, + LanguageServerId, + Entity, +)> 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, LanguageServerId, Entity, diff --git a/crates/editor/src/mouse_context_menu.rs b/crates/editor/src/mouse_context_menu.rs index 2ddbb48b5fc434f65521c6dd230537aedb71dabb..0028f52d3d91ca9e6ea660dec0628e7ca6b9e520 100644 --- a/crates/editor/src/mouse_context_menu.rs +++ b/crates/editor/src/mouse_context_menu.rs @@ -205,16 +205,17 @@ pub fn deploy_context_menu( .all::(&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); diff --git a/crates/editor/src/movement.rs b/crates/editor/src/movement.rs index 955f511577d2cbfede1a4cb4eb6d99e429c879d6..67869f770b81f315680388165111bbc1a2e0f111 100644 --- a/crates/editor/src/movement.rs +++ b/crates/editor/src/movement.rs @@ -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 diff --git a/crates/editor/src/runnables.rs b/crates/editor/src/runnables.rs index 92663ff9a96d1f84e2de387917e2d6a32b16aa00..f451eb7d61d6a2513e1ebf6ec96062b600cbecb6 100644 --- a/crates/editor/src/runnables.rs +++ b/crates/editor/src/runnables.rs @@ -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, ) -> Option<(Entity, u32, Arc)> { let snapshot = self.buffer.read(cx).snapshot(cx); - let offset = self - .selections - .newest::(&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()))); } } diff --git a/crates/editor/src/rust_analyzer_ext.rs b/crates/editor/src/rust_analyzer_ext.rs index 6ffdf1a248a0e605f623254bbfa36776adf77cda..6d4d599961761789dbf14c77cd3843b036d05b5e 100644 --- a/crates/editor/src/rust_analyzer_ext.rs +++ b/crates/editor/src/rust_analyzer_ext.rs @@ -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)? diff --git a/crates/editor/src/scroll.rs b/crates/editor/src/scroll.rs index c2280e90f7d30d53c0818119df70b7c32161b78b..42b865b17ca4e241b8f0728488cacd42d52d257c 100644 --- a/crates/editor/src/scroll.rs +++ b/crates/editor/src/scroll.rs @@ -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 { 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(); diff --git a/crates/editor/src/scroll/actions.rs b/crates/editor/src/scroll/actions.rs index 3d22db2a4dc3c9339e51b0dae02d6d598400ad64..48438b6592a3a75c405fee496fbbd55091389a8f 100644 --- a/crates/editor/src/scroll/actions.rs +++ b/crates/editor/src/scroll/actions.rs @@ -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); diff --git a/crates/editor/src/selections_collection.rs b/crates/editor/src/selections_collection.rs index 7331205d22b779b17af2186757a6b96f59b5616c..51dcca149ce597df076a083f7d0bc3ad223edae2 100644 --- a/crates/editor/src/selections_collection.rs +++ b/crates/editor/src/selections_collection.rs @@ -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 { - 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::(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<'_, '_> { diff --git a/crates/editor/src/semantic_tokens.rs b/crates/editor/src/semantic_tokens.rs index 8408438f17533098f906c75bcc03983edfb7acf8..ebb4454f0d30f5d6343bfa3392cb795e031272fa 100644 --- a/crates/editor/src/semantic_tokens.rs +++ b/crates/editor/src/semantic_tokens.rs @@ -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); }); }); diff --git a/crates/editor/src/split.rs b/crates/editor/src/split.rs index cdb016ea4b612aaae288acd008f745ef2ecf0f1d..ee15583072144ca170328988ebec9959b391dbf1 100644 --- a/crates/editor/src/split.rs +++ b/crates/editor/src/split.rs @@ -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, rhs_snapshot: &MultiBufferSnapshot, lhs_snapshot: &MultiBufferSnapshot, - lhs_bounds: (Bound, Bound), + lhs_bounds: Range, ) -> Vec { 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, lhs_snapshot: &MultiBufferSnapshot, rhs_snapshot: &MultiBufferSnapshot, - rhs_bounds: (Bound, Bound), + rhs_bounds: Range, ) -> Vec { 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, + diff_snapshot: &BufferDiffSnapshot, + rhs_buffer_snapshot: &text::BufferSnapshot, +) -> Range { + 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>, Option)>, splittable: &SplittableEditor, @@ -168,22 +179,18 @@ fn translate_lhs_hunks_to_rhs( } fn patches_for_range( - excerpt_map: &HashMap, source_snapshot: &MultiBufferSnapshot, target_snapshot: &MultiBufferSnapshot, - source_bounds: (Bound, Bound), + source_bounds: Range, translate_fn: F, ) -> Vec where F: Fn(&BufferDiffSnapshot, RangeInclusive, &text::BufferSnapshot) -> Patch, { - 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, buffer_point_range: Range, - source_context_range: Range, } 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, + source_buffer_snapshot: &language::BufferSnapshot, + target_buffer_snapshot: &language::BufferSnapshot, + source_excerpt_range: ExcerptRange, + target_excerpt_range: ExcerptRange, patch: &Patch, source_edited_range: Range, ) -> 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, editor: Entity, + companion: Entity, was_last_focused: bool, _subscriptions: Vec, } @@ -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::>(); + 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, @@ -1012,122 +1046,94 @@ impl SplittableEditor { context_line_count: u32, diff: Entity, cx: &mut Context, - ) -> (Vec>, 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 = 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 + Clone, + excerpt_anchors: impl Iterator + Clone, lines: u32, direction: ExpandExcerptDirection, cx: &mut Context, ) { 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::>() .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) { - 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 = self - .rhs_multibuffer - .read(cx) - .excerpts_for_path(&path) - .collect(); - let lhs_excerpt_ids: Vec = - 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, Entity)>, + paths: Vec<(PathKey, Entity)>, companion: &Entity, cx: &mut Context, ) { 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 = - 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 = - 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> = rhs_multibuffer - .excerpts_for_buffer(main_buffer_snapshot.remote_id(), cx) + let mut paired_ranges: Vec<(Range, ExcerptRange)> = 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| { - 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::>(); - 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::>())); + 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::>(); + 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::>(); + let lhs_snapshot = lhs.multibuffer.read(cx).snapshot(cx); + let lhs_excerpts = lhs_snapshot.excerpts().collect::>(); + 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 = 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 = self.rhs_multibuffer.read(cx).paths().cloned().collect(); - let lhs_paths: Vec = 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 = self + let diff = self .rhs_multibuffer .read(cx) - .excerpts_for_path(path) - .collect(); - let lhs_path_excerpts: Vec = - 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 = 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 => { @@ -2395,7 +2331,14 @@ mod tests { let buffer = buffer.clone(); 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, + ); }); } 55..=64 => { @@ -2407,16 +2350,14 @@ mod tests { } 65..=74 => { log::info!("removing excerpts for a random path"); - let paths = editor.update(cx, |editor, cx| { - editor - .rhs_multibuffer - .read(cx) - .paths() - .cloned() - .collect::>() + let ids = editor.update(cx, |editor, cx| { + let snapshot = editor.rhs_multibuffer.read(cx).snapshot(cx); + snapshot.all_buffer_ids().collect::>() }); - if let Some(path) = paths.choose(rng) { + if let Some(id) = ids.choose(rng) { editor.update(cx, |editor, cx| { + let snapshot = editor.rhs_multibuffer.read(cx).snapshot(cx); + let path = snapshot.path_for_buffer(*id).unwrap(); editor.remove_excerpts_for_path(path.clone(), cx); }); } @@ -2432,18 +2373,21 @@ mod tests { }); } 80..=89 => { - let excerpt_ids = editor.update(cx, |editor, cx| { - editor.rhs_multibuffer.read(cx).excerpt_ids() + let snapshot = editor.update(cx, |editor, cx| { + editor.rhs_multibuffer.read(cx).snapshot(cx) }); - if !excerpt_ids.is_empty() { - let count = rng.random_range(1..=excerpt_ids.len().min(3)); + let excerpts = snapshot.excerpts().collect::>(); + if !excerpts.is_empty() { + let count = rng.random_range(1..=excerpts.len().min(3)); let chosen: Vec<_> = - excerpt_ids.choose_multiple(rng, count).copied().collect(); + excerpts.choose_multiple(rng, count).cloned().collect(); let line_count = rng.random_range(1..5); log::info!("expanding {count} excerpts by {line_count} lines"); editor.update(cx, |editor, cx| { editor.expand_excerpts( - chosen.into_iter(), + chosen.into_iter().map(|excerpt| { + snapshot.anchor_in_excerpt(excerpt.context.start).unwrap() + }), line_count, ExpandExcerptDirection::UpAndDown, cx, @@ -2474,7 +2418,7 @@ mod tests { .collect::>(); editor.update(cx, |editor, cx| { let path = PathKey::for_buffer(&buffer, cx); - editor.set_excerpts_for_path(path, buffer, ranges, 2, diff, cx); + editor.update_excerpts_for_path(path, buffer, ranges, 2, diff, cx); }); } quiesced = true; @@ -2511,7 +2455,7 @@ mod tests { editor.update(cx, |editor, cx| { let path = PathKey::for_buffer(&buffer, cx); - editor.set_excerpts_for_path(path, buffer.clone(), ranges, 0, diff.clone(), cx); + editor.update_excerpts_for_path(path, buffer.clone(), ranges, 0, diff.clone(), cx); }); cx.run_until_parked(); @@ -2523,12 +2467,16 @@ mod tests { ); }); - let excerpt_ids = editor.update(cx, |editor, cx| { - editor.rhs_multibuffer.read(cx).excerpt_ids() + let excerpts = editor.update(cx, |editor, cx| { + let snapshot = editor.rhs_multibuffer.read(cx).snapshot(cx); + snapshot + .excerpts() + .map(|excerpt| snapshot.anchor_in_excerpt(excerpt.context.start).unwrap()) + .collect::>() }); editor.update(cx, |editor, cx| { editor.expand_excerpts( - excerpt_ids.iter().copied(), + excerpts.into_iter(), 2, multi_buffer::ExpandExcerptDirection::UpAndDown, cx, @@ -2564,7 +2512,7 @@ mod tests { editor.update(cx, |editor, cx| { let path = PathKey::for_buffer(&buffer, cx); - editor.set_excerpts_for_path( + editor.update_excerpts_for_path( path, buffer.clone(), vec![Point::new(0, 0)..buffer.read(cx).max_point()], @@ -2693,7 +2641,7 @@ mod tests { editor.update(cx, |editor, cx| { let path1 = PathKey::for_buffer(&buffer1, cx); - editor.set_excerpts_for_path( + editor.update_excerpts_for_path( path1, buffer1.clone(), vec![Point::new(0, 0)..buffer1.read(cx).max_point()], @@ -2702,7 +2650,7 @@ mod tests { cx, ); let path2 = PathKey::for_buffer(&buffer2, cx); - editor.set_excerpts_for_path( + editor.update_excerpts_for_path( path2, buffer2.clone(), vec![Point::new(0, 0)..buffer2.read(cx).max_point()], @@ -2851,7 +2799,7 @@ mod tests { editor.update(cx, |editor, cx| { let path = PathKey::for_buffer(&buffer, cx); - editor.set_excerpts_for_path( + editor.update_excerpts_for_path( path, buffer.clone(), vec![Point::new(0, 0)..buffer.read(cx).max_point()], @@ -2978,7 +2926,7 @@ mod tests { editor.update(cx, |editor, cx| { let path = PathKey::for_buffer(&buffer, cx); - editor.set_excerpts_for_path( + editor.update_excerpts_for_path( path, buffer.clone(), vec![Point::new(0, 0)..buffer.read(cx).max_point()], @@ -3097,7 +3045,7 @@ mod tests { editor.update(cx, |editor, cx| { let path = PathKey::for_buffer(&buffer, cx); - editor.set_excerpts_for_path( + editor.update_excerpts_for_path( path, buffer.clone(), vec![Point::new(0, 0)..buffer.read(cx).max_point()], @@ -3227,7 +3175,7 @@ mod tests { editor.update(cx, |editor, cx| { let path = PathKey::for_buffer(&buffer, cx); - editor.set_excerpts_for_path( + editor.update_excerpts_for_path( path, buffer.clone(), vec![Point::new(0, 0)..buffer.read(cx).max_point()], @@ -3324,7 +3272,7 @@ mod tests { editor.update(cx, |editor, cx| { let end = Point::new(0, text.len() as u32); let path1 = PathKey::for_buffer(&buffer1, cx); - editor.set_excerpts_for_path( + editor.update_excerpts_for_path( path1, buffer1.clone(), vec![Point::new(0, 0)..end], @@ -3333,7 +3281,7 @@ mod tests { cx, ); let path2 = PathKey::for_buffer(&buffer2, cx); - editor.set_excerpts_for_path( + editor.update_excerpts_for_path( path2, buffer2.clone(), vec![Point::new(0, 0)..end], @@ -3401,7 +3349,7 @@ mod tests { editor.update(cx, |editor, cx| { let path = PathKey::for_buffer(&buffer, cx); - editor.set_excerpts_for_path( + editor.update_excerpts_for_path( path, buffer.clone(), vec![Point::new(0, 0)..buffer.read(cx).max_point()], @@ -3464,7 +3412,7 @@ mod tests { editor.update(cx, |editor, cx| { let path = PathKey::for_buffer(&buffer, cx); - editor.set_excerpts_for_path( + editor.update_excerpts_for_path( path, buffer.clone(), vec![Point::new(0, 0)..buffer.read(cx).max_point()], @@ -3525,7 +3473,7 @@ mod tests { editor.update(cx, |editor, cx| { let path = PathKey::for_buffer(&buffer, cx); - editor.set_excerpts_for_path( + editor.update_excerpts_for_path( path, buffer.clone(), vec![Point::new(0, 0)..buffer.read(cx).max_point()], @@ -3641,7 +3589,7 @@ mod tests { editor.update(cx, |editor, cx| { let path1 = PathKey::for_buffer(&buffer1, cx); - editor.set_excerpts_for_path( + editor.update_excerpts_for_path( path1, buffer1.clone(), vec![Point::new(0, 0)..buffer1.read(cx).max_point()], @@ -3651,7 +3599,7 @@ mod tests { ); let path2 = PathKey::for_buffer(&buffer2, cx); - editor.set_excerpts_for_path( + editor.update_excerpts_for_path( path2, buffer2.clone(), vec![Point::new(0, 0)..buffer2.read(cx).max_point()], @@ -3749,7 +3697,7 @@ mod tests { editor.update(cx, |editor, cx| { let path = PathKey::for_buffer(&buffer, cx); - editor.set_excerpts_for_path( + editor.update_excerpts_for_path( path, buffer.clone(), vec![Point::new(0, 0)..buffer.read(cx).max_point()], @@ -3825,7 +3773,7 @@ mod tests { editor.update(cx, |editor, cx| { let path = PathKey::for_buffer(&buffer, cx); - editor.set_excerpts_for_path( + editor.update_excerpts_for_path( path, buffer.clone(), vec![Point::new(0, 0)..buffer.read(cx).max_point()], @@ -3912,7 +3860,7 @@ mod tests { editor.update(cx, |editor, cx| { let path = PathKey::for_buffer(&buffer, cx); - editor.set_excerpts_for_path( + editor.update_excerpts_for_path( path, buffer.clone(), vec![Point::new(0, 0)..buffer.read(cx).max_point()], @@ -4026,7 +3974,7 @@ mod tests { editor.update(cx, |editor, cx| { let path = PathKey::for_buffer(&buffer, cx); - editor.set_excerpts_for_path( + editor.update_excerpts_for_path( path, buffer.clone(), vec![Point::new(0, 0)..buffer.read(cx).max_point()], @@ -4110,7 +4058,7 @@ mod tests { editor.update(cx, |editor, cx| { let path = PathKey::for_buffer(&buffer, cx); - editor.set_excerpts_for_path( + editor.update_excerpts_for_path( path, buffer.clone(), vec![Point::new(0, 0)..buffer.read(cx).max_point()], @@ -4194,7 +4142,7 @@ mod tests { editor.update(cx, |editor, cx| { let path = PathKey::for_buffer(&buffer, cx); - editor.set_excerpts_for_path( + editor.update_excerpts_for_path( path, buffer.clone(), vec![Point::new(0, 0)..buffer.read(cx).max_point()], @@ -4286,7 +4234,7 @@ mod tests { editor.update(cx, |editor, cx| { let path = PathKey::for_buffer(&buffer, cx); - editor.set_excerpts_for_path( + editor.update_excerpts_for_path( path, buffer.clone(), vec![Point::new(0, 0)..buffer.read(cx).max_point()], @@ -4414,7 +4362,7 @@ mod tests { editor.update(cx, |editor, cx| { let path = PathKey::for_buffer(&buffer, cx); - editor.set_excerpts_for_path( + editor.update_excerpts_for_path( path, buffer.clone(), vec![Point::new(0, 0)..buffer.read(cx).max_point()], @@ -4561,7 +4509,7 @@ mod tests { editor.update(cx, |editor, cx| { let path = PathKey::for_buffer(&buffer, cx); - editor.set_excerpts_for_path( + editor.update_excerpts_for_path( path, buffer.clone(), vec![Point::new(0, 0)..buffer.read(cx).max_point()], @@ -4783,7 +4731,7 @@ mod tests { editor.update(cx, |editor, cx| { let path = PathKey::for_buffer(&buffer, cx); - editor.set_excerpts_for_path( + editor.update_excerpts_for_path( path, buffer.clone(), vec![Point::new(0, 0)..buffer.read(cx).max_point()], @@ -5122,7 +5070,7 @@ mod tests { editor.update(cx, |editor, cx| { let path1 = PathKey::for_buffer(&buffer1, cx); - editor.set_excerpts_for_path( + editor.update_excerpts_for_path( path1, buffer1.clone(), vec![Point::new(0, 0)..buffer1.read(cx).max_point()], @@ -5131,7 +5079,7 @@ mod tests { cx, ); let path2 = PathKey::for_buffer(&buffer2, cx); - editor.set_excerpts_for_path( + editor.update_excerpts_for_path( path2, buffer2.clone(), vec![Point::new(0, 0)..buffer2.read(cx).max_point()], @@ -5287,7 +5235,7 @@ mod tests { editor.update(cx, |editor, cx| { let path = PathKey::for_buffer(&buffer, cx); - editor.set_excerpts_for_path( + editor.update_excerpts_for_path( path, buffer.clone(), vec![Point::new(0, 0)..buffer.read(cx).max_point()], @@ -5448,7 +5396,7 @@ mod tests { editor.update(cx, |editor, cx| { let path = PathKey::for_buffer(&buffer, cx); - editor.set_excerpts_for_path( + editor.update_excerpts_for_path( path, buffer.clone(), vec![Point::new(0, 0)..buffer.read(cx).max_point()], @@ -5607,7 +5555,7 @@ mod tests { editor.update(cx, |editor, cx| { let path = PathKey::for_buffer(&buffer, cx); - editor.set_excerpts_for_path( + editor.update_excerpts_for_path( path, buffer.clone(), vec![Point::new(0, 0)..buffer.read(cx).max_point()], @@ -5738,7 +5686,7 @@ mod tests { editor.update(cx, |editor, cx| { let path = PathKey::for_buffer(&buffer, cx); - editor.set_excerpts_for_path( + editor.update_excerpts_for_path( path, buffer.clone(), vec![ @@ -5799,7 +5747,7 @@ mod tests { editor.update(cx, |editor, cx| { let path = PathKey::for_buffer(&buffer, cx); - editor.set_excerpts_for_path( + editor.update_excerpts_for_path( path, buffer.clone(), vec![Point::new(0, 0)..buffer.read(cx).max_point()], @@ -5882,7 +5830,7 @@ mod tests { editor.update(cx, |editor, cx| { let path = PathKey::for_buffer(&buffer, cx); - editor.set_excerpts_for_path( + editor.update_excerpts_for_path( path, buffer.clone(), vec![Point::new(0, 0)..Point::new(3, 3)], @@ -5994,7 +5942,7 @@ mod tests { let path_b = cx.read(|cx| PathKey::for_buffer(&buffer_b, cx)); editor.update(cx, |editor, cx| { - editor.set_excerpts_for_path( + editor.update_excerpts_for_path( path_a.clone(), buffer_a.clone(), vec![Point::new(0, 0)..buffer_a.read(cx).max_point()], @@ -6002,7 +5950,7 @@ mod tests { diff_a.clone(), cx, ); - editor.set_excerpts_for_path( + editor.update_excerpts_for_path( path_b.clone(), buffer_b.clone(), vec![Point::new(0, 0)..buffer_b.read(cx).max_point()], @@ -6032,7 +5980,7 @@ mod tests { cx.run_until_parked(); editor.update(cx, |editor, cx| { - editor.set_excerpts_for_path( + editor.update_excerpts_for_path( path_a.clone(), buffer_a.clone(), vec![Point::new(0, 0)..buffer_a.read(cx).max_point()], @@ -6089,7 +6037,7 @@ mod tests { }; editor.update(cx, |editor, cx| { - editor.set_excerpts_for_path( + editor.update_excerpts_for_path( path_key_1.clone(), buffer.clone(), vec![Point::new(0, 0)..Point::new(1, 0)], @@ -6097,7 +6045,7 @@ mod tests { diff.clone(), cx, ); - editor.set_excerpts_for_path( + editor.update_excerpts_for_path( path_key_2.clone(), buffer.clone(), vec![Point::new(1, 0)..buffer.read(cx).max_point()], diff --git a/crates/editor/src/split_editor_view.rs b/crates/editor/src/split_editor_view.rs index 454013c530ab8389314892011e5eb115ee6e0957..02388df9a7516e72810b91d65292795e6375470e 100644 --- a/crates/editor/src/split_editor_view.rs +++ b/crates/editor/src/split_editor_view.rs @@ -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 = 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, latest_selection_anchors: &HashMap, - sticky_header_excerpt_id: Option, + sticky_header: Option<&ExcerptBoundaryInfo>, window: &mut Window, cx: &mut App, ) -> Vec { @@ -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, diff --git a/crates/editor/src/tasks.rs b/crates/editor/src/tasks.rs new file mode 100644 index 0000000000000000000000000000000000000000..7323d4159cec58a5a7db7daa42ca201125200fae --- /dev/null +++ b/crates/editor/src/tasks.rs @@ -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> { + 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> { + 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 + }, + ) + } +} diff --git a/crates/editor/src/test.rs b/crates/editor/src/test.rs index bef2b3fc3ec2b949ffb8288d59b1201f6f3dde90..22f686668bd98b4c5b5235e34c0881d6583ed3bc 100644 --- a/crates/editor/src/test.rs +++ b/crates/editor/src/test.rs @@ -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("") @@ -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("") diff --git a/crates/editor/src/test/editor_test_context.rs b/crates/editor/src/test/editor_test_context.rs index 101c1559a7a0fb6e5d0d5bba7281a0cb78ab4b65..84b03d91ca1cf2e0ba858398bcf8134ce16edb41 100644 --- a/crates/editor/src/test/editor_test_context.rs +++ b/crates/editor/src/test/editor_test_context.rs @@ -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::>(); (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::(), 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::>(); // 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::>(); @@ -673,7 +712,7 @@ impl EditorTestContext { struct FormatMultiBufferAsMarkedText { multibuffer_snapshot: MultiBufferSnapshot, selections: Vec>, - excerpts: Vec<(ExcerptId, BufferSnapshot, ExcerptRange, bool)>, + excerpts: Vec<(BufferSnapshot, ExcerptRange, 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::(); 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::>(); diff --git a/crates/encoding_selector/src/active_buffer_encoding.rs b/crates/encoding_selector/src/active_buffer_encoding.rs index 417ff241b72300aa90496f896fcf6c3ed3a363c7..42fd5f662f66c8e9f1eaa18953c6765c51244e77 100644 --- a/crates/encoding_selector/src/active_buffer_encoding.rs +++ b/crates/encoding_selector/src/active_buffer_encoding.rs @@ -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(); diff --git a/crates/encoding_selector/src/encoding_selector.rs b/crates/encoding_selector/src/encoding_selector.rs index 3954bf29a30a0981c25bee3eb88829a7002881ad..e99b475de6773c647ef19195ef42052d37769346 100644 --- a/crates/encoding_selector/src/encoding_selector.rs +++ b/crates/encoding_selector/src/encoding_selector.rs @@ -47,11 +47,11 @@ impl EncodingSelector { window: &mut Window, cx: &mut Context, ) -> Option<()> { - let (_, buffer, _) = workspace + let buffer = workspace .active_item(cx)? .act_as::(cx)? .read(cx) - .active_excerpt(cx)?; + .active_buffer(cx)?; let buffer_handle = buffer.read(cx); let project = workspace.project().read(cx); diff --git a/crates/git_ui/src/commit_view.rs b/crates/git_ui/src/commit_view.rs index 757ec1e0ebb92431e110e20f0833e2fcd0a88177..a298380336515aad24e9c55d637d392fa6898b35 100644 --- a/crates/git_ui/src/commit_view.rs +++ b/crates/git_ui/src/commit_view.rs @@ -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), diff --git a/crates/git_ui/src/conflict_view.rs b/crates/git_ui/src/conflict_view.rs index c61214123dff8cbd414c89b586f1176f7255266e..95d46676a80ebca3b2db1ba1d7c88edee32df9ea 100644 --- a/crates/git_ui/src/conflict_view.rs +++ b/crates/git_ui/src/conflict_view.rs @@ -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, 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::().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, - cx: &mut Context, -) { - 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::() - .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, cx: &mut Context) { +fn buffer_ranges_updated(editor: &mut Editor, buffer: Entity, cx: &mut Context) { 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::().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, ) -> 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, 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, - excerpt_id: ExcerptId, resolved_conflict: ConflictRegion, ranges: Vec>, 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::().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::(vec![range.clone()], cx); diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index d8ef930cb2509b0e92b7fe8f90c4cbaf4121132c..00a3b4041b91454d0587a1503b66dc3fa8629917 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -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 { - let file = excerpt_info.buffer.file()?; + let file = buffer.file()?; let git_panel = self.workspace.upgrade()?.read(cx).panel::(cx)?; git_panel diff --git a/crates/git_ui/src/project_diff.rs b/crates/git_ui/src/project_diff.rs index ae27b6e51fcb8f72b86f819a1aa4ac05c17c6e5f..8fa4680593a7565c84efd7503f6cf9d188d3be35 100644 --- a/crates/git_ui/src/project_diff.rs +++ b/crates/git_ui/src/project_diff.rs @@ -501,9 +501,11 @@ impl ProjectDiff { pub fn active_path(&self, cx: &App) -> Option { 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::>(); 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::>(); 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> { - 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::>(); 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::>() }); 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::>(); + 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, diff --git a/crates/git_ui/src/text_diff_view.rs b/crates/git_ui/src/text_diff_view.rs index 2dfef13f72681456174737af61380b87caae0ae1..fe2add8177e2c9ca92eb8d08776d561e1adaba91 100644 --- a/crates/git_ui/src/text_diff_view.rs +++ b/crates/git_ui/src/text_diff_view.rs @@ -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::(&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)) }); diff --git a/crates/go_to_line/src/cursor_position.rs b/crates/go_to_line/src/cursor_position.rs index 042d9a46b6c76a461e60d9002a2362190e253cd4..03bec51ac209fd6e3c254689b3b7caa2695fa450 100644 --- a/crates/go_to_line/src/cursor_position.rs +++ b/crates/go_to_line/src/cursor_position.rs @@ -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::(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::(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::(line_start..selection_end) - .chars as u32; - (selection_end.row, chars_to_last_position) - }; + let chars_to_last_position = snapshot + .text_summary_for_range::(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::(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) diff --git a/crates/go_to_line/src/go_to_line.rs b/crates/go_to_line/src/go_to_line.rs index a5332e96c731a29027ea6a69288d7d9556cb2da0..561d6a7d31398ab2a8eb74042fc1a617b7159d33 100644 --- a/crates/go_to_line/src/go_to_line.rs +++ b/crates/go_to_line/src/go_to_line.rs @@ -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, ))?; diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 7790480e32149fa33dfd082df7a8cdbb09568134..f9885f634d962b167bcf32cc459d5bf6e0d5661e 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -5496,6 +5496,8 @@ pub enum ElementId { CodeLocation(core::panic::Location<'static>), /// A labeled child of an element. NamedChild(Arc, 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)] diff --git a/crates/inspector_ui/src/div_inspector.rs b/crates/inspector_ui/src/div_inspector.rs index a7616e134a16bbe2b96a6d23d20453b9a5ee4e5f..7ec2d7ba8303e899331d3f38642a9a51f4c14d4c 100644 --- a/crates/inspector_ui/src/div_inspector.rs +++ b/crates/inspector_ui/src/div_inspector.rs @@ -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, position: Anchor, _: editor::CompletionContext, diff --git a/crates/keymap_editor/src/action_completion_provider.rs b/crates/keymap_editor/src/action_completion_provider.rs index 98428baeb2f7b419ba7354130e12f1a4710c8aea..10d977572b9c52cba1ad9d87c7035bd1552d5e33 100644 --- a/crates/keymap_editor/src/action_completion_provider.rs +++ b/crates/keymap_editor/src/action_completion_provider.rs @@ -26,7 +26,6 @@ impl ActionCompletionProvider { impl CompletionProvider for ActionCompletionProvider { fn completions( &self, - _excerpt_id: editor::ExcerptId, buffer: &Entity, buffer_position: language::Anchor, _trigger: editor::CompletionContext, diff --git a/crates/keymap_editor/src/keymap_editor.rs b/crates/keymap_editor/src/keymap_editor.rs index 2e3172dac95fe91ed5b2a5a187ca57bbd9154fae..ee9f6a11c2b51f7993b17c01352cfb97b535049a 100644 --- a/crates/keymap_editor/src/keymap_editor.rs +++ b/crates/keymap_editor/src/keymap_editor.rs @@ -3480,7 +3480,6 @@ struct KeyContextCompletionProvider { impl CompletionProvider for KeyContextCompletionProvider { fn completions( &self, - _excerpt_id: editor::ExcerptId, buffer: &Entity, buffer_position: language::Anchor, _trigger: editor::CompletionContext, diff --git a/crates/language/src/diagnostic_set.rs b/crates/language/src/diagnostic_set.rs index fa3263df48ff773b32332980e7341fa8a453ba4f..04564ecd6575f9470315e0571a60126c69d81d2b 100644 --- a/crates/language/src/diagnostic_set.rs +++ b/crates/language/src/diagnostic_set.rs @@ -326,23 +326,17 @@ impl DiagnosticEntry { } } -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<'_>) { diff --git a/crates/language/src/proto.rs b/crates/language/src/proto.rs index 89c44513067f6d2309d68a9f38984988358d8877..5e3179e929da012cce8e7da6b436e89c0c4519de 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -174,11 +174,11 @@ pub fn serialize_selection(selection: &Selection) -> 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 { timestamp, anchor.offset as u32, bias, - buffer_id, + buffer_id?, )) } diff --git a/crates/language/src/syntax_map.rs b/crates/language/src/syntax_map.rs index f2f79b9a793f303fef66fb4266d67f1fbd2ed52d..b73276ffd92be8915e2272b5242770fc52854af1 100644 --- a/crates/language/src/syntax_map.rs +++ b/crates/language/src/syntax_map.rs @@ -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() { diff --git a/crates/language_selector/src/active_buffer_language.rs b/crates/language_selector/src/active_buffer_language.rs index c75c3954cc6590c2e0cb4326c073ed004eaac280..1f280282af933094cf46cd9e7ab790efd07b8a12 100644 --- a/crates/language_selector/src/active_buffer_language.rs +++ b/crates/language_selector/src/active_buffer_language.rs @@ -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())); diff --git a/crates/language_selector/src/language_selector.rs b/crates/language_selector/src/language_selector.rs index e5e6a2e264dbb923390e05b283fe341a3336af97..70a03514f45371d58d0a8ee0a14eb87565d3a514 100644 --- a/crates/language_selector/src/language_selector.rs +++ b/crates/language_selector/src/language_selector.rs @@ -51,11 +51,11 @@ impl LanguageSelector { cx: &mut Context, ) -> Option<()> { let registry = workspace.app_state().languages.clone(); - let (_, buffer, _) = workspace + let buffer = workspace .active_item(cx)? .act_as::(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 { 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); } diff --git a/crates/language_tools/src/highlights_tree_view.rs b/crates/language_tools/src/highlights_tree_view.rs index c2f684c11dc148c8f66b6cf20e0ca06e40905db7..aec0cad5b1cf4be043ca21298995b08ceb93f3f2 100644 --- a/crates/language_tools/src/highlights_tree_view.rs +++ b/crates/language_tools/src/highlights_tree_view.rs @@ -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, + buffer_id: BufferId, + buffer_point_range: Range, 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 = None; + let mut last_range_end: Option = 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, - 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)> { + 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 { diff --git a/crates/language_tools/src/lsp_button.rs b/crates/language_tools/src/lsp_button.rs index 59b14d470003f3a8a4f45b7b2b3e51505f562e56..43b1736223478fe29f45aac0a712fafad1d2dcbe 100644 --- a/crates/language_tools/src/lsp_button.rs +++ b/crates/language_tools/src/lsp_button.rs @@ -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() { diff --git a/crates/language_tools/src/syntax_tree_view.rs b/crates/language_tools/src/syntax_tree_view.rs index b44d2e05d90733469a5385c2695b3fda3ff47c5e..9c751dd8eaf71272b649b037425caa4aa73b39cc 100644 --- a/crates/language_tools/src/syntax_tree_view.rs +++ b/crates/language_tools/src/syntax_tree_view.rs @@ -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, - excerpt_id: ExcerptId, active_layer: Option, } @@ -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::(&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. diff --git a/crates/line_ending_selector/src/line_ending_indicator.rs b/crates/line_ending_selector/src/line_ending_indicator.rs index ee858d706b3a8152c868a5bd629c112a4d1b225f..9c493344e757174035a30e42126389ced9ea1624 100644 --- a/crates/line_ending_selector/src/line_ending_indicator.rs +++ b/crates/line_ending_selector/src/line_ending_indicator.rs @@ -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()); diff --git a/crates/line_ending_selector/src/line_ending_selector.rs b/crates/line_ending_selector/src/line_ending_selector.rs index 504c327a349c97214e801f6bd375d61c7847f2be..455807565f8be52e574327f10d5881bb575c60f3 100644 --- a/crates/line_ending_selector/src/line_ending_selector.rs +++ b/crates/line_ending_selector/src/line_ending_selector.rs @@ -40,7 +40,7 @@ impl LineEndingSelector { fn toggle(editor: &WeakEntity, 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() diff --git a/crates/markdown_preview/src/markdown_preview_view.rs b/crates/markdown_preview/src/markdown_preview_view.rs index 6dbf44c20f3ce453a7ef711e1854b806cf29737a..f978fdfcce13808b58cd1d7467379c44b95e7433 100644 --- a/crates/markdown_preview/src/markdown_preview_view.rs +++ b/crates/markdown_preview/src/markdown_preview_view.rs @@ -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 { .. } => { diff --git a/crates/multi_buffer/src/anchor.rs b/crates/multi_buffer/src/anchor.rs index cf4df9f53ccd2ca86fc6c064d51b7557404dd251..08b159effafa2f34dbf1b10768bf356aaf74ae31 100644 --- a/crates/multi_buffer/src/anchor.rs +++ b/crates/multi_buffer/src/anchor.rs @@ -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, +} /// 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, +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, + }, + 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 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) -> Range { - 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::(()); + 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 { + 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) -> Range { + 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(&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 { + 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 { + match self { + Anchor::Min | Anchor::Max => None, + Anchor::Excerpt(excerpt_anchor) => Some(*excerpt_anchor), + } + } + + pub(crate) fn text_anchor(&self) -> Option { + 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 { + 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 { + 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 { + 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 } } diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index 21b4d0e1a6c84189a9926d2d181f097c2bdf4ea7..44e2152f5258b19aada8b5b602075c2b57a1baf1 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -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 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 @@ -79,10 +76,6 @@ pub struct MultiBuffer { snapshot: RefCell, /// Contains the state of the buffers being edited buffers: BTreeMap, - /// Mapping from path keys to their excerpts. - excerpts_by_path: BTreeMap>, - /// Mapping from excerpt IDs to their path key. - paths_by_excerpt: HashMap, /// Mapping from buffer IDs to their diff states diffs: HashMap, subscriptions: Topic, @@ -98,24 +91,20 @@ pub struct MultiBuffer { buffer_changed_since_sync: Rc>, } +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +struct PathKeyIndex(u64); + #[derive(Clone, Debug, PartialEq, Eq)] pub enum Event { - ExcerptsAdded { + BufferRangesUpdated { buffer: Entity, - predecessor: ExcerptId, - excerpts: Vec<(ExcerptId, ExcerptRange)>, + path_key: PathKey, + ranges: Vec>, }, - ExcerptsRemoved { - ids: Vec, - /// 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, }, - ExcerptsExpanded { - ids: Vec, - }, - ExcerptsEdited { - excerpt_ids: Vec, + BuffersEdited { buffer_ids: Vec, }, 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, - /// 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, /// The status of this hunk (added/modified/deleted and secondary status). pub status: DiffHunkStatus, /// The word diffs for this hunk. pub word_diffs: Vec>, + pub excerpt_range: ExcerptRange, + pub multi_buffer_range: Range, } 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 { - 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; -type ExcerptPoint = ExcerptDimension; +/// 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, - last_version: RefCell, - last_non_text_state_update_count: Cell, - // Note, any changes to this field value require updating snapshot.buffer_locators as well - excerpts: Vec, _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, - buffer_locators: TreeMap>, + buffers: TreeMap, + path_keys_by_index: TreeMap, + indices_by_path_key: TreeMap, diffs: SumTree, diff_transforms: SumTree, - excerpt_ids: SumTree, - replaced_excerpts: Arc>, 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, }, - // 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(&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, - pub buffer_id: BufferId, +pub struct ExcerptBoundaryInfo { + pub start_anchor: Anchor, pub range: ExcerptRange, 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::()) - .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, - pub next: ExcerptInfo, + pub prev: Option, + 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, + 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, + pub(crate) range: ExcerptRange, + /// 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>, - /// 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 ExcerptRange { } } -#[derive(Clone, Debug, Default)] +impl ExcerptRange { + 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, 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>, - diffs: &'a SumTree, diff_base_chunks: Option<(BufferId, BufferChunks<'a>)>, buffer_chunk: Option>, range: Range, excerpt_offset_range: Range, excerpt_chunks: Option>, 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>, diff_transforms: Cursor<'a, 'static, DiffTransform, DiffTransforms>, - diffs: &'a SumTree, cached_region: OnceCell>>, + 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, is_insertion: bool, original_indent_column: Option, - 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, Arc)>, snapshot: &MultiBufferSnapshot, original_indent_columns: &[Option], - ) -> (HashMap>, Vec) { + ) -> HashMap> { let mut buffer_edits: HashMap> = Default::default(); - let mut edited_excerpt_ids = Vec::new(); let mut cursor = snapshot.cursor::(); 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(&mut self, ranges: I, cx: &mut Context) @@ -1706,7 +1679,7 @@ impl MultiBuffer { edits: Vec<(Range, Arc)>, cx: &mut Context, ) { - 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>> = Default::default(); - let snapshot = self.read(cx); - let mut cursor = snapshot.excerpts.cursor::>(()); + let snapshot = self.snapshot(cx); + let mut cursor = snapshot.excerpts.cursor::(()); 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> + 'a, - ) -> (Vec>, Vec) { + ) -> Vec> { + let mut sorted: Vec<_> = expanded_ranges.into_iter().collect(); + sorted.sort_by_key(|range| range.context.start); let mut merged_ranges: Vec> = Vec::new(); - let mut counts: Vec = 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( - &mut self, - prev_excerpt_id: ExcerptId, - buffer: Entity, - ranges: impl IntoIterator>, - cx: &mut Context, - ) -> Vec - 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( - &mut self, - prev_excerpt_id: ExcerptId, - buffer: Entity, - ranges: impl IntoIterator)>, - cx: &mut Context, - ) 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::>(()); - 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.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, ExcerptRange)> { - let mut excerpts = Vec::new(); - let snapshot = self.read(cx); - let mut cursor = snapshot.excerpts.cursor::>(()); - 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> { - let snapshot = self.read(cx); - let mut excerpts = snapshot - .excerpts - .cursor::, ExcerptPoint>>(()); - let mut diff_transforms = snapshot - .diff_transforms - .cursor::>>(()); - 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 { - self.snapshot - .borrow() - .excerpts - .iter() - .map(|entry| entry.buffer_id) - .collect() - } - - pub fn excerpt_ids(&self) -> Vec { - 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, Range)> { + pub fn range_for_buffer(&self, buffer_id: BufferId, cx: &App) -> Option> { let snapshot = self.read(cx); - let offset = position.to_offset(&snapshot); - - let mut cursor = snapshot.cursor::(); - 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> { - 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 @@ -2193,15 +1887,10 @@ impl MultiBuffer { &self, point: T, cx: &App, - ) -> Option<(Entity, Point, ExcerptId)> { + ) -> Option<(Entity, Point)> { let snapshot = self.read(cx); - let (buffer, point, is_main_buffer) = - snapshot.point_to_buffer_point(point.to_point(&snapshot))?; - Some(( - self.buffers.get(&buffer.remote_id())?.buffer.clone(), - point, - is_main_buffer, - )) + let (buffer, point) = snapshot.point_to_buffer_point(point.to_point(&snapshot))?; + Some((self.buffers.get(&buffer.remote_id())?.buffer.clone(), point)) } pub fn buffer_point_to_anchor( @@ -2212,266 +1901,86 @@ impl MultiBuffer { cx: &App, ) -> Option { let mut found = None; - let snapshot = buffer.read(cx).snapshot(); - for (excerpt_id, _, range) in self.excerpts_for_buffer(snapshot.remote_id(), cx) { - let start = range.context.start.to_point(&snapshot); - let end = range.context.end.to_point(&snapshot); - if start <= point && point < end { - found = Some((snapshot.clip_point(point, Bias::Left), excerpt_id)); + let buffer_snapshot = buffer.read(cx).snapshot(); + let text_anchor = buffer_snapshot.anchor_after(&point); + let snapshot = self.snapshot(cx); + let path_key_index = snapshot.path_key_index_for_buffer(buffer_snapshot.remote_id())?; + for excerpt in snapshot.excerpts_for_buffer(buffer_snapshot.remote_id()) { + if excerpt + .context + .start + .cmp(&text_anchor, &buffer_snapshot) + .is_gt() + { + found = Some(Anchor::in_buffer(path_key_index, excerpt.context.start)); + break; + } else if excerpt + .context + .end + .cmp(&text_anchor, &buffer_snapshot) + .is_ge() + { + found = Some(Anchor::in_buffer(path_key_index, text_anchor)); break; } - if point < start { - found = Some((start, excerpt_id)); - } - if point >= end { - found = Some((end, excerpt_id)); - } + found = Some(Anchor::in_buffer(path_key_index, excerpt.context.end)); } - found.map(|(point, excerpt_id)| { - let text_anchor = snapshot.anchor_after(point); - Anchor::in_buffer(excerpt_id, text_anchor) - }) + found } - pub fn buffer_anchor_to_anchor( + pub fn wait_for_anchors<'a, Anchors: 'a + Iterator>( &self, - // todo(lw): We shouldn't need this? - buffer: &Entity, - anchor: text::Anchor, - cx: &App, - ) -> Option { - let snapshot = buffer.read(cx).snapshot(); - for (excerpt_id, _, range) in self.excerpts_for_buffer(snapshot.remote_id(), cx) { - if range.context.start.cmp(&anchor, &snapshot).is_le() - && range.context.end.cmp(&anchor, &snapshot).is_ge() - { - return Some(Anchor::in_buffer(excerpt_id, anchor)); + anchors: Anchors, + cx: &mut Context, + ) -> impl 'static + Future> + use { + let mut error = None; + let mut futures = Vec::new(); + for anchor in anchors { + if let Some(excerpt_anchor) = anchor.excerpt_anchor() { + if let Some(buffer) = self.buffers.get(&excerpt_anchor.text_anchor.buffer_id) { + buffer.buffer.update(cx, |buffer, _| { + futures.push(buffer.wait_for_anchors([excerpt_anchor.text_anchor()])) + }); + } else { + error = Some(anyhow!( + "buffer {:?} is not part of this multi-buffer", + excerpt_anchor.text_anchor.buffer_id + )); + break; + } + } + } + async move { + if let Some(error) = error { + Err(error)?; + } + for future in futures { + future.await?; } + Ok(()) } + } - None + pub fn text_anchor_for_position( + &self, + position: T, + cx: &App, + ) -> Option<(Entity, text::Anchor)> { + let snapshot = self.read(cx); + let anchor = snapshot.anchor_before(position).excerpt_anchor()?; + let buffer = self + .buffers + .get(&anchor.text_anchor.buffer_id)? + .buffer + .clone(); + Some((buffer, anchor.text_anchor())) } - pub fn merge_excerpts( + fn on_buffer_event( &mut self, - excerpt_ids: &[ExcerptId], - cx: &mut Context, - ) -> ExcerptId { - debug_assert!(!excerpt_ids.is_empty()); - if excerpt_ids.len() == 1 { - return excerpt_ids[0]; - } - - let snapshot = self.snapshot(cx); - - let first_range = snapshot - .context_range_for_excerpt(excerpt_ids[0]) - .expect("first excerpt must exist"); - let last_range = snapshot - .context_range_for_excerpt(*excerpt_ids.last().unwrap()) - .expect("last excerpt must exist"); - - let union_range = first_range.start..last_range.end; - - drop(snapshot); - - self.resize_excerpt(excerpt_ids[0], union_range, cx); - let removed = &excerpt_ids[1..]; - for &excerpt_id in removed { - if let Some(path) = self.paths_by_excerpt.get(&excerpt_id) { - if let Some(excerpt_list) = self.excerpts_by_path.get_mut(path) { - excerpt_list.retain(|id| *id != excerpt_id); - if excerpt_list.is_empty() { - let path = path.clone(); - self.excerpts_by_path.remove(&path); - } - } - } - } - self.remove_excerpts(removed.iter().copied(), cx); - - excerpt_ids[0] - } - - pub fn remove_excerpts( - &mut self, - excerpt_ids: impl IntoIterator, - cx: &mut Context, - ) { - self.sync_mut(cx); - let ids = excerpt_ids.into_iter().collect::>(); - if ids.is_empty() { - return; - } - self.buffer_changed_since_sync.replace(true); - - let mut snapshot = self.snapshot.get_mut(); - let mut new_excerpts = SumTree::default(); - let mut cursor = snapshot - .excerpts - .cursor::, ExcerptOffset>>(()); - let mut edits = Vec::new(); - let mut excerpt_ids = ids.iter().copied().peekable(); - let mut removed_buffer_ids = Vec::new(); - let mut removed_excerpts_for_buffers = HashSet::default(); - - while let Some(excerpt_id) = excerpt_ids.next() { - self.paths_by_excerpt.remove(&excerpt_id); - // Seek to the next excerpt to remove, preserving any preceding excerpts. - let locator = snapshot.excerpt_locator_for_id(excerpt_id); - new_excerpts.append(cursor.slice(&Some(locator), Bias::Left), ()); - - if let Some(mut excerpt) = cursor.item() { - if excerpt.id != excerpt_id { - continue; - } - let mut old_start = cursor.start().1; - - // Skip over the removed excerpt. - 'remove_excerpts: loop { - if let Some(buffer_state) = self.buffers.get_mut(&excerpt.buffer_id) { - removed_excerpts_for_buffers.insert(excerpt.buffer_id); - buffer_state.excerpts.retain(|l| l != &excerpt.locator); - if buffer_state.excerpts.is_empty() { - log::debug!( - "removing buffer and diff for buffer {}", - excerpt.buffer_id - ); - self.buffers.remove(&excerpt.buffer_id); - removed_buffer_ids.push(excerpt.buffer_id); - } - } - cursor.next(); - - // Skip over any subsequent excerpts that are also removed. - if let Some(&next_excerpt_id) = excerpt_ids.peek() { - let next_locator = snapshot.excerpt_locator_for_id(next_excerpt_id); - if let Some(next_excerpt) = cursor.item() - && next_excerpt.locator == *next_locator - { - excerpt_ids.next(); - excerpt = next_excerpt; - continue 'remove_excerpts; - } - } - - break; - } - - // When removing the last excerpt, remove the trailing newline from - // the previous excerpt. - if cursor.item().is_none() && old_start > MultiBufferOffset::ZERO { - old_start -= 1; - new_excerpts.update_last(|e| e.has_trailing_newline = false, ()); - } - - // Push an edit for the removal of this run of excerpts. - let old_end = cursor.start().1; - let new_start = ExcerptDimension(new_excerpts.summary().text.len); - edits.push(Edit { - old: old_start..old_end, - new: new_start..new_start, - }); - } - } - let suffix = cursor.suffix(); - let changed_trailing_excerpt = suffix.is_empty(); - new_excerpts.append(suffix, ()); - drop(cursor); - for buffer_id in removed_excerpts_for_buffers { - match self.buffers.get(&buffer_id) { - Some(buffer_state) => { - snapshot - .buffer_locators - .insert(buffer_id, buffer_state.excerpts.iter().cloned().collect()); - } - None => { - snapshot.buffer_locators.remove(&buffer_id); - } - } - } - snapshot.excerpts = new_excerpts; - for buffer_id in &removed_buffer_ids { - self.diffs.remove(buffer_id); - remove_diff_state(&mut snapshot.diffs, *buffer_id); - } - - if !removed_buffer_ids.is_empty() { - snapshot.has_inverted_diff = - snapshot.diffs.iter().any(|diff| diff.main_buffer.is_some()); - } - - if changed_trailing_excerpt { - snapshot.trailing_excerpt_update_count += 1; - } - - let edits = Self::sync_diff_transforms(&mut snapshot, edits, DiffChangeKind::BufferEdited); - if !edits.is_empty() { - self.subscriptions.publish(edits); - } - cx.emit(Event::Edited { - edited_buffer: None, - is_local: true, - }); - cx.emit(Event::ExcerptsRemoved { - ids, - removed_buffer_ids, - }); - cx.notify(); - } - - pub fn wait_for_anchors<'a, Anchors: 'a + Iterator>( - &self, - anchors: Anchors, - cx: &mut Context, - ) -> impl 'static + Future> + use { - let mut error = None; - let mut futures = Vec::new(); - for anchor in anchors { - if let Some(buffer_id) = anchor.text_anchor.buffer_id { - if let Some(buffer) = self.buffers.get(&buffer_id) { - buffer.buffer.update(cx, |buffer, _| { - futures.push(buffer.wait_for_anchors([anchor.text_anchor])) - }); - } else { - error = Some(anyhow!( - "buffer {buffer_id} is not part of this multi-buffer" - )); - break; - } - } - } - async move { - if let Some(error) = error { - Err(error)?; - } - for future in futures { - future.await?; - } - Ok(()) - } - } - - pub fn text_anchor_for_position( - &self, - position: T, - cx: &App, - ) -> Option<(Entity, language::Anchor)> { - let snapshot = self.read(cx); - let anchor = snapshot.anchor_before(position); - let buffer = self - .buffers - .get(&anchor.text_anchor.buffer_id?)? - .buffer - .clone(); - Some((buffer, anchor.text_anchor)) - } - - fn on_buffer_event( - &mut self, - buffer: Entity, - event: &language::BufferEvent, + buffer: Entity, + event: &language::BufferEvent, cx: &mut Context, ) { use language::BufferEvent; @@ -2532,12 +2041,15 @@ impl MultiBuffer { range: Range, cx: &mut Context, ) { - self.sync_mut(cx); + let Some(buffer) = self.buffer(diff.read(cx).buffer_id) else { + return; + }; + let snapshot = self.sync_mut(cx); let diff = diff.read(cx); let buffer_id = diff.buffer_id; - let Some(buffer_state) = self.buffers.get(&buffer_id) else { + let Some(path) = snapshot.path_for_buffer(buffer_id).cloned() else { return; }; let new_diff = DiffStateSnapshot { @@ -2545,17 +2057,17 @@ impl MultiBuffer { diff: diff.snapshot(cx), main_buffer: None, }; - let mut snapshot = self.snapshot.get_mut(); + let snapshot = self.snapshot.get_mut(); let base_text_changed = find_diff_state(&snapshot.diffs, buffer_id) .is_none_or(|old_diff| !new_diff.base_texts_definitely_eq(old_diff)); snapshot.diffs.insert_or_replace(new_diff, ()); - let buffer = buffer_state.buffer.read(cx); + let buffer = buffer.read(cx); let diff_change_range = range.to_offset(buffer); - let excerpt_edits = snapshot.excerpt_edits_for_diff_change(buffer_state, diff_change_range); + let excerpt_edits = snapshot.excerpt_edits_for_diff_change(&path, diff_change_range); let edits = Self::sync_diff_transforms( - &mut snapshot, + snapshot, excerpt_edits, DiffChangeKind::DiffUpdated { base_changed: base_text_changed, @@ -2577,10 +2089,10 @@ impl MultiBuffer { diff_change_range: Option>, cx: &mut Context, ) { - self.sync_mut(cx); + let snapshot = self.sync_mut(cx); let base_text_buffer_id = diff.read(cx).base_text_buffer().read(cx).remote_id(); - let Some(buffer_state) = self.buffers.get(&base_text_buffer_id) else { + let Some(path) = snapshot.path_for_buffer(base_text_buffer_id).cloned() else { return; }; @@ -2591,16 +2103,16 @@ impl MultiBuffer { diff: diff.snapshot(cx), main_buffer: Some(main_buffer_snapshot), }; - let mut snapshot = self.snapshot.get_mut(); + let snapshot = self.snapshot.get_mut(); snapshot.diffs.insert_or_replace(new_diff, ()); let Some(diff_change_range) = diff_change_range else { return; }; - let excerpt_edits = snapshot.excerpt_edits_for_diff_change(buffer_state, diff_change_range); + let excerpt_edits = snapshot.excerpt_edits_for_diff_change(&path, diff_change_range); let edits = Self::sync_diff_transforms( - &mut snapshot, + snapshot, excerpt_edits, DiffChangeKind::DiffUpdated { // We don't read this field for inverted diffs. @@ -2624,14 +2136,6 @@ impl MultiBuffer { self.all_buffers_iter().collect() } - pub fn all_buffer_ids_iter(&self) -> impl Iterator { - self.buffers.keys().copied() - } - - pub fn all_buffer_ids(&self) -> Vec { - self.all_buffer_ids_iter().collect() - } - pub fn buffer(&self, buffer_id: BufferId) -> Option> { self.buffers .get(&buffer_id) @@ -2644,14 +2148,11 @@ impl MultiBuffer { } pub fn language_settings<'a>(&'a self, cx: &'a App) -> Cow<'a, LanguageSettings> { - let buffer_id = self - .snapshot - .borrow() + let snapshot = self.snapshot(cx); + snapshot .excerpts .first() - .map(|excerpt| excerpt.buffer.remote_id()); - buffer_id - .and_then(|buffer_id| self.buffer(buffer_id)) + .and_then(|excerpt| self.buffer(excerpt.range.context.start.buffer_id)) .map(|buffer| LanguageSettings::for_buffer(&buffer.read(cx), cx)) .unwrap_or_else(move || self.language_settings_at(MultiBufferOffset::default(), cx)) } @@ -2814,7 +2315,7 @@ impl MultiBuffer { pub fn set_all_diff_hunks_expanded(&mut self, cx: &mut Context) { self.snapshot.get_mut().all_diff_hunks_expanded = true; - self.expand_or_collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], true, cx); + self.expand_or_collapse_diff_hunks(vec![Anchor::Min..Anchor::Max], true, cx); } pub fn all_diff_hunks_expanded(&self) -> bool { @@ -2823,7 +2324,7 @@ impl MultiBuffer { pub fn set_all_diff_hunks_collapsed(&mut self, cx: &mut Context) { self.snapshot.get_mut().all_diff_hunks_expanded = false; - self.expand_or_collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], false, cx); + self.expand_or_collapse_diff_hunks(vec![Anchor::Min..Anchor::Max], false, cx); } pub fn set_show_deleted_hunks(&mut self, show: bool, cx: &mut Context) { @@ -2833,7 +2334,7 @@ impl MultiBuffer { let old_len = self.snapshot.borrow().len(); - let ranges = std::iter::once((Point::zero()..Point::MAX, ExcerptId::max())); + let ranges = std::iter::once((Point::zero()..Point::MAX, None)); let _ = self.expand_or_collapse_diff_hunks_inner(ranges, true, cx); let new_len = self.snapshot.borrow().len(); @@ -2856,7 +2357,7 @@ impl MultiBuffer { pub fn has_multiple_hunks(&self, cx: &App) -> bool { self.read(cx) - .diff_hunks_in_range(Anchor::min()..Anchor::max()) + .diff_hunks_in_range(Anchor::Min..Anchor::Max) .nth(1) .is_some() } @@ -2902,7 +2403,7 @@ impl MultiBuffer { pub fn expand_or_collapse_diff_hunks_inner( &mut self, - ranges: impl IntoIterator, ExcerptId)>, + ranges: impl IntoIterator, Option)>, expand: bool, cx: &mut Context, ) -> Vec> { @@ -2913,18 +2414,34 @@ impl MultiBuffer { let mut snapshot = self.snapshot.get_mut(); let mut excerpt_edits = Vec::new(); let mut last_hunk_row = None; - for (range, end_excerpt_id) in ranges { + for (range, end_anchor) in ranges { for diff_hunk in snapshot.diff_hunks_in_range(range) { - if diff_hunk.excerpt_id.cmp(&end_excerpt_id, &snapshot).is_gt() { + if let Some(end_anchor) = &end_anchor + && let Some(hunk_end_anchor) = + snapshot.anchor_in_excerpt(diff_hunk.excerpt_range.context.end) + && hunk_end_anchor.cmp(end_anchor, snapshot).is_gt() + { + continue; + } + let hunk_range = diff_hunk.multi_buffer_range; + if let Some(excerpt_start_anchor) = + snapshot.anchor_in_excerpt(diff_hunk.excerpt_range.context.start) + && hunk_range.start.to_point(snapshot) < excerpt_start_anchor.to_point(snapshot) + { continue; } if last_hunk_row.is_some_and(|row| row >= diff_hunk.row_range.start) { continue; } - let start = Anchor::in_buffer(diff_hunk.excerpt_id, diff_hunk.buffer_range.start); - let end = Anchor::in_buffer(diff_hunk.excerpt_id, diff_hunk.buffer_range.end); - let start = snapshot.excerpt_offset_for_anchor(&start); - let end = snapshot.excerpt_offset_for_anchor(&end); + let mut start = snapshot.excerpt_offset_for_anchor(&hunk_range.start); + let mut end = snapshot.excerpt_offset_for_anchor(&hunk_range.end); + if let Some(excerpt_end_anchor) = + snapshot.anchor_in_excerpt(diff_hunk.excerpt_range.context.end) + { + let excerpt_end = snapshot.excerpt_offset_for_anchor(&excerpt_end_anchor); + start = start.min(excerpt_end); + end = end.min(excerpt_end); + }; last_hunk_row = Some(diff_hunk.row_range.start); excerpt_edits.push(text::Edit { old: start..end, @@ -2947,15 +2464,18 @@ impl MultiBuffer { cx: &mut Context, ) { let snapshot = self.snapshot.borrow().clone(); - let ranges = ranges.iter().map(move |range| { - let end_excerpt_id = range.end.excerpt_id; - let range = range.to_point(&snapshot); - let mut peek_end = range.end; - if range.end.row < snapshot.max_row().0 { - peek_end = Point::new(range.end.row + 1, 0); - }; - (range.start..peek_end, end_excerpt_id) - }); + let ranges = + ranges.iter().map(move |range| { + let excerpt_end = snapshot.excerpt_containing(range.end..range.end).and_then( + |(_, excerpt_range)| snapshot.anchor_in_excerpt(excerpt_range.context.end), + ); + let range = range.to_point(&snapshot); + let mut peek_end = range.end; + if range.end.row < snapshot.max_row().0 { + peek_end = Point::new(range.end.row + 1, 0); + }; + (range.start..peek_end, excerpt_end) + }); let edits = self.expand_or_collapse_diff_hunks_inner(ranges, expand, cx); if !edits.is_empty() { self.subscriptions.publish(edits); @@ -2967,184 +2487,6 @@ impl MultiBuffer { }); } - pub fn resize_excerpt( - &mut self, - id: ExcerptId, - range: Range, - cx: &mut Context, - ) { - self.sync_mut(cx); - - let mut snapshot = self.snapshot.get_mut(); - let locator = snapshot.excerpt_locator_for_id(id); - let mut new_excerpts = SumTree::default(); - let mut cursor = snapshot - .excerpts - .cursor::, ExcerptOffset>>(()); - let mut edits = Vec::>::new(); - - let prefix = cursor.slice(&Some(locator), Bias::Left); - new_excerpts.append(prefix, ()); - - let mut excerpt = cursor.item().unwrap().clone(); - let old_text_len = excerpt.text_summary.len; - - excerpt.range.context.start = range.start; - excerpt.range.context.end = range.end; - excerpt.max_buffer_row = range.end.to_point(&excerpt.buffer).row; - - excerpt.text_summary = excerpt - .buffer - .text_summary_for_range(excerpt.range.context.clone()); - - let new_start_offset = ExcerptDimension(new_excerpts.summary().text.len); - let old_start_offset = cursor.start().1; - let new_text_len = excerpt.text_summary.len; - let edit = Edit { - old: old_start_offset..old_start_offset + old_text_len, - new: new_start_offset..new_start_offset + new_text_len, - }; - - if let Some(last_edit) = edits.last_mut() { - if last_edit.old.end == edit.old.start { - last_edit.old.end = edit.old.end; - last_edit.new.end = edit.new.end; - } else { - edits.push(edit); - } - } else { - edits.push(edit); - } - - new_excerpts.push(excerpt, ()); - - cursor.next(); - - new_excerpts.append(cursor.suffix(), ()); - - drop(cursor); - snapshot.excerpts = new_excerpts; - - let edits = Self::sync_diff_transforms(&mut snapshot, edits, DiffChangeKind::BufferEdited); - if !edits.is_empty() { - self.subscriptions.publish(edits); - } - cx.emit(Event::Edited { - edited_buffer: None, - is_local: true, - }); - cx.emit(Event::ExcerptsExpanded { ids: vec![id] }); - cx.notify(); - } - - pub fn expand_excerpts( - &mut self, - ids: impl IntoIterator, - line_count: u32, - direction: ExpandExcerptDirection, - cx: &mut Context, - ) { - if line_count == 0 { - return; - } - self.sync_mut(cx); - if !self.excerpts_by_path.is_empty() { - self.expand_excerpts_with_paths(ids, line_count, direction, cx); - return; - } - let mut snapshot = self.snapshot.get_mut(); - - let ids = ids.into_iter().collect::>(); - let locators = snapshot.excerpt_locators_for_ids(ids.iter().copied()); - let mut new_excerpts = SumTree::default(); - let mut cursor = snapshot - .excerpts - .cursor::, ExcerptOffset>>(()); - let mut edits = Vec::>::new(); - - for locator in &locators { - let prefix = cursor.slice(&Some(locator), Bias::Left); - new_excerpts.append(prefix, ()); - - let mut excerpt = cursor.item().unwrap().clone(); - let old_text_len = excerpt.text_summary.len; - - let up_line_count = if direction.should_expand_up() { - line_count - } else { - 0 - }; - - let start_row = excerpt - .range - .context - .start - .to_point(&excerpt.buffer) - .row - .saturating_sub(up_line_count); - let start_point = Point::new(start_row, 0); - excerpt.range.context.start = excerpt.buffer.anchor_before(start_point); - - let down_line_count = if direction.should_expand_down() { - line_count - } else { - 0 - }; - - let mut end_point = excerpt.buffer.clip_point( - excerpt.range.context.end.to_point(&excerpt.buffer) - + Point::new(down_line_count, 0), - Bias::Left, - ); - end_point.column = excerpt.buffer.line_len(end_point.row); - excerpt.range.context.end = excerpt.buffer.anchor_after(end_point); - excerpt.max_buffer_row = end_point.row; - - excerpt.text_summary = excerpt - .buffer - .text_summary_for_range(excerpt.range.context.clone()); - - let new_start_offset = ExcerptDimension(new_excerpts.summary().text.len); - let old_start_offset = cursor.start().1; - let new_text_len = excerpt.text_summary.len; - let edit = Edit { - old: old_start_offset..old_start_offset + old_text_len, - new: new_start_offset..new_start_offset + new_text_len, - }; - - if let Some(last_edit) = edits.last_mut() { - if last_edit.old.end == edit.old.start { - last_edit.old.end = edit.old.end; - last_edit.new.end = edit.new.end; - } else { - edits.push(edit); - } - } else { - edits.push(edit); - } - - new_excerpts.push(excerpt, ()); - - cursor.next(); - } - - new_excerpts.append(cursor.suffix(), ()); - - drop(cursor); - snapshot.excerpts = new_excerpts; - - let edits = Self::sync_diff_transforms(&mut snapshot, edits, DiffChangeKind::BufferEdited); - if !edits.is_empty() { - self.subscriptions.publish(edits); - } - cx.emit(Event::Edited { - edited_buffer: None, - is_local: true, - }); - cx.emit(Event::ExcerptsExpanded { ids }); - cx.notify(); - } - #[ztracing::instrument(skip_all)] fn sync(&self, cx: &App) { let changed = self.buffer_changed_since_sync.replace(false); @@ -3162,17 +2504,19 @@ impl MultiBuffer { } } - fn sync_mut(&mut self, cx: &App) { + fn sync_mut(&mut self, cx: &App) -> &mut MultiBufferSnapshot { + let snapshot = self.snapshot.get_mut(); let changed = self.buffer_changed_since_sync.replace(false); if !changed { - return; + return snapshot; } - let edits = - Self::sync_from_buffer_changes(self.snapshot.get_mut(), &self.buffers, &self.diffs, cx); + let edits = Self::sync_from_buffer_changes(snapshot, &self.buffers, &self.diffs, cx); if !edits.is_empty() { self.subscriptions.publish(edits); } + + snapshot } fn sync_from_buffer_changes( @@ -3183,8 +2527,10 @@ impl MultiBuffer { ) -> Vec> { let MultiBufferSnapshot { excerpts, - buffer_locators: _, diffs: buffer_diff, + buffers: buffer_snapshots, + path_keys_by_index: _, + indices_by_path_key: _, diff_transforms: _, non_text_state_update_count, edit_count, @@ -3193,8 +2539,6 @@ impl MultiBuffer { has_conflict, has_inverted_diff: _, singleton: _, - excerpt_ids: _, - replaced_excerpts: _, trailing_excerpt_update_count: _, all_diff_hunks_expanded: _, show_deleted_hunks: _, @@ -3227,28 +2571,32 @@ impl MultiBuffer { buffer_diff.edit(diffs_to_add, ()); } - let mut excerpts_to_edit = Vec::new(); + let mut paths_to_edit = Vec::new(); let mut non_text_state_updated = false; let mut edited = false; for buffer_state in buffers.values() { let buffer = buffer_state.buffer.read(cx); - let version = buffer.version(); + let last_snapshot = buffer_snapshots + .get(&buffer.remote_id()) + .expect("each buffer should have a snapshot"); + let current_version = buffer.version(); let non_text_state_update_count = buffer.non_text_state_update_count(); - let buffer_edited = version.changed_since(&buffer_state.last_version.borrow()); - let buffer_non_text_state_updated = - non_text_state_update_count > buffer_state.last_non_text_state_update_count.get(); + let buffer_edited = + current_version.changed_since(last_snapshot.buffer_snapshot.version()); + let buffer_non_text_state_updated = non_text_state_update_count + > last_snapshot.buffer_snapshot.non_text_state_update_count(); if buffer_edited || buffer_non_text_state_updated { - *buffer_state.last_version.borrow_mut() = version; - buffer_state - .last_non_text_state_update_count - .set(non_text_state_update_count); - excerpts_to_edit.extend( - buffer_state - .excerpts - .iter() - .map(|locator| (locator, buffer_state.buffer.clone(), buffer_edited)), - ); + paths_to_edit.push(( + last_snapshot.path_key.clone(), + last_snapshot.path_key_index, + buffer_state.buffer.clone(), + if buffer_edited { + Some(last_snapshot.buffer_snapshot.version().clone()) + } else { + None + }, + )); } edited |= buffer_edited; @@ -3266,55 +2614,64 @@ impl MultiBuffer { *non_text_state_update_count += 1; } - excerpts_to_edit.sort_unstable_by_key(|&(locator, _, _)| locator); + paths_to_edit.sort_unstable_by_key(|(path, _, _, _)| path.clone()); let mut edits = Vec::new(); let mut new_excerpts = SumTree::default(); - let mut cursor = excerpts.cursor::, ExcerptOffset>>(()); + let mut cursor = excerpts.cursor::(()); - for (locator, buffer, buffer_edited) in excerpts_to_edit { - new_excerpts.append(cursor.slice(&Some(locator), Bias::Left), ()); - let old_excerpt = cursor.item().unwrap(); + for (path, path_key_index, buffer, prev_version) in paths_to_edit { + new_excerpts.append(cursor.slice(&path, Bias::Left), ()); let buffer = buffer.read(cx); let buffer_id = buffer.remote_id(); - let mut new_excerpt; - if buffer_edited { - edits.extend( - buffer - .edits_since_in_range::( - old_excerpt.buffer.version(), - old_excerpt.range.context.clone(), - ) - .map(|edit| { - let excerpt_old_start = cursor.start().1; - let excerpt_new_start = - ExcerptDimension(new_excerpts.summary().text.len); - let old_start = excerpt_old_start + edit.old.start; - let old_end = excerpt_old_start + edit.old.end; - let new_start = excerpt_new_start + edit.new.start; - let new_end = excerpt_new_start + edit.new.end; - Edit { - old: old_start..old_end, - new: new_start..new_end, - } - }), - ); - new_excerpt = Excerpt::new( - old_excerpt.id, - locator.clone(), - buffer_id, - Arc::new(buffer.snapshot()), - old_excerpt.range.clone(), - old_excerpt.has_trailing_newline, - ); - } else { - new_excerpt = old_excerpt.clone(); - new_excerpt.buffer = Arc::new(buffer.snapshot()); - } + buffer_snapshots.insert( + buffer_id, + BufferStateSnapshot { + path_key: path.clone(), + path_key_index, + buffer_snapshot: buffer.snapshot(), + }, + ); - new_excerpts.push(new_excerpt, ()); - cursor.next(); + if let Some(prev_version) = &prev_version { + while let Some(old_excerpt) = cursor.item() + && &old_excerpt.path_key == &path + { + edits.extend( + buffer + .edits_since_in_range::( + prev_version, + old_excerpt.range.context.clone(), + ) + .map(|edit| { + let excerpt_old_start = cursor.start().len(); + let excerpt_new_start = + ExcerptDimension(new_excerpts.summary().text.len); + let old_start = excerpt_old_start + edit.old.start; + let old_end = excerpt_old_start + edit.old.end; + let new_start = excerpt_new_start + edit.new.start; + let new_end = excerpt_new_start + edit.new.end; + Edit { + old: old_start..old_end, + new: new_start..new_end, + } + }), + ); + + let excerpt = Excerpt::new( + old_excerpt.path_key.clone(), + old_excerpt.path_key_index, + &buffer.snapshot(), + old_excerpt.range.clone(), + old_excerpt.has_trailing_newline, + ); + new_excerpts.push(excerpt, ()); + cursor.next(); + } + } else { + new_excerpts.append(cursor.slice(&path, Bias::Right), ()); + }; } new_excerpts.append(cursor.suffix(), ()); @@ -3416,7 +2773,8 @@ impl MultiBuffer { { return true; } - hunk.hunk_start_anchor.is_valid(&excerpt.buffer) + hunk.hunk_start_anchor + .is_valid(&excerpt.buffer_snapshot(&snapshot)) }), _ => true, }; @@ -3513,10 +2871,10 @@ impl MultiBuffer { // Recompute the expanded hunks in the portion of the excerpt that // intersects the edit. if let Some(diff) = find_diff_state(&snapshot.diffs, excerpt.buffer_id) { - let buffer = &excerpt.buffer; + let buffer_snapshot = &excerpt.buffer_snapshot(&snapshot); let excerpt_start = *excerpts.start(); let excerpt_end = excerpt_start + excerpt.text_summary.len; - let excerpt_buffer_start = excerpt.range.context.start.to_offset(buffer); + let excerpt_buffer_start = excerpt.range.context.start.to_offset(buffer_snapshot); let excerpt_buffer_end = excerpt_buffer_start + excerpt.text_summary.len; let edit_buffer_start = excerpt_buffer_start + edit.new.start.saturating_sub(excerpt_start); @@ -3535,7 +2893,6 @@ impl MultiBuffer { log::trace!("skipping hunk that starts before excerpt"); continue; } - hunk_buffer_range.end.to_point(&excerpt.buffer); let hunk_excerpt_start = excerpt_start + hunk_buffer_range.start.saturating_sub(excerpt_buffer_start); let hunk_excerpt_end = excerpt_end @@ -3548,9 +2905,10 @@ impl MultiBuffer { ); if !hunk_buffer_range.is_empty() { let hunk_info = DiffTransformHunkInfo { - excerpt_id: excerpt.id, + buffer_id: buffer_snapshot.remote_id(), hunk_start_anchor: hunk.buffer_range.start, hunk_secondary_status: hunk.secondary_status, + excerpt_end: excerpt.end_anchor(), is_logically_deleted: true, }; *end_of_current_insert = @@ -3558,23 +2916,24 @@ impl MultiBuffer { } } } else { - let edit_anchor_range = buffer.anchor_before(edit_buffer_start) - ..buffer.anchor_after(edit_buffer_end); - for hunk in diff.hunks_intersecting_range(edit_anchor_range, buffer) { + let edit_anchor_range = buffer_snapshot.anchor_before(edit_buffer_start) + ..buffer_snapshot.anchor_after(edit_buffer_end); + for hunk in diff.hunks_intersecting_range(edit_anchor_range, buffer_snapshot) { if hunk.is_created_file() && !all_diff_hunks_expanded { continue; } - let hunk_buffer_range = hunk.buffer_range.to_offset(buffer); + let hunk_buffer_range = hunk.buffer_range.to_offset(buffer_snapshot); if hunk_buffer_range.start < excerpt_buffer_start { log::trace!("skipping hunk that starts before excerpt"); continue; } let hunk_info = DiffTransformHunkInfo { - excerpt_id: excerpt.id, + buffer_id: buffer_snapshot.remote_id(), hunk_start_anchor: hunk.buffer_range.start, hunk_secondary_status: hunk.secondary_status, + excerpt_end: excerpt.end_anchor(), is_logically_deleted: false, }; @@ -3599,7 +2958,7 @@ impl MultiBuffer { } DiffChangeKind::ExpandOrCollapseHunks { expand } => { let intersects = hunk_buffer_range.is_empty() - || hunk_buffer_range.end > edit_buffer_start; + || (hunk_buffer_range.end > edit_buffer_start); if *expand { intersects || was_previously_expanded || all_diff_hunks_expanded } else { @@ -3613,9 +2972,8 @@ impl MultiBuffer { if should_expand_hunk { did_expand_hunks = true; log::trace!( - "expanding hunk {:?}, excerpt:{:?}", + "expanding hunk {:?}", hunk_excerpt_start..hunk_excerpt_end, - excerpt.id ); if !hunk.diff_base_byte_range.is_empty() @@ -3639,7 +2997,7 @@ impl MultiBuffer { DiffTransform::DeletedHunk { base_text_byte_range: hunk.diff_base_byte_range.clone(), summary: base_text_summary, - buffer_id: excerpt.buffer_id, + buffer_id: buffer_snapshot.remote_id(), hunk_info, has_trailing_newline, }, @@ -3766,11 +3124,13 @@ impl MultiBuffer { pub fn toggle_single_diff_hunk(&mut self, range: Range, cx: &mut Context) { let snapshot = self.snapshot(cx); - let excerpt_id = range.end.excerpt_id; + let excerpt_end = snapshot + .excerpt_containing(range.end..range.end) + .and_then(|(_, excerpt_range)| snapshot.anchor_in_excerpt(excerpt_range.context.end)); let point_range = range.to_point(&snapshot); let expand = !self.single_hunk_is_expanded(range, cx); let edits = - self.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx); + self.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_end)], expand, cx); if !edits.is_empty() { self.subscriptions.publish(edits); } @@ -3896,38 +3256,15 @@ impl MultiBuffer { use std::env; use util::RandomCharIter; - let max_excerpts = env::var("MAX_EXCERPTS") + let max_buffers = env::var("MAX_BUFFERS") .map(|i| i.parse().expect("invalid `MAX_EXCERPTS` variable")) .unwrap_or(5); let mut buffers = Vec::new(); for _ in 0..mutation_count { - if rng.random_bool(0.05) { - log::info!("Clearing multi-buffer"); - self.clear(cx); - continue; - } else if rng.random_bool(0.1) && !self.excerpt_ids().is_empty() { - let ids = self.excerpt_ids(); - let mut excerpts = HashSet::default(); - for _ in 0..rng.random_range(0..ids.len()) { - excerpts.extend(ids.choose(rng).copied()); - } - - let line_count = rng.random_range(0..5); - - log::info!("Expanding excerpts {excerpts:?} by {line_count} lines"); - - self.expand_excerpts( - excerpts.iter().cloned(), - line_count, - ExpandExcerptDirection::UpAndDown, - cx, - ); - continue; - } - - let excerpt_ids = self.excerpt_ids(); - if excerpt_ids.is_empty() || (rng.random() && excerpt_ids.len() < max_excerpts) { + let snapshot = self.snapshot(cx); + let buffer_ids = snapshot.all_buffer_ids().collect::>(); + if buffer_ids.is_empty() || (rng.random() && buffer_ids.len() < max_buffers) { let buffer_handle = if rng.random() || self.buffers.is_empty() { let text = RandomCharIter::new(&mut *rng).take(10).collect::(); buffers.push(cx.new(|cx| Buffer::local(text, cx))); @@ -3944,12 +3281,21 @@ impl MultiBuffer { let buffer = buffer_handle.read(cx); let buffer_text = buffer.text(); + let buffer_snapshot = buffer.snapshot(); + let mut next_min_start_ix = 0; let ranges = (0..rng.random_range(0..5)) - .map(|_| { - let end_ix = - buffer.clip_offset(rng.random_range(0..=buffer.len()), Bias::Right); - let start_ix = buffer.clip_offset(rng.random_range(0..=end_ix), Bias::Left); - ExcerptRange::new(start_ix..end_ix) + .filter_map(|_| { + if next_min_start_ix >= buffer.len() { + return None; + } + let end_ix = buffer.clip_offset( + rng.random_range(next_min_start_ix..=buffer.len()), + Bias::Right, + ); + let start_ix = buffer + .clip_offset(rng.random_range(next_min_start_ix..=end_ix), Bias::Left); + next_min_start_ix = buffer.text().ceil_char_boundary(end_ix + 1); + Some(ExcerptRange::new(start_ix..end_ix)) }) .collect::>(); log::info!( @@ -3961,21 +3307,27 @@ impl MultiBuffer { .map(|r| &buffer_text[r.context.clone()]) .collect::>() ); - - let excerpt_id = - self.insert_excerpts_after(ExcerptId::max(), buffer_handle, ranges, cx); - log::info!("Inserted with ids: {:?}", excerpt_id); + + let path_key = PathKey::for_buffer(&buffer_handle, cx); + self.set_merged_excerpt_ranges_for_path( + path_key.clone(), + buffer_handle, + &buffer_snapshot, + ranges, + cx, + ); + log::info!("Inserted with path_key: {:?}", path_key); } else { - let remove_count = rng.random_range(1..=excerpt_ids.len()); - let mut excerpts_to_remove = excerpt_ids - .choose_multiple(rng, remove_count) - .cloned() - .collect::>(); - let snapshot = self.snapshot.borrow(); - excerpts_to_remove.sort_unstable_by(|a, b| a.cmp(b, &snapshot)); - drop(snapshot); - log::info!("Removing excerpts {:?}", excerpts_to_remove); - self.remove_excerpts(excerpts_to_remove, cx); + let path_key = self + .snapshot + .borrow() + .buffers + .get(&buffer_ids.choose(rng).unwrap()) + .unwrap() + .path_key + .clone(); + log::info!("Removing excerpts {:?}", path_key); + self.remove_excerpts(path_key, cx); } } } @@ -4083,7 +3435,7 @@ impl MultiBufferSnapshot { } pub fn diff_hunks(&self) -> impl Iterator + '_ { - self.diff_hunks_in_range(Anchor::min()..Anchor::max()) + self.diff_hunks_in_range(Anchor::Min..Anchor::Max) } pub fn diff_hunks_in_range( @@ -4121,6 +3473,7 @@ impl MultiBufferSnapshot { })) }) .filter_map(move |(range, (hunk, is_inverted), excerpt)| { + let buffer_snapshot = excerpt.buffer_snapshot(self); if range.start != range.end && range.end == query_range.start && !hunk.range.is_empty() { return None; @@ -4139,12 +3492,12 @@ impl MultiBufferSnapshot { if self.show_deleted_hunks || is_inverted { let hunk_start_offset = if is_inverted { Anchor::in_buffer( - excerpt.id, - excerpt.buffer.anchor_after(hunk.diff_base_byte_range.start), + excerpt.path_key_index, + buffer_snapshot.anchor_after(hunk.diff_base_byte_range.start), ) .to_offset(self) } else { - Anchor::in_buffer(excerpt.id, hunk.buffer_range.start) + Anchor::in_buffer(excerpt.path_key_index, hunk.buffer_range.start) .to_offset(self) }; @@ -4155,7 +3508,8 @@ impl MultiBufferSnapshot { if !is_inverted { word_diffs.extend(hunk.buffer_word_diffs.into_iter().map(|diff| { - Anchor::range_in_buffer(excerpt.id, diff).to_offset(self) + Anchor::range_in_buffer(excerpt.path_key_index, diff) + .to_offset(self) })); } word_diffs @@ -4163,8 +3517,8 @@ impl MultiBufferSnapshot { .unwrap_or_default(); let buffer_range = if is_inverted { - excerpt.buffer.anchor_after(hunk.diff_base_byte_range.start) - ..excerpt.buffer.anchor_before(hunk.diff_base_byte_range.end) + buffer_snapshot.anchor_after(hunk.diff_base_byte_range.start) + ..buffer_snapshot.anchor_before(hunk.diff_base_byte_range.end) } else { hunk.buffer_range.clone() }; @@ -4175,10 +3529,11 @@ impl MultiBufferSnapshot { } else { DiffHunkStatusKind::Modified }; + let multi_buffer_range = + Anchor::range_in_buffer(excerpt.path_key_index, buffer_range.clone()); Some(MultiBufferDiffHunk { row_range: MultiBufferRow(range.start.row)..MultiBufferRow(end_row), - buffer_id: excerpt.buffer_id, - excerpt_id: excerpt.id, + buffer_id: buffer_snapshot.remote_id(), buffer_range, word_diffs, diff_base_byte_range: BufferOffset(hunk.diff_base_byte_range.start) @@ -4187,6 +3542,8 @@ impl MultiBufferSnapshot { kind: status_kind, secondary: hunk.secondary_status, }, + excerpt_range: excerpt.range.clone(), + multi_buffer_range, }) }) } @@ -4211,19 +3568,12 @@ impl MultiBufferSnapshot { }) } - pub fn excerpt_ids_for_range( - &self, - range: Range, - ) -> impl Iterator + '_ { - self.excerpts_for_range(range).map(|excerpt| excerpt.id) - } - pub fn buffer_ids_for_range( &self, range: Range, ) -> impl Iterator + '_ { self.excerpts_for_range(range) - .map(|excerpt| excerpt.buffer_id) + .map(|excerpt| excerpt.buffer_snapshot(self).remote_id()) } /// Resolves the given [`text::Anchor`]s to [`crate::Anchor`]s if the anchor is within a visible excerpt. @@ -4236,69 +3586,66 @@ impl MultiBufferSnapshot { let anchors = anchors.into_iter(); let mut result = Vec::with_capacity(anchors.size_hint().0); let mut anchors = anchors.peekable(); - let mut cursor = self.excerpts.cursor::>(()); + let mut cursor = self.excerpts.cursor::(()); 'anchors: while let Some(anchor) = anchors.peek() { - let Some(buffer_id) = anchor.buffer_id else { - anchors.next(); - result.push(None); - continue 'anchors; - }; - let mut same_buffer_anchors = - anchors.peeking_take_while(|a| a.buffer_id.is_some_and(|b| buffer_id == b)); + let buffer_id = anchor.buffer_id; + let mut same_buffer_anchors = anchors.peeking_take_while(|a| a.buffer_id == buffer_id); - if let Some(locators) = self.buffer_locators.get(&buffer_id) { + if let Some(buffer) = self.buffers.get(&buffer_id) { + let path = &buffer.path_key; let Some(mut next) = same_buffer_anchors.next() else { continue 'anchors; }; - 'excerpts: for locator in locators.iter() { - if cursor.seek_forward(&Some(locator), Bias::Left) - && let Some(excerpt) = cursor.item() - { - loop { - // anchor is before the first excerpt - if excerpt - .range - .context - .start - .cmp(&next, &excerpt.buffer) - .is_gt() - { - // so we skip it and try the next anchor - result.push(None); - match same_buffer_anchors.next() { - Some(anchor) => next = anchor, - None => continue 'anchors, - } - // anchor is within the excerpt - } else if excerpt - .range - .context - .end - .cmp(&next, &excerpt.buffer) - .is_ge() - { - // record it and all following anchors that are within - result.push(Some(Anchor::in_buffer(excerpt.id, next))); - result.extend( - same_buffer_anchors - .peeking_take_while(|a| { - excerpt - .range - .context - .end - .cmp(a, &excerpt.buffer) - .is_ge() - }) - .map(|a| Some(Anchor::in_buffer(excerpt.id, a))), - ); - match same_buffer_anchors.next() { - Some(anchor) => next = anchor, - None => continue 'anchors, - } - // anchor is after the excerpt, try the next one - } else { - continue 'excerpts; + cursor.seek_forward(path, Bias::Left); + 'excerpts: loop { + let Some(excerpt) = cursor.item() else { + break; + }; + if &excerpt.path_key != path { + break; + } + let buffer_snapshot = excerpt.buffer_snapshot(self); + + loop { + // anchor is before the first excerpt + if excerpt + .range + .context + .start + .cmp(&next, &buffer_snapshot) + .is_gt() + { + // so we skip it and try the next anchor + result.push(None); + match same_buffer_anchors.next() { + Some(anchor) => next = anchor, + None => continue 'anchors, + } + // anchor is within the excerpt + } else if excerpt + .range + .context + .end + .cmp(&next, &buffer_snapshot) + .is_ge() + { + // record it and all following anchors that are within + result.push(Some(Anchor::in_buffer(excerpt.path_key_index, next))); + result.extend( + same_buffer_anchors + .peeking_take_while(|a| { + excerpt.range.context.end.cmp(a, &buffer_snapshot).is_ge() + }) + .map(|a| Some(Anchor::in_buffer(excerpt.path_key_index, a))), + ); + match same_buffer_anchors.next() { + Some(anchor) => next = anchor, + None => continue 'anchors, } + // anchor is after the excerpt, try the next one + } else { + cursor.next(); + continue 'excerpts; } } } @@ -4311,79 +3658,31 @@ impl MultiBufferSnapshot { result } - pub fn ranges_to_buffer_ranges( - &self, - ranges: impl Iterator>, - ) -> impl Iterator, ExcerptId)> { - ranges.flat_map(|range| { - self.range_to_buffer_ranges((Bound::Included(range.start), Bound::Included(range.end))) - .into_iter() - }) - } - - pub fn range_to_buffer_ranges( - &self, - range: R, - ) -> Vec<(&BufferSnapshot, Range, ExcerptId)> - where - R: RangeBounds, - T: ToOffset, - { - self.range_to_buffer_ranges_with_context(range) - .into_iter() - .map(|(buffer, range, id, _context)| (buffer, range, id)) - .collect() - } - - pub fn range_to_buffer_ranges_with_context( + pub fn range_to_buffer_ranges( &self, - range: R, + range: Range, ) -> Vec<( - &BufferSnapshot, + BufferSnapshot, Range, - ExcerptId, - Range, - )> - where - R: RangeBounds, - T: ToOffset, - { - let start = match range.start_bound() { - Bound::Included(start) => start.to_offset(self), - Bound::Excluded(_) => panic!("excluded start bound not supported"), - Bound::Unbounded => MultiBufferOffset::ZERO, - }; - let end_bound = match range.end_bound() { - Bound::Included(end) => Bound::Included(end.to_offset(self)), - Bound::Excluded(end) => Bound::Excluded(end.to_offset(self)), - Bound::Unbounded => Bound::Unbounded, - }; - let bounds = (Bound::Included(start), end_bound); - + ExcerptRange, + )> { let mut cursor = self.cursor::(); + let start = range.start.to_offset(self); + let end = range.end.to_offset(self); cursor.seek(&start); let mut result: Vec<( - &BufferSnapshot, + BufferSnapshot, Range, - ExcerptId, - Range, + ExcerptRange, )> = Vec::new(); while let Some(region) = cursor.region() { - let dominated_by_end_bound = match end_bound { - Bound::Included(end) => region.range.start > end, - Bound::Excluded(end) => region.range.start >= end, - Bound::Unbounded => false, - }; - if dominated_by_end_bound { + if region.range.start >= end { break; } if region.is_main_buffer { let start_overshoot = start.saturating_sub(region.range.start); - let end_offset = match end_bound { - Bound::Included(end) | Bound::Excluded(end) => end, - Bound::Unbounded => region.range.end, - }; + let end_offset = end; let end_overshoot = end_offset.saturating_sub(region.range.start); let start = region .buffer_range @@ -4393,34 +3692,46 @@ impl MultiBufferSnapshot { .buffer_range .end .min(region.buffer_range.start + end_overshoot); - let context = region.excerpt.range.context.clone(); - if let Some(prev) = result.last_mut().filter(|(_, prev_range, excerpt_id, _)| { - *excerpt_id == region.excerpt.id && prev_range.end == start - }) { + let excerpt_range = region.excerpt.range.clone(); + if let Some(prev) = + result + .last_mut() + .filter(|(prev_buffer, prev_range, prev_excerpt)| { + prev_buffer.remote_id() == region.buffer.remote_id() + && prev_range.end == start + && prev_excerpt.context.start == excerpt_range.context.start + }) + { prev.1.end = end; } else { - result.push((region.buffer, start..end, region.excerpt.id, context)); + result.push((region.buffer.clone(), start..end, excerpt_range)); } } cursor.next(); } - if let Some(excerpt) = cursor.excerpt() { - let dominated_by_prev_excerpt = - result.last().is_some_and(|(_, _, id, _)| *id == excerpt.id); - if !dominated_by_prev_excerpt && excerpt.text_summary.len == 0 { - let excerpt_position = self.len(); - if bounds.contains(&excerpt_position) { - let buffer_offset = - BufferOffset(excerpt.range.context.start.to_offset(&excerpt.buffer)); - let context = excerpt.range.context.clone(); - result.push(( - &excerpt.buffer, - buffer_offset..buffer_offset, - excerpt.id, - context, - )); - } + if let Some(excerpt) = cursor.excerpt() + && excerpt.text_summary.len == 0 + && end == self.len() + { + let buffer_snapshot = excerpt.buffer_snapshot(self); + + let buffer_offset = + BufferOffset(excerpt.range.context.start.to_offset(buffer_snapshot)); + let excerpt_range = excerpt.range.clone(); + if result + .last_mut() + .is_none_or(|(prev_buffer, prev_range, prev_excerpt)| { + prev_buffer.remote_id() != buffer_snapshot.remote_id() + || prev_range.end != buffer_offset + || prev_excerpt.context.start != excerpt_range.context.start + }) + { + result.push(( + buffer_snapshot.clone(), + buffer_offset..buffer_offset, + excerpt_range, + )); } } @@ -4430,14 +3741,7 @@ impl MultiBufferSnapshot { pub fn range_to_buffer_ranges_with_deleted_hunks( &self, range: Range, - ) -> impl Iterator< - Item = ( - &BufferSnapshot, - Range, - ExcerptId, - Option, - ), - > + '_ { + ) -> impl Iterator, Option)> + '_ { let start = range.start.to_offset(self); let end = range.end.to_offset(self); @@ -4460,18 +3764,12 @@ impl MultiBufferSnapshot { .end .min(region.buffer_range.start + end_overshoot); - let region_excerpt_id = region.excerpt.id; let deleted_hunk_anchor = if region.is_main_buffer { None } else { Some(self.anchor_before(region.range.start)) }; - let result = ( - region.buffer, - start..end, - region_excerpt_id, - deleted_hunk_anchor, - ); + let result = (region.buffer, start..end, deleted_hunk_anchor); cursor.next(); Some(result) }) @@ -4504,7 +3802,7 @@ impl MultiBufferSnapshot { + AddAssign + Ord, { - let mut current_excerpt_metadata: Option<(ExcerptId, I)> = None; + let mut current_excerpt_metadata: Option<(ExcerptRange, I)> = None; let mut cursor = self.cursor::(); // Find the excerpt and buffer offset where the given range ends. @@ -4519,7 +3817,7 @@ impl MultiBufferSnapshot { ::default() }; buffer_end = buffer_end + overshoot; - range_end = Some((region.excerpt.id, buffer_end)); + range_end = Some((region.excerpt.range.clone(), buffer_end)); break; } cursor.next(); @@ -4544,11 +3842,12 @@ impl MultiBufferSnapshot { iter::from_fn(move || { loop { let excerpt = cursor.excerpt()?; + let buffer_snapshot = excerpt.buffer_snapshot(self); // If we have already retrieved metadata for this excerpt, continue to use it. let metadata_iter = if let Some((_, metadata)) = current_excerpt_metadata .as_mut() - .filter(|(excerpt_id, _)| *excerpt_id == excerpt.id) + .filter(|(excerpt_info, _)| excerpt_info == &excerpt.range) { Some(metadata) } @@ -4571,16 +3870,20 @@ impl MultiBufferSnapshot { .range .context .end - .summary::(&excerpt.buffer); - if let Some((end_excerpt_id, end_buffer_offset)) = range_end - && excerpt.id == end_excerpt_id + .summary::(&buffer_snapshot); + if let Some((end_excerpt, end_buffer_offset)) = &range_end + && &excerpt.range == end_excerpt { - buffer_end = buffer_end.min(end_buffer_offset); + buffer_end = buffer_end.min(*end_buffer_offset); } - get_buffer_metadata(&excerpt.buffer, buffer_start..buffer_end).map(|iterator| { - &mut current_excerpt_metadata.insert((excerpt.id, iterator)).1 - }) + get_buffer_metadata(&buffer_snapshot, buffer_start..buffer_end).map( + |iterator| { + &mut current_excerpt_metadata + .insert((excerpt.range.clone(), iterator)) + .1 + }, + ) }; // Visit each metadata item. @@ -4644,8 +3947,8 @@ impl MultiBufferSnapshot { // When there are no more metadata items for this excerpt, move to the next excerpt. else { current_excerpt_metadata.take(); - if let Some((end_excerpt_id, _)) = range_end - && excerpt.id == end_excerpt_id + if let Some((end_excerpt, _)) = &range_end + && &excerpt.range == end_excerpt { return None; } @@ -4668,12 +3971,14 @@ impl MultiBufferSnapshot { cursor.seek_to_start_of_current_excerpt(); let excerpt = cursor.excerpt()?; - let excerpt_start = excerpt.range.context.start.to_offset(&excerpt.buffer); - let excerpt_end = excerpt.range.context.end.to_offset(&excerpt.buffer); - let current_position = self - .anchor_before(offset) - .text_anchor - .to_offset(&excerpt.buffer); + let buffer = excerpt.buffer_snapshot(self); + let excerpt_start = excerpt.range.context.start.to_offset(buffer); + let excerpt_end = excerpt.range.context.end.to_offset(buffer); + let current_position = match self.anchor_before(offset) { + Anchor::Min => 0, + Anchor::Excerpt(excerpt_anchor) => excerpt_anchor.text_anchor().to_offset(buffer), + Anchor::Max => unreachable!(), + }; if let Some(diff) = self.diff_state(excerpt.buffer_id) { if let Some(main_buffer) = &diff.main_buffer { @@ -4683,24 +3988,22 @@ impl MultiBufferSnapshot { if hunk.diff_base_byte_range.end >= current_position { continue; } - let hunk_start = excerpt.buffer.anchor_after(hunk.diff_base_byte_range.start); - let start = Anchor::in_buffer(excerpt.id, hunk_start).to_point(self); + let hunk_start = buffer.anchor_after(hunk.diff_base_byte_range.start); + let start = + Anchor::in_buffer(excerpt.path_key_index, hunk_start).to_point(self); return Some(MultiBufferRow(start.row)); } } else { - let excerpt_end = excerpt - .buffer - .anchor_before(excerpt_end.min(current_position)); - for hunk in diff.hunks_intersecting_range_rev( - excerpt.range.context.start..excerpt_end, - &excerpt.buffer, - ) { - let hunk_end = hunk.buffer_range.end.to_offset(&excerpt.buffer); + let excerpt_end = buffer.anchor_before(excerpt_end.min(current_position)); + for hunk in diff + .hunks_intersecting_range_rev(excerpt.range.context.start..excerpt_end, buffer) + { + let hunk_end = hunk.buffer_range.end.to_offset(buffer); if hunk_end >= current_position { continue; } - let start = - Anchor::in_buffer(excerpt.id, hunk.buffer_range.start).to_point(self); + let start = Anchor::in_buffer(excerpt.path_key_index, hunk.buffer_range.start) + .to_point(self); return Some(MultiBufferRow(start.row)); } } @@ -4709,6 +4012,7 @@ impl MultiBufferSnapshot { loop { cursor.prev_excerpt(); let excerpt = cursor.excerpt()?; + let buffer = excerpt.buffer_snapshot(self); let Some(diff) = self.diff_state(excerpt.buffer_id) else { continue; @@ -4716,24 +4020,25 @@ impl MultiBufferSnapshot { if let Some(main_buffer) = &diff.main_buffer { let Some(hunk) = diff .hunks_intersecting_base_text_range_rev( - excerpt.range.context.to_offset(&excerpt.buffer), + excerpt.range.context.to_offset(buffer), main_buffer, ) .next() else { continue; }; - let hunk_start = excerpt.buffer.anchor_after(hunk.diff_base_byte_range.start); - let start = Anchor::in_buffer(excerpt.id, hunk_start).to_point(self); + let hunk_start = buffer.anchor_after(hunk.diff_base_byte_range.start); + let start = Anchor::in_buffer(excerpt.path_key_index, hunk_start).to_point(self); return Some(MultiBufferRow(start.row)); } else { let Some(hunk) = diff - .hunks_intersecting_range_rev(excerpt.range.context.clone(), &excerpt.buffer) + .hunks_intersecting_range_rev(excerpt.range.context.clone(), buffer) .next() else { continue; }; - let start = Anchor::in_buffer(excerpt.id, hunk.buffer_range.start).to_point(self); + let start = Anchor::in_buffer(excerpt.path_key_index, hunk.buffer_range.start) + .to_point(self); return Some(MultiBufferRow(start.row)); } } @@ -4808,16 +4113,17 @@ impl MultiBufferSnapshot { .map(|ch| classifier.kind(ch)) } + pub fn all_buffer_ids(&self) -> impl Iterator + '_ { + self.buffers.iter().map(|(id, _)| *id) + } + pub fn is_singleton(&self) -> bool { self.singleton } - pub fn as_singleton(&self) -> Option<(ExcerptId, BufferId, &BufferSnapshot)> { - if self.singleton { - self.excerpts - .iter() - .next() - .map(|e| (e.id, e.buffer_id, &*e.buffer)) + pub fn as_singleton(&self) -> Option<&BufferSnapshot> { + if self.is_singleton() { + Some(self.excerpts.first()?.buffer_snapshot(&self)) } else { None } @@ -4914,11 +4220,11 @@ impl MultiBufferSnapshot { range: MultiBufferOffset::ZERO..MultiBufferOffset::ZERO, excerpts: self.excerpts.cursor(()), diff_transforms: self.diff_transforms.cursor(()), - diffs: &self.diffs, diff_base_chunks: None, excerpt_chunks: None, buffer_chunk: None, language_aware, + snapshot: self, }; let range = range.start.to_offset(self)..range.end.to_offset(self); chunks.seek(range); @@ -5065,7 +4371,7 @@ impl MultiBufferSnapshot { && !region.is_main_buffer { let main_buffer_position = cursor.main_buffer_position()?; - let buffer_snapshot = &cursor.excerpt()?.buffer; + let buffer_snapshot = cursor.excerpt()?.buffer_snapshot(self); return Some((buffer_snapshot, main_buffer_position)); } else if buffer_offset > BufferOffset(region.buffer.len()) { return None; @@ -5073,10 +4379,7 @@ impl MultiBufferSnapshot { Some((region.buffer, buffer_offset)) } - pub fn point_to_buffer_point( - &self, - point: Point, - ) -> Option<(&BufferSnapshot, Point, ExcerptId)> { + pub fn point_to_buffer_point(&self, point: Point) -> Option<(&BufferSnapshot, Point)> { let mut cursor = self.cursor::(); cursor.seek(&point); let region = cursor.region()?; @@ -5087,11 +4390,14 @@ impl MultiBufferSnapshot { && region.has_trailing_newline && !region.is_main_buffer { - return Some((&excerpt.buffer, cursor.main_buffer_position()?, excerpt.id)); + return Some(( + &excerpt.buffer_snapshot(self), + cursor.main_buffer_position()?, + )); } else if buffer_point > region.buffer.max_point() { return None; } - Some((region.buffer, buffer_point, excerpt.id)) + Some((region.buffer, buffer_point)) } pub fn suggested_indents( @@ -5408,21 +4714,20 @@ impl MultiBufferSnapshot { let mut cursor = self.excerpts.cursor::(()); cursor.seek(&range.start, Bias::Right); if let Some(excerpt) = cursor.item() { + let buffer_snapshot = excerpt.buffer_snapshot(self); let mut end_before_newline = cursor.end(); if excerpt.has_trailing_newline { end_before_newline -= 1; } - let excerpt_start = excerpt.range.context.start.to_offset(&excerpt.buffer); + let excerpt_start = excerpt.range.context.start.to_offset(&buffer_snapshot); let start_in_excerpt = excerpt_start + (range.start - *cursor.start()); let end_in_excerpt = excerpt_start + (cmp::min(end_before_newline, range.end) - *cursor.start()); summary.add_text_dim( - &excerpt - .buffer - .text_summary_for_range::( - start_in_excerpt..end_in_excerpt, - ), + &buffer_snapshot.text_summary_for_range::( + start_in_excerpt..end_in_excerpt, + ), ); if range.end > end_before_newline { @@ -5437,16 +4742,15 @@ impl MultiBufferSnapshot { .summary::<_, ExcerptDimension>(&range.end, Bias::Right) .0; if let Some(excerpt) = cursor.item() { + let buffer_snapshot = excerpt.buffer_snapshot(self); range.end = cmp::max(*cursor.start(), range.end); - let excerpt_start = excerpt.range.context.start.to_offset(&excerpt.buffer); + let excerpt_start = excerpt.range.context.start.to_offset(&buffer_snapshot); let end_in_excerpt = excerpt_start + (range.end - *cursor.start()); summary.add_text_dim( - &excerpt - .buffer - .text_summary_for_range::( - excerpt_start..end_in_excerpt, - ), + &buffer_snapshot.text_summary_for_range::( + excerpt_start..end_in_excerpt, + ), ); } } @@ -5464,38 +4768,42 @@ impl MultiBufferSnapshot { + Add, MBD::TextDimension: Sub + Ord, { - let excerpt_id = self.latest_excerpt_id(anchor.excerpt_id); - let locator = self.excerpt_locator_for_id(excerpt_id); - let (start, _, mut item) = self - .excerpts - .find::((), locator, Bias::Left); - let mut start = MBD::from_summary(&start.text); - if item.is_none() && excerpt_id == ExcerptId::max() { - item = self.excerpts.last(); - if let Some(last_summary) = self.excerpts.last_summary() { - start = start - ::from_text_summary(&last_summary.text.into()); + let target = anchor.seek_target(self); + let anchor = match anchor { + Anchor::Min => { + return MBD::default(); } - } + Anchor::Excerpt(excerpt_anchor) => excerpt_anchor, + Anchor::Max => { + return MBD::from_summary(&self.text_summary()); + } + }; + + let (start, _, item) = self + .excerpts + .find::((), &target, Bias::Left); + let start = MBD::from_summary(&start.text); let excerpt_start_position = ExcerptDimension(start); if self.diff_transforms.is_empty() { if let Some(excerpt) = item { - if excerpt.id != excerpt_id && excerpt_id != ExcerptId::max() { + if !excerpt.contains(anchor, self) { return excerpt_start_position.0; } + let buffer_snapshot = excerpt.buffer_snapshot(self); let excerpt_buffer_start = excerpt .range .context .start - .summary::(&excerpt.buffer); + .summary::(&buffer_snapshot); let excerpt_buffer_end = excerpt .range .context .end - .summary::(&excerpt.buffer); + .summary::(&buffer_snapshot); let buffer_summary = anchor - .text_anchor - .summary::(&excerpt.buffer); + .text_anchor() + .summary::(&buffer_snapshot); let summary = cmp::min(excerpt_buffer_end, buffer_summary); let mut position = excerpt_start_position; if summary > excerpt_buffer_start { @@ -5513,26 +4821,27 @@ impl MultiBufferSnapshot { diff_transforms_cursor.next(); if let Some(excerpt) = item { - if excerpt.id != excerpt_id && excerpt_id != ExcerptId::max() { - return self.resolve_summary_for_min_or_max_anchor( - &Anchor::min(), + if !excerpt.contains(anchor, self) { + return self.summary_for_excerpt_position_without_hunks( + Bias::Left, excerpt_start_position, &mut diff_transforms_cursor, ); } + let buffer_snapshot = excerpt.buffer_snapshot(self); let excerpt_buffer_start = excerpt .range .context .start - .summary::(&excerpt.buffer); + .summary::(&buffer_snapshot); let excerpt_buffer_end = excerpt .range .context .end - .summary::(&excerpt.buffer); + .summary::(&buffer_snapshot); let buffer_summary = anchor - .text_anchor - .summary::(&excerpt.buffer); + .text_anchor() + .summary::(&buffer_snapshot); let summary = cmp::min(excerpt_buffer_end, buffer_summary); let mut position = excerpt_start_position; if summary > excerpt_buffer_start { @@ -5542,16 +4851,16 @@ impl MultiBufferSnapshot { if diff_transforms_cursor.start().0 < position { diff_transforms_cursor.seek_forward(&position, Bias::Left); } - self.resolve_summary_for_anchor( - &anchor, + self.summary_for_anchor_with_excerpt_position( + *anchor, position, &mut diff_transforms_cursor, - &excerpt.buffer, + &buffer_snapshot, ) } else { diff_transforms_cursor.seek_forward(&excerpt_start_position, Bias::Left); - self.resolve_summary_for_min_or_max_anchor( - &Anchor::max(), + self.summary_for_excerpt_position_without_hunks( + Bias::Right, excerpt_start_position, &mut diff_transforms_cursor, ) @@ -5562,9 +4871,9 @@ impl MultiBufferSnapshot { /// Maps an anchor's excerpt-space position to its output-space position by /// walking the diff transforms. The cursor is shared across consecutive /// calls, so it may already be partway through the transform list. - fn resolve_summary_for_anchor( + fn summary_for_anchor_with_excerpt_position( &self, - anchor: &Anchor, + anchor: ExcerptAnchor, excerpt_position: ExcerptDimension, diff_transforms: &mut Cursor< DiffTransform, @@ -5595,7 +4904,7 @@ impl MultiBufferSnapshot { hunk_info, .. }) => { - if let Some(diff_base_anchor) = &anchor.diff_base_anchor + if let Some(diff_base_anchor) = anchor.diff_base_anchor && let Some(base_text) = self.diff_state(*buffer_id).map(|diff| diff.base_text()) && diff_base_anchor.is_valid(&base_text) @@ -5619,7 +4928,7 @@ impl MultiBufferSnapshot { } } else if at_transform_end && anchor - .text_anchor + .text_anchor() .cmp(&hunk_info.hunk_start_anchor, excerpt_buffer) .is_gt() { @@ -5654,9 +4963,9 @@ impl MultiBufferSnapshot { } /// Like `resolve_summary_for_anchor` but optimized for min/max anchors. - fn resolve_summary_for_min_or_max_anchor( + fn summary_for_excerpt_position_without_hunks( &self, - anchor: &Anchor, + bias: Bias, excerpt_position: ExcerptDimension, diff_transforms: &mut Cursor< DiffTransform, @@ -5673,7 +4982,7 @@ impl MultiBufferSnapshot { // A right-biased anchor at a transform boundary belongs to the // *next* transform, so advance past the current one. - if anchor.text_anchor.bias == Bias::Right && at_transform_end { + if bias == Bias::Right && at_transform_end { diff_transforms.next(); continue; } @@ -5689,27 +4998,27 @@ impl MultiBufferSnapshot { } fn excerpt_offset_for_anchor(&self, anchor: &Anchor) -> ExcerptOffset { - let mut cursor = self - .excerpts - .cursor::, ExcerptOffset>>(()); - let locator = self.excerpt_locator_for_id(anchor.excerpt_id); + let anchor = match anchor { + Anchor::Min => return ExcerptOffset::default(), + Anchor::Excerpt(excerpt_anchor) => excerpt_anchor, + Anchor::Max => return self.excerpts.summary().len(), + }; + let mut cursor = self.excerpts.cursor::(()); + let target = anchor.seek_target(self); - cursor.seek(&Some(locator), Bias::Left); - if cursor.item().is_none() && anchor.excerpt_id == ExcerptId::max() { - cursor.prev(); - } + cursor.seek(&target, Bias::Left); - let mut position = cursor.start().1; + let mut position = cursor.start().len(); if let Some(excerpt) = cursor.item() - && (excerpt.id == anchor.excerpt_id || anchor.excerpt_id == ExcerptId::max()) + && excerpt.contains(anchor, self) { - let excerpt_buffer_start = excerpt - .buffer - .offset_for_anchor(&excerpt.range.context.start); - let excerpt_buffer_end = excerpt.buffer.offset_for_anchor(&excerpt.range.context.end); + let buffer_snapshot = excerpt.buffer_snapshot(self); + let excerpt_buffer_start = + buffer_snapshot.offset_for_anchor(&excerpt.range.context.start); + let excerpt_buffer_end = buffer_snapshot.offset_for_anchor(&excerpt.range.context.end); let buffer_position = cmp::min( excerpt_buffer_end, - excerpt.buffer.offset_for_anchor(&anchor.text_anchor), + buffer_snapshot.offset_for_anchor(&anchor.text_anchor()), ); if buffer_position > excerpt_buffer_start { position += buffer_position - excerpt_buffer_start; @@ -5718,13 +5027,6 @@ impl MultiBufferSnapshot { position } - pub fn latest_excerpt_id(&self, mut excerpt_id: ExcerptId) -> ExcerptId { - while let Some(replacement) = self.replaced_excerpts.get(&excerpt_id) { - excerpt_id = *replacement; - } - excerpt_id - } - pub fn summaries_for_anchors<'a, MBD, I>(&'a self, anchors: I) -> Vec where MBD: MultiBufferDimension @@ -5743,43 +5045,56 @@ impl MultiBufferSnapshot { let mut summaries = Vec::new(); while let Some(anchor) = anchors.peek() { - let excerpt_id = self.latest_excerpt_id(anchor.excerpt_id); - - let excerpt_anchors = anchors.peeking_take_while(|anchor| { - self.latest_excerpt_id(anchor.excerpt_id) == excerpt_id - }); + let target = anchor.seek_target(self); + let excerpt_anchor = match anchor { + Anchor::Min => { + summaries.push(MBD::default()); + anchors.next(); + continue; + } + Anchor::Excerpt(excerpt_anchor) => excerpt_anchor, + Anchor::Max => { + summaries.push(MBD::from_summary(&self.text_summary())); + anchors.next(); + continue; + } + }; - let locator = self.excerpt_locator_for_id(excerpt_id); - cursor.seek_forward(locator, Bias::Left); - if cursor.item().is_none() && excerpt_id == ExcerptId::max() { - cursor.prev(); - } + cursor.seek_forward(&target, Bias::Left); let excerpt_start_position = ExcerptDimension(MBD::from_summary(&cursor.start().text)); if let Some(excerpt) = cursor.item() { - if excerpt.id != excerpt_id && excerpt_id != ExcerptId::max() { - let position = self.resolve_summary_for_min_or_max_anchor( - &Anchor::min(), + let buffer_snapshot = excerpt.buffer_snapshot(self); + if !excerpt.contains(&excerpt_anchor, self) { + let position = self.summary_for_excerpt_position_without_hunks( + Bias::Left, excerpt_start_position, &mut diff_transforms_cursor, ); - summaries.extend(excerpt_anchors.map(|_| position)); + summaries.push(position); + anchors.next(); continue; } let excerpt_buffer_start = excerpt .range .context .start - .summary::(&excerpt.buffer); + .summary::(buffer_snapshot); let excerpt_buffer_end = excerpt .range .context .end - .summary::(&excerpt.buffer); - for (buffer_summary, anchor) in excerpt - .buffer + .summary::(buffer_snapshot); + for (buffer_summary, excerpt_anchor) in buffer_snapshot .summaries_for_anchors_with_payload::( - excerpt_anchors.map(|a| (&a.text_anchor, a)), + std::iter::from_fn(|| { + let excerpt_anchor = anchors.peek()?.excerpt_anchor()?; + if !excerpt.contains(&excerpt_anchor, self) { + return None; + } + anchors.next(); + Some((excerpt_anchor.text_anchor(), excerpt_anchor)) + }), ) { let summary = cmp::min(excerpt_buffer_end, buffer_summary); @@ -5792,21 +5107,22 @@ impl MultiBufferSnapshot { diff_transforms_cursor.seek_forward(&position, Bias::Left); } - summaries.push(self.resolve_summary_for_anchor( - anchor, + summaries.push(self.summary_for_anchor_with_excerpt_position( + excerpt_anchor, position, &mut diff_transforms_cursor, - &excerpt.buffer, + &buffer_snapshot, )); } } else { diff_transforms_cursor.seek_forward(&excerpt_start_position, Bias::Left); - let position = self.resolve_summary_for_min_or_max_anchor( - &Anchor::max(), + let position = self.summary_for_excerpt_position_without_hunks( + Bias::Right, excerpt_start_position, &mut diff_transforms_cursor, ); - summaries.extend(excerpt_anchors.map(|_| position)); + summaries.push(position); + anchors.next(); } } @@ -5853,92 +5169,27 @@ impl MultiBufferSnapshot { }) } - pub fn refresh_anchors<'a, I>(&'a self, anchors: I) -> Vec<(usize, Anchor, bool)> - where - I: 'a + IntoIterator, - { - let mut anchors = anchors.into_iter().enumerate().peekable(); - let mut cursor = self.excerpts.cursor::>(()); - cursor.next(); - - let mut result = Vec::new(); - - while let Some((_, anchor)) = anchors.peek() { - let old_excerpt_id = anchor.excerpt_id; - - // Find the location where this anchor's excerpt should be. - let old_locator = self.excerpt_locator_for_id(old_excerpt_id); - cursor.seek_forward(&Some(old_locator), Bias::Left); - - let next_excerpt = cursor.item(); - let prev_excerpt = cursor.prev_item(); - - // Process all of the anchors for this excerpt. - while let Some((anchor_ix, &anchor)) = - anchors.next_if(|(_, anchor)| anchor.excerpt_id == old_excerpt_id) - { - let mut anchor = anchor; - - // Leave min and max anchors unchanged if invalid or - // if the old excerpt still exists at this location - let mut kept_position = next_excerpt - .is_some_and(|e| e.id == old_excerpt_id && e.contains(&anchor)) - || old_excerpt_id == ExcerptId::max() - || old_excerpt_id == ExcerptId::min(); - - // If the old excerpt no longer exists at this location, then attempt to - // find an equivalent position for this anchor in an adjacent excerpt. - if !kept_position { - for excerpt in [next_excerpt, prev_excerpt].iter().filter_map(|e| *e) { - if excerpt.contains(&anchor) { - anchor.excerpt_id = excerpt.id; - kept_position = true; - break; - } - } - } - - // If there's no adjacent excerpt that contains the anchor's position, - // then report that the anchor has lost its position. - if !kept_position { - anchor = if let Some(excerpt) = next_excerpt { - let mut text_anchor = excerpt - .range - .context - .start - .bias(anchor.text_anchor.bias, &excerpt.buffer); - if text_anchor - .cmp(&excerpt.range.context.end, &excerpt.buffer) - .is_gt() - { - text_anchor = excerpt.range.context.end; - } - Anchor::in_buffer(excerpt.id, text_anchor) - } else if let Some(excerpt) = prev_excerpt { - let mut text_anchor = excerpt - .range - .context - .end - .bias(anchor.text_anchor.bias, &excerpt.buffer); - if text_anchor - .cmp(&excerpt.range.context.start, &excerpt.buffer) - .is_lt() - { - text_anchor = excerpt.range.context.start; - } - Anchor::in_buffer(excerpt.id, text_anchor) - } else if anchor.text_anchor.bias == Bias::Left { - Anchor::min() - } else { - Anchor::max() - }; + pub fn excerpts_for_buffer( + &self, + buffer_id: BufferId, + ) -> impl Iterator> { + if let Some(buffer_state) = self.buffers.get(&buffer_id) { + let path_key = buffer_state.path_key.clone(); + let mut cursor = self.excerpts.cursor::(()); + cursor.seek_forward(&path_key, Bias::Left); + Some(iter::from_fn(move || { + let excerpt = cursor.item()?; + if excerpt.path_key != path_key { + return None; } - - result.push((anchor_ix, anchor, kept_position)); - } + cursor.next(); + Some(excerpt.range.clone()) + })) + } else { + None } - result.sort_unstable_by(|a, b| a.1.cmp(&b.1, self)); - result + .into_iter() + .flatten() } pub fn anchor_before(&self, position: T) -> Anchor { @@ -5993,132 +5244,158 @@ impl MultiBufferSnapshot { let mut excerpts = self .excerpts - .cursor::>>(()); + .cursor::>(()); excerpts.seek(&excerpt_offset, Bias::Right); if excerpts.item().is_none() && excerpt_offset == excerpts.start().0 && bias == Bias::Left { excerpts.prev(); } if let Some(excerpt) = excerpts.item() { + let buffer_snapshot = excerpt.buffer_snapshot(self); let mut overshoot = excerpt_offset.saturating_sub(excerpts.start().0); if excerpt.has_trailing_newline && excerpt_offset == excerpts.end().0 { overshoot -= 1; bias = Bias::Right; } - let buffer_start = excerpt.range.context.start.to_offset(&excerpt.buffer); - let text_anchor = - excerpt.clip_anchor(excerpt.buffer.anchor_at(buffer_start + overshoot, bias)); - let anchor = Anchor::in_buffer(excerpt.id, text_anchor); - match diff_base_anchor { + let buffer_start = excerpt.range.context.start.to_offset(&buffer_snapshot); + let text_anchor = excerpt.clip_anchor( + buffer_snapshot.anchor_at(buffer_start + overshoot, bias), + self, + ); + let anchor = ExcerptAnchor::in_buffer(excerpt.path_key_index, text_anchor); + let anchor = match diff_base_anchor { Some(diff_base_anchor) => anchor.with_diff_base_anchor(diff_base_anchor), None => anchor, - } + }; + anchor.into() } else if excerpt_offset == ExcerptDimension(MultiBufferOffset::ZERO) && bias == Bias::Left { - Anchor::min() + Anchor::Min } else { - Anchor::max() + Anchor::Max } } - /// Wraps the [`text::Anchor`] in a [`crate::Anchor`] if this multi-buffer is a singleton. - pub fn as_singleton_anchor(&self, text_anchor: text::Anchor) -> Option { - let (excerpt, buffer, _) = self.as_singleton()?; - if text_anchor.buffer_id.is_none_or(|id| id == buffer) { - Some(Anchor::in_buffer(excerpt, text_anchor)) - } else { - None - } + /// Lifts a buffer anchor to a multibuffer anchor without checking against excerpt boundaries. Returns `None` if there are no excerpts for the buffer + pub fn anchor_in_buffer(&self, anchor: text::Anchor) -> Option { + let path_key_index = self.path_key_index_for_buffer(anchor.buffer_id)?; + Some(Anchor::in_buffer(path_key_index, anchor)) } - /// Returns an anchor for the given excerpt and text anchor, - /// Returns [`None`] if the excerpt_id is no longer valid or the text anchor range is out of excerpt's bounds. - pub fn anchor_range_in_excerpt( - &self, - excerpt_id: ExcerptId, - text_anchor: Range, - ) -> Option> { - let excerpt = self.excerpt(self.latest_excerpt_id(excerpt_id))?; - - Some( - Self::anchor_in_excerpt_(excerpt, text_anchor.start)? - ..Self::anchor_in_excerpt_(excerpt, text_anchor.end)?, - ) - } + /// Creates a multibuffer anchor for the given buffer anchor, if it is contained in any excerpt. + pub fn anchor_in_excerpt(&self, text_anchor: text::Anchor) -> Option { + for excerpt in { + let this = &self; + let buffer_id = text_anchor.buffer_id; + if let Some(buffer_state) = this.buffers.get(&buffer_id) { + let path_key = buffer_state.path_key.clone(); + let mut cursor = this.excerpts.cursor::(()); + cursor.seek_forward(&path_key, Bias::Left); + Some(iter::from_fn(move || { + let excerpt = cursor.item()?; + if excerpt.path_key != path_key { + return None; + } + cursor.next(); + Some(excerpt) + })) + } else { + None + } + .into_iter() + .flatten() + } { + let buffer_snapshot = excerpt.buffer_snapshot(self); + if excerpt.range.contains(&text_anchor, &buffer_snapshot) { + return Some(Anchor::in_buffer(excerpt.path_key_index, text_anchor)); + } + } - /// Returns an anchor for the given excerpt and text anchor, - /// Returns [`None`] if the excerpt_id is no longer valid or the text anchor range is out of excerpt's bounds. - pub fn anchor_in_excerpt( - &self, - excerpt_id: ExcerptId, - text_anchor: text::Anchor, - ) -> Option { - let excerpt = self.excerpt(self.latest_excerpt_id(excerpt_id))?; - Self::anchor_in_excerpt_(excerpt, text_anchor) + None } - /// Same as [`MultiBuffer::anchor_in_excerpt`], but more efficient than calling it multiple times. - pub fn anchors_in_excerpt( + /// Creates a multibuffer anchor for the given buffer anchor, if it is contained in any excerpt. + pub fn buffer_anchor_range_to_anchor_range( &self, - excerpt_id: ExcerptId, - text_anchors: impl IntoIterator, - ) -> Option>> { - let excerpt = self.excerpt(self.latest_excerpt_id(excerpt_id))?; - Some( - text_anchors - .into_iter() - .map(|text_anchor| Self::anchor_in_excerpt_(excerpt, text_anchor)), - ) - } - - fn anchor_in_excerpt_(excerpt: &Excerpt, text_anchor: text::Anchor) -> Option { - match text_anchor.buffer_id { - Some(buffer_id) if buffer_id == excerpt.buffer_id => (), - Some(_) => return None, - None if text_anchor.is_max() || text_anchor.is_min() => { - return Some(Anchor::in_buffer(excerpt.id, text_anchor)); + text_anchor: Range, + ) -> Option> { + for excerpt in { + let this = &self; + let buffer_id = text_anchor.start.buffer_id; + if let Some(buffer_state) = this.buffers.get(&buffer_id) { + let path_key = buffer_state.path_key.clone(); + let mut cursor = this.excerpts.cursor::(()); + cursor.seek_forward(&path_key, Bias::Left); + Some(iter::from_fn(move || { + let excerpt = cursor.item()?; + if excerpt.path_key != path_key { + return None; + } + cursor.next(); + Some(excerpt) + })) + } else { + None + } + .into_iter() + .flatten() + } { + let buffer_snapshot = excerpt.buffer_snapshot(self); + if excerpt.range.contains(&text_anchor.start, &buffer_snapshot) + && excerpt.range.contains(&text_anchor.end, &buffer_snapshot) + { + return Some(Anchor::range_in_buffer(excerpt.path_key_index, text_anchor)); } - None => return None, - } - - let context = &excerpt.range.context; - if context.start.cmp(&text_anchor, &excerpt.buffer).is_gt() - || context.end.cmp(&text_anchor, &excerpt.buffer).is_lt() - { - return None; } - Some(Anchor::in_buffer(excerpt.id, text_anchor)) - } - - pub fn context_range_for_excerpt(&self, excerpt_id: ExcerptId) -> Option> { - Some(self.excerpt(excerpt_id)?.range.context.clone()) + None } - pub fn excerpt_range_for_excerpt( + /// Returns a buffer anchor and its buffer snapshot for the given anchor, if it is in the multibuffer. + pub fn anchor_to_buffer_anchor( &self, - excerpt_id: ExcerptId, - ) -> Option> { - Some(self.excerpt(excerpt_id)?.range.clone()) + anchor: Anchor, + ) -> Option<(text::Anchor, &BufferSnapshot)> { + match anchor { + Anchor::Min => { + let excerpt = self.excerpts.first()?; + let buffer = excerpt.buffer_snapshot(self); + Some((excerpt.range.context.start, buffer)) + } + Anchor::Excerpt(excerpt_anchor) => { + let buffer = self.buffer_for_id(excerpt_anchor.buffer_id())?; + Some((excerpt_anchor.text_anchor, buffer)) + } + Anchor::Max => { + let excerpt = self.excerpts.last()?; + let buffer = excerpt.buffer_snapshot(self); + Some((excerpt.range.context.end, buffer)) + } + } } pub fn can_resolve(&self, anchor: &Anchor) -> bool { - if anchor.is_min() || anchor.is_max() { + match anchor { // todo(lw): should be `!self.is_empty()` - true - } else if let Some(excerpt) = self.excerpt(anchor.excerpt_id) { - excerpt.buffer.can_resolve(&anchor.text_anchor) - } else { - false + Anchor::Min | Anchor::Max => true, + Anchor::Excerpt(excerpt_anchor) => { + let Some(target) = excerpt_anchor.try_seek_target(self) else { + return false; + }; + let mut cursor = self.excerpts.cursor::(()); + cursor.seek(&target, Bias::Left); + let Some(excerpt) = cursor.item() else { + return false; + }; + excerpt + .buffer_snapshot(self) + .can_resolve(&excerpt_anchor.text_anchor()) + } } } - pub fn excerpts( - &self, - ) -> impl Iterator)> { - self.excerpts - .iter() - .map(|excerpt| (excerpt.id, &*excerpt.buffer, excerpt.range.clone())) + pub fn excerpts(&self) -> impl Iterator> { + self.excerpts.iter().map(|excerpt| excerpt.range.clone()) } fn cursor<'a, MBD, BD>(&'a self) -> MultiBufferCursor<'a, MBD, BD> @@ -6131,35 +5408,17 @@ impl MultiBufferSnapshot { MultiBufferCursor { excerpts, diff_transforms, - diffs: &self.diffs, cached_region: OnceCell::new(), + snapshot: self, } } - pub fn excerpt_before(&self, excerpt_id: ExcerptId) -> Option> { - let start_locator = self.excerpt_locator_for_id(excerpt_id); - let mut excerpts = self - .excerpts - .cursor::, ExcerptOffset>>(()); - excerpts.seek(&Some(start_locator), Bias::Left); + pub fn excerpt_before(&self, anchor: Anchor) -> Option> { + let target = anchor.try_seek_target(&self)?; + let mut excerpts = self.excerpts.cursor::(()); + excerpts.seek(&target, Bias::Left); excerpts.prev(); - - let mut diff_transforms = self - .diff_transforms - .cursor::>(()); - diff_transforms.seek(&excerpts.start().1, Bias::Left); - if diff_transforms.end().excerpt_dimension < excerpts.start().1 { - diff_transforms.next(); - } - - let excerpt = excerpts.item()?; - Some(MultiBufferExcerpt { - excerpt, - offset: diff_transforms.start().output_dimension.0, - buffer_offset: BufferOffset(excerpt.range.context.start.to_offset(&excerpt.buffer)), - excerpt_offset: excerpts.start().1, - diff_transforms, - }) + Some(excerpts.item()?.range.clone()) } pub fn excerpt_boundaries_in_range( @@ -6204,7 +5463,7 @@ impl MultiBufferSnapshot { } else { cursor.seek_to_start_of_current_excerpt(); } - let mut prev_region = cursor + let mut prev_excerpt = cursor .fetch_excerpt_with_range() .map(|(excerpt, _)| excerpt); @@ -6219,7 +5478,7 @@ impl MultiBufferSnapshot { let (next_excerpt, next_range) = cursor.fetch_excerpt_with_range()?; cursor.next_excerpt_forwards(); if !bounds.contains(&next_range.start.key) { - prev_region = Some(next_excerpt); + prev_excerpt = Some(next_excerpt); continue; } @@ -6230,18 +5489,20 @@ impl MultiBufferSnapshot { self.max_point() }; - let prev = prev_region.as_ref().map(|region| ExcerptInfo { - id: region.id, - buffer: region.buffer.clone(), - buffer_id: region.buffer_id, - range: region.range.clone(), + let prev = prev_excerpt.as_ref().map(|excerpt| ExcerptBoundaryInfo { + start_anchor: Anchor::in_buffer( + excerpt.path_key_index, + excerpt.range.context.start, + ), + range: excerpt.range.clone(), end_row: MultiBufferRow(next_region_start.row), }); - let next = ExcerptInfo { - id: next_excerpt.id, - buffer: next_excerpt.buffer.clone(), - buffer_id: next_excerpt.buffer_id, + let next = ExcerptBoundaryInfo { + start_anchor: Anchor::in_buffer( + next_excerpt.path_key_index, + next_excerpt.range.context.start, + ), range: next_excerpt.range.clone(), end_row: if next_excerpt.has_trailing_newline { MultiBufferRow(next_region_end.row - 1) @@ -6252,7 +5513,7 @@ impl MultiBufferSnapshot { let row = MultiBufferRow(next_region_start.row); - prev_region = Some(next_excerpt); + prev_excerpt = Some(next_excerpt); return Some(ExcerptBoundary { row, prev, next }); } @@ -6267,6 +5528,91 @@ impl MultiBufferSnapshot { self.non_text_state_update_count } + /// Allows converting several ranges within the same excerpt between buffer offsets and multibuffer offsets. + /// + /// If the input range is contained in a single excerpt, invokes the callback with the full range of that excerpt + /// and the input range both converted to buffer coordinates. The buffer ranges returned by the callback are lifted back + /// to multibuffer offsets and returned. + /// + /// Returns `None` if the input range spans multiple excerpts. + pub fn map_excerpt_ranges<'a, T>( + &'a self, + position: Range, + f: impl FnOnce( + &'a BufferSnapshot, + ExcerptRange, + Range, + ) -> Vec<(Range, T)>, + ) -> Option, T)>> { + let mut cursor = self.cursor::(); + cursor.seek(&position.start); + + let region = cursor.region()?; + if !region.is_main_buffer { + return None; + } + let excerpt = cursor.excerpt()?; + let excerpt_start = *cursor.excerpts.start(); + let input_buffer_start = cursor.buffer_position_at(&position.start)?; + + cursor.seek_forward(&position.end); + if cursor.excerpt()? != excerpt { + return None; + } + let region = cursor.region()?; + if !region.is_main_buffer { + return None; + } + let input_buffer_end = cursor.buffer_position_at(&position.end)?; + let input_buffer_range = input_buffer_start..input_buffer_end; + let buffer = excerpt.buffer_snapshot(self); + let excerpt_context_range = excerpt.range.context.to_offset(buffer); + let excerpt_context_range = + BufferOffset(excerpt_context_range.start)..BufferOffset(excerpt_context_range.end); + let excerpt_primary_range = excerpt.range.primary.to_offset(buffer); + let excerpt_primary_range = + BufferOffset(excerpt_primary_range.start)..BufferOffset(excerpt_primary_range.end); + let results = f( + buffer, + ExcerptRange { + context: excerpt_context_range.clone(), + primary: excerpt_primary_range, + }, + input_buffer_range, + ); + let mut diff_transforms = cursor.diff_transforms; + Some( + results + .into_iter() + .map(|(buffer_range, metadata)| { + let clamped_start = buffer_range + .start + .max(excerpt_context_range.start) + .min(excerpt_context_range.end); + let clamped_end = buffer_range + .end + .max(clamped_start) + .min(excerpt_context_range.end); + let excerpt_offset_start = + excerpt_start + (clamped_start.0 - excerpt_context_range.start.0); + let excerpt_offset_end = + excerpt_start + (clamped_end.0 - excerpt_context_range.start.0); + + diff_transforms.seek(&excerpt_offset_start, Bias::Right); + let mut output_start = diff_transforms.start().output_dimension; + output_start += + excerpt_offset_start - diff_transforms.start().excerpt_dimension; + + diff_transforms.seek_forward(&excerpt_offset_end, Bias::Right); + let mut output_end = diff_transforms.start().output_dimension; + output_end += excerpt_offset_end - diff_transforms.start().excerpt_dimension; + + (output_start.0..output_end.0, metadata) + }) + .collect(), + ) + } + /// Returns the smallest enclosing bracket ranges containing the given range or /// None if no brackets contain range or the range is not contained in a single /// excerpt @@ -6281,32 +5627,31 @@ impl MultiBufferSnapshot { >, ) -> Option<(Range, Range)> { let range = range.start.to_offset(self)..range.end.to_offset(self); - let mut excerpt = self.excerpt_containing(range.clone())?; - let buffer = excerpt.buffer(); - let excerpt_buffer_range = excerpt.buffer_range(); - - // Filter to ranges contained in the excerpt - let range_filter = |open: Range, close: Range| -> bool { - excerpt_buffer_range.contains(&BufferOffset(open.start)) - && excerpt_buffer_range.contains(&BufferOffset(close.end)) - && range_filter.is_none_or(|filter| { - filter( - buffer, - BufferOffset(open.start)..BufferOffset(close.end), - BufferOffset(close.start)..BufferOffset(close.end), - ) - }) - }; - - let (open, close) = excerpt.buffer().innermost_enclosing_bracket_ranges( - excerpt.map_range_to_buffer(range), - Some(&range_filter), - )?; - - Some(( - excerpt.map_range_from_buffer(BufferOffset(open.start)..BufferOffset(open.end)), - excerpt.map_range_from_buffer(BufferOffset(close.start)..BufferOffset(close.end)), - )) + let results = + self.map_excerpt_ranges(range, |buffer, excerpt_range, input_buffer_range| { + let filter = |open: Range, close: Range| -> bool { + excerpt_range.context.start.0 <= open.start + && close.end <= excerpt_range.context.end.0 + && range_filter.is_none_or(|filter| { + filter( + buffer, + BufferOffset(open.start)..BufferOffset(close.end), + BufferOffset(close.start)..BufferOffset(close.end), + ) + }) + }; + let Some((open, close)) = + buffer.innermost_enclosing_bracket_ranges(input_buffer_range, Some(&filter)) + else { + return Vec::new(); + }; + vec![ + (BufferOffset(open.start)..BufferOffset(open.end), ()), + (BufferOffset(close.start)..BufferOffset(close.end), ()), + ] + })?; + let [(open, _), (close, _)] = results.try_into().ok()?; + Some((open, close)) } /// Returns enclosing bracket ranges containing the given range or returns None if the range is @@ -6314,30 +5659,33 @@ impl MultiBufferSnapshot { pub fn enclosing_bracket_ranges( &self, range: Range, - ) -> Option, Range)> + '_> - { + ) -> Option, Range)>> { let range = range.start.to_offset(self)..range.end.to_offset(self); - let mut excerpt = self.excerpt_containing(range.clone())?; - - Some( - excerpt - .buffer() - .enclosing_bracket_ranges(excerpt.map_range_to_buffer(range)) - .filter_map(move |pair| { - let open_range = - BufferOffset(pair.open_range.start)..BufferOffset(pair.open_range.end); - let close_range = - BufferOffset(pair.close_range.start)..BufferOffset(pair.close_range.end); - if excerpt.contains_buffer_range(open_range.start..close_range.end) { - Some(( - excerpt.map_range_from_buffer(open_range), - excerpt.map_range_from_buffer(close_range), - )) - } else { - None - } - }), - ) + let results = + self.map_excerpt_ranges(range, |buffer, excerpt_range, input_buffer_range| { + buffer + .enclosing_bracket_ranges(input_buffer_range) + .filter(|pair| { + excerpt_range.context.start.0 <= pair.open_range.start + && pair.close_range.end <= excerpt_range.context.end.0 + }) + .flat_map(|pair| { + [ + ( + BufferOffset(pair.open_range.start) + ..BufferOffset(pair.open_range.end), + (), + ), + ( + BufferOffset(pair.close_range.start) + ..BufferOffset(pair.close_range.end), + (), + ), + ] + }) + .collect() + })?; + Some(results.into_iter().map(|(range, _)| range).tuples()) } /// Returns enclosing bracket ranges containing the given range or returns None if the range is @@ -6348,54 +5696,55 @@ impl MultiBufferSnapshot { options: TreeSitterOptions, ) -> impl Iterator, TextObject)> + '_ { let range = range.start.to_offset(self)..range.end.to_offset(self); - self.excerpt_containing(range.clone()) - .map(|mut excerpt| { - excerpt - .buffer() - .text_object_ranges(excerpt.map_range_to_buffer(range), options) - .filter_map(move |(range, text_object)| { - let range = BufferOffset(range.start)..BufferOffset(range.end); - if excerpt.contains_buffer_range(range.clone()) { - Some((excerpt.map_range_from_buffer(range), text_object)) - } else { - None - } - }) - }) - .into_iter() - .flatten() + self.map_excerpt_ranges(range, |buffer, excerpt_range, input_buffer_range| { + buffer + .text_object_ranges(input_buffer_range, options) + .filter(|(range, _)| { + excerpt_range.context.start.0 <= range.start + && range.end <= excerpt_range.context.end.0 + }) + .map(|(range, text_object)| { + ( + BufferOffset(range.start)..BufferOffset(range.end), + text_object, + ) + }) + .collect() + }) + .into_iter() + .flatten() } - /// Returns bracket range pairs overlapping the given `range` or returns None if the `range` is - /// not contained in a single excerpt pub fn bracket_ranges( &self, range: Range, - ) -> Option, Range)> + '_> - { + ) -> Option, Range)>> { let range = range.start.to_offset(self)..range.end.to_offset(self); - let mut excerpt = self.excerpt_containing(range.clone())?; - Some( - excerpt - .buffer() - .bracket_ranges(excerpt.map_range_to_buffer(range)) - .filter_map(move |pair| { - let open_range = - BufferOffset(pair.open_range.start)..BufferOffset(pair.open_range.end); - let close_range = - BufferOffset(pair.close_range.start)..BufferOffset(pair.close_range.end); - excerpt - .contains_buffer_range(open_range.start..close_range.end) - .then(|| BracketMatch { - open_range: excerpt.map_range_from_buffer(open_range), - close_range: excerpt.map_range_from_buffer(close_range), - color_index: pair.color_index, - newline_only: pair.newline_only, - syntax_layer_depth: pair.syntax_layer_depth, - }) - }) - .map(BracketMatch::bracket_ranges), - ) + let results = + self.map_excerpt_ranges(range, |buffer, excerpt_range, input_buffer_range| { + buffer + .bracket_ranges(input_buffer_range) + .filter(|pair| { + excerpt_range.context.start.0 <= pair.open_range.start + && pair.close_range.end <= excerpt_range.context.end.0 + }) + .flat_map(|pair| { + [ + ( + BufferOffset(pair.open_range.start) + ..BufferOffset(pair.open_range.end), + (), + ), + ( + BufferOffset(pair.close_range.start) + ..BufferOffset(pair.close_range.end), + (), + ), + ] + }) + .collect() + })?; + Some(results.into_iter().map(|(range, _)| range).tuples()) } pub fn redacted_ranges<'a, T: ToOffset>( @@ -6448,7 +5797,7 @@ impl MultiBufferSnapshot { cursor.seek(&Point::new(start_row.0, 0)); iter::from_fn(move || { let mut region = cursor.region()?; - while !buffer_filter(®ion.excerpt.buffer) { + while !buffer_filter(®ion.excerpt.buffer_snapshot(self)) { cursor.next(); region = cursor.region()?; } @@ -6470,11 +5819,11 @@ impl MultiBufferSnapshot { .line_indents_in_row_range(buffer_start_row..buffer_end_row); let region_buffer_row = region.buffer_range.start.row; let region_row = region.range.start.row; - let region_buffer = ®ion.excerpt.buffer; + let region_buffer = region.excerpt.buffer_snapshot(self); cursor.next(); Some(line_indents.map(move |(buffer_row, indent)| { let row = region_row + (buffer_row - region_buffer_row); - (MultiBufferRow(row), indent, region_buffer.as_ref()) + (MultiBufferRow(row), indent, region_buffer) })) }) .flatten() @@ -6490,7 +5839,7 @@ impl MultiBufferSnapshot { cursor.seek(&Point::new(end_row.0, 0)); iter::from_fn(move || { let mut region = cursor.region()?; - while !buffer_filter(®ion.excerpt.buffer) { + while !buffer_filter(®ion.excerpt.buffer_snapshot(self)) { cursor.prev(); region = cursor.region()?; } @@ -6514,11 +5863,11 @@ impl MultiBufferSnapshot { .reversed_line_indents_in_row_range(buffer_start_row..buffer_end_row); let region_buffer_row = region.buffer_range.start.row; let region_row = region.range.start.row; - let region_buffer = ®ion.excerpt.buffer; + let region_buffer = region.excerpt.buffer_snapshot(self); cursor.prev(); Some(line_indents.map(move |(buffer_row, indent)| { let row = region_row + (buffer_row - region_buffer_row); - (MultiBufferRow(row), indent, region_buffer.as_ref()) + (MultiBufferRow(row), indent, region_buffer) })) }) .flatten() @@ -6788,7 +6137,7 @@ impl MultiBufferSnapshot { fn language_settings<'a>(&'a self, cx: &'a App) -> Cow<'a, LanguageSettings> { self.excerpts .first() - .map(|excerpt| &excerpt.buffer) + .map(|excerpt| excerpt.buffer_snapshot(self)) .map(|buffer| LanguageSettings::for_buffer_snapshot(buffer, None, cx)) .unwrap_or_else(move || self.language_settings_at(MultiBufferOffset::ZERO, cx)) } @@ -6840,7 +6189,7 @@ impl MultiBufferSnapshot { pub fn has_diagnostics(&self) -> bool { self.excerpts .iter() - .any(|excerpt| excerpt.buffer.has_diagnostics()) + .any(|excerpt| excerpt.buffer_snapshot(self).has_diagnostics()) } pub fn diagnostic_group( @@ -6919,7 +6268,12 @@ impl MultiBufferSnapshot { .map(|entry| (entry.range, entry.diagnostic)), ) }) - .map(|(range, diagnostic, b)| (b.buffer_id, DiagnosticEntryRef { diagnostic, range })) + .map(|(range, diagnostic, excerpt)| { + ( + excerpt.buffer_snapshot(self).remote_id(), + DiagnosticEntryRef { diagnostic, range }, + ) + }) } pub fn syntax_ancestor( @@ -6927,41 +6281,52 @@ impl MultiBufferSnapshot { range: Range, ) -> Option<(tree_sitter::Node<'_>, Range)> { let range = range.start.to_offset(self)..range.end.to_offset(self); - let mut excerpt = self.excerpt_containing(range.clone())?; - let node = excerpt - .buffer() - .syntax_ancestor(excerpt.map_range_to_buffer(range))?; - let node_range = node.byte_range(); - let node_range = BufferOffset(node_range.start)..BufferOffset(node_range.end); - if !excerpt.contains_buffer_range(node_range.clone()) { - return None; - }; - Some((node, excerpt.map_range_from_buffer(node_range))) + let results = + self.map_excerpt_ranges(range, |buffer, excerpt_range, input_buffer_range| { + let Some(node) = buffer.syntax_ancestor(input_buffer_range) else { + return vec![]; + }; + let node_range = node.byte_range(); + if excerpt_range.context.start.0 <= node_range.start + && node_range.end <= excerpt_range.context.end.0 + { + vec![( + BufferOffset(node_range.start)..BufferOffset(node_range.end), + node, + )] + } else { + vec![] + } + })?; + let (output_range, node) = results.into_iter().next()?; + Some((node, output_range)) } pub fn outline(&self, theme: Option<&SyntaxTheme>) -> Option> { - let (excerpt_id, _, buffer) = self.as_singleton()?; - let outline = buffer.outline(theme); + let buffer_snapshot = self.as_singleton()?; + let excerpt = self.excerpts.first()?; + let path_key_index = excerpt.path_key_index; + let outline = buffer_snapshot.outline(theme); Some(Outline::new( outline .items .into_iter() - .flat_map(|item| { - Some(OutlineItem { - depth: item.depth, - range: self.anchor_range_in_excerpt(excerpt_id, item.range)?, - source_range_for_text: self - .anchor_range_in_excerpt(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.and_then(|body_range| { - self.anchor_range_in_excerpt(excerpt_id, body_range) - }), - annotation_range: item.annotation_range.and_then(|annotation_range| { - self.anchor_range_in_excerpt(excerpt_id, annotation_range) - }), - }) + .map(|item| OutlineItem { + depth: item.depth, + range: Anchor::range_in_buffer(path_key_index, item.range), + source_range_for_text: Anchor::range_in_buffer( + path_key_index, + item.source_range_for_text, + ), + text: item.text, + highlight_ranges: item.highlight_ranges, + name_ranges: item.name_ranges, + body_range: item + .body_range + .map(|body_range| Anchor::range_in_buffer(path_key_index, body_range)), + annotation_range: item.annotation_range.map(|annotation_range| { + Anchor::range_in_buffer(path_key_index, annotation_range) + }), }) .collect(), )) @@ -6973,173 +6338,90 @@ impl MultiBufferSnapshot { theme: Option<&SyntaxTheme>, ) -> Option<(BufferId, Vec>)> { let anchor = self.anchor_before(offset); - let excerpt @ &Excerpt { - id: excerpt_id, - buffer_id, - ref buffer, - .. - } = self.excerpt(anchor.excerpt_id)?; - if cfg!(debug_assertions) { - match anchor.text_anchor.buffer_id { - // we clearly are hitting this according to sentry, but in what situations can this occur? - Some(anchor_buffer_id) => { - assert_eq!( - anchor_buffer_id, buffer_id, - "anchor {anchor:?} does not match with resolved excerpt {excerpt:?}" - ) - } - None => assert!(anchor.is_max()), - } - }; + let target = anchor.try_seek_target(&self)?; + let (_, _, excerpt) = self.excerpts.find((), &target, Bias::Left); + let excerpt = excerpt?; + let buffer_snapshot = excerpt.buffer_snapshot(self); Some(( - buffer_id, - buffer - .symbols_containing(anchor.text_anchor, theme) + buffer_snapshot.remote_id(), + buffer_snapshot + .symbols_containing( + anchor + .excerpt_anchor() + .map(|anchor| anchor.text_anchor()) + .unwrap_or(text::Anchor::min_for_buffer(buffer_snapshot.remote_id())), + theme, + ) .into_iter() .flat_map(|item| { Some(OutlineItem { depth: item.depth, source_range_for_text: Anchor::range_in_buffer( - excerpt_id, + excerpt.path_key_index, item.source_range_for_text, ), - range: Anchor::range_in_buffer(excerpt_id, item.range), + range: Anchor::range_in_buffer(excerpt.path_key_index, item.range), text: item.text, highlight_ranges: item.highlight_ranges, name_ranges: item.name_ranges, - body_range: item - .body_range - .map(|body_range| Anchor::range_in_buffer(excerpt_id, body_range)), - annotation_range: item - .annotation_range - .map(|body_range| Anchor::range_in_buffer(excerpt_id, body_range)), + body_range: item.body_range.map(|body_range| { + Anchor::range_in_buffer(excerpt.path_key_index, body_range) + }), + annotation_range: item.annotation_range.map(|body_range| { + Anchor::range_in_buffer(excerpt.path_key_index, body_range) + }), }) }) .collect(), )) } - fn excerpt_locator_for_id(&self, id: ExcerptId) -> &Locator { - self.try_excerpt_locator_for_id(id) - .unwrap_or_else(|| panic!("invalid excerpt id {id:?}")) + pub fn buffer_for_path(&self, path: &PathKey) -> Option<&BufferSnapshot> { + let (_, _, excerpt) = self + .excerpts + .find::((), path, Bias::Left); + Some(excerpt?.buffer_snapshot(self)) } - fn try_excerpt_locator_for_id(&self, id: ExcerptId) -> Option<&Locator> { - if id == ExcerptId::min() { - Some(Locator::min_ref()) - } else if id == ExcerptId::max() { - Some(Locator::max_ref()) - } else { - let (_, _, item) = self.excerpt_ids.find::((), &id, Bias::Left); - if let Some(entry) = item - && entry.id == id - { - return Some(&entry.locator); - } - None - } + pub fn path_for_buffer(&self, buffer_id: BufferId) -> Option<&PathKey> { + Some(&self.buffers.get(&buffer_id)?.path_key) } - /// Returns the locators referenced by the given excerpt IDs, sorted by locator. - fn excerpt_locators_for_ids( - &self, - ids: impl IntoIterator, - ) -> SmallVec<[Locator; 1]> { - let mut sorted_ids = ids.into_iter().collect::>(); - sorted_ids.sort_unstable(); - sorted_ids.dedup(); - let mut locators = SmallVec::new(); - - while sorted_ids.last() == Some(&ExcerptId::max()) { - sorted_ids.pop(); - locators.push(Locator::max()); - } - - let mut sorted_ids = sorted_ids.into_iter().peekable(); - locators.extend( - sorted_ids - .peeking_take_while(|excerpt| *excerpt == ExcerptId::min()) - .map(|_| Locator::min()), - ); - - let mut cursor = self.excerpt_ids.cursor::(()); - for id in sorted_ids { - if cursor.seek_forward(&id, Bias::Left) { - locators.push(cursor.item().unwrap().locator.clone()); - } else { - panic!("invalid excerpt id {:?}", id); - } - } + pub(crate) fn path_key_index_for_buffer(&self, buffer_id: BufferId) -> Option { + let snapshot = self.buffers.get(&buffer_id)?; + Some(snapshot.path_key_index) + } - locators.sort_unstable(); - locators + fn first_excerpt_for_buffer(&self, buffer_id: BufferId) -> Option<&Excerpt> { + let path_key = &self.buffers.get(&buffer_id)?.path_key; + self.first_excerpt_for_path(path_key) } - pub fn buffer_id_for_excerpt(&self, excerpt_id: ExcerptId) -> Option { - Some(self.excerpt(excerpt_id)?.buffer_id) + fn first_excerpt_for_path(&self, path_key: &PathKey) -> Option<&Excerpt> { + let (_, _, first_excerpt) = + self.excerpts + .find::((), path_key, Bias::Left); + first_excerpt } - pub fn buffer_for_excerpt(&self, excerpt_id: ExcerptId) -> Option<&BufferSnapshot> { - Some(&self.excerpt(excerpt_id)?.buffer) + pub fn buffer_for_id(&self, id: BufferId) -> Option<&BufferSnapshot> { + self.buffers.get(&id).map(|state| &state.buffer_snapshot) } - pub fn range_for_excerpt(&self, excerpt_id: ExcerptId) -> Option> { - let mut cursor = self - .excerpts - .cursor::, ExcerptPoint>>(()); - let locator = self.excerpt_locator_for_id(excerpt_id); - let mut sought_exact = cursor.seek(&Some(locator), Bias::Left); - if cursor.item().is_none() && excerpt_id == ExcerptId::max() { - sought_exact = true; - cursor.prev(); - } else if excerpt_id == ExcerptId::min() { - sought_exact = true; - } - if sought_exact { - let start = cursor.start().1; - let end = cursor.end().1; - let mut diff_transforms = self - .diff_transforms - .cursor::>>(()); - diff_transforms.seek(&start, Bias::Left); - let overshoot = start - diff_transforms.start().0; - let start = diff_transforms.start().1 + overshoot; - diff_transforms.seek(&end, Bias::Right); - let overshoot = end - diff_transforms.start().0; - let end = diff_transforms.start().1 + overshoot; - Some(start.0..end.0) - } else { - None - } + fn try_path_for_anchor(&self, anchor: ExcerptAnchor) -> Option { + self.path_keys_by_index.get(&anchor.path).cloned() } - /// Returns the excerpt for the given id. The returned excerpt is guaranteed - /// to have the latest excerpt id for the one passed in and will also remap - /// `ExcerptId::max()` to the corresponding excertp ID. - /// - /// Callers of this function should generally use the resulting excerpt's `id` field - /// afterwards. - fn excerpt(&self, excerpt_id: ExcerptId) -> Option<&Excerpt> { - let excerpt_id = self.latest_excerpt_id(excerpt_id); - let locator = self.try_excerpt_locator_for_id(excerpt_id)?; - let (_, _, item) = - self.excerpts - .find::, _>((), &Some(locator), Bias::Left); - if let Some(excerpt) = item - && excerpt.id == excerpt_id - { - return Some(excerpt); - } else if item.is_none() && excerpt_id == ExcerptId::max() { - return self.excerpts.last(); - } - None + pub fn path_for_anchor(&self, anchor: ExcerptAnchor) -> PathKey { + self.try_path_for_anchor(anchor) + .expect("invalid anchor: path was never added to multibuffer") } /// Returns the excerpt containing range and its offset start within the multibuffer or none if `range` spans multiple excerpts pub fn excerpt_containing( &self, range: Range, - ) -> Option> { + ) -> Option<(&BufferSnapshot, ExcerptRange)> { let range = range.start.to_offset(self)..range.end.to_offset(self); let mut cursor = self.cursor::(); cursor.seek(&range.start); @@ -7147,31 +6429,15 @@ impl MultiBufferSnapshot { let start_excerpt = cursor.excerpt()?; if range.end != range.start { cursor.seek_forward(&range.end); - if cursor.excerpt()?.id != start_excerpt.id { + if cursor.excerpt()? != start_excerpt { return None; } } - cursor.seek_to_start_of_current_excerpt(); - let region = cursor.region()?; - let offset = region.range.start; - let buffer_offset = start_excerpt.buffer_start_offset(); - let excerpt_offset = *cursor.excerpts.start(); - Some(MultiBufferExcerpt { - diff_transforms: cursor.diff_transforms, - excerpt: start_excerpt, - offset, - buffer_offset, - excerpt_offset, - }) - } - - pub fn buffer_id_for_anchor(&self, anchor: Anchor) -> Option { - if let Some(id) = anchor.text_anchor.buffer_id { - return Some(id); - } - let excerpt = self.excerpt_containing(anchor..anchor)?; - Some(excerpt.buffer_id()) + Some(( + start_excerpt.buffer_snapshot(self), + start_excerpt.range.clone(), + )) } pub fn selections_in_range<'a>( @@ -7180,27 +6446,34 @@ impl MultiBufferSnapshot { include_local: bool, ) -> impl 'a + Iterator)> { let mut cursor = self.excerpts.cursor::(()); - let start_locator = self.excerpt_locator_for_id(range.start.excerpt_id); - let end_locator = self.excerpt_locator_for_id(range.end.excerpt_id); - cursor.seek(start_locator, Bias::Left); + cursor.seek(&range.start.seek_target(self), Bias::Left); cursor - .take_while(move |excerpt| excerpt.locator <= *end_locator) + .take_while(move |excerpt| { + let excerpt_start = + Anchor::in_buffer(excerpt.path_key_index, excerpt.range.context.start); + excerpt_start.cmp(&range.end, self).is_le() + }) .flat_map(move |excerpt| { + let buffer_snapshot = excerpt.buffer_snapshot(self); let mut query_range = excerpt.range.context.start..excerpt.range.context.end; - if excerpt.id == range.start.excerpt_id { - query_range.start = range.start.text_anchor; + if let Some(excerpt_anchor) = range.start.excerpt_anchor() + && excerpt.contains(&excerpt_anchor, self) + { + query_range.start = excerpt_anchor.text_anchor(); } - if excerpt.id == range.end.excerpt_id { - query_range.end = range.end.text_anchor; + if let Some(excerpt_anchor) = range.end.excerpt_anchor() + && excerpt.contains(&excerpt_anchor, self) + { + query_range.end = excerpt_anchor.text_anchor(); } - excerpt - .buffer + buffer_snapshot .selections_in_range(query_range, include_local) .flat_map(move |(replica_id, line_mode, cursor_shape, selections)| { selections.map(move |selection| { - let mut start = Anchor::in_buffer(excerpt.id, selection.start); - let mut end = Anchor::in_buffer(excerpt.id, selection.end); + let mut start = + Anchor::in_buffer(excerpt.path_key_index, selection.start); + let mut end = Anchor::in_buffer(excerpt.path_key_index, selection.end); if range.start.cmp(&start, self).is_gt() { start = range.start; } @@ -7237,91 +6510,251 @@ impl MultiBufferSnapshot { find_diff_state(&self.diffs, buffer_id) } - pub fn total_changed_lines(&self) -> (u32, u32) { - let summary = self.diffs.summary(); - (summary.added_rows, summary.removed_rows) - } + pub fn total_changed_lines(&self) -> (u32, u32) { + let summary = self.diffs.summary(); + (summary.added_rows, summary.removed_rows) + } + + pub fn all_diff_hunks_expanded(&self) -> bool { + self.all_diff_hunks_expanded + } + + /// Visually annotates a position or range with the `Debug` representation of a value. The + /// callsite of this function is used as a key - previous annotations will be removed. + #[cfg(debug_assertions)] + #[track_caller] + pub fn debug(&self, ranges: &R, value: V) + where + R: debug::ToMultiBufferDebugRanges, + V: std::fmt::Debug, + { + self.debug_with_key(std::panic::Location::caller(), ranges, value); + } + + /// Visually annotates a position or range with the `Debug` representation of a value. Previous + /// debug annotations with the same key will be removed. The key is also used to determine the + /// annotation's color. + #[cfg(debug_assertions)] + #[track_caller] + pub fn debug_with_key(&self, key: &K, ranges: &R, value: V) + where + K: std::hash::Hash + 'static, + R: debug::ToMultiBufferDebugRanges, + V: std::fmt::Debug, + { + let text_ranges = ranges + .to_multi_buffer_debug_ranges(self) + .into_iter() + .flat_map(|range| { + self.range_to_buffer_ranges(range) + .into_iter() + .map(|(buffer_snapshot, range, _)| { + buffer_snapshot.anchor_after(range.start) + ..buffer_snapshot.anchor_before(range.end) + }) + }) + .collect(); + text::debug::GlobalDebugRanges::with_locked(|debug_ranges| { + debug_ranges.insert(key, text_ranges, format!("{value:?}").into()) + }); + } + + fn excerpt_edits_for_diff_change( + &self, + path: &PathKey, + diff_change_range: Range, + ) -> Vec>> { + let mut excerpt_edits = Vec::new(); + let mut cursor = self.excerpts.cursor::(()); + cursor.seek(path, Bias::Left); + while let Some(excerpt) = cursor.item() + && &excerpt.path_key == path + { + let buffer_snapshot = excerpt.buffer_snapshot(self); + let excerpt_buffer_range = excerpt.range.context.to_offset(buffer_snapshot); + let excerpt_start = cursor.start().clone(); + let excerpt_len = excerpt.text_summary.len; + cursor.next(); + if diff_change_range.end < excerpt_buffer_range.start + || diff_change_range.start > excerpt_buffer_range.end + { + continue; + } + let diff_change_start_in_excerpt = diff_change_range + .start + .saturating_sub(excerpt_buffer_range.start); + let diff_change_end_in_excerpt = diff_change_range + .end + .saturating_sub(excerpt_buffer_range.start); + let edit_start = excerpt_start.len() + diff_change_start_in_excerpt.min(excerpt_len); + let edit_end = excerpt_start.len() + diff_change_end_in_excerpt.min(excerpt_len); + excerpt_edits.push(Edit { + old: edit_start..edit_end, + new: edit_start..edit_end, + }); + } + excerpt_edits + } + + fn excerpts_for_path<'a>( + &'a self, + path_key: &'a PathKey, + ) -> impl Iterator> + 'a { + let mut cursor = self.excerpts.cursor::(()); + cursor.seek(path_key, Bias::Left); + cursor + .take_while(move |item| &item.path_key == path_key) + .map(|excerpt| excerpt.range.clone()) + } + + /// If the given multibuffer range is contained in a single excerpt and contains no deleted hunks, + /// returns the corresponding buffer range. + /// + /// Otherwise, returns None. + pub fn range_to_buffer_range( + &self, + range: Range, + ) -> Option<(&BufferSnapshot, Range)> + where + MBD: MultiBufferDimension + Ord + Sub + ops::AddAssign<::Output>, + MBD::TextDimension: AddAssign<::Output>, + { + let mut cursor = self.cursor::(); + cursor.seek(&range.start); + + let start_region = cursor.region()?.clone(); + + while let Some(region) = cursor.region() + && region.range.end < range.end + { + if !region.is_main_buffer { + return None; + } + cursor.next(); + } + + let end_region = cursor.region()?; + if end_region.buffer.remote_id() != start_region.buffer.remote_id() { + return None; + } + + let mut buffer_start = start_region.buffer_range.start; + buffer_start += range.start - start_region.range.start; + let mut buffer_end = end_region.buffer_range.start; + buffer_end += range.end - end_region.range.start; + + Some((start_region.buffer, buffer_start..buffer_end)) + } + + /// If the two endpoints of the range lie in the same excerpt, return the corresponding + /// buffer range. Intervening deleted hunks are allowed. + pub fn anchor_range_to_buffer_anchor_range( + &self, + range: Range, + ) -> Option<(&BufferSnapshot, Range)> { + let mut cursor = self.excerpts.cursor::(()); + cursor.seek(&range.start.seek_target(&self), Bias::Left); + + let start_excerpt = cursor.item()?; + + let snapshot = start_excerpt.buffer_snapshot(&self); + + cursor.seek(&range.end.seek_target(&self), Bias::Left); + + let end_excerpt = cursor.item()?; + + if start_excerpt != end_excerpt { + return None; + } + + if let Anchor::Excerpt(excerpt_anchor) = range.start + && (excerpt_anchor.path != start_excerpt.path_key_index + || excerpt_anchor.buffer_id() != snapshot.remote_id()) + { + return None; + } + if let Anchor::Excerpt(excerpt_anchor) = range.end + && (excerpt_anchor.path != end_excerpt.path_key_index + || excerpt_anchor.buffer_id() != snapshot.remote_id()) + { + return None; + } + + Some(( + snapshot, + range.start.text_anchor_in(snapshot)..range.end.text_anchor_in(snapshot), + )) + } + + /// Returns all nonempty intersections of the given buffer range with excerpts in the multibuffer in order. + /// + /// The multibuffer ranges are split to not intersect deleted hunks. + pub fn buffer_range_to_excerpt_ranges( + &self, + range: Range, + ) -> impl Iterator> { + assert!(range.start.buffer_id == range.end.buffer_id); + + let buffer_id = range.start.buffer_id; + self.buffers + .get(&buffer_id) + .map(|buffer_state_snapshot| { + let path_key_index = buffer_state_snapshot.path_key_index; + let buffer_snapshot = &buffer_state_snapshot.buffer_snapshot; + let buffer_range = range.to_offset(buffer_snapshot); + + let start = Anchor::in_buffer(path_key_index, range.start).to_offset(self); + let mut cursor = self.cursor::(); + cursor.seek(&start); + std::iter::from_fn(move || { + while let Some(region) = cursor.region() + && !region.is_main_buffer + { + cursor.next(); + } - pub fn all_diff_hunks_expanded(&self) -> bool { - self.all_diff_hunks_expanded - } + let region = cursor.region()?; + if region.buffer.remote_id() != buffer_id + || region.buffer_range.start > BufferOffset(buffer_range.end) + { + return None; + } - /// Visually annotates a position or range with the `Debug` representation of a value. The - /// callsite of this function is used as a key - previous annotations will be removed. - #[cfg(debug_assertions)] - #[track_caller] - pub fn debug(&self, ranges: &R, value: V) - where - R: debug::ToMultiBufferDebugRanges, - V: std::fmt::Debug, - { - self.debug_with_key(std::panic::Location::caller(), ranges, value); - } + let start = region + .buffer_range + .start + .max(BufferOffset(buffer_range.start)); + let mut end = region.buffer_range.end.min(BufferOffset(buffer_range.end)); - /// Visually annotates a position or range with the `Debug` representation of a value. Previous - /// debug annotations with the same key will be removed. The key is also used to determine the - /// annotation's color. - #[cfg(debug_assertions)] - #[track_caller] - pub fn debug_with_key(&self, key: &K, ranges: &R, value: V) - where - K: std::hash::Hash + 'static, - R: debug::ToMultiBufferDebugRanges, - V: std::fmt::Debug, - { - let text_ranges = ranges - .to_multi_buffer_debug_ranges(self) - .into_iter() - .flat_map(|range| { - self.range_to_buffer_ranges(range.start..=range.end) - .into_iter() - .map(|(buffer, range, _excerpt_id)| { - buffer.anchor_after(range.start)..buffer.anchor_before(range.end) - }) + cursor.next(); + while let Some(region) = cursor.region() + && region.is_main_buffer + && region.buffer.remote_id() == buffer_id + && region.buffer_range.start <= end + { + end = end + .max(region.buffer_range.end) + .min(BufferOffset(buffer_range.end)); + cursor.next(); + } + + let multibuffer_range = Anchor::range_in_buffer( + path_key_index, + buffer_snapshot.anchor_range_inside(start..end), + ); + Some(multibuffer_range) + }) }) - .collect(); - text::debug::GlobalDebugRanges::with_locked(|debug_ranges| { - debug_ranges.insert(key, text_ranges, format!("{value:?}").into()) - }); + .into_iter() + .flatten() } - fn excerpt_edits_for_diff_change( - &self, - buffer_state: &BufferState, - diff_change_range: Range, - ) -> Vec>> { - let mut excerpt_edits = Vec::new(); - for locator in &buffer_state.excerpts { - let mut cursor = self - .excerpts - .cursor::, ExcerptOffset>>(()); - cursor.seek_forward(&Some(locator), Bias::Left); - if let Some(excerpt) = cursor.item() - && excerpt.locator == *locator - { - let excerpt_buffer_range = excerpt.range.context.to_offset(&excerpt.buffer); - if diff_change_range.end < excerpt_buffer_range.start - || diff_change_range.start > excerpt_buffer_range.end - { - continue; - } - let excerpt_start = cursor.start().1; - let excerpt_len = excerpt.text_summary.len; - let diff_change_start_in_excerpt = diff_change_range - .start - .saturating_sub(excerpt_buffer_range.start); - let diff_change_end_in_excerpt = diff_change_range - .end - .saturating_sub(excerpt_buffer_range.start); - let edit_start = excerpt_start + diff_change_start_in_excerpt.min(excerpt_len); - let edit_end = excerpt_start + diff_change_end_in_excerpt.min(excerpt_len); - excerpt_edits.push(Edit { - old: edit_start..edit_end, - new: edit_start..edit_end, - }); - } - } - excerpt_edits + pub fn buffers_with_paths<'a>( + &'a self, + ) -> impl 'a + Iterator { + self.buffers + .values() + .map(|buffer| (&buffer.buffer_snapshot, &buffer.path_key)) } /// Returns the number of graphemes in `range`. @@ -7350,27 +6783,74 @@ impl MultiBufferSnapshot { #[cfg(any(test, feature = "test-support"))] fn check_invariants(&self) { let excerpts = self.excerpts.items(()); - let excerpt_ids = self.excerpt_ids.items(()); + + let mut all_buffer_path_keys = HashSet::default(); + for buffer in self.buffers.values() { + let path_key = buffer.path_key.clone(); + assert!( + all_buffer_path_keys.insert(path_key), + "path key reused for multiple buffers: {:#?}", + self.buffers + ); + } + + let all_excerpt_path_keys = HashSet::from_iter(excerpts.iter().map(|e| e.path_key.clone())); for (ix, excerpt) in excerpts.iter().enumerate() { - if ix == 0 { - if excerpt.locator <= Locator::min() { - panic!("invalid first excerpt locator {:?}", excerpt.locator); + if ix > 0 { + let prev = &excerpts[ix - 1]; + + if excerpt.path_key < prev.path_key { + panic!("excerpt path_keys are out-of-order: {:#?}", excerpts); + } else if excerpt.path_key == prev.path_key { + assert_eq!( + excerpt.buffer_id, prev.buffer_id, + "excerpts with same path_key have different buffer_ids: {:#?}", + excerpts + ); + if excerpt + .start_anchor() + .cmp(&prev.end_anchor(), &self) + .is_le() + { + panic!("excerpt anchors are out-of-order: {:#?}", excerpts); + } + if excerpt + .start_anchor() + .cmp(&excerpt.end_anchor(), &self) + .is_ge() + { + panic!("excerpt with backward range: {:#?}", excerpts); + } } - } else if excerpt.locator <= excerpts[ix - 1].locator { - panic!("excerpts are out-of-order: {:?}", excerpts); } - } - for (ix, entry) in excerpt_ids.iter().enumerate() { - if ix == 0 { - if entry.id.cmp(&ExcerptId::min(), self).is_le() { - panic!("invalid first excerpt id {:?}", entry.id); - } - } else if entry.id <= excerpt_ids[ix - 1].id { - panic!("excerpt ids are out-of-order: {:?}", excerpt_ids); + if ix < excerpts.len() - 1 { + assert!( + excerpt.has_trailing_newline, + "non-trailing excerpt has no trailing newline: {:#?}", + excerpts + ); + } else { + assert!( + !excerpt.has_trailing_newline, + "trailing excerpt has trailing newline: {:#?}", + excerpts + ); } + assert!( + all_buffer_path_keys.contains(&excerpt.path_key), + "excerpt path key not found in active path keys: {:#?}", + excerpt.path_key + ); + assert_eq!( + self.path_keys_by_index.get(&excerpt.path_key_index), + Some(&excerpt.path_key), + "excerpt path key index does not match path key: {:#?}", + excerpt.path_key, + ); } + assert_eq!(all_buffer_path_keys, all_excerpt_path_keys); if self.diff_transforms.summary().input != self.excerpts.summary().text { panic!( @@ -7518,7 +6998,7 @@ where && self .excerpts .item() - .is_some_and(|excerpt| excerpt.id != hunk_info.excerpt_id) + .is_some_and(|excerpt| excerpt.end_anchor() != hunk_info.excerpt_end) { self.excerpts.next(); } @@ -7584,13 +7064,13 @@ where DiffTransform::DeletedHunk { hunk_info, .. } => self .excerpts .item() - .is_some_and(|excerpt| excerpt.id != hunk_info.excerpt_id), + .is_some_and(|excerpt| excerpt.end_anchor() != hunk_info.excerpt_end), }) } fn main_buffer_position(&self) -> Option { let excerpt = self.excerpts.item()?; - let buffer = &excerpt.buffer; + let buffer = excerpt.buffer_snapshot(self.snapshot); let buffer_context_start = excerpt.range.context.start.summary::(buffer); let mut buffer_start = buffer_context_start; let overshoot = self.diff_transforms.end().excerpt_dimension - *self.excerpts.start(); @@ -7598,6 +7078,19 @@ where Some(buffer_start) } + fn buffer_position_at(&self, output_position: &MBD) -> Option { + let excerpt = self.excerpts.item()?; + let buffer = excerpt.buffer_snapshot(self.snapshot); + let buffer_context_start = excerpt.range.context.start.summary::(buffer); + let mut excerpt_offset = self.diff_transforms.start().excerpt_dimension; + if let Some(DiffTransform::BufferContent { .. }) = self.diff_transforms.item() { + excerpt_offset += *output_position - self.diff_transforms.start().output_dimension.0; + } + let mut result = buffer_context_start; + result += excerpt_offset - *self.excerpts.start(); + Some(result) + } + fn build_region(&self) -> Option> { let excerpt = self.excerpts.item()?; match self.diff_transforms.item()? { @@ -7608,7 +7101,7 @@ where hunk_info, .. } => { - let diff = find_diff_state(self.diffs, *buffer_id)?; + let diff = find_diff_state(&self.snapshot.diffs, *buffer_id)?; let buffer = diff.base_text(); let mut rope_cursor = buffer.as_rope().cursor(0); let buffer_start = rope_cursor.summary::(base_text_byte_range.start); @@ -7632,7 +7125,7 @@ where DiffTransform::BufferContent { inserted_hunk_info, .. } => { - let buffer = &excerpt.buffer; + let buffer = excerpt.buffer_snapshot(self.snapshot); let buffer_context_start = excerpt.range.context.start.summary::(buffer); let mut start = self.diff_transforms.start().output_dimension.0; @@ -7726,28 +7219,47 @@ where impl Excerpt { fn new( - id: ExcerptId, - locator: Locator, - buffer_id: BufferId, - buffer: Arc, + path_key: PathKey, + path_key_index: PathKeyIndex, + buffer_snapshot: &BufferSnapshot, range: ExcerptRange, has_trailing_newline: bool, ) -> Self { Excerpt { - id, - locator, - max_buffer_row: range.context.end.to_point(&buffer).row, - text_summary: buffer - .text_summary_for_range::(range.context.to_offset(&buffer)), - buffer_id, - buffer, + path_key, + path_key_index, + buffer_id: buffer_snapshot.remote_id(), + max_buffer_row: range.context.end.to_point(&buffer_snapshot).row, + text_summary: buffer_snapshot.text_summary_for_range::( + range.context.to_offset(&buffer_snapshot), + ), range, has_trailing_newline, } } - fn chunks_in_range(&self, range: Range, language_aware: bool) -> ExcerptChunks<'_> { - let content_start = self.range.context.start.to_offset(&self.buffer); + fn buffer_snapshot<'a>(&self, snapshot: &'a MultiBufferSnapshot) -> &'a BufferSnapshot { + &snapshot + .buffers + .get(&self.buffer_id) + .expect("buffer snapshot not found for excerpt") + .buffer_snapshot + } + + fn buffer(&self, multibuffer: &MultiBuffer) -> Entity { + multibuffer + .buffer(self.buffer_id) + .expect("buffer entity not found for excerpt") + } + + fn chunks_in_range<'a>( + &'a self, + range: Range, + language_aware: bool, + snapshot: &'a MultiBufferSnapshot, + ) -> ExcerptChunks<'a> { + let buffer = self.buffer_snapshot(snapshot); + let content_start = self.range.context.start.to_offset(buffer); let chunks_start = content_start + range.start; let chunks_end = content_start + cmp::min(range.end, self.text_summary.len); @@ -7755,17 +7267,23 @@ impl Excerpt { && range.start <= self.text_summary.len && range.end > self.text_summary.len; - let content_chunks = self.buffer.chunks(chunks_start..chunks_end, language_aware); + let content_chunks = buffer.chunks(chunks_start..chunks_end, language_aware); ExcerptChunks { - excerpt_id: self.id, content_chunks, has_footer, + end: self.end_anchor(), } } - fn seek_chunks(&self, excerpt_chunks: &mut ExcerptChunks, range: Range) { - let content_start = self.range.context.start.to_offset(&self.buffer); + fn seek_chunks( + &self, + excerpt_chunks: &mut ExcerptChunks, + range: Range, + snapshot: &MultiBufferSnapshot, + ) { + let buffer = self.buffer_snapshot(snapshot); + let content_start = self.range.context.start.to_offset(buffer); let chunks_start = content_start + range.start; let chunks_end = content_start + cmp::min(range.end, self.text_summary.len); excerpt_chunks.content_chunks.seek(chunks_start..chunks_end); @@ -7774,218 +7292,43 @@ impl Excerpt { && range.end > self.text_summary.len; } - fn clip_anchor(&self, text_anchor: text::Anchor) -> text::Anchor { - if text_anchor - .cmp(&self.range.context.start, &self.buffer) - .is_lt() - { + fn clip_anchor( + &self, + text_anchor: text::Anchor, + snapshot: &MultiBufferSnapshot, + ) -> text::Anchor { + let buffer = self.buffer_snapshot(snapshot); + if text_anchor.cmp(&self.range.context.start, buffer).is_lt() { self.range.context.start - } else if text_anchor - .cmp(&self.range.context.end, &self.buffer) - .is_gt() - { + } else if text_anchor.cmp(&self.range.context.end, buffer).is_gt() { self.range.context.end } else { text_anchor } } - fn contains(&self, anchor: &Anchor) -> bool { - (anchor.text_anchor.buffer_id == None - || anchor.text_anchor.buffer_id == Some(self.buffer_id)) - && self - .range - .context - .start - .cmp(&anchor.text_anchor, &self.buffer) - .is_le() + pub(crate) fn contains(&self, anchor: &ExcerptAnchor, snapshot: &MultiBufferSnapshot) -> bool { + self.path_key_index == anchor.path + && self.buffer_id == anchor.text_anchor.buffer_id && self .range - .context - .end - .cmp(&anchor.text_anchor, &self.buffer) - .is_ge() - } - - /// The [`Excerpt`]'s start offset in its [`Buffer`] - fn buffer_start_offset(&self) -> BufferOffset { - BufferOffset(self.range.context.start.to_offset(&self.buffer)) - } - - /// The [`Excerpt`]'s end offset in its [`Buffer`] - fn buffer_end_offset(&self) -> BufferOffset { - self.buffer_start_offset() + self.text_summary.len - } -} - -impl<'a> MultiBufferExcerpt<'a> { - pub fn id(&self) -> ExcerptId { - self.excerpt.id - } - - pub fn buffer_id(&self) -> BufferId { - self.excerpt.buffer_id - } - - pub fn start_anchor(&self) -> Anchor { - Anchor::in_buffer(self.excerpt.id, self.excerpt.range.context.start) - } - - pub fn end_anchor(&self) -> Anchor { - Anchor::in_buffer(self.excerpt.id, self.excerpt.range.context.end) - } - - pub fn buffer(&self) -> &'a BufferSnapshot { - &self.excerpt.buffer - } - - pub fn buffer_range(&self) -> Range { - self.buffer_offset - ..BufferOffset( - self.excerpt - .range - .context - .end - .to_offset(&self.excerpt.buffer.text), - ) - } - - pub fn start_offset(&self) -> MultiBufferOffset { - self.offset - } - - /// Maps an offset within the [`MultiBuffer`] to an offset within the [`Buffer`] - pub fn map_offset_to_buffer(&mut self, offset: MultiBufferOffset) -> BufferOffset { - self.map_range_to_buffer(offset..offset).start - } - - /// Maps a range within the [`MultiBuffer`] to a range within the [`Buffer`] - pub fn map_range_to_buffer(&mut self, range: Range) -> Range { - self.diff_transforms - .seek(&OutputDimension(range.start), Bias::Right); - let start = self.map_offset_to_buffer_internal(range.start); - let end = if range.end > range.start { - self.diff_transforms - .seek_forward(&OutputDimension(range.end), Bias::Right); - self.map_offset_to_buffer_internal(range.end) - } else { - start - }; - start..end - } - - fn map_offset_to_buffer_internal(&self, offset: MultiBufferOffset) -> BufferOffset { - let mut excerpt_offset = self.diff_transforms.start().excerpt_dimension; - if let Some(DiffTransform::BufferContent { .. }) = self.diff_transforms.item() { - excerpt_offset += offset - self.diff_transforms.start().output_dimension.0; - }; - let offset_in_excerpt = excerpt_offset.saturating_sub(self.excerpt_offset); - self.buffer_offset + offset_in_excerpt - } - - /// Map an offset within the [`Buffer`] to an offset within the [`MultiBuffer`] - pub fn map_offset_from_buffer(&mut self, buffer_offset: BufferOffset) -> MultiBufferOffset { - self.map_range_from_buffer(buffer_offset..buffer_offset) - .start - } - - /// Map a range within the [`Buffer`] to a range within the [`MultiBuffer`] - pub fn map_range_from_buffer( - &mut self, - buffer_range: Range, - ) -> Range { - if buffer_range.start < self.buffer_offset { - log::warn!( - "Attempting to map a range from a buffer offset that starts before the current buffer offset" - ); - return self.offset..self.offset; - } - let overshoot = buffer_range.start - self.buffer_offset; - let excerpt_offset = self.excerpt_offset + overshoot; - let excerpt_seek_dim = excerpt_offset; - self.diff_transforms.seek(&excerpt_seek_dim, Bias::Right); - if self.diff_transforms.start().excerpt_dimension > excerpt_offset { - log::warn!( - "Attempting to map a range from a buffer offset that starts before the current buffer offset" - ); - return self.offset..self.offset; - } - let overshoot = excerpt_offset - self.diff_transforms.start().excerpt_dimension; - let start = self.diff_transforms.start().output_dimension.0 + overshoot; - - let end = if buffer_range.start < buffer_range.end { - let overshoot = buffer_range.end - self.buffer_offset; - let excerpt_offset = self.excerpt_offset + overshoot; - let excerpt_seek_dim = excerpt_offset; - self.diff_transforms - .seek_forward(&excerpt_seek_dim, Bias::Right); - let overshoot = excerpt_offset - self.diff_transforms.start().excerpt_dimension; - // todo(lw): Clamp end to the excerpt boundaries - self.diff_transforms.start().output_dimension.0 + overshoot - } else { - start - }; - - start..end - } - - /// Returns true if the entirety of the given range is in the buffer's excerpt - pub fn contains_buffer_range(&self, range: Range) -> bool { - range.start >= self.excerpt.buffer_start_offset() - && range.end <= self.excerpt.buffer_end_offset() - } - - /// Returns true if any part of the given range is in the buffer's excerpt - pub fn contains_partial_buffer_range(&self, range: Range) -> bool { - range.start <= self.excerpt.buffer_end_offset() - && range.end >= self.excerpt.buffer_start_offset() - } - - pub fn max_buffer_row(&self) -> u32 { - self.excerpt.max_buffer_row - } -} - -impl ExcerptId { - pub fn min() -> Self { - Self(0) - } - - pub fn max() -> Self { - Self(u32::MAX) - } - - pub fn to_proto(self) -> u64 { - self.0 as _ + .contains(&anchor.text_anchor(), self.buffer_snapshot(snapshot)) } - pub fn from_proto(proto: u64) -> Self { - Self(proto as _) + fn start_anchor(&self) -> ExcerptAnchor { + ExcerptAnchor::in_buffer(self.path_key_index, self.range.context.start) } - pub fn cmp(&self, other: &Self, snapshot: &MultiBufferSnapshot) -> cmp::Ordering { - let a = snapshot.excerpt_locator_for_id(*self); - let b = snapshot.excerpt_locator_for_id(*other); - a.cmp(b).then_with(|| self.0.cmp(&other.0)) + fn end_anchor(&self) -> ExcerptAnchor { + ExcerptAnchor::in_buffer(self.path_key_index, self.range.context.end) } } -impl From for usize { - fn from(val: ExcerptId) -> Self { - val.0 as usize - } -} - -impl fmt::Debug for Excerpt { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Excerpt") - .field("id", &self.id) - .field("locator", &self.locator) - .field("buffer_id", &self.buffer_id) - .field("range", &self.range) - .field("text_summary", &self.text_summary) - .field("has_trailing_newline", &self.has_trailing_newline) - .finish() +impl PartialEq for Excerpt { + fn eq(&self, other: &Self) -> bool { + self.path_key_index == other.path_key_index + && self.buffer_id == other.buffer_id + && self.range.context == other.range.context } } @@ -7998,8 +7341,8 @@ impl sum_tree::Item for Excerpt { text += TextSummary::from("\n"); } ExcerptSummary { - excerpt_id: self.id, - excerpt_locator: self.locator.clone(), + path_key: self.path_key.clone(), + max_anchor: Some(self.range.context.end), widest_line_number: self.max_buffer_row, text: text.into(), count: 1, @@ -8007,22 +7350,6 @@ impl sum_tree::Item for Excerpt { } } -impl sum_tree::Item for ExcerptIdMapping { - type Summary = ExcerptId; - - fn summary(&self, _cx: ()) -> Self::Summary { - self.id - } -} - -impl sum_tree::KeyedItem for ExcerptIdMapping { - type Key = ExcerptId; - - fn key(&self) -> Self::Key { - self.id - } -} - impl DiffTransform { fn hunk_info(&self) -> Option { match self { @@ -8071,45 +7398,98 @@ impl sum_tree::ContextLessSummary for DiffTransformSummary { } } -impl sum_tree::ContextLessSummary for ExcerptId { - fn zero() -> Self { - Self(0) +impl sum_tree::Dimension<'_, ExcerptSummary> for PathKey { + fn zero(_: ::Context<'_>) -> Self { + PathKey::min() } - fn add_summary(&mut self, summary: &Self) { - *self = cmp::max(*self, *summary); + fn add_summary( + &mut self, + summary: &'_ ExcerptSummary, + _cx: ::Context<'_>, + ) { + *self = summary.path_key.clone(); + } +} + +impl sum_tree::Dimension<'_, ExcerptSummary> for MultiBufferOffset { + fn zero(_: ::Context<'_>) -> Self { + MultiBufferOffset::ZERO + } + + fn add_summary( + &mut self, + summary: &'_ ExcerptSummary, + _cx: ::Context<'_>, + ) { + *self += summary.text.len } } impl sum_tree::ContextLessSummary for ExcerptSummary { fn zero() -> Self { - Self::default() + Self::min() } fn add_summary(&mut self, summary: &Self) { debug_assert!( - summary.excerpt_locator > self.excerpt_locator - || self.excerpt_locator == Locator::min(), - "Excerpt locators must be in ascending order: {:?} > {:?}", - summary.excerpt_locator, - self.excerpt_locator + summary.path_key >= self.path_key, + "Path keys must be in ascending order: {:?} > {:?}", + summary.path_key, + self.path_key ); - self.excerpt_locator = summary.excerpt_locator.clone(); + + self.path_key = summary.path_key.clone(); + self.max_anchor = summary.max_anchor; self.text += summary.text; self.widest_line_number = cmp::max(self.widest_line_number, summary.widest_line_number); self.count += summary.count; } } -impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, Option<&'a Locator>> for Locator { - fn cmp(&self, cursor_location: &Option<&'a Locator>, _: ()) -> cmp::Ordering { - Ord::cmp(&Some(self), cursor_location) +impl sum_tree::SeekTarget<'_, ExcerptSummary, ExcerptSummary> for AnchorSeekTarget { + fn cmp( + &self, + cursor_location: &ExcerptSummary, + _cx: ::Context<'_>, + ) -> cmp::Ordering { + match self { + AnchorSeekTarget::Excerpt { + path_key, + anchor, + snapshot, + } => { + let path_comparison = Ord::cmp(path_key, &cursor_location.path_key); + if path_comparison.is_ne() { + path_comparison + } else if let Some(snapshot) = snapshot { + if anchor.text_anchor.buffer_id != snapshot.remote_id() { + Ordering::Greater + } else if let Some(max_anchor) = cursor_location.max_anchor { + debug_assert_eq!(max_anchor.buffer_id, snapshot.remote_id()); + anchor.text_anchor().cmp(&max_anchor, snapshot) + } else { + Ordering::Greater + } + } else { + // shouldn't happen because we expect this buffer not to have any excerpts + // (otherwise snapshot would have been Some) + Ordering::Equal + } + } + // This should be dead code because Empty is only constructed for an empty snapshot + AnchorSeekTarget::Empty => Ordering::Equal, + } } } -impl sum_tree::SeekTarget<'_, ExcerptSummary, ExcerptSummary> for Locator { - fn cmp(&self, cursor_location: &ExcerptSummary, _: ()) -> cmp::Ordering { - Ord::cmp(self, &cursor_location.excerpt_locator) +impl sum_tree::SeekTarget<'_, ExcerptSummary, ExcerptSummary> for PathKey { + fn cmp( + &self, + cursor_location: &ExcerptSummary, + _cx: ::Context<'_>, + ) -> cmp::Ordering { + Ord::cmp(self, &cursor_location.path_key) } } @@ -8126,26 +7506,6 @@ where } } -impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Option<&'a Locator> { - fn zero(_cx: ()) -> Self { - Default::default() - } - - fn add_summary(&mut self, summary: &'a ExcerptSummary, _: ()) { - *self = Some(&summary.excerpt_locator); - } -} - -impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Option { - fn zero(_cx: ()) -> Self { - Default::default() - } - - fn add_summary(&mut self, summary: &'a ExcerptSummary, _: ()) { - *self = Some(summary.excerpt_id); - } -} - #[derive(Copy, Clone, PartialOrd, Ord, Eq, PartialEq, Debug)] struct OutputDimension(T); @@ -8201,7 +7561,7 @@ where } } -#[derive(Copy, Clone, PartialOrd, Ord, Eq, PartialEq, Debug)] +#[derive(Copy, Clone, PartialOrd, Ord, Eq, PartialEq, Debug, Default)] struct ExcerptDimension(T); impl PartialEq for ExcerptDimension { @@ -8361,18 +7721,14 @@ impl Iterator for MultiBufferRows<'_> { .excerpts .item() .or(self.cursor.excerpts.prev_item())?; - let last_row = last_excerpt - .range - .context - .end - .to_point(&last_excerpt.buffer) - .row; + let buffer_snapshot = last_excerpt.buffer_snapshot(self.cursor.snapshot); + let last_row = last_excerpt.range.context.end.to_point(buffer_snapshot).row; let first_row = last_excerpt .range .context .start - .to_point(&last_excerpt.buffer) + .to_point(buffer_snapshot) .row; let expand_info = if self.is_singleton { @@ -8381,7 +7737,7 @@ impl Iterator for MultiBufferRows<'_> { let needs_expand_up = first_row == last_row && last_row > 0 && !region.diff_hunk_status.is_some_and(|d| d.is_deleted()); - let needs_expand_down = last_row < last_excerpt.buffer.max_point().row; + let needs_expand_down = last_row < buffer_snapshot.max_point().row; if needs_expand_up && needs_expand_down { Some(ExpandExcerptDirection::UpAndDown) @@ -8394,7 +7750,7 @@ impl Iterator for MultiBufferRows<'_> { } .map(|direction| ExpandInfo { direction, - excerpt_id: last_excerpt.id, + start_anchor: Anchor::Excerpt(last_excerpt.start_anchor()), }) }; self.point += Point::new(1, 0); @@ -8436,7 +7792,7 @@ impl Iterator for MultiBufferRows<'_> { } .map(|direction| ExpandInfo { direction, - excerpt_id: region.excerpt.id, + start_anchor: Anchor::Excerpt(region.excerpt.start_anchor()), }) }; @@ -8488,18 +7844,20 @@ impl<'a> MultiBufferChunks<'a> { if let Some(excerpt_chunks) = self .excerpt_chunks .as_mut() - .filter(|chunks| excerpt.id == chunks.excerpt_id) + .filter(|chunks| excerpt.end_anchor() == chunks.end) { excerpt.seek_chunks( excerpt_chunks, (self.excerpt_offset_range.start - excerpt_start) ..(self.excerpt_offset_range.end - excerpt_start), + self.snapshot, ); } else { self.excerpt_chunks = Some(excerpt.chunks_in_range( (self.excerpt_offset_range.start - excerpt_start) ..(self.excerpt_offset_range.end - excerpt_start), self.language_aware, + self.snapshot, )); } } else { @@ -8521,6 +7879,7 @@ impl<'a> MultiBufferChunks<'a> { self.excerpt_chunks = Some(excerpt.chunks_in_range( 0..(self.excerpt_offset_range.end - *self.excerpts.start()), self.language_aware, + self.snapshot, )); } } @@ -8636,7 +7995,8 @@ impl<'a> Iterator for MultiBufferChunks<'a> { } chunks } else { - let base_buffer = &find_diff_state(self.diffs, *buffer_id)?.base_text(); + let base_buffer = + &find_diff_state(&self.snapshot.diffs, *buffer_id)?.base_text(); base_buffer.chunks(base_text_start..base_text_end, self.language_aware) }; @@ -8833,12 +8193,6 @@ impl ToPoint for PointUtf16 { } } -impl From for EntityId { - fn from(id: ExcerptId) -> Self { - EntityId::from(id.0 as u64) - } -} - #[cfg(debug_assertions)] pub mod debug { use super::*; diff --git a/crates/multi_buffer/src/multi_buffer_tests.rs b/crates/multi_buffer/src/multi_buffer_tests.rs index e44a38e4abed8438bcdcbf1f2c8c55c465d98e2d..b0e541ed11d1e9200b22ce682cf3175fae30e8cf 100644 --- a/crates/multi_buffer/src/multi_buffer_tests.rs +++ b/crates/multi_buffer/src/multi_buffer_tests.rs @@ -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::(), 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::( - &snapshot_1.anchor_before(MultiBufferOffset(2)) - ), - MultiBufferOffset(0) - ); - assert_eq!( - snapshot_2.summaries_for_anchors::(&[ - 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::(&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::>(), - &[(0, true), (1, true), (2, true), (3, true)] - ); - assert_eq!( - snapshot_3.summaries_for_anchors::(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>> = 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::>(), + &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, diffs: HashMap>, inverted_diffs: HashMap, Entity)>, + expanded_diff_hunks_by_buffer: HashMap>, } -#[derive(Debug)] +#[derive(Clone, Debug)] struct ReferenceExcerpt { - id: ExcerptId, + path_key: PathKey, + path_key_index: PathKeyIndex, buffer: Entity, range: Range, - expanded_diff_hunks: Vec, } -#[derive(Debug)] +#[derive(Clone, Debug)] struct ReferenceRegion { buffer_id: Option, range: Range, buffer_range: Option>, status: Option, - excerpt_id: Option, + excerpt_range: Option>, + excerpt_path_key_index: Option, } impl ReferenceMultibuffer { - fn expand_excerpts(&mut self, excerpts: &HashSet, line_count: u32, cx: &App) { - if line_count == 0 { + fn expand_excerpts( + &mut self, + excerpts: &HashSet>, + 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>> = + 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::(), - ); - 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::>(); + + 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, Range), + path_key: PathKey, + path_key_index: PathKeyIndex, + buffer: Entity, + buffer_snapshot: &BufferSnapshot, + ranges: Vec>, + 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, cx: &App) { + fn expand_diff_hunks(&mut self, path_key: PathKey, range: Range, 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(); @@ -2530,36 +3025,39 @@ impl ReferenceMultibuffer { let Some(diff) = self.diffs.get(&buffer_id) else { return; }; - let excerpt_range = excerpt.range.to_offset(&buffer); + let excerpt_range = excerpt.range.to_point(&buffer); + let expanded_diff_hunks = self + .expanded_diff_hunks_by_buffer + .entry(buffer_id) + .or_default(); for hunk in diff .read(cx) .snapshot(cx) .hunks_intersecting_range(range, &buffer) { - let hunk_range = hunk.buffer_range.to_offset(&buffer); + let hunk_range = hunk.buffer_range.to_point(&buffer); if hunk_range.start < excerpt_range.start || hunk_range.start > excerpt_range.end { continue; } - if let Err(ix) = excerpt - .expanded_diff_hunks + if let Err(ix) = expanded_diff_hunks .binary_search_by(|anchor| anchor.cmp(&hunk.buffer_range.start, &buffer)) { log::info!( - "expanding diff hunk {:?}. excerpt:{:?}, excerpt range:{:?}", + "expanding diff hunk {:?}. excerpt range: {:?}, buffer {:?}", hunk_range, - excerpt_id, - excerpt_range + excerpt_range, + buffer.remote_id() ); - excerpt - .expanded_diff_hunks - .insert(ix, hunk.buffer_range.start); + expanded_diff_hunks.insert(ix, hunk.buffer_range.start); } else { - log::trace!("hunk {hunk_range:?} already expanded in excerpt {excerpt_id:?}"); + log::trace!("hunk {hunk_range:?} already expanded in excerpt"); } } } fn expected_content(&self, cx: &App) -> (String, Vec, HashSet) { + use util::maybe; + let mut text = String::new(); let mut regions = Vec::::new(); let mut excerpt_boundary_rows = HashSet::default(); @@ -2599,7 +3097,8 @@ impl ReferenceMultibuffer { (offset..hunk_base_range.start).to_point(&buffer), ), status: None, - excerpt_id: Some(excerpt.id), + excerpt_range: Some(excerpt.range.clone()), + excerpt_path_key_index: Some(excerpt.path_key_index), }); } } @@ -2613,7 +3112,8 @@ impl ReferenceMultibuffer { range: len..text.len(), buffer_range: Some(hunk_base_range.to_point(&buffer)), status: Some(DiffHunkStatus::deleted(hunk.secondary_status)), - excerpt_id: Some(excerpt.id), + excerpt_range: Some(excerpt.range.clone()), + excerpt_path_key_index: Some(excerpt.path_key_index), }); } @@ -2629,7 +3129,8 @@ impl ReferenceMultibuffer { range: len..text.len(), buffer_range: Some((offset..buffer_range.end).to_point(&buffer)), status: None, - excerpt_id: Some(excerpt.id), + excerpt_range: Some(excerpt.range.clone()), + excerpt_path_key_index: Some(excerpt.path_key_index), }); } else { let diff = self.diffs.get(&buffer_id).unwrap().read(cx).snapshot(cx); @@ -2651,10 +3152,18 @@ impl ReferenceMultibuffer { continue; } - if !excerpt.expanded_diff_hunks.iter().any(|expanded_anchor| { - expanded_anchor.to_offset(buffer).max(buffer_range.start) - == hunk_range.start.max(buffer_range.start) - }) { + if !self + .expanded_diff_hunks_by_buffer + .get(&buffer_id) + .cloned() + .into_iter() + .flatten() + .any(|expanded_anchor| { + expanded_anchor + .cmp(&hunk.buffer_range.start, buffer) + .is_eq() + }) + { log::trace!("skipping a hunk that's not marked as expanded"); continue; } @@ -2674,7 +3183,8 @@ impl ReferenceMultibuffer { range: len..text.len(), buffer_range: Some((offset..hunk_range.start).to_point(&buffer)), status: None, - excerpt_id: Some(excerpt.id), + excerpt_range: Some(excerpt.range.clone()), + excerpt_path_key_index: Some(excerpt.path_key_index), }); } @@ -2695,7 +3205,8 @@ impl ReferenceMultibuffer { hunk.diff_base_byte_range.to_point(&base_buffer), ), status: Some(DiffHunkStatus::deleted(hunk.secondary_status)), - excerpt_id: Some(excerpt.id), + excerpt_range: Some(excerpt.range.clone()), + excerpt_path_key_index: Some(excerpt.path_key_index), }); } @@ -2712,7 +3223,8 @@ impl ReferenceMultibuffer { range, buffer_range: Some((offset..hunk_range.end).to_point(&buffer)), status: Some(DiffHunkStatus::added(hunk.secondary_status)), - excerpt_id: Some(excerpt.id), + excerpt_range: Some(excerpt.range.clone()), + excerpt_path_key_index: Some(excerpt.path_key_index), }; offset = hunk_range.end; regions.push(region); @@ -2728,7 +3240,8 @@ impl ReferenceMultibuffer { range: len..text.len(), buffer_range: Some((offset..buffer_range.end).to_point(&buffer)), status: None, - excerpt_id: Some(excerpt.id), + excerpt_range: Some(excerpt.range.clone()), + excerpt_path_key_index: Some(excerpt.path_key_index), }); } } @@ -2740,7 +3253,8 @@ impl ReferenceMultibuffer { range: 0..1, buffer_range: Some(Point::new(0, 0)..Point::new(0, 1)), status: None, - excerpt_id: None, + excerpt_range: None, + excerpt_path_key_index: None, }); } else { text.pop(); @@ -2756,7 +3270,7 @@ impl ReferenceMultibuffer { .iter() .position(|region| region.range.contains(&ix)) .map_or(RowInfo::default(), |region_ix| { - let region = ®ions[region_ix]; + let region = regions[region_ix].clone(); let buffer_row = region.buffer_range.as_ref().map(|buffer_range| { buffer_range.start.row + text[region.range.start..ix].matches('\n').count() as u32 @@ -2764,13 +3278,13 @@ impl ReferenceMultibuffer { let main_buffer = self .excerpts .iter() - .find(|e| e.id == region.excerpt_id.unwrap()) + .find(|e| e.range == region.excerpt_range.clone().unwrap()) .map(|e| e.buffer.clone()); let is_excerpt_start = region_ix == 0 - || ®ions[region_ix - 1].excerpt_id != ®ion.excerpt_id + || ®ions[region_ix - 1].excerpt_range != ®ion.excerpt_range || regions[region_ix - 1].range.is_empty(); let mut is_excerpt_end = region_ix == regions.len() - 1 - || ®ions[region_ix + 1].excerpt_id != ®ion.excerpt_id; + || ®ions[region_ix + 1].excerpt_range != ®ion.excerpt_range; let is_start = !text[region.range.start..ix].contains('\n'); let mut is_end = if region.range.end > text.len() { !text[ix..].contains('\n') @@ -2784,7 +3298,7 @@ impl ReferenceMultibuffer { && !text[ix..].contains("\n") && (region.status == Some(DiffHunkStatus::added_none()) || region.status.is_some_and(|s| s.is_deleted())) - && regions[region_ix + 1].excerpt_id == region.excerpt_id + && regions[region_ix + 1].excerpt_range == region.excerpt_range && regions[region_ix + 1].range.start == text.len() { is_end = true; @@ -2816,12 +3330,18 @@ impl ReferenceMultibuffer { wrapped_buffer_row: None, multibuffer_row: Some(multibuffer_row), - expand_info: expand_direction.zip(region.excerpt_id).map( - |(direction, excerpt_id)| ExpandInfo { + expand_info: maybe!({ + let direction = expand_direction?; + let excerpt_range = region.excerpt_range?; + let path_key_index = region.excerpt_path_key_index?; + Some(ExpandInfo { direction, - excerpt_id, - }, - ), + start_anchor: Anchor::in_buffer( + path_key_index, + excerpt_range.start, + ), + }) + }), } }); ix += line.len() + 1; @@ -2832,41 +3352,10 @@ impl ReferenceMultibuffer { (text, row_infos, excerpt_boundary_rows) } - fn diffs_updated(&mut self, cx: &App) { - for excerpt in &mut self.excerpts { - let buffer = excerpt.buffer.read(cx).snapshot(); - let buffer_id = buffer.remote_id(); - - // Skip inverted diff excerpts - hunks are always expanded - if self.inverted_diffs.contains_key(&buffer_id) { - continue; - } - - let excerpt_range = excerpt.range.to_offset(&buffer); - let Some(diff) = self.diffs.get(&buffer_id) else { - continue; - }; - let diff = diff.read(cx).snapshot(cx); - let mut hunks = diff.hunks_in_row_range(0..u32::MAX, &buffer).peekable(); - excerpt.expanded_diff_hunks.retain(|hunk_anchor| { - if !hunk_anchor.is_valid(&buffer) { - return false; - } - while let Some(hunk) = hunks.peek() { - match hunk.buffer_range.start.cmp(hunk_anchor, &buffer) { - cmp::Ordering::Less => { - hunks.next(); - } - cmp::Ordering::Equal => { - let hunk_range = hunk.buffer_range.to_offset(&buffer); - return hunk_range.end >= excerpt_range.start - && hunk_range.start <= excerpt_range.end; - } - cmp::Ordering::Greater => break, - } - } - false - }); + fn diffs_updated(&mut self, cx: &mut App) { + let buffer_ids = self.diffs.keys().copied().collect::>(); + for buffer_id in buffer_ids { + self.update_expanded_diff_hunks_for_buffer(buffer_id, cx); } } @@ -2885,6 +3374,46 @@ impl ReferenceMultibuffer { self.inverted_diffs .insert(base_text_buffer_id, (diff, main_buffer)); } + + fn update_expanded_diff_hunks_for_buffer(&mut self, buffer_id: BufferId, cx: &mut App) { + let excerpts = self + .excerpts + .iter() + .filter(|excerpt| excerpt.buffer.read(cx).remote_id() == buffer_id) + .collect::>(); + let Some(buffer) = excerpts.first().map(|excerpt| excerpt.buffer.clone()) else { + self.expanded_diff_hunks_by_buffer.remove(&buffer_id); + return; + }; + let buffer_snapshot = buffer.read(cx).snapshot(); + let Some(diff) = self.diffs.get(&buffer_id) else { + self.expanded_diff_hunks_by_buffer.remove(&buffer_id); + return; + }; + let diff = diff.read(cx).snapshot(cx); + let hunks = diff + .hunks_in_row_range(0..u32::MAX, &buffer_snapshot) + .collect::>(); + self.expanded_diff_hunks_by_buffer + .entry(buffer_id) + .or_default() + .retain(|hunk_anchor| { + if !hunk_anchor.is_valid(&buffer_snapshot) { + return false; + } + + let Ok(ix) = hunks.binary_search_by(|hunk| { + hunk.buffer_range.start.cmp(hunk_anchor, &buffer_snapshot) + }) else { + return false; + }; + let hunk_range = hunks[ix].buffer_range.to_point(&buffer_snapshot); + excerpts.iter().any(|excerpt| { + let excerpt_range = excerpt.range.to_point(&buffer_snapshot); + hunk_range.start >= excerpt_range.start && hunk_range.start <= excerpt_range.end + }) + }); + } } #[gpui::test(iterations = 100)] @@ -2917,7 +3446,7 @@ async fn test_random_set_ranges(cx: &mut TestAppContext, mut rng: StdRng) { .collect::>(); ranges.sort_by_key(|range| range.start); log::info!("Setting ranges: {:?}", row_ranges(&ranges)); - let (created, _) = multibuffer.update(cx, |multibuffer, cx| { + multibuffer.update(cx, |multibuffer, cx| { multibuffer.set_excerpts_for_path( PathKey::for_buffer(&buf, cx), buf.clone(), @@ -2927,15 +3456,16 @@ async fn test_random_set_ranges(cx: &mut TestAppContext, mut rng: StdRng) { ) }); - assert_eq!(created.len(), ranges.len()); - let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx)); let mut last_end = None; let mut seen_ranges = Vec::default(); - for (_, buf, range) in snapshot.excerpts() { - let start = range.context.start.to_point(buf); - let end = range.context.end.to_point(buf); + for info in snapshot.excerpts() { + let buffer_snapshot = snapshot + .buffer_for_id(info.context.start.buffer_id) + .unwrap(); + let start = info.context.start.to_point(buffer_snapshot); + let end = info.context.end.to_point(buffer_snapshot); seen_ranges.push(start..end); if let Some(last_end) = last_end.take() { @@ -2987,23 +3517,32 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) { }); cx.update(|cx| reference.diffs_updated(cx)); } - 15..=19 if !reference.excerpts.is_empty() => { + 15..=24 if !reference.excerpts.is_empty() => { multibuffer.update(cx, |multibuffer, cx| { - let ids = multibuffer.excerpt_ids(); + let snapshot = multibuffer.snapshot(cx); + let infos = snapshot.excerpts().collect::>(); let mut excerpts = HashSet::default(); - for _ in 0..rng.random_range(0..ids.len()) { - excerpts.extend(ids.choose(&mut rng).copied()); + for _ in 0..rng.random_range(0..infos.len()) { + excerpts.extend(infos.choose(&mut rng).cloned()); } let line_count = rng.random_range(0..5); let excerpt_ixs = excerpts .iter() - .map(|id| reference.excerpts.iter().position(|e| e.id == *id).unwrap()) + .map(|info| { + reference + .excerpts + .iter() + .position(|e| e.range == info.context) + .unwrap() + }) .collect::>(); log::info!("Expanding excerpts {excerpt_ixs:?} by {line_count} lines"); multibuffer.expand_excerpts( - excerpts.iter().cloned(), + excerpts + .iter() + .map(|info| snapshot.anchor_in_excerpt(info.context.end).unwrap()), line_count, ExpandExcerptDirection::UpAndDown, cx, @@ -3012,25 +3551,7 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) { reference.expand_excerpts(&excerpts, line_count, cx); }); } - 20..=29 if !reference.excerpts.is_empty() => { - let mut ids_to_remove = vec![]; - for _ in 0..rng.random_range(1..=3) { - let Some(excerpt) = reference.excerpts.choose(&mut rng) else { - break; - }; - let id = excerpt.id; - cx.update(|cx| reference.remove_excerpt(id, cx)); - ids_to_remove.push(id); - } - let snapshot = - multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx)); - ids_to_remove.sort_unstable_by(|a, b| a.cmp(b, &snapshot)); - drop(snapshot); - multibuffer.update(cx, |multibuffer, cx| { - multibuffer.remove_excerpts(ids_to_remove, cx) - }); - } - 30..=39 if !reference.excerpts.is_empty() => { + 25..=34 if !reference.excerpts.is_empty() => { let multibuffer = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx)); let offset = multibuffer.clip_offset( @@ -3046,32 +3567,7 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) { anchors.push(multibuffer.anchor_at(offset, bias)); anchors.sort_by(|a, b| a.cmp(b, &multibuffer)); } - 40..=44 if !anchors.is_empty() => { - let multibuffer = - multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx)); - let prev_len = anchors.len(); - anchors = multibuffer - .refresh_anchors(&anchors) - .into_iter() - .map(|a| a.1) - .collect(); - - // Ensure the newly-refreshed anchors point to a valid excerpt and don't - // overshoot its boundaries. - assert_eq!(anchors.len(), prev_len); - for anchor in &anchors { - if anchor.excerpt_id == ExcerptId::min() - || anchor.excerpt_id == ExcerptId::max() - { - continue; - } - - let excerpt = multibuffer.excerpt(anchor.excerpt_id).unwrap(); - assert_eq!(excerpt.id, anchor.excerpt_id); - assert!(excerpt.contains(anchor)); - } - } - 45..=55 if !reference.excerpts.is_empty() => { + 35..=45 if !reference.excerpts.is_empty() => { multibuffer.update(cx, |multibuffer, cx| { let snapshot = multibuffer.snapshot(cx); let excerpt_ix = rng.random_range(0..reference.excerpts.len()); @@ -3085,20 +3581,19 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) { let start = excerpt.range.start; let end = excerpt.range.end; - let range = snapshot.anchor_in_excerpt(excerpt.id, start).unwrap() - ..snapshot.anchor_in_excerpt(excerpt.id, end).unwrap(); + let range = snapshot.anchor_in_excerpt(start).unwrap() + ..snapshot.anchor_in_excerpt(end).unwrap(); log::info!( - "expanding diff hunks in range {:?} (excerpt id {:?}, index {excerpt_ix:?}, buffer id {:?})", - range.to_offset(&snapshot), - excerpt.id, + "expanding diff hunks in range {:?} (excerpt index {excerpt_ix:?}, buffer id {:?})", + range.to_point(&snapshot), buffer_id, ); - reference.expand_diff_hunks(excerpt.id, start..end, cx); + reference.expand_diff_hunks(excerpt.path_key.clone(), start..end, cx); multibuffer.expand_diff_hunks(vec![range], cx); }); } - 56..=85 if needs_diff_calculation => { + 46..=75 if needs_diff_calculation => { multibuffer.update(cx, |multibuffer, cx| { for buffer in multibuffer.all_buffers() { let snapshot = buffer.read(cx).snapshot(); @@ -3129,13 +3624,6 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) { // Decide if we're creating a new buffer or reusing an existing one let create_new_buffer = buffers.is_empty() || rng.random_bool(0.4); - let prev_excerpt_ix = rng.random_range(0..=reference.excerpts.len()); - let prev_excerpt_id = reference - .excerpts - .get(prev_excerpt_ix) - .map_or(ExcerptId::max(), |e| e.id); - let excerpt_ix = (prev_excerpt_ix + 1).min(reference.excerpts.len()); - let (excerpt_buffer, diff, inverted_main_buffer) = if create_new_buffer { let create_inverted = rng.random_bool(0.3); @@ -3213,43 +3701,45 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) { } }; - let (range, anchor_range) = excerpt_buffer.read_with(cx, |buffer, _| { - let end_row = rng.random_range(0..=buffer.max_point().row); - let start_row = rng.random_range(0..=end_row); - let end_ix = buffer.point_to_offset(Point::new(end_row, 0)); - let start_ix = buffer.point_to_offset(Point::new(start_row, 0)); - let anchor_range = buffer.anchor_before(start_ix)..buffer.anchor_after(end_ix); - - log::info!( - "Inserting excerpt at {} of {} for buffer {}: {:?}[{:?}] = {:?}", - excerpt_ix, - reference.excerpts.len(), - buffer.remote_id(), - buffer.text(), - start_ix..end_ix, - &buffer.text()[start_ix..end_ix] - ); - - (start_ix..end_ix, anchor_range) + let excerpt_buffer_snapshot = + excerpt_buffer.read_with(cx, |excerpt_buffer, _| excerpt_buffer.snapshot()); + let mut ranges = reference + .excerpts + .iter() + .filter(|excerpt| excerpt.buffer == excerpt_buffer) + .map(|excerpt| excerpt.range.to_point(&excerpt_buffer_snapshot)) + .collect::>(); + mutate_excerpt_ranges(&mut rng, &mut ranges, &excerpt_buffer_snapshot, 1); + let ranges = ranges + .iter() + .cloned() + .map(ExcerptRange::new) + .collect::>(); + let path = cx.update(|cx| PathKey::for_buffer(&excerpt_buffer, cx)); + let path_key_index = multibuffer.update(cx, |multibuffer, _| { + multibuffer.get_or_create_path_key_index(&path) }); - let excerpt_id = multibuffer.update(cx, |multibuffer, cx| { - multibuffer - .insert_excerpts_after( - prev_excerpt_id, - excerpt_buffer.clone(), - [ExcerptRange::new(range.clone())], - cx, - ) - .pop() - .unwrap() + multibuffer.update(cx, |multibuffer, cx| { + multibuffer.set_excerpt_ranges_for_path( + path.clone(), + excerpt_buffer.clone(), + &excerpt_buffer_snapshot, + ranges.clone(), + cx, + ) }); - reference.insert_excerpt_after( - prev_excerpt_id, - excerpt_id, - (excerpt_buffer.clone(), anchor_range), - ); + cx.update(|cx| { + reference.set_excerpts( + path, + path_key_index, + excerpt_buffer.clone(), + &excerpt_buffer_snapshot, + ranges, + cx, + ) + }); let excerpt_buffer_id = excerpt_buffer.read_with(cx, |buffer, _| buffer.remote_id()); @@ -3283,6 +3773,38 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) { } } +fn mutate_excerpt_ranges( + rng: &mut StdRng, + existing_ranges: &mut Vec>, + buffer: &BufferSnapshot, + operations: u32, +) { + let mut ranges_to_add = Vec::new(); + + for _ in 0..operations { + match rng.random_range(0..5) { + 0..=1 if !existing_ranges.is_empty() => { + let index = rng.random_range(0..existing_ranges.len()); + log::info!("Removing excerpt at index {index}"); + existing_ranges.remove(index); + } + _ => { + let end_row = rng.random_range(0..=buffer.max_point().row); + let start_row = rng.random_range(0..=end_row); + log::info!( + "Inserting excerpt for buffer {:?}, row range {:?}", + buffer.remote_id(), + start_row..end_row + ); + ranges_to_add.push(Point::new(start_row, 0)..Point::new(end_row, 0)); + } + } + } + + existing_ranges.extend(ranges_to_add); + existing_ranges.sort_by(|l, r| l.start.cmp(&r.start)); +} + fn check_multibuffer( multibuffer: &MultiBuffer, reference: &ReferenceMultibuffer, @@ -3364,24 +3886,15 @@ fn check_multibuffer( .unwrap() + 1 ); - let reference_ranges = reference - .excerpts - .iter() - .map(|excerpt| { - ( - excerpt.id, - excerpt.range.to_offset(&excerpt.buffer.read(cx).snapshot()), - ) - }) - .collect::>(); for i in 0..snapshot.len().0 { - let excerpt = snapshot + let (_, excerpt_range) = snapshot .excerpt_containing(MultiBufferOffset(i)..MultiBufferOffset(i)) .unwrap(); - assert_eq!( - excerpt.buffer_range().start.0..excerpt.buffer_range().end.0, - reference_ranges[&excerpt.id()] - ); + reference + .excerpts + .iter() + .find(|reference_excerpt| reference_excerpt.range == excerpt_range.context) + .expect("corresponding excerpt should exist in reference multibuffer"); } assert_consistent_line_numbers(&snapshot); @@ -3560,8 +4073,8 @@ fn test_history(cx: &mut App) { assert_eq!( multibuffer.edited_ranges_for_transaction(transaction_1, cx), &[ - Point::new(0, 0)..Point::new(0, 2), - Point::new(1, 0)..Point::new(1, 2) + MultiBufferOffset(0)..MultiBufferOffset(2), + MultiBufferOffset(7)..MultiBufferOffset(9), ] ); @@ -3777,7 +4290,6 @@ async fn test_summaries_for_anchors(cx: &mut TestAppContext) { }); cx.run_until_parked(); - let mut ids = vec![]; let multibuffer = cx.new(|cx| { let mut multibuffer = MultiBuffer::new(Capability::ReadWrite); multibuffer.set_all_diff_hunks_expanded(cx); @@ -3797,7 +4309,6 @@ async fn test_summaries_for_anchors(cx: &mut TestAppContext) { ); multibuffer.add_diff(diff_1.clone(), cx); multibuffer.add_diff(diff_2.clone(), cx); - ids = multibuffer.excerpt_ids(); multibuffer }); @@ -3821,11 +4332,21 @@ async fn test_summaries_for_anchors(cx: &mut TestAppContext) { ), ); - let anchor_1 = Anchor::in_buffer(ids[0], text::Anchor::MIN); + let anchor_1 = multibuffer.read_with(cx, |multibuffer, cx| { + multibuffer + .snapshot(cx) + .anchor_in_excerpt(text::Anchor::min_for_buffer(buffer_1.read(cx).remote_id())) + .unwrap() + }); let point_1 = snapshot.summaries_for_anchors::([&anchor_1])[0]; assert_eq!(point_1, Point::new(0, 0)); - let anchor_2 = Anchor::in_buffer(ids[1], text::Anchor::MIN); + let anchor_2 = multibuffer.read_with(cx, |multibuffer, cx| { + multibuffer + .snapshot(cx) + .anchor_in_excerpt(text::Anchor::min_for_buffer(buffer_2.read(cx).remote_id())) + .unwrap() + }); let point_2 = snapshot.summaries_for_anchors::([&anchor_2])[0]; assert_eq!(point_2, Point::new(3, 0)); } @@ -3851,7 +4372,7 @@ async fn test_trailing_deletion_without_newline(cx: &mut TestAppContext) { cx, ); multibuffer.add_diff(diff_1.clone(), cx); - multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx); + multibuffer.expand_diff_hunks(vec![Anchor::Min..Anchor::Max], cx); multibuffer }); @@ -3884,7 +4405,7 @@ async fn test_trailing_deletion_without_newline(cx: &mut TestAppContext) { let (_, translated_offset) = snapshot.point_to_buffer_offset(Point::new(2, 0)).unwrap(); assert_eq!(translated_offset.0, "one\n".len()); - let (_, translated_point, _) = snapshot.point_to_buffer_point(Point::new(2, 0)).unwrap(); + let (_, translated_point) = snapshot.point_to_buffer_point(Point::new(2, 0)).unwrap(); assert_eq!(translated_point, Point::new(1, 0)); // The same, for an excerpt that's not at the end of the multibuffer. @@ -3927,7 +4448,7 @@ async fn test_trailing_deletion_without_newline(cx: &mut TestAppContext) { let (buffer, translated_offset) = snapshot.point_to_buffer_offset(Point::new(2, 0)).unwrap(); assert_eq!(buffer.remote_id(), buffer_1_id); assert_eq!(translated_offset.0, "one\n".len()); - let (buffer, translated_point, _) = snapshot.point_to_buffer_point(Point::new(2, 0)).unwrap(); + let (buffer, translated_point) = snapshot.point_to_buffer_point(Point::new(2, 0)).unwrap(); assert_eq!(buffer.remote_id(), buffer_1_id); assert_eq!(translated_point, Point::new(1, 0)); } @@ -3967,6 +4488,7 @@ fn format_diff( }; let expand = info .expand_info + .as_ref() .map(|expand_info| match expand_info.direction { ExpandExcerptDirection::Up => " [↑]", ExpandExcerptDirection::Down => " [↓]", @@ -4310,9 +4832,15 @@ fn assert_excerpts_match( ) { let mut output = String::new(); multibuffer.read_with(cx, |multibuffer, cx| { - for (_, buffer, range) in multibuffer.snapshot(cx).excerpts() { + let snapshot = multibuffer.snapshot(cx); + for excerpt in multibuffer.snapshot(cx).excerpts() { output.push_str("-----\n"); - output.extend(buffer.text_for_range(range.context)); + output.extend( + snapshot + .buffer_for_id(excerpt.context.start.buffer_id) + .unwrap() + .text_for_range(excerpt.context), + ); if !output.ends_with('\n') { output.push('\n'); } @@ -4525,14 +5053,14 @@ fn assert_position_translation(snapshot: &MultiBufferSnapshot) { if let Some((buffer, offset)) = snapshot.point_to_buffer_offset(snapshot.max_point()) { assert!(offset.0 <= buffer.len()); } - if let Some((buffer, point, _)) = snapshot.point_to_buffer_point(snapshot.max_point()) { + if let Some((buffer, point)) = snapshot.point_to_buffer_point(snapshot.max_point()) { assert!(point <= buffer.max_point()); } } fn assert_line_indents(snapshot: &MultiBufferSnapshot) { let max_row = snapshot.max_point().row; - let buffer_id = snapshot.excerpts().next().unwrap().1.remote_id(); + let buffer_id = snapshot.excerpts().next().unwrap().context.start.buffer_id; let text = text::Buffer::new(ReplicaId::LOCAL, buffer_id, snapshot.text()); let mut line_indents = text .line_indents_in_row_range(0..max_row + 1) @@ -4720,7 +5248,8 @@ fn test_random_chunk_bitmaps_with_diffs(cx: &mut App, mut rng: StdRng) { let mut diffs = Vec::new(); multibuffer.update(cx, |multibuffer, cx| { - for buffer_id in multibuffer.excerpt_buffer_ids() { + let snapshot = multibuffer.snapshot(cx); + for buffer_id in snapshot.all_buffer_ids() { if rng.random_bool(0.7) { if let Some(buffer_handle) = multibuffer.buffer(buffer_id) { let buffer_text = buffer_handle.read(cx).text(); @@ -4881,7 +5410,7 @@ fn collect_word_diffs( }); 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); }); let snapshot = multibuffer.read_with(cx, |multibuffer, cx| multibuffer.snapshot(cx)); @@ -4996,38 +5525,40 @@ fn test_excerpts_containment_functions(cx: &mut App) { let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite)); - let (excerpt_1_id, excerpt_2_id, excerpt_3_id) = multibuffer.update(cx, |multibuffer, cx| { - multibuffer.set_excerpts_for_path( - PathKey::sorted(0), - buffer_1.clone(), - [Point::new(0, 0)..Point::new(1, 3)], - 0, - cx, - ); + let (excerpt_1_info, excerpt_2_info, excerpt_3_info) = + multibuffer.update(cx, |multibuffer, cx| { + multibuffer.set_excerpts_for_path( + PathKey::sorted(0), + buffer_1.clone(), + [Point::new(0, 0)..Point::new(1, 3)], + 0, + cx, + ); - multibuffer.set_excerpts_for_path( - PathKey::sorted(1), - buffer_2.clone(), - [Point::new(0, 0)..Point::new(1, 3)], - 0, - cx, - ); + multibuffer.set_excerpts_for_path( + PathKey::sorted(1), + buffer_2.clone(), + [Point::new(0, 0)..Point::new(1, 3)], + 0, + cx, + ); - multibuffer.set_excerpts_for_path( - PathKey::sorted(2), - buffer_3.clone(), - [Point::new(0, 0)..Point::new(0, 3)], - 0, - cx, - ); + multibuffer.set_excerpts_for_path( + PathKey::sorted(2), + buffer_3.clone(), + [Point::new(0, 0)..Point::new(0, 3)], + 0, + cx, + ); - let mut ids = multibuffer.excerpt_ids().into_iter(); - ( - ids.next().unwrap(), - ids.next().unwrap(), - ids.next().unwrap(), - ) - }); + let snapshot = multibuffer.snapshot(cx); + let mut excerpts = snapshot.excerpts(); + ( + excerpts.next().unwrap(), + excerpts.next().unwrap(), + excerpts.next().unwrap(), + ) + }); let snapshot = multibuffer.read(cx).snapshot(cx); @@ -5045,24 +5576,24 @@ fn test_excerpts_containment_functions(cx: &mut App) { let excerpts: Vec<_> = snapshot.excerpts_for_range(p00..p00).collect(); assert_eq!(excerpts.len(), 1); - assert_eq!(excerpts[0].id, excerpt_1_id); + assert_eq!(excerpts[0].range, excerpt_1_info); // Cursor at very end of excerpt 3 let excerpts: Vec<_> = snapshot.excerpts_for_range(p43..p43).collect(); assert_eq!(excerpts.len(), 1); - assert_eq!(excerpts[0].id, excerpt_3_id); + assert_eq!(excerpts[0].range, excerpt_3_info); let excerpts: Vec<_> = snapshot.excerpts_for_range(p00..p23).collect(); assert_eq!(excerpts.len(), 2); - assert_eq!(excerpts[0].id, excerpt_1_id); - assert_eq!(excerpts[1].id, excerpt_2_id); + assert_eq!(excerpts[0].range, excerpt_1_info); + assert_eq!(excerpts[1].range, excerpt_2_info); // This range represent an selection with end-point just inside excerpt_2 // Today we only expand the first excerpt, but another interpretation that // we could consider is expanding both here let excerpts: Vec<_> = snapshot.excerpts_for_range(p10..p20).collect(); assert_eq!(excerpts.len(), 1); - assert_eq!(excerpts[0].id, excerpt_1_id); + assert_eq!(excerpts[0].range, excerpt_1_info); //// Test that `excerpts_for_range` and `excerpt_containing` agree for all single offsets (cursor positions) for offset in 0..=snapshot.len().0 { @@ -5074,15 +5605,15 @@ fn test_excerpts_containment_functions(cx: &mut App) { "Expected exactly one excerpt for offset {offset}", ); - let excerpt_containing = snapshot.excerpt_containing(offset..offset); - assert!( - excerpt_containing.is_some(), - "Expected excerpt_containing to find excerpt for offset {offset}", - ); + let (_, excerpt_containing) = + snapshot + .excerpt_containing(offset..offset) + .unwrap_or_else(|| { + panic!("Expected excerpt_containing to find excerpt for offset {offset}") + }); assert_eq!( - excerpts_for_range[0].id, - excerpt_containing.unwrap().id(), + excerpts_for_range[0].range, excerpt_containing, "excerpts_for_range and excerpt_containing should agree for offset {offset}", ); } @@ -5090,9 +5621,8 @@ fn test_excerpts_containment_functions(cx: &mut App) { //// Test `excerpt_containing` behavior with ranges: // Ranges intersecting a single-excerpt - let containing = snapshot.excerpt_containing(p00..p13); - assert!(containing.is_some()); - assert_eq!(containing.unwrap().id(), excerpt_1_id); + let (_, containing) = snapshot.excerpt_containing(p00..p13).unwrap(); + assert_eq!(containing, excerpt_1_info); // Ranges intersecting multiple excerpts (should return None) let containing = snapshot.excerpt_containing(p20..p40); @@ -5103,14 +5633,12 @@ fn test_excerpts_containment_functions(cx: &mut App) { } #[gpui::test] -fn test_range_to_buffer_ranges_with_range_bounds(cx: &mut App) { - use std::ops::Bound; - +fn test_range_to_buffer_ranges(cx: &mut App) { let buffer_1 = cx.new(|cx| Buffer::local("aaa\nbbb", cx)); let buffer_2 = cx.new(|cx| Buffer::local("ccc", cx)); let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite)); - let (excerpt_1_id, excerpt_2_id) = multibuffer.update(cx, |multibuffer, cx| { + multibuffer.update(cx, |multibuffer, cx| { multibuffer.set_excerpts_for_path( PathKey::sorted(0), buffer_1.clone(), @@ -5126,10 +5654,6 @@ fn test_range_to_buffer_ranges_with_range_bounds(cx: &mut App) { 0, cx, ); - - let excerpt_ids = multibuffer.excerpt_ids(); - - (excerpt_ids[0], excerpt_ids[1]) }); let snapshot = multibuffer.read(cx).snapshot(cx); @@ -5143,41 +5667,15 @@ fn test_range_to_buffer_ranges_with_range_bounds(cx: &mut App) { 1, "Half-open range ending at excerpt start should EXCLUDE that excerpt" ); - assert_eq!(ranges_half_open[0].2, excerpt_1_id); - - let ranges_inclusive = snapshot.range_to_buffer_ranges(Point::zero()..=excerpt_2_start); - assert_eq!( - ranges_inclusive.len(), - 2, - "Inclusive range ending at excerpt start should INCLUDE that excerpt" - ); - assert_eq!(ranges_inclusive[0].2, excerpt_1_id); - assert_eq!(ranges_inclusive[1].2, excerpt_2_id); - - let ranges_unbounded = - snapshot.range_to_buffer_ranges((Bound::Included(Point::zero()), Bound::Unbounded)); - assert_eq!( - ranges_unbounded.len(), - 2, - "Unbounded end should include all excerpts" - ); - assert_eq!(ranges_unbounded[0].2, excerpt_1_id); - assert_eq!(ranges_unbounded[1].2, excerpt_2_id); - - let ranges_excluded_end = snapshot.range_to_buffer_ranges(( - Bound::Included(Point::zero()), - Bound::Excluded(excerpt_2_start), - )); + assert_eq!(ranges_half_open[0].1, BufferOffset(0)..BufferOffset(7)); assert_eq!( - ranges_excluded_end.len(), - 1, - "Excluded end bound should exclude excerpt starting at that point" + ranges_half_open[0].0.remote_id(), + buffer_1.read(cx).remote_id() ); - assert_eq!(ranges_excluded_end[0].2, excerpt_1_id); let buffer_empty = cx.new(|cx| Buffer::local("", cx)); let multibuffer_trailing_empty = cx.new(|_| MultiBuffer::new(Capability::ReadWrite)); - let (te_excerpt_1_id, te_excerpt_2_id) = + let (_te_excerpt_1_info, _te_excerpt_2_info) = multibuffer_trailing_empty.update(cx, |multibuffer, cx| { multibuffer.set_excerpts_for_path( PathKey::sorted(0), @@ -5195,8 +5693,9 @@ fn test_range_to_buffer_ranges_with_range_bounds(cx: &mut App) { cx, ); - let excerpt_ids = multibuffer.excerpt_ids(); - (excerpt_ids[0], excerpt_ids[1]) + let snapshot = multibuffer.snapshot(cx); + let mut infos = snapshot.excerpts(); + (infos.next().unwrap(), infos.next().unwrap()) }); let snapshot_trailing = multibuffer_trailing_empty.read(cx).snapshot(cx); @@ -5207,29 +5706,130 @@ fn test_range_to_buffer_ranges_with_range_bounds(cx: &mut App) { let ranges_half_open_max = snapshot_trailing.range_to_buffer_ranges(Point::zero()..max_point); assert_eq!( ranges_half_open_max.len(), - 1, - "Half-open range to max_point should EXCLUDE trailing empty excerpt at max_point" + 2, + "Should include trailing empty excerpts" + ); + assert_eq!(ranges_half_open_max[1].1, BufferOffset(0)..BufferOffset(0)); +} + +#[gpui::test] +async fn test_buffer_range_to_excerpt_ranges(cx: &mut TestAppContext) { + let base_text = indoc!( + " + aaa + bbb + ccc + ddd + eee + ppp + qqq + rrr + fff + ggg + hhh + " + ); + let text = indoc!( + " + aaa + BBB + ddd + eee + ppp + qqq + rrr + FFF + ggg + hhh + " + ); + + 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, 3), + Point::new(7, 0)..Point::new(9, 3), + ], + 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::>(), + &Default::default(), + None, + ); + let expected_diff = indoc!( + " + aaa + - bbb + - ccc + + BBB + ddd + eee [\u{2193}] + - fff [\u{2191}] + + FFF + ggg + hhh [\u{2193}]" ); - assert_eq!(ranges_half_open_max[0].2, te_excerpt_1_id); + pretty_assertions::assert_eq!(actual_diff, expected_diff); + + let buffer_snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot()); - let ranges_inclusive_max = snapshot_trailing.range_to_buffer_ranges(Point::zero()..=max_point); + let query_spanning_deleted_hunk = buffer_snapshot.anchor_after(Point::new(0, 0)) + ..buffer_snapshot.anchor_before(Point::new(1, 3)); assert_eq!( - ranges_inclusive_max.len(), - 2, - "Inclusive range to max_point should INCLUDE trailing empty excerpt" + snapshot + .buffer_range_to_excerpt_ranges(query_spanning_deleted_hunk) + .map(|range| range.to_point(&snapshot)) + .collect::>(), + vec![ + Point::new(0, 0)..Point::new(1, 0), + Point::new(3, 0)..Point::new(3, 3), + ], ); - assert_eq!(ranges_inclusive_max[0].2, te_excerpt_1_id); - assert_eq!(ranges_inclusive_max[1].2, te_excerpt_2_id); - let ranges_unbounded_trailing = snapshot_trailing - .range_to_buffer_ranges((Bound::Included(Point::zero()), Bound::Unbounded)); + let query_within_contiguous_main_buffer = buffer_snapshot.anchor_after(Point::new(1, 0)) + ..buffer_snapshot.anchor_before(Point::new(2, 3)); assert_eq!( - ranges_unbounded_trailing.len(), - 2, - "Unbounded end should include trailing empty excerpt" + snapshot + .buffer_range_to_excerpt_ranges(query_within_contiguous_main_buffer) + .map(|range| range.to_point(&snapshot)) + .collect::>(), + vec![Point::new(3, 0)..Point::new(4, 3)], + ); + + let query_spanning_both_excerpts = buffer_snapshot.anchor_after(Point::new(2, 0)) + ..buffer_snapshot.anchor_before(Point::new(8, 3)); + assert_eq!( + snapshot + .buffer_range_to_excerpt_ranges(query_spanning_both_excerpts) + .map(|range| range.to_point(&snapshot)) + .collect::>(), + vec![ + Point::new(4, 0)..Point::new(5, 3), + Point::new(7, 0)..Point::new(8, 3), + ], ); - assert_eq!(ranges_unbounded_trailing[0].2, te_excerpt_1_id); - assert_eq!(ranges_unbounded_trailing[1].2, te_excerpt_2_id); } #[gpui::test] @@ -5275,17 +5875,14 @@ fn test_cannot_seek_backward_after_excerpt_replacement(cx: &mut TestAppContext) let (anchor_in_e_b2, anchor_in_e_b3) = multibuffer.read_with(cx, |multibuffer, cx| { let snapshot = multibuffer.snapshot(cx); - let excerpt_ids: Vec = snapshot.excerpts().map(|(id, _, _)| id).collect(); - assert_eq!(excerpt_ids.len(), 4, "expected 4 excerpts (3×B + 1×C)"); - - let e_b2_id = excerpt_ids[1]; - let e_b3_id = excerpt_ids[2]; + let excerpt_infos = snapshot.excerpts().collect::>(); + assert_eq!(excerpt_infos.len(), 4, "expected 4 excerpts (3×B + 1×C)"); - let e_b2 = snapshot.excerpt(e_b2_id).expect("E_B2 should exist"); - let e_b3 = snapshot.excerpt(e_b3_id).expect("E_B3 should exist"); + let e_b2_info = excerpt_infos[1].clone(); + let e_b3_info = excerpt_infos[2].clone(); - let anchor_b2 = Anchor::in_buffer(e_b2_id, e_b2.range.context.start); - let anchor_b3 = Anchor::in_buffer(e_b3_id, e_b3.range.context.start); + let anchor_b2 = snapshot.anchor_in_excerpt(e_b2_info.context.start).unwrap(); + let anchor_b3 = snapshot.anchor_in_excerpt(e_b3_info.context.start).unwrap(); (anchor_b2, anchor_b3) }); diff --git a/crates/multi_buffer/src/path_key.rs b/crates/multi_buffer/src/path_key.rs index 09d17d7b7fe2e9e666ba6c5777216c9c8ba4dea0..5c2123d0f9c1b09c16fd99531973df81c45140f7 100644 --- a/crates/multi_buffer/src/path_key.rs +++ b/crates/multi_buffer/src/path_key.rs @@ -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, - 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 + '_ { - self.excerpts_by_path.keys() - } - - pub fn excerpts_for_path(&self, path: &PathKey) -> impl '_ + Iterator { - 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 { - self.paths_by_excerpt.get(&excerpt).cloned() - } - - pub fn remove_excerpts_for_path(&mut self, path: PathKey, cx: &mut Context) { - 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> { - 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 { - 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>, context_line_count: u32, cx: &mut Context, - ) -> (Vec>, 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>, context_line_count: u32, cx: &mut Context, - ) -> (Vec>, 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, + ranges: impl IntoIterator>, + context_line_count: u32, + cx: &mut Context, + ) -> 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>, + cx: &App, + ) -> Vec> { + 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>, cx: &mut Context, - ) -> (Vec>, 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, + anchors: impl IntoIterator, line_count: u32, direction: ExpandExcerptDirection, cx: &mut Context, ) { - let mut sorted_ids: Vec = 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::>()))) + .filter_map(|anchor| anchor.excerpt_anchor()) .collect::>(); - 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::(()); + + 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> = 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( &mut self, path: PathKey, buffer: Entity, - ranges: Vec>, buffer_snapshot: &BufferSnapshot, - new: Vec>, - counts: Vec, + new: Vec>, cx: &mut Context, - ) -> (Vec>, 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::>(); + 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_snapshot: &BufferSnapshot, - new: Vec>, + to_insert: &Vec>, cx: &mut Context, - ) -> 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)> = 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::>(()); - 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::>(()); + 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) { + 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) { + assert_eq!(self.history.transaction_depth(), 0); + self.sync_mut(cx); + + let mut snapshot = self.snapshot.get_mut(); + let mut cursor = snapshot + .excerpts + .cursor::>(()); + 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(); } } diff --git a/crates/multi_buffer/src/transaction.rs b/crates/multi_buffer/src/transaction.rs index a65e394c8f1834a95ccbc70532aa03d2a3e6e34c..a3afe55cd6928b9e908d0249af5fb8fe7fc4bbe4 100644 --- a/crates/multi_buffer/src/transaction.rs +++ b/crates/multi_buffer/src/transaction.rs @@ -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( + pub fn edited_ranges_for_transaction( &self, transaction_id: TransactionId, cx: &App, - ) -> Vec> - where - D: MultiBufferDimension - + Ord - + Sub - + AddAssign, - D::TextDimension: PartialOrd + Sub, - { + ) -> Vec> { 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::(()); + 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::(*buffer_transaction) + for range in buffer + .read(cx) + .edited_ranges_for_transaction_id::(*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::(buffer); - let excerpt_buffer_end = excerpt - .range - .context - .end - .summary::(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::>() } pub fn merge_transactions( diff --git a/crates/outline/src/outline.rs b/crates/outline/src/outline.rs index a03c87d9f68e41dd29d9d614f714db47083831ef..af5671632fdac175e5d31ae15c5890d439b7860f 100644 --- a/crates/outline/src/outline.rs +++ b/crates/outline/src/outline.rs @@ -79,29 +79,37 @@ fn outline_for_editor( cx: &mut App, ) -> Option>>> { 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() })) diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index aa6f89cb8c11c40d4121ab12720069ee7fe66844..b7d5afcb687c017fdf253717a9dae2c95c55b53b 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -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, _subscriptions: Vec, - new_entries_for_fs_update: HashSet, + new_entries_for_fs_update: HashSet, fs_entries_update_task: Task<()>, cached_entries_update_task: Task<()>, reveal_selection_task: Task>, outline_fetch_tasks: HashMap>, - excerpts: HashMap>, + buffers: HashMap, cached_entries: Vec, filter_editor: Entity, mode: ItemsDisplayMode, @@ -334,42 +334,41 @@ enum CollapsedEntry { Dir(WorktreeId, ProjectEntryId), File(WorktreeId, BufferId), ExternalFile(BufferId), - Excerpt(BufferId, ExcerptId), - Outline(BufferId, ExcerptId, Range), + Excerpt(ExcerptRange), + Outline(Range), } -#[derive(Debug)] -struct Excerpt { - range: ExcerptRange, - outlines: ExcerptOutlines, +struct BufferOutlines { + excerpts: Vec>, + 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 { 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), Invalidated(Vec), NotFetched, @@ -536,54 +535,24 @@ impl SearchData { } } -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -struct OutlineEntryExcerpt { - id: ExcerptId, - buffer_id: BufferId, - range: ExcerptRange, -} - -#[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(&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), + 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 { + 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, + excerpts: Vec>, } impl PartialEq for FsEntryFile { @@ -631,7 +600,7 @@ impl Hash for FsEntryDirectory { #[derive(Debug, Clone, Eq)] struct FsEntryExternalFile { buffer_id: BufferId, - excerpts: Vec, + excerpts: Vec>, } 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, depth: usize, window: &mut Window, cx: &mut Context, ) -> Option> { - 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, - cx: &App, - ) -> Option { - let buffer_snapshot = self.buffer_snapshot_for_id(buffer_id, cx)?; + fn excerpt_label(&self, range: &ExcerptRange, cx: &App) -> Option { + 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, ) -> Stateful
{ 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::>()) .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::>::default(); + let mut new_buffers = HashMap::::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::>::default(); let mut worktree_excerpts = HashMap::< WorktreeId, - HashMap)>, + HashMap>)>, >::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, ) -> Option { - let selection = editor.update(cx, |editor, cx| { - editor - .selections - .newest::(&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 { 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) { - 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::>(); 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()), + ); + }); } } @@ -3548,73 +3482,35 @@ impl OutlinePanel { .is_some_and(|active_editor| active_editor.read(cx).buffer().read(cx).is_singleton()) } - fn invalidate_outlines(&mut self, ids: &[ExcerptId]) { + fn invalidate_outlines(&mut self, ids: &[BufferId]) { self.outline_fetch_tasks.clear(); let mut ids = ids.iter().collect::>(); - for excerpts in self.excerpts.values_mut() { - ids.retain(|id| { - if let Some(excerpt) = excerpts.get_mut(id) { - excerpt.invalidate_outlines(); - false - } else { - true - } - }); + for (buffer_id, buffer) in self.buffers.iter_mut() { + if ids.remove(&buffer_id) { + buffer.invalidate_outlines(); + } if ids.is_empty() { break; } } } - fn excerpt_fetch_ranges( - &self, - cx: &App, - ) -> HashMap< - BufferId, - ( - BufferSnapshot, - HashMap>, - ), - > { + fn buffers_to_fetch(&self) -> HashSet { self.fs_entries .iter() - .fold(HashMap::default(), |mut excerpts_to_fetch, fs_entry| { + .fold(HashSet::default(), |mut buffers_to_fetch, fs_entry| { match fs_entry { - FsEntry::File(FsEntryFile { - buffer_id, - excerpts: file_excerpts, - .. - }) - | FsEntry::ExternalFile(FsEntryExternalFile { - buffer_id, - excerpts: file_excerpts, - }) => { - let excerpts = self.excerpts.get(buffer_id); - for &file_excerpt in file_excerpts { - if let Some(excerpt) = excerpts - .and_then(|excerpts| excerpts.get(&file_excerpt)) - .filter(|excerpt| excerpt.should_fetch_outlines()) - { - match excerpts_to_fetch.entry(*buffer_id) { - hash_map::Entry::Occupied(mut o) => { - o.get_mut().1.insert(file_excerpt, excerpt.range.clone()); - } - hash_map::Entry::Vacant(v) => { - if let Some(buffer_snapshot) = - self.buffer_snapshot_for_id(*buffer_id, cx) - { - v.insert((buffer_snapshot, HashMap::default())) - .1 - .insert(file_excerpt, excerpt.range.clone()); - } - } - } - } + FsEntry::File(FsEntryFile { buffer_id, .. }) + | FsEntry::ExternalFile(FsEntryExternalFile { buffer_id, .. }) => { + if let Some(buffer) = self.buffers.get(buffer_id) + && buffer.should_fetch_outlines() + { + buffers_to_fetch.insert(*buffer_id); } } FsEntry::Directory(..) => {} } - excerpts_to_fetch + buffers_to_fetch }) } @@ -4012,13 +3908,12 @@ impl OutlinePanel { } else { None }; - if let Some((buffer_id, entry_excerpts)) = excerpts_to_consider + if let Some((buffer_id, _entry_excerpts)) = excerpts_to_consider && !active_editor.read(cx).is_buffer_folded(buffer_id, cx) { - outline_panel.add_excerpt_entries( + outline_panel.add_buffer_entries( &mut generation_state, buffer_id, - entry_excerpts, depth, track_matches, is_singleton, @@ -4166,7 +4061,7 @@ impl OutlinePanel { } PanelEntry::Outline(OutlineEntry::Outline(outline_entry)) => state .match_candidates - .push(StringMatchCandidate::new(id, &outline_entry.outline.text)), + .push(StringMatchCandidate::new(id, &outline_entry.text)), PanelEntry::Outline(OutlineEntry::Excerpt(_)) => {} PanelEntry::Search(new_search_entry) => { if let Some(search_data) = new_search_entry.render_data.get() { @@ -4333,131 +4228,118 @@ impl OutlinePanel { update_cached_entries } - fn add_excerpt_entries( + fn add_buffer_entries( &mut self, state: &mut GenerationState, buffer_id: BufferId, - entries_to_add: &[ExcerptId], parent_depth: usize, track_matches: bool, is_singleton: bool, query: Option<&str>, cx: &mut Context, ) { - if let Some(excerpts) = self.excerpts.get(&buffer_id) { - let buffer_snapshot = self.buffer_snapshot_for_id(buffer_id, cx); + let Some(buffer) = self.buffers.get(&buffer_id) else { + return; + }; - for &excerpt_id in entries_to_add { - let Some(excerpt) = excerpts.get(&excerpt_id) else { - continue; - }; - let excerpt_depth = parent_depth + 1; - self.push_entry( - state, - track_matches, - PanelEntry::Outline(OutlineEntry::Excerpt(OutlineEntryExcerpt { - buffer_id, - id: excerpt_id, - range: excerpt.range.clone(), - })), - excerpt_depth, - cx, - ); + let buffer_snapshot = self.buffer_snapshot_for_id(buffer_id, cx); - let mut outline_base_depth = excerpt_depth + 1; - if is_singleton { - outline_base_depth = 0; - state.clear(); - } else if query.is_none() - && self - .collapsed_entries - .contains(&CollapsedEntry::Excerpt(buffer_id, excerpt_id)) - { - continue; - } + for excerpt in &buffer.excerpts { + let excerpt_depth = parent_depth + 1; + self.push_entry( + state, + track_matches, + PanelEntry::Outline(OutlineEntry::Excerpt(excerpt.clone())), + excerpt_depth, + cx, + ); + + let mut outline_base_depth = excerpt_depth + 1; + if is_singleton { + outline_base_depth = 0; + state.clear(); + } else if query.is_none() + && self + .collapsed_entries + .contains(&CollapsedEntry::Excerpt(excerpt.clone())) + { + continue; + } - let mut last_depth_at_level: Vec>> = vec![None; 10]; + let mut last_depth_at_level: Vec>> = vec![None; 10]; - let all_outlines: Vec<_> = excerpt.iter_outlines().collect(); + let all_outlines: Vec<_> = buffer.iter_outlines().collect(); - let mut outline_has_children = HashMap::default(); - let mut visible_outlines = Vec::new(); - let mut collapsed_state: Option<(usize, Range)> = None; + let mut outline_has_children = HashMap::default(); + let mut visible_outlines = Vec::new(); + let mut collapsed_state: Option<(usize, Range)> = None; - for (i, &outline) in all_outlines.iter().enumerate() { - let has_children = all_outlines - .get(i + 1) - .map(|next| next.depth > outline.depth) - .unwrap_or(false); + for (i, &outline) in all_outlines.iter().enumerate() { + let has_children = all_outlines + .get(i + 1) + .map(|next| next.depth > outline.depth) + .unwrap_or(false); - outline_has_children - .insert((outline.range.clone(), outline.depth), has_children); + outline_has_children.insert((outline.range.clone(), outline.depth), has_children); - let mut should_include = true; + let mut should_include = true; - if let Some((collapsed_depth, collapsed_range)) = &collapsed_state { - if outline.depth <= *collapsed_depth { + if let Some((collapsed_depth, collapsed_range)) = &collapsed_state { + if outline.depth <= *collapsed_depth { + collapsed_state = None; + } else if let Some(buffer_snapshot) = buffer_snapshot.as_ref() { + let outline_start = outline.range.start; + if outline_start + .cmp(&collapsed_range.start, buffer_snapshot) + .is_ge() + && outline_start + .cmp(&collapsed_range.end, buffer_snapshot) + .is_lt() + { + should_include = false; // Skip - inside collapsed range + } else { collapsed_state = None; - } else if let Some(buffer_snapshot) = buffer_snapshot.as_ref() { - let outline_start = outline.range.start; - if outline_start - .cmp(&collapsed_range.start, buffer_snapshot) - .is_ge() - && outline_start - .cmp(&collapsed_range.end, buffer_snapshot) - .is_lt() - { - should_include = false; // Skip - inside collapsed range - } else { - collapsed_state = None; - } } } + } - // Check if this outline itself is collapsed - if should_include - && self.collapsed_entries.contains(&CollapsedEntry::Outline( - buffer_id, - excerpt_id, - outline.range.clone(), - )) - { - collapsed_state = Some((outline.depth, outline.range.clone())); - } + // Check if this outline itself is collapsed + if should_include + && self + .collapsed_entries + .contains(&CollapsedEntry::Outline(outline.range.clone())) + { + collapsed_state = Some((outline.depth, outline.range.clone())); + } - if should_include { - visible_outlines.push(outline); - } + if should_include { + visible_outlines.push(outline); } + } - self.outline_children_cache - .entry(buffer_id) - .or_default() - .extend(outline_has_children); + self.outline_children_cache + .entry(buffer_id) + .or_default() + .extend(outline_has_children); - for outline in visible_outlines { - let outline_entry = OutlineEntryOutline { - buffer_id, - excerpt_id, - outline: outline.clone(), - }; + for outline in visible_outlines { + let outline_entry = outline.clone(); - if outline.depth < last_depth_at_level.len() { - last_depth_at_level[outline.depth] = Some(outline.range.clone()); - // Clear deeper levels when we go back to a shallower depth - for d in (outline.depth + 1)..last_depth_at_level.len() { - last_depth_at_level[d] = None; - } + if outline.depth < last_depth_at_level.len() { + last_depth_at_level[outline.depth] = Some(outline.range.clone()); + // Clear deeper levels when we go back to a shallower depth + for d in (outline.depth + 1)..last_depth_at_level.len() { + last_depth_at_level[d] = None; } - - self.push_entry( - state, - track_matches, - PanelEntry::Outline(OutlineEntry::Outline(outline_entry)), - outline_base_depth + outline.depth, - cx, - ); } + + self.push_entry( + state, + track_matches, + PanelEntry::Outline(OutlineEntry::Outline(outline_entry)), + outline_base_depth + outline.depth, + cx, + ); } } } @@ -4483,32 +4365,37 @@ impl OutlinePanel { FsEntry::File(file) => &file.excerpts, } .iter() - .copied() + .cloned() .collect::>(); let depth = if is_singleton { 0 } else { parent_depth + 1 }; - let new_search_matches = search_state - .matches - .iter() - .filter(|(match_range, _)| { - related_excerpts.contains(&match_range.start.excerpt_id) - || related_excerpts.contains(&match_range.end.excerpt_id) - }) - .filter(|(match_range, _)| { - let editor = active_editor.read(cx); - let snapshot = editor.buffer().read(cx).snapshot(cx); - if let Some(buffer_id) = snapshot.buffer_id_for_anchor(match_range.start) - && editor.is_buffer_folded(buffer_id, cx) - { - return false; - } - if let Some(buffer_id) = snapshot.buffer_id_for_anchor(match_range.end) - && editor.is_buffer_folded(buffer_id, cx) - { + let new_search_matches = search_state.matches.iter().filter(|(match_range, _)| { + let editor = active_editor.read(cx); + let snapshot = editor.buffer().read(cx).snapshot(cx); + if !related_excerpts.iter().any(|excerpt| { + let (Some(start), Some(end)) = ( + snapshot.anchor_in_buffer(excerpt.context.start), + snapshot.anchor_in_buffer(excerpt.context.end), + ) else { return false; - } - true - }); + }; + let excerpt_range = start..end; + excerpt_range.overlaps(match_range, &snapshot) + }) { + return false; + }; + if let Some((buffer_anchor, _)) = snapshot.anchor_to_buffer_anchor(match_range.start) + && editor.is_buffer_folded(buffer_anchor.buffer_id, cx) + { + return false; + } + if let Some((buffer_anchor, _)) = snapshot.anchor_to_buffer_anchor(match_range.end) + && editor.is_buffer_folded(buffer_anchor.buffer_id, cx) + { + return false; + } + true + }); let new_search_entries = new_search_matches .map(|(match_range, search_data)| SearchEntry { @@ -4626,10 +4513,10 @@ impl OutlinePanel { + folded_dirs.entries.len().saturating_sub(1) * "/".len() } PanelEntry::Outline(OutlineEntry::Excerpt(excerpt)) => self - .excerpt_label(excerpt.buffer_id, &excerpt.range, cx) + .excerpt_label(&excerpt, cx) .map(|label| label.len()) .unwrap_or_default(), - PanelEntry::Outline(OutlineEntry::Outline(entry)) => entry.outline.text.len(), + PanelEntry::Outline(OutlineEntry::Outline(entry)) => entry.text.len(), PanelEntry::Search(search) => search .render_data .get() @@ -5212,31 +5099,21 @@ fn subscribe_for_editor_events( outline_panel.reveal_entry_for_selection(editor.clone(), window, cx); cx.notify(); } - EditorEvent::ExcerptsAdded { excerpts, .. } => { + EditorEvent::BuffersRemoved { removed_buffer_ids } => { outline_panel - .new_entries_for_fs_update - .extend(excerpts.iter().map(|&(excerpt_id, _)| excerpt_id)); + .buffers + .retain(|buffer_id, _| !removed_buffer_ids.contains(buffer_id)); outline_panel.update_fs_entries(editor.clone(), debounce, window, cx); } - EditorEvent::ExcerptsRemoved { ids, .. } => { - let mut ids = ids.iter().collect::>(); - for excerpts in outline_panel.excerpts.values_mut() { - excerpts.retain(|excerpt_id, _| !ids.remove(excerpt_id)); - if ids.is_empty() { - break; - } - } + EditorEvent::BufferRangesUpdated { buffer, .. } => { + outline_panel + .new_entries_for_fs_update + .insert(buffer.read(cx).remote_id()); + outline_panel.invalidate_outlines(&[buffer.read(cx).remote_id()]); outline_panel.update_fs_entries(editor.clone(), debounce, window, cx); } - EditorEvent::ExcerptsExpanded { ids } => { - outline_panel.invalidate_outlines(ids); - let update_cached_items = outline_panel.update_non_fs_items(window, cx); - if update_cached_items { - outline_panel.update_cached_entries(Some(UPDATE_DEBOUNCE), window, cx); - } - } - EditorEvent::ExcerptsEdited { ids } => { - outline_panel.invalidate_outlines(ids); + EditorEvent::BuffersEdited { buffer_ids } => { + outline_panel.invalidate_outlines(buffer_ids); let update_cached_items = outline_panel.update_non_fs_items(window, cx); if update_cached_items { outline_panel.update_cached_entries(Some(UPDATE_DEBOUNCE), window, cx); @@ -5250,29 +5127,20 @@ fn subscribe_for_editor_events( outline_panel.new_entries_for_fs_update.extend( ids.iter() .filter(|id| { - outline_panel - .excerpts - .iter() - .find_map(|(buffer_id, excerpts)| { - if excerpts.contains_key(id) { - ignore_selections_change |= outline_panel - .preserve_selection_on_buffer_fold_toggles - .remove(buffer_id); - Some(buffer_id) - } else { - None - } - }) - .map(|buffer_id| { - if editor.read(cx).is_buffer_folded(*buffer_id, cx) { - latest_folded_buffer_id = Some(*buffer_id); - false - } else { - latest_unfolded_buffer_id = Some(*buffer_id); - true - } - }) - .unwrap_or(true) + if outline_panel.buffers.contains_key(&id) { + ignore_selections_change |= outline_panel + .preserve_selection_on_buffer_fold_toggles + .remove(&id); + if editor.read(cx).is_buffer_folded(**id, cx) { + latest_folded_buffer_id = Some(**id); + false + } else { + latest_unfolded_buffer_id = Some(**id); + true + } + } else { + false + } }) .copied(), ); @@ -5308,10 +5176,8 @@ fn subscribe_for_editor_events( outline_panel.update_fs_entries(editor.clone(), debounce, window, cx); } EditorEvent::Reparsed(buffer_id) => { - if let Some(excerpts) = outline_panel.excerpts.get_mut(buffer_id) { - for excerpt in excerpts.values_mut() { - excerpt.invalidate_outlines(); - } + if let Some(buffer) = outline_panel.buffers.get_mut(buffer_id) { + buffer.invalidate_outlines(); } let update_cached_items = outline_panel.update_non_fs_items(window, cx); if update_cached_items { @@ -5319,10 +5185,8 @@ fn subscribe_for_editor_events( } } EditorEvent::OutlineSymbolsChanged => { - 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(); } if matches!( outline_panel.selected_entry(), @@ -6875,7 +6739,7 @@ outline: struct OutlineEntryExcerpt PanelEntry::Outline(outline_entry) => match outline_entry { OutlineEntry::Excerpt(_) => continue, OutlineEntry::Outline(outline_entry) => { - format!("outline: {}", outline_entry.outline.text) + format!("outline: {}", outline_entry.text) } }, PanelEntry::Search(search_entry) => { @@ -7243,10 +7107,9 @@ outline: fn main" PanelEntry::Outline(OutlineEntry::Outline(outline)) if panel .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() @@ -7333,9 +7196,9 @@ outline: fn main" PanelEntry::Outline(OutlineEntry::Outline(outline)) if panel .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() @@ -7711,10 +7574,9 @@ outline: fn main" PanelEntry::Outline(OutlineEntry::Outline(outline)) if panel .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() @@ -8173,7 +8035,7 @@ outline: struct Foo <==== selected outline_panel.read_with(cx, |panel, _cx| { panel.selected_entry().and_then(|entry| match entry { PanelEntry::Outline(OutlineEntry::Outline(outline)) => { - Some(outline.outline.text.clone()) + Some(outline.text.clone()) } _ => None, }) diff --git a/crates/project/src/lsp_store/semantic_tokens.rs b/crates/project/src/lsp_store/semantic_tokens.rs index 7865e8f20ca0e4dbc9d06c2ffd808fe4090634ed..0f01c6350ece89569535dca571c28597ff77384b 100644 --- a/crates/project/src/lsp_store/semantic_tokens.rs +++ b/crates/project/src/lsp_store/semantic_tokens.rs @@ -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, }) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 96b82a16930543028b7588a843433c6a70bf34e6..0c1b8942cc26976d51d406bfa9f67da714110623 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -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( diff --git a/crates/project/tests/integration/project_tests.rs b/crates/project/tests/integration/project_tests.rs index 8603a904acd2c0cd52fcdc9d102be0f2efeb0636..6601b0744aa770917390e03b16ae93d3bc7f637f 100644 --- a/crates/project/tests/integration/project_tests.rs +++ b/crates/project/tests/integration/project_tests.rs @@ -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(), &[( diff --git a/crates/proto/proto/buffer.proto b/crates/proto/proto/buffer.proto index 01f4bda9e9f450ed65d4f6cb8dc9abc7c35451dd..69bd844ee743ef9038beb25b98b9b31ffb130b2c 100644 --- a/crates/proto/proto/buffer.proto +++ b/crates/proto/proto/buffer.proto @@ -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; diff --git a/crates/proto/proto/call.proto b/crates/proto/proto/call.proto index 4d2bf62eade7aaf633ea899cd106e8d9cb3be25d..aa964c64cd04db71a71ac081e034be10cbf95048 100644 --- a/crates/proto/proto/call.proto +++ b/crates/proto/proto/call.proto @@ -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; diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index cab8e20cd22e1f4155232f36416be77d4f2ca24d..93fbab59a6f1b9da0cb9faf0657fc4a1c5f679bd 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -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); diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 991f8d1076a985e1413b0045aa42d424f094cd9c..1bccf1ae52fb2c52a8d01e53aabb1b3ff5c7c16f 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -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) { - let (deleted_paths, removed_excerpt_ids) = { - let excerpts = self.excerpts.read(cx); - let deleted_paths: Vec = 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 = 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::>(); - 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"); diff --git a/crates/tasks_ui/src/tasks_ui.rs b/crates/tasks_ui/src/tasks_ui.rs index da351ad410d078e79aa4c3038fcf88184bc648fa..d83cdfc830fc1abb19b8d05261aba711dbb14c1d 100644 --- a/crates/tasks_ui/src/tasks_ui.rs +++ b/crates/tasks_ui/src/tasks_ui.rs @@ -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 diff --git a/crates/text/src/anchor.rs b/crates/text/src/anchor.rs index 5c4cce0f11d7db7b7593631e796c0f5e3d50adab..4dbe0e377afb86d176e8cd336e186d209a9d3c78 100644 --- a/crates/text/src/anchor.rs +++ b/crates/text/src/anchor.rs @@ -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, + 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, - ) -> 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, buffer: &BufferSnapshot) -> Ordering; fn overlaps(&self, b: &Range, buffer: &BufferSnapshot) -> bool; + fn contains_anchor(&self, b: Anchor, buffer: &BufferSnapshot) -> bool; } impl AnchorRangeExt for Range { @@ -250,4 +242,8 @@ impl AnchorRangeExt for Range { fn overlaps(&self, other: &Range, 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() + } } diff --git a/crates/text/src/patch.rs b/crates/text/src/patch.rs index eff3d0af110763074d7ca9fdc7842d45eece03c1..376d284473d09df16b93a609c8d49c443aa8a4ab 100644 --- a/crates/text/src/patch.rs +++ b/crates/text/src/patch.rs @@ -56,7 +56,10 @@ where if edit.is_empty() { return; } + self.push_maybe_empty(edit); + } + pub fn push_maybe_empty(&mut self, edit: Edit) { if let Some(last) = self.0.last_mut() { if last.old.end >= edit.old.start { last.old.end = edit.old.end; diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index b8f2ce6ce9b66040b4e633d28bfb42e1791a38ca..026f1272790740c9c2277004e8e96800d87bab15 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -2377,7 +2377,7 @@ impl BufferSnapshot { pub fn summaries_for_anchors<'a, D, A>(&'a self, anchors: A) -> impl 'a + Iterator where D: 'a + TextDimension, - A: 'a + IntoIterator, + A: 'a + IntoIterator, { let anchors = anchors.into_iter(); self.summaries_for_anchors_with_payload::(anchors.map(|a| (a, ()))) @@ -2390,7 +2390,7 @@ impl BufferSnapshot { ) -> impl 'a + Iterator where D: 'a + TextDimension, - A: 'a + IntoIterator, + A: 'a + IntoIterator, { 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(&self, position: Range) -> Range { + pub fn anchor_range_inside(&self, position: Range) -> Range { 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(&self, position: Range) -> Range { + pub fn anchor_range_outside(&self, position: Range) -> Range { 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 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 { diff --git a/crates/toolchain_selector/src/active_toolchain.rs b/crates/toolchain_selector/src/active_toolchain.rs index e3766e73bbc29d9548f785018e9f4aa40ab968a1..a9218564b5567d86f097781b224ac0658a0d5221 100644 --- a/crates/toolchain_selector/src/active_toolchain.rs +++ b/crates/toolchain_selector/src/active_toolchain.rs @@ -117,7 +117,7 @@ impl ActiveToolchain { cx: &mut Context, ) { 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( diff --git a/crates/toolchain_selector/src/toolchain_selector.rs b/crates/toolchain_selector/src/toolchain_selector.rs index 7447975aa835c7a4c73068d20b55619f7db5231c..010003cd572f85b1aa8e6d31b0fc0a511f2ebd7f 100644 --- a/crates/toolchain_selector/src/toolchain_selector.rs +++ b/crates/toolchain_selector/src/toolchain_selector.rs @@ -584,11 +584,11 @@ impl ToolchainSelector { window: &mut Window, cx: &mut Context, ) -> Option<()> { - let (_, buffer, _) = workspace + let buffer = workspace .active_item(cx)? .act_as::(cx)? .read(cx) - .active_excerpt(cx)?; + .active_buffer(cx)?; let project = workspace.project().clone(); let language_name = buffer.read(cx).language()?.name(); diff --git a/crates/vim/src/command.rs b/crates/vim/src/command.rs index 362fed2df3543c5571f83db2c964a8c17fcebcb3..fd19a5dc400a24b9f27617c44bd71fe38073c757 100644 --- a/crates/vim/src/command.rs +++ b/crates/vim/src/command.rs @@ -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() { diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index 579af3d314ef114381de892b147d8d0a540656fb..6bf2afd09ae07ff8453a481a8d6e6e6a254e670f 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -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, ) } diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index 118805586118e36269a1f0c1d1d619058133da30..b54a0262744afddbefbd3d4ce5a737dfe3ee7502 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -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) diff --git a/crates/vim/src/object.rs b/crates/vim/src/object.rs index 1c96ba74b455c5d94e53a0ab9c78cd3ae8af5b3c..67b4b16b178e75316eb10b051ab9153737777e3f 100644 --- a/crates/vim/src/object.rs +++ b/crates/vim/src/object.rs @@ -203,33 +203,24 @@ fn find_mini_delimiters( is_valid_delimiter: &DelimiterPredicate, ) -> Option> { 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, close: Range| { - 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::(open.clone())?; + let (_, buffer_close) = + snapshot.range_to_buffer_range::(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, close: Range| { + 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> = 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> = 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> = 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> = 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> = 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> = 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> { - // 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::(); let bracket_filter = |open: Range, close: Range| { - // 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::>(); 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); diff --git a/crates/vim/src/state.rs b/crates/vim/src/state.rs index 2fa5382c542999b8d3cb53ea85bed4c99257a3ea..4dd557199ab9aebe0a2b26438bdaa0e321a956b2 100644 --- a/crates/vim/src/state.rs +++ b/crates/vim/src/state.rs @@ -426,7 +426,7 @@ impl MarksState { name.clone(), buffer .read(cx) - .summaries_for_anchors::(anchors) + .summaries_for_anchors::(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, cx: &mut Context, ) { + 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::(&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() diff --git a/crates/vim/src/test.rs b/crates/vim/src/test.rs index 2d0ec4f69a0aaa93b191933565b9db27d8fb3198..961729e0e24a66a624e30ca7c72bfe5f13e10bca 100644 --- a/crates/vim/src/test.rs +++ b/crates/vim/src/test.rs @@ -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::>(); // 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::>(); editor.fold_buffer(buffer_ids[1], cx); }); diff --git a/crates/workspace/src/notifications.rs b/crates/workspace/src/notifications.rs index b4f683fa6952b9d6f26b8933e010f4c7d2de898c..ce54765e3ff81fde015d465d18b03cea44bbbe8f 100644 --- a/crates/workspace/src/notifications.rs +++ b/crates/workspace/src/notifications.rs @@ -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(); diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 3fa4800afb6088e0d106c8b60a835073978e598c..c5f78eef6c4a7403589cb4e947326f9fe87ec610 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -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:#}")), + ); }, ) }, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index ae05c2c59012b2caf217ac54a80b377aee87f09d..aa692ab39a6084126c9b15b07856549364b13842 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -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:#}")) + }); } }