mirror of
https://github.com/zed-industries/zed.git
synced 2026-05-31 19:05:00 +07:00
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<multi_buffer::Anchor>;
}
```
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<text::Anchor>`.
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<text::Anchor>,
},
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<text::Anchor>;
}
```
- `MultiBufferExcerpt` has been removed in favor of a new
`map_excerpt_ranges` API directly on `MultiBufferSnapshot`.
## Self-Review Checklist
<!-- Check before requesting review: -->
- [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 <conrad.irwin@gmail.com>
Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
Co-authored-by: Jakub Konka <kubkon@jakubkonka.com>
Co-authored-by: Conrad <conrad@zed.dev>
This commit is contained in:
parent
b9eda0f5e3
commit
2a15bf630d
126 changed files with 7230 additions and 7143 deletions
|
|
@ -2616,7 +2616,7 @@ impl AcpThread {
|
|||
text_diff(old_text.as_str(), &content)
|
||||
.into_iter()
|
||||
.map(|(range, replacement)| {
|
||||
(snapshot.anchor_range_around(range), replacement)
|
||||
(snapshot.anchor_range_inside(range), replacement)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -191,7 +191,7 @@ impl Diff {
|
|||
}
|
||||
|
||||
pub fn has_revealed_range(&self, cx: &App) -> bool {
|
||||
self.multibuffer().read(cx).paths().next().is_some()
|
||||
!self.multibuffer().read(cx).is_empty()
|
||||
}
|
||||
|
||||
pub fn needs_update(&self, old_text: &str, new_text: &str, cx: &App) -> bool {
|
||||
|
|
|
|||
|
|
@ -738,6 +738,7 @@ impl ActionLog {
|
|||
let task = if let Some(existing_file_content) = existing_file_content {
|
||||
// Capture the agent's content before restoring existing file content
|
||||
let agent_content = buffer.read(cx).text();
|
||||
let buffer_id = buffer.read(cx).remote_id();
|
||||
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.start_transaction();
|
||||
|
|
@ -750,7 +751,10 @@ impl ActionLog {
|
|||
|
||||
undo_info = Some(PerBufferUndo {
|
||||
buffer: buffer.downgrade(),
|
||||
edits_to_restore: vec![(Anchor::MIN..Anchor::MAX, agent_content)],
|
||||
edits_to_restore: vec![(
|
||||
Anchor::min_for_buffer(buffer_id)..Anchor::max_for_buffer(buffer_id),
|
||||
agent_content,
|
||||
)],
|
||||
status: UndoBufferStatus::Created {
|
||||
had_existing_content: true,
|
||||
},
|
||||
|
|
@ -990,8 +994,8 @@ impl ActionLog {
|
|||
let mut valid_edits = Vec::new();
|
||||
|
||||
for (anchor_range, text_to_restore) in per_buffer_undo.edits_to_restore {
|
||||
if anchor_range.start.buffer_id == Some(buffer.remote_id())
|
||||
&& anchor_range.end.buffer_id == Some(buffer.remote_id())
|
||||
if anchor_range.start.buffer_id == buffer.remote_id()
|
||||
&& anchor_range.end.buffer_id == buffer.remote_id()
|
||||
{
|
||||
valid_edits.push((anchor_range, text_to_restore));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -374,13 +374,13 @@ impl EditAgent {
|
|||
buffer.edit(edits.iter().cloned(), None, cx);
|
||||
let max_edit_end = buffer
|
||||
.summaries_for_anchors::<Point, _>(
|
||||
edits.iter().map(|(range, _)| &range.end),
|
||||
edits.iter().map(|(range, _)| range.end),
|
||||
)
|
||||
.max()
|
||||
.unwrap();
|
||||
let min_edit_start = buffer
|
||||
.summaries_for_anchors::<Point, _>(
|
||||
edits.iter().map(|(range, _)| &range.start),
|
||||
edits.iter().map(|(range, _)| range.start),
|
||||
)
|
||||
.min()
|
||||
.unwrap();
|
||||
|
|
|
|||
|
|
@ -760,7 +760,7 @@ impl EditSession {
|
|||
{
|
||||
if let Some(match_range) = matcher.push(chunk, None) {
|
||||
let anchor_range = self.buffer.read_with(cx, |buffer, _cx| {
|
||||
buffer.anchor_range_between(match_range.clone())
|
||||
buffer.anchor_range_outside(match_range.clone())
|
||||
});
|
||||
self.diff
|
||||
.update(cx, |diff, cx| diff.reveal_range(anchor_range, cx));
|
||||
|
|
@ -795,7 +795,7 @@ impl EditSession {
|
|||
|
||||
let anchor_range = self
|
||||
.buffer
|
||||
.read_with(cx, |buffer, _cx| buffer.anchor_range_between(range.clone()));
|
||||
.read_with(cx, |buffer, _cx| buffer.anchor_range_outside(range.clone()));
|
||||
self.diff
|
||||
.update(cx, |diff, cx| diff.reveal_range(anchor_range, cx));
|
||||
|
||||
|
|
@ -953,7 +953,7 @@ fn apply_char_operations(
|
|||
}
|
||||
CharOperation::Delete { bytes } => {
|
||||
let delete_end = *edit_cursor + bytes;
|
||||
let anchor_range = snapshot.anchor_range_around(*edit_cursor..delete_end);
|
||||
let anchor_range = snapshot.anchor_range_inside(*edit_cursor..delete_end);
|
||||
agent_edit_buffer(&buffer, [(anchor_range, "")], action_log, cx);
|
||||
*edit_cursor = delete_end;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -138,11 +138,12 @@ impl AgentDiffPane {
|
|||
path_a.cmp(&path_b)
|
||||
});
|
||||
|
||||
let mut paths_to_delete = self
|
||||
let mut buffers_to_delete = self
|
||||
.multibuffer
|
||||
.read(cx)
|
||||
.paths()
|
||||
.cloned()
|
||||
.snapshot(cx)
|
||||
.excerpts()
|
||||
.map(|excerpt| excerpt.context.start.buffer_id)
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
for (buffer, diff_handle) in sorted_buffers {
|
||||
|
|
@ -151,7 +152,7 @@ impl AgentDiffPane {
|
|||
}
|
||||
|
||||
let path_key = PathKey::for_buffer(&buffer, cx);
|
||||
paths_to_delete.remove(&path_key);
|
||||
buffers_to_delete.remove(&buffer.read(cx).remote_id());
|
||||
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
|
||||
|
|
@ -168,7 +169,7 @@ impl AgentDiffPane {
|
|||
let (was_empty, is_excerpt_newly_added) =
|
||||
self.multibuffer.update(cx, |multibuffer, cx| {
|
||||
let was_empty = multibuffer.is_empty();
|
||||
let (_, is_excerpt_newly_added) = multibuffer.set_excerpts_for_path(
|
||||
let is_excerpt_newly_added = multibuffer.update_excerpts_for_path(
|
||||
path_key.clone(),
|
||||
buffer.clone(),
|
||||
diff_hunk_ranges,
|
||||
|
|
@ -183,13 +184,13 @@ impl AgentDiffPane {
|
|||
if was_empty {
|
||||
let first_hunk = editor
|
||||
.diff_hunks_in_ranges(
|
||||
&[editor::Anchor::min()..editor::Anchor::max()],
|
||||
&[editor::Anchor::Min..editor::Anchor::Max],
|
||||
&self.multibuffer.read(cx).read(cx),
|
||||
)
|
||||
.next();
|
||||
|
||||
if let Some(first_hunk) = first_hunk {
|
||||
let first_hunk_start = first_hunk.multi_buffer_range().start;
|
||||
let first_hunk_start = first_hunk.multi_buffer_range.start;
|
||||
editor.change_selections(Default::default(), window, cx, |selections| {
|
||||
selections.select_anchor_ranges([first_hunk_start..first_hunk_start]);
|
||||
})
|
||||
|
|
@ -208,8 +209,8 @@ impl AgentDiffPane {
|
|||
}
|
||||
|
||||
self.multibuffer.update(cx, |multibuffer, cx| {
|
||||
for path in paths_to_delete {
|
||||
multibuffer.remove_excerpts_for_path(path, cx);
|
||||
for buffer_id in buffers_to_delete {
|
||||
multibuffer.remove_excerpts_for_buffer(buffer_id, cx);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -239,13 +240,13 @@ impl AgentDiffPane {
|
|||
self.editor.update(cx, |editor, cx| {
|
||||
let first_hunk = editor
|
||||
.diff_hunks_in_ranges(
|
||||
&[position..editor::Anchor::max()],
|
||||
&[position..editor::Anchor::Max],
|
||||
&self.multibuffer.read(cx).read(cx),
|
||||
)
|
||||
.next();
|
||||
|
||||
if let Some(first_hunk) = first_hunk {
|
||||
let first_hunk_start = first_hunk.multi_buffer_range().start;
|
||||
let first_hunk_start = first_hunk.multi_buffer_range.start;
|
||||
editor.change_selections(Default::default(), window, cx, |selections| {
|
||||
selections.select_anchor_ranges([first_hunk_start..first_hunk_start]);
|
||||
})
|
||||
|
|
@ -282,7 +283,7 @@ impl AgentDiffPane {
|
|||
editor,
|
||||
&snapshot,
|
||||
&self.thread,
|
||||
vec![editor::Anchor::min()..editor::Anchor::max()],
|
||||
vec![editor::Anchor::Min..editor::Anchor::Max],
|
||||
self.workspace.clone(),
|
||||
window,
|
||||
cx,
|
||||
|
|
@ -451,20 +452,20 @@ fn update_editor_selection(
|
|||
diff_hunks
|
||||
.last()
|
||||
.and_then(|last_kept_hunk| {
|
||||
let last_kept_hunk_end = last_kept_hunk.multi_buffer_range().end;
|
||||
let last_kept_hunk_end = last_kept_hunk.multi_buffer_range.end;
|
||||
editor
|
||||
.diff_hunks_in_ranges(
|
||||
&[last_kept_hunk_end..editor::Anchor::max()],
|
||||
&[last_kept_hunk_end..editor::Anchor::Max],
|
||||
buffer_snapshot,
|
||||
)
|
||||
.nth(1)
|
||||
})
|
||||
.or_else(|| {
|
||||
let first_kept_hunk = diff_hunks.first()?;
|
||||
let first_kept_hunk_start = first_kept_hunk.multi_buffer_range().start;
|
||||
let first_kept_hunk_start = first_kept_hunk.multi_buffer_range.start;
|
||||
editor
|
||||
.diff_hunks_in_ranges(
|
||||
&[editor::Anchor::min()..first_kept_hunk_start],
|
||||
&[editor::Anchor::Min..first_kept_hunk_start],
|
||||
buffer_snapshot,
|
||||
)
|
||||
.next()
|
||||
|
|
@ -473,7 +474,7 @@ fn update_editor_selection(
|
|||
|
||||
if let Some(target_hunk) = target_hunk {
|
||||
editor.change_selections(Default::default(), window, cx, |selections| {
|
||||
let next_hunk_start = target_hunk.multi_buffer_range().start;
|
||||
let next_hunk_start = target_hunk.multi_buffer_range.start;
|
||||
selections.select_anchor_ranges([next_hunk_start..next_hunk_start]);
|
||||
})
|
||||
}
|
||||
|
|
@ -1567,7 +1568,7 @@ impl AgentDiff {
|
|||
editor.update(cx, |editor, cx| {
|
||||
let snapshot = multibuffer.read(cx).snapshot(cx);
|
||||
if let Some(first_hunk) = snapshot.diff_hunks().next() {
|
||||
let first_hunk_start = first_hunk.multi_buffer_range().start;
|
||||
let first_hunk_start = first_hunk.multi_buffer_range.start;
|
||||
|
||||
editor.change_selections(
|
||||
SelectionEffects::scroll(Autoscroll::center()),
|
||||
|
|
@ -1648,7 +1649,7 @@ impl AgentDiff {
|
|||
editor,
|
||||
&snapshot,
|
||||
thread,
|
||||
vec![editor::Anchor::min()..editor::Anchor::max()],
|
||||
vec![editor::Anchor::Min..editor::Anchor::Max],
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
|
|
@ -1669,7 +1670,7 @@ impl AgentDiff {
|
|||
editor,
|
||||
&snapshot,
|
||||
thread,
|
||||
vec![editor::Anchor::min()..editor::Anchor::max()],
|
||||
vec![editor::Anchor::Min..editor::Anchor::Max],
|
||||
workspace.clone(),
|
||||
window,
|
||||
cx,
|
||||
|
|
|
|||
|
|
@ -303,7 +303,7 @@ impl CodegenAlternative {
|
|||
let snapshot = buffer.read(cx).snapshot(cx);
|
||||
|
||||
let (old_buffer, _, _) = snapshot
|
||||
.range_to_buffer_ranges(range.start..=range.end)
|
||||
.range_to_buffer_ranges(range.start..range.end)
|
||||
.pop()
|
||||
.unwrap();
|
||||
let old_buffer = cx.new(|cx| {
|
||||
|
|
@ -684,7 +684,7 @@ impl CodegenAlternative {
|
|||
let language_name = {
|
||||
let multibuffer = self.buffer.read(cx);
|
||||
let snapshot = multibuffer.snapshot(cx);
|
||||
let ranges = snapshot.range_to_buffer_ranges(self.range.start..=self.range.end);
|
||||
let ranges = snapshot.range_to_buffer_ranges(self.range.start..self.range.end);
|
||||
ranges
|
||||
.first()
|
||||
.and_then(|(buffer, _, _)| buffer.language())
|
||||
|
|
|
|||
|
|
@ -9,9 +9,7 @@ use crate::ThreadHistory;
|
|||
use acp_thread::MentionUri;
|
||||
use agent_client_protocol as acp;
|
||||
use anyhow::Result;
|
||||
use editor::{
|
||||
CompletionProvider, Editor, ExcerptId, code_context_menus::COMPLETION_MENU_MAX_WIDTH,
|
||||
};
|
||||
use editor::{CompletionProvider, Editor, code_context_menus::COMPLETION_MENU_MAX_WIDTH};
|
||||
use futures::FutureExt as _;
|
||||
use fuzzy::{PathMatch, StringMatch, StringMatchCandidate};
|
||||
use gpui::{App, BackgroundExecutor, Entity, SharedString, Task, WeakEntity};
|
||||
|
|
@ -621,7 +619,7 @@ impl<T: PromptCompletionProviderDelegate> PromptCompletionProvider<T> {
|
|||
for (terminal_text, terminal_range) in terminal_ranges {
|
||||
let snapshot = editor.read(cx).buffer().read(cx).snapshot(cx);
|
||||
let Some(start) =
|
||||
snapshot.as_singleton_anchor(source_range.start)
|
||||
snapshot.anchor_in_excerpt(source_range.start)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
|
@ -1235,7 +1233,6 @@ impl<T: PromptCompletionProviderDelegate> PromptCompletionProvider<T> {
|
|||
impl<T: PromptCompletionProviderDelegate> CompletionProvider for PromptCompletionProvider<T> {
|
||||
fn completions(
|
||||
&self,
|
||||
_excerpt_id: ExcerptId,
|
||||
buffer: &Entity<Buffer>,
|
||||
buffer_position: Anchor,
|
||||
_trigger: CompletionContext,
|
||||
|
|
|
|||
|
|
@ -7126,17 +7126,10 @@ impl ThreadView {
|
|||
};
|
||||
|
||||
active_editor.update_in(cx, |editor, window, cx| {
|
||||
let singleton = editor
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.read(cx)
|
||||
.as_singleton()
|
||||
.map(|(a, b, _)| (a, b));
|
||||
if let Some((excerpt_id, buffer_id)) = singleton
|
||||
&& let Some(agent_buffer) = agent_location.buffer.upgrade()
|
||||
&& agent_buffer.read(cx).remote_id() == buffer_id
|
||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
if snapshot.as_singleton().is_some()
|
||||
&& let Some(anchor) = snapshot.anchor_in_excerpt(agent_location.position)
|
||||
{
|
||||
let anchor = editor::Anchor::in_buffer(excerpt_id, agent_location.position);
|
||||
editor.change_selections(Default::default(), window, cx, |selections| {
|
||||
selections.select_anchor_ranges([anchor..anchor]);
|
||||
})
|
||||
|
|
|
|||
|
|
@ -27,8 +27,8 @@ use editor::RowExt;
|
|||
use editor::SelectionEffects;
|
||||
use editor::scroll::ScrollOffset;
|
||||
use editor::{
|
||||
Anchor, AnchorRangeExt, CodeActionProvider, Editor, EditorEvent, ExcerptId, HighlightKey,
|
||||
MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint,
|
||||
Anchor, AnchorRangeExt, CodeActionProvider, Editor, EditorEvent, HighlightKey, MultiBuffer,
|
||||
MultiBufferSnapshot, ToOffset as _, ToPoint,
|
||||
actions::SelectAll,
|
||||
display_map::{
|
||||
BlockContext, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, EditorMargins,
|
||||
|
|
@ -443,15 +443,17 @@ impl InlineAssistant {
|
|||
let newest_selection = newest_selection.unwrap();
|
||||
|
||||
let mut codegen_ranges = Vec::new();
|
||||
for (buffer, buffer_range, excerpt_id) in
|
||||
snapshot.ranges_to_buffer_ranges(selections.iter().map(|selection| {
|
||||
snapshot.anchor_before(selection.start)..snapshot.anchor_after(selection.end)
|
||||
}))
|
||||
for (buffer, buffer_range, _) in selections
|
||||
.iter()
|
||||
.flat_map(|selection| snapshot.range_to_buffer_ranges(selection.start..selection.end))
|
||||
{
|
||||
let anchor_range = Anchor::range_in_buffer(
|
||||
excerpt_id,
|
||||
buffer.anchor_before(buffer_range.start)..buffer.anchor_after(buffer_range.end),
|
||||
);
|
||||
let (Some(start), Some(end)) = (
|
||||
snapshot.anchor_in_buffer(buffer.anchor_before(buffer_range.start)),
|
||||
snapshot.anchor_in_buffer(buffer.anchor_after(buffer_range.end)),
|
||||
) else {
|
||||
continue;
|
||||
};
|
||||
let anchor_range = start..end;
|
||||
|
||||
codegen_ranges.push(anchor_range);
|
||||
|
||||
|
|
@ -982,8 +984,7 @@ impl InlineAssistant {
|
|||
match event {
|
||||
EditorEvent::Edited { transaction_id } => {
|
||||
let buffer = editor.read(cx).buffer().read(cx);
|
||||
let edited_ranges =
|
||||
buffer.edited_ranges_for_transaction::<MultiBufferOffset>(*transaction_id, cx);
|
||||
let edited_ranges = buffer.edited_ranges_for_transaction(*transaction_id, cx);
|
||||
let snapshot = buffer.snapshot(cx);
|
||||
|
||||
for assist_id in editor_assists.assist_ids.clone() {
|
||||
|
|
@ -1089,7 +1090,7 @@ impl InlineAssistant {
|
|||
let multibuffer = editor.read(cx).buffer().read(cx);
|
||||
let snapshot = multibuffer.snapshot(cx);
|
||||
let ranges =
|
||||
snapshot.range_to_buffer_ranges(assist.range.start..=assist.range.end);
|
||||
snapshot.range_to_buffer_ranges(assist.range.start..assist.range.end);
|
||||
ranges
|
||||
.first()
|
||||
.and_then(|(buffer, _, _)| buffer.language())
|
||||
|
|
@ -1496,10 +1497,10 @@ impl InlineAssistant {
|
|||
|
||||
let mut new_blocks = Vec::new();
|
||||
for (new_row, old_row_range) in deleted_row_ranges {
|
||||
let (_, start, _) = old_snapshot
|
||||
let (_, start) = old_snapshot
|
||||
.point_to_buffer_point(Point::new(*old_row_range.start(), 0))
|
||||
.unwrap();
|
||||
let (_, end, _) = old_snapshot
|
||||
let (_, end) = old_snapshot
|
||||
.point_to_buffer_point(Point::new(
|
||||
*old_row_range.end(),
|
||||
old_snapshot.line_len(MultiBufferRow(*old_row_range.end())),
|
||||
|
|
@ -1530,7 +1531,7 @@ impl InlineAssistant {
|
|||
editor.set_read_only(true);
|
||||
editor.set_show_edit_predictions(Some(false), window, cx);
|
||||
editor.highlight_rows::<DeletedLines>(
|
||||
Anchor::min()..Anchor::max(),
|
||||
Anchor::Min..Anchor::Max,
|
||||
cx.theme().status().deleted_background,
|
||||
Default::default(),
|
||||
cx,
|
||||
|
|
@ -1938,9 +1939,8 @@ impl CodeActionProvider for AssistantCodeActionProvider {
|
|||
|
||||
fn apply_code_action(
|
||||
&self,
|
||||
buffer: Entity<Buffer>,
|
||||
_buffer: Entity<Buffer>,
|
||||
action: CodeAction,
|
||||
excerpt_id: ExcerptId,
|
||||
_push_to_history: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
|
|
@ -1970,31 +1970,8 @@ impl CodeActionProvider for AssistantCodeActionProvider {
|
|||
let range = editor
|
||||
.update(cx, |editor, cx| {
|
||||
editor.buffer().update(cx, |multibuffer, cx| {
|
||||
let buffer = buffer.read(cx);
|
||||
let multibuffer_snapshot = multibuffer.read(cx);
|
||||
|
||||
let old_context_range =
|
||||
multibuffer_snapshot.context_range_for_excerpt(excerpt_id)?;
|
||||
let mut new_context_range = old_context_range.clone();
|
||||
if action
|
||||
.range
|
||||
.start
|
||||
.cmp(&old_context_range.start, buffer)
|
||||
.is_lt()
|
||||
{
|
||||
new_context_range.start = action.range.start;
|
||||
}
|
||||
if action.range.end.cmp(&old_context_range.end, buffer).is_gt() {
|
||||
new_context_range.end = action.range.end;
|
||||
}
|
||||
drop(multibuffer_snapshot);
|
||||
|
||||
if new_context_range != old_context_range {
|
||||
multibuffer.resize_excerpt(excerpt_id, new_context_range, cx);
|
||||
}
|
||||
|
||||
let multibuffer_snapshot = multibuffer.read(cx);
|
||||
multibuffer_snapshot.anchor_range_in_excerpt(excerpt_id, action.range)
|
||||
multibuffer_snapshot.buffer_anchor_range_to_anchor_range(action.range)
|
||||
})
|
||||
})
|
||||
.context("invalid range")?;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use agent_servers::{AgentServer, AgentServerDelegate};
|
|||
use anyhow::{Context as _, Result, anyhow};
|
||||
use collections::{HashMap, HashSet};
|
||||
use editor::{
|
||||
Anchor, Editor, EditorSnapshot, ExcerptId, FoldPlaceholder, ToOffset,
|
||||
Anchor, Editor, EditorSnapshot, FoldPlaceholder, ToOffset,
|
||||
display_map::{Crease, CreaseId, CreaseMetadata, FoldId},
|
||||
scroll::Autoscroll,
|
||||
};
|
||||
|
|
@ -204,10 +204,9 @@ impl MentionSet {
|
|||
};
|
||||
|
||||
let snapshot = editor.update(cx, |editor, cx| editor.snapshot(window, cx));
|
||||
let Some(start_anchor) = snapshot.buffer_snapshot().as_singleton_anchor(start) else {
|
||||
let Some(start_anchor) = snapshot.buffer_snapshot().anchor_in_excerpt(start) else {
|
||||
return Task::ready(());
|
||||
};
|
||||
let excerpt_id = start_anchor.excerpt_id;
|
||||
let end_anchor = snapshot.buffer_snapshot().anchor_before(
|
||||
start_anchor.to_offset(&snapshot.buffer_snapshot()) + content_len + 1usize,
|
||||
);
|
||||
|
|
@ -234,7 +233,6 @@ impl MentionSet {
|
|||
})
|
||||
.shared();
|
||||
insert_crease_for_mention(
|
||||
excerpt_id,
|
||||
start,
|
||||
content_len,
|
||||
mention_uri.name().into(),
|
||||
|
|
@ -249,7 +247,6 @@ impl MentionSet {
|
|||
)
|
||||
} else {
|
||||
insert_crease_for_mention(
|
||||
excerpt_id,
|
||||
start,
|
||||
content_len,
|
||||
crease_text,
|
||||
|
|
@ -468,7 +465,7 @@ impl MentionSet {
|
|||
};
|
||||
|
||||
let snapshot = editor.read(cx).buffer().read(cx).snapshot(cx);
|
||||
let Some(start) = snapshot.as_singleton_anchor(source_range.start) else {
|
||||
let Some(start) = snapshot.anchor_in_excerpt(source_range.start) else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
|
@ -745,19 +742,17 @@ pub(crate) async fn insert_images_as_context(
|
|||
let replacement_text = MentionUri::PastedImage.as_link().to_string();
|
||||
|
||||
for (image, name) in images {
|
||||
let Some((excerpt_id, text_anchor, multibuffer_anchor)) = editor
|
||||
let Some((text_anchor, multibuffer_anchor)) = editor
|
||||
.update_in(cx, |editor, window, cx| {
|
||||
let snapshot = editor.snapshot(window, cx);
|
||||
let (excerpt_id, _, buffer_snapshot) =
|
||||
snapshot.buffer_snapshot().as_singleton().unwrap();
|
||||
|
||||
let cursor_anchor = editor.selections.newest_anchor().start.text_anchor;
|
||||
let text_anchor = cursor_anchor.bias_left(&buffer_snapshot);
|
||||
let multibuffer_anchor = snapshot
|
||||
let (cursor_anchor, buffer_snapshot) = snapshot
|
||||
.buffer_snapshot()
|
||||
.anchor_in_excerpt(excerpt_id, text_anchor);
|
||||
.anchor_to_buffer_anchor(editor.selections.newest_anchor().start)
|
||||
.unwrap();
|
||||
let text_anchor = cursor_anchor.bias_left(buffer_snapshot);
|
||||
let multibuffer_anchor = snapshot.buffer_snapshot().anchor_in_excerpt(text_anchor);
|
||||
editor.insert(&format!("{replacement_text} "), window, cx);
|
||||
(excerpt_id, text_anchor, multibuffer_anchor)
|
||||
(text_anchor, multibuffer_anchor)
|
||||
})
|
||||
.ok()
|
||||
else {
|
||||
|
|
@ -775,7 +770,6 @@ pub(crate) async fn insert_images_as_context(
|
|||
let image = Arc::new(image);
|
||||
let Ok(Some((crease_id, tx))) = cx.update(|window, cx| {
|
||||
insert_crease_for_mention(
|
||||
excerpt_id,
|
||||
text_anchor,
|
||||
content_len,
|
||||
name.clone(),
|
||||
|
|
@ -909,7 +903,6 @@ pub(crate) fn paste_images_as_context(
|
|||
}
|
||||
|
||||
pub(crate) fn insert_crease_for_mention(
|
||||
excerpt_id: ExcerptId,
|
||||
anchor: text::Anchor,
|
||||
content_len: usize,
|
||||
crease_label: SharedString,
|
||||
|
|
@ -927,7 +920,7 @@ pub(crate) fn insert_crease_for_mention(
|
|||
let crease_id = editor.update(cx, |editor, cx| {
|
||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
|
||||
let start = snapshot.anchor_in_excerpt(excerpt_id, anchor)?;
|
||||
let start = snapshot.anchor_in_excerpt(anchor)?;
|
||||
|
||||
let start = start.bias_right(&snapshot);
|
||||
let end = snapshot.anchor_before(start.to_offset(&snapshot) + content_len);
|
||||
|
|
|
|||
|
|
@ -203,12 +203,10 @@ fn insert_mention_for_project_path(
|
|||
MentionInsertPosition::AtCursor => editor.update(cx, |editor, cx| {
|
||||
let buffer = editor.buffer().read(cx);
|
||||
let snapshot = buffer.snapshot(cx);
|
||||
let (_, _, buffer_snapshot) = snapshot.as_singleton()?;
|
||||
let text_anchor = editor
|
||||
.selections
|
||||
.newest_anchor()
|
||||
.start
|
||||
.text_anchor
|
||||
let buffer_snapshot = snapshot.as_singleton()?;
|
||||
let text_anchor = snapshot
|
||||
.anchor_to_buffer_anchor(editor.selections.newest_anchor().start)?
|
||||
.0
|
||||
.bias_left(&buffer_snapshot);
|
||||
|
||||
editor.insert(&mention_text, window, cx);
|
||||
|
|
@ -224,7 +222,7 @@ fn insert_mention_for_project_path(
|
|||
editor.update(cx, |editor, cx| {
|
||||
editor.edit(
|
||||
[(
|
||||
multi_buffer::Anchor::max()..multi_buffer::Anchor::max(),
|
||||
multi_buffer::Anchor::Max..multi_buffer::Anchor::Max,
|
||||
new_text,
|
||||
)],
|
||||
cx,
|
||||
|
|
@ -603,7 +601,7 @@ impl MessageEditor {
|
|||
COMMAND_HINT_INLAY_ID,
|
||||
hint_pos,
|
||||
&InlayHint {
|
||||
position: hint_pos.text_anchor,
|
||||
position: snapshot.anchor_to_buffer_anchor(hint_pos)?.0,
|
||||
label: InlayHintLabel::String(hint),
|
||||
kind: Some(InlayHintKind::Parameter),
|
||||
padding_left: false,
|
||||
|
|
@ -640,12 +638,11 @@ impl MessageEditor {
|
|||
|
||||
let start = self.editor.update(cx, |editor, cx| {
|
||||
editor.set_text(content, window, cx);
|
||||
editor
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.snapshot(cx)
|
||||
.anchor_before(Point::zero())
|
||||
.text_anchor
|
||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
snapshot
|
||||
.anchor_to_buffer_anchor(snapshot.anchor_before(Point::zero()))
|
||||
.unwrap()
|
||||
.0
|
||||
});
|
||||
|
||||
let supports_images = self.session_capabilities.read().supports_images();
|
||||
|
|
@ -999,13 +996,10 @@ impl MessageEditor {
|
|||
|
||||
if should_insert_creases && let Some(selections) = editor_clipboard_selections {
|
||||
cx.stop_propagation();
|
||||
let insertion_target = self
|
||||
.editor
|
||||
.read(cx)
|
||||
.selections
|
||||
.newest_anchor()
|
||||
.start
|
||||
.text_anchor;
|
||||
let snapshot = self.editor.read(cx).buffer().read(cx).snapshot(cx);
|
||||
let (insertion_target, _) = snapshot
|
||||
.anchor_to_buffer_anchor(self.editor.read(cx).selections.newest_anchor().start)
|
||||
.unwrap();
|
||||
|
||||
let project = workspace.read(cx).project().clone();
|
||||
for selection in selections {
|
||||
|
|
@ -1021,21 +1015,19 @@ impl MessageEditor {
|
|||
};
|
||||
|
||||
let mention_text = mention_uri.as_link().to_string();
|
||||
let (excerpt_id, text_anchor, content_len) =
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
let buffer = editor.buffer().read(cx);
|
||||
let snapshot = buffer.snapshot(cx);
|
||||
let (excerpt_id, _, buffer_snapshot) = snapshot.as_singleton().unwrap();
|
||||
let text_anchor = insertion_target.bias_left(&buffer_snapshot);
|
||||
let (text_anchor, content_len) = self.editor.update(cx, |editor, cx| {
|
||||
let buffer = editor.buffer().read(cx);
|
||||
let snapshot = buffer.snapshot(cx);
|
||||
let buffer_snapshot = snapshot.as_singleton().unwrap();
|
||||
let text_anchor = insertion_target.bias_left(&buffer_snapshot);
|
||||
|
||||
editor.insert(&mention_text, window, cx);
|
||||
editor.insert(" ", window, cx);
|
||||
editor.insert(&mention_text, window, cx);
|
||||
editor.insert(" ", window, cx);
|
||||
|
||||
(excerpt_id, text_anchor, mention_text.len())
|
||||
});
|
||||
(text_anchor, mention_text.len())
|
||||
});
|
||||
|
||||
let Some((crease_id, tx)) = insert_crease_for_mention(
|
||||
excerpt_id,
|
||||
text_anchor,
|
||||
content_len,
|
||||
crease_text.into(),
|
||||
|
|
@ -1145,8 +1137,7 @@ impl MessageEditor {
|
|||
|
||||
for (anchor, content_len, mention_uri) in all_mentions {
|
||||
let Some((crease_id, tx)) = insert_crease_for_mention(
|
||||
anchor.excerpt_id,
|
||||
anchor.text_anchor,
|
||||
snapshot.anchor_to_buffer_anchor(anchor).unwrap().0,
|
||||
content_len,
|
||||
mention_uri.name().into(),
|
||||
mention_uri.icon_path(cx),
|
||||
|
|
@ -1339,25 +1330,23 @@ impl MessageEditor {
|
|||
};
|
||||
let mention_text = mention_uri.as_link().to_string();
|
||||
|
||||
let (excerpt_id, text_anchor, content_len) = editor.update(cx, |editor, cx| {
|
||||
let (text_anchor, content_len) = editor.update(cx, |editor, cx| {
|
||||
let buffer = editor.buffer().read(cx);
|
||||
let snapshot = buffer.snapshot(cx);
|
||||
let (excerpt_id, _, buffer_snapshot) = snapshot.as_singleton().unwrap();
|
||||
let text_anchor = editor
|
||||
.selections
|
||||
.newest_anchor()
|
||||
.start
|
||||
.text_anchor
|
||||
let buffer_snapshot = snapshot.as_singleton().unwrap();
|
||||
let text_anchor = snapshot
|
||||
.anchor_to_buffer_anchor(editor.selections.newest_anchor().start)
|
||||
.unwrap()
|
||||
.0
|
||||
.bias_left(&buffer_snapshot);
|
||||
|
||||
editor.insert(&mention_text, window, cx);
|
||||
editor.insert(" ", window, cx);
|
||||
|
||||
(excerpt_id, text_anchor, mention_text.len())
|
||||
(text_anchor, mention_text.len())
|
||||
});
|
||||
|
||||
let Some((crease_id, tx)) = insert_crease_for_mention(
|
||||
excerpt_id,
|
||||
text_anchor,
|
||||
content_len,
|
||||
mention_uri.name().into(),
|
||||
|
|
@ -1700,8 +1689,7 @@ impl MessageEditor {
|
|||
let adjusted_start = insertion_start + range.start;
|
||||
let anchor = snapshot.anchor_before(MultiBufferOffset(adjusted_start));
|
||||
let Some((crease_id, tx)) = insert_crease_for_mention(
|
||||
anchor.excerpt_id,
|
||||
anchor.text_anchor,
|
||||
snapshot.anchor_to_buffer_anchor(anchor).unwrap().0,
|
||||
range.end - range.start,
|
||||
mention_uri.name().into(),
|
||||
mention_uri.icon_path(cx),
|
||||
|
|
@ -2077,23 +2065,13 @@ mod tests {
|
|||
|
||||
cx.run_until_parked();
|
||||
|
||||
let excerpt_id = editor.update(cx, |editor, cx| {
|
||||
editor
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.excerpt_ids()
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap()
|
||||
});
|
||||
let completions = editor.update_in(cx, |editor, window, cx| {
|
||||
editor.set_text("Hello @file ", window, cx);
|
||||
let buffer = editor.buffer().read(cx).as_singleton().unwrap();
|
||||
let completion_provider = editor.completion_provider().unwrap();
|
||||
completion_provider.completions(
|
||||
excerpt_id,
|
||||
&buffer,
|
||||
text::Anchor::MAX,
|
||||
text::Anchor::max_for_buffer(buffer.read(cx).remote_id()),
|
||||
CompletionContext {
|
||||
trigger_kind: CompletionTriggerKind::TRIGGER_CHARACTER,
|
||||
trigger_character: Some("@".into()),
|
||||
|
|
@ -2114,7 +2092,7 @@ mod tests {
|
|||
editor.update_in(cx, |editor, window, cx| {
|
||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
let range = snapshot
|
||||
.anchor_range_in_excerpt(excerpt_id, completion.replace_range)
|
||||
.buffer_anchor_range_to_anchor_range(completion.replace_range)
|
||||
.unwrap();
|
||||
editor.edit([(range, completion.new_text)], cx);
|
||||
(completion.confirm.unwrap())(CompletionIntent::Complete, window, cx);
|
||||
|
|
|
|||
|
|
@ -171,9 +171,9 @@ impl sum_tree::Item for PendingHunk {
|
|||
impl sum_tree::Summary for DiffHunkSummary {
|
||||
type Context<'a> = &'a text::BufferSnapshot;
|
||||
|
||||
fn zero(_cx: Self::Context<'_>) -> Self {
|
||||
fn zero(buffer: &text::BufferSnapshot) -> Self {
|
||||
DiffHunkSummary {
|
||||
buffer_range: Anchor::MIN..Anchor::MIN,
|
||||
buffer_range: Anchor::min_min_range_for_buffer(buffer.remote_id()),
|
||||
diff_base_byte_range: 0..0,
|
||||
added_rows: 0,
|
||||
removed_rows: 0,
|
||||
|
|
@ -248,6 +248,10 @@ impl BufferDiffSnapshot {
|
|||
buffer_diff.update(cx, |buffer_diff, cx| buffer_diff.snapshot(cx))
|
||||
}
|
||||
|
||||
pub fn buffer_id(&self) -> BufferId {
|
||||
self.inner.buffer_snapshot.remote_id()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.inner.hunks.is_empty()
|
||||
}
|
||||
|
|
@ -953,7 +957,7 @@ impl BufferDiffInner<language::BufferSnapshot> {
|
|||
.flat_map(move |hunk| {
|
||||
[
|
||||
(
|
||||
&hunk.buffer_range.start,
|
||||
hunk.buffer_range.start,
|
||||
(
|
||||
hunk.buffer_range.start,
|
||||
hunk.diff_base_byte_range.start,
|
||||
|
|
@ -961,7 +965,7 @@ impl BufferDiffInner<language::BufferSnapshot> {
|
|||
),
|
||||
),
|
||||
(
|
||||
&hunk.buffer_range.end,
|
||||
hunk.buffer_range.end,
|
||||
(hunk.buffer_range.end, hunk.diff_base_byte_range.end, hunk),
|
||||
),
|
||||
]
|
||||
|
|
@ -1653,7 +1657,7 @@ impl BufferDiff {
|
|||
) {
|
||||
let hunks = self
|
||||
.snapshot(cx)
|
||||
.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, buffer)
|
||||
.hunks_intersecting_range(Anchor::min_max_range_for_buffer(buffer.remote_id()), buffer)
|
||||
.collect::<Vec<_>>();
|
||||
let Some(secondary) = self.secondary_diff.clone() else {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ use language::LanguageRegistry;
|
|||
use livekit::{LocalTrackPublication, ParticipantIdentity, RoomEvent};
|
||||
use livekit_client::{self as livekit, AudioStream, TrackSid};
|
||||
use postage::{sink::Sink, stream::Stream, watch};
|
||||
use project::Project;
|
||||
use project::{CURRENT_PROJECT_FEATURES, Project};
|
||||
use settings::Settings as _;
|
||||
use std::sync::atomic::AtomicU64;
|
||||
use std::{future::Future, mem, rc::Rc, sync::Arc, time::Duration, time::Instant};
|
||||
|
|
@ -1237,6 +1237,10 @@ impl Room {
|
|||
worktrees: project.read(cx).worktree_metadata_protos(cx),
|
||||
is_ssh_project: project.read(cx).is_via_remote_server(),
|
||||
windows_paths: Some(project.read(cx).path_style(cx) == PathStyle::Windows),
|
||||
features: CURRENT_PROJECT_FEATURES
|
||||
.iter()
|
||||
.map(|s| s.to_string())
|
||||
.collect(),
|
||||
});
|
||||
|
||||
cx.spawn(async move |this, cx| {
|
||||
|
|
|
|||
|
|
@ -2141,11 +2141,13 @@ mod tests {
|
|||
project_id: 1,
|
||||
committer_name: None,
|
||||
committer_email: None,
|
||||
features: Vec::new(),
|
||||
});
|
||||
server.send(proto::JoinProject {
|
||||
project_id: 2,
|
||||
committer_name: None,
|
||||
committer_email: None,
|
||||
features: Vec::new(),
|
||||
});
|
||||
done_rx1.recv().await.unwrap();
|
||||
done_rx2.recv().await.unwrap();
|
||||
|
|
|
|||
|
|
@ -48,7 +48,8 @@ CREATE TABLE "projects" (
|
|||
"host_connection_id" INTEGER,
|
||||
"host_connection_server_id" INTEGER REFERENCES servers (id) ON DELETE CASCADE,
|
||||
"unregistered" BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
"windows_paths" BOOLEAN NOT NULL DEFAULT FALSE
|
||||
"windows_paths" BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
"features" TEXT NOT NULL DEFAULT ''
|
||||
);
|
||||
|
||||
CREATE INDEX "index_projects_on_host_connection_server_id" ON "projects" ("host_connection_server_id");
|
||||
|
|
|
|||
|
|
@ -332,7 +332,8 @@ CREATE TABLE public.projects (
|
|||
room_id integer,
|
||||
host_connection_id integer,
|
||||
host_connection_server_id integer,
|
||||
windows_paths boolean DEFAULT false
|
||||
windows_paths boolean DEFAULT false,
|
||||
features text NOT NULL DEFAULT ''
|
||||
);
|
||||
|
||||
CREATE SEQUENCE public.projects_id_seq
|
||||
|
|
|
|||
|
|
@ -589,6 +589,7 @@ pub struct Project {
|
|||
pub repositories: Vec<proto::UpdateRepository>,
|
||||
pub language_servers: Vec<LanguageServer>,
|
||||
pub path_style: PathStyle,
|
||||
pub features: Vec<String>,
|
||||
}
|
||||
|
||||
pub struct ProjectCollaborator {
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ impl Database {
|
|||
worktrees: &[proto::WorktreeMetadata],
|
||||
is_ssh_project: bool,
|
||||
windows_paths: bool,
|
||||
features: &[String],
|
||||
) -> Result<TransactionGuard<(ProjectId, proto::Room)>> {
|
||||
self.room_transaction(room_id, |tx| async move {
|
||||
let participant = room_participant::Entity::find()
|
||||
|
|
@ -71,6 +72,7 @@ impl Database {
|
|||
))),
|
||||
id: ActiveValue::NotSet,
|
||||
windows_paths: ActiveValue::set(windows_paths),
|
||||
features: ActiveValue::set(serde_json::to_string(features).unwrap()),
|
||||
}
|
||||
.insert(&*tx)
|
||||
.await?;
|
||||
|
|
@ -948,6 +950,7 @@ impl Database {
|
|||
} else {
|
||||
PathStyle::Posix
|
||||
};
|
||||
let features: Vec<String> = serde_json::from_str(&project.features).unwrap_or_default();
|
||||
|
||||
let project = Project {
|
||||
id: project.id,
|
||||
|
|
@ -977,6 +980,7 @@ impl Database {
|
|||
})
|
||||
.collect(),
|
||||
path_style,
|
||||
features,
|
||||
};
|
||||
Ok((project, replica_id as ReplicaId))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ pub struct Model {
|
|||
pub host_connection_id: Option<i32>,
|
||||
pub host_connection_server_id: Option<ServerId>,
|
||||
pub windows_paths: bool,
|
||||
pub features: String,
|
||||
}
|
||||
|
||||
impl Model {
|
||||
|
|
|
|||
|
|
@ -1775,6 +1775,7 @@ async fn share_project(
|
|||
&request.worktrees,
|
||||
request.is_ssh_project,
|
||||
request.windows_paths.unwrap_or(false),
|
||||
&request.features,
|
||||
)
|
||||
.await?;
|
||||
response.send(proto::ShareProjectResponse {
|
||||
|
|
@ -1840,6 +1841,28 @@ async fn join_project(
|
|||
tracing::info!(%project_id, "join project");
|
||||
|
||||
let db = session.db().await;
|
||||
let project_model = db.get_project(project_id).await?;
|
||||
let host_features: Vec<String> =
|
||||
serde_json::from_str(&project_model.features).unwrap_or_default();
|
||||
let guest_features: HashSet<_> = request.features.iter().collect();
|
||||
let host_features_set: HashSet<_> = host_features.iter().collect();
|
||||
if guest_features != host_features_set {
|
||||
let host_connection_id = project_model.host_connection()?;
|
||||
let mut pool = session.connection_pool().await;
|
||||
let host_version = pool
|
||||
.connection(host_connection_id)
|
||||
.map(|c| c.zed_version.to_string());
|
||||
let guest_version = pool
|
||||
.connection(session.connection_id)
|
||||
.map(|c| c.zed_version.to_string());
|
||||
drop(pool);
|
||||
Err(anyhow!(
|
||||
"The host (v{}) and guest (v{}) are using incompatible versions of Zed. The peer with the older version must update to collaborate.",
|
||||
host_version.as_deref().unwrap_or("unknown"),
|
||||
guest_version.as_deref().unwrap_or("unknown"),
|
||||
))?;
|
||||
}
|
||||
|
||||
let (project, replica_id) = &mut *db
|
||||
.join_project(
|
||||
project_id,
|
||||
|
|
@ -1850,6 +1873,7 @@ async fn join_project(
|
|||
)
|
||||
.await?;
|
||||
drop(db);
|
||||
|
||||
tracing::info!(%project_id, "join remote project");
|
||||
let collaborators = project
|
||||
.collaborators
|
||||
|
|
@ -1909,6 +1933,7 @@ async fn join_project(
|
|||
language_server_capabilities,
|
||||
role: project.role.into(),
|
||||
windows_paths: project.path_style == PathStyle::Windows,
|
||||
features: project.features.clone(),
|
||||
})?;
|
||||
|
||||
for (worktree_id, worktree) in mem::take(&mut project.worktrees) {
|
||||
|
|
|
|||
|
|
@ -313,7 +313,7 @@ fn assert_remote_selections(
|
|||
let snapshot = editor.snapshot(window, cx);
|
||||
let hub = editor.collaboration_hub().unwrap();
|
||||
let collaborators = hub.collaborators(cx);
|
||||
let range = Anchor::min()..Anchor::max();
|
||||
let range = Anchor::Min..Anchor::Max;
|
||||
let remote_selections = snapshot
|
||||
.remote_selections_in_range(&range, hub, cx)
|
||||
.map(|s| {
|
||||
|
|
|
|||
|
|
@ -350,20 +350,41 @@ async fn test_project_count(db: &Arc<Database>) {
|
|||
.unwrap();
|
||||
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0);
|
||||
|
||||
db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[], false, false)
|
||||
.await
|
||||
.unwrap();
|
||||
db.share_project(
|
||||
room_id,
|
||||
ConnectionId { owner_id, id: 1 },
|
||||
&[],
|
||||
false,
|
||||
false,
|
||||
&[],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 1);
|
||||
|
||||
db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[], false, false)
|
||||
.await
|
||||
.unwrap();
|
||||
db.share_project(
|
||||
room_id,
|
||||
ConnectionId { owner_id, id: 1 },
|
||||
&[],
|
||||
false,
|
||||
false,
|
||||
&[],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
|
||||
|
||||
// Projects shared by admins aren't counted.
|
||||
db.share_project(room_id, ConnectionId { owner_id, id: 0 }, &[], false, false)
|
||||
.await
|
||||
.unwrap();
|
||||
db.share_project(
|
||||
room_id,
|
||||
ConnectionId { owner_id, id: 0 },
|
||||
&[],
|
||||
false,
|
||||
false,
|
||||
&[],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
|
||||
|
||||
db.leave_room(ConnectionId { owner_id, id: 1 })
|
||||
|
|
|
|||
|
|
@ -2184,6 +2184,7 @@ async fn test_following_after_replacement(cx_a: &mut TestAppContext, cx_b: &mut
|
|||
);
|
||||
mb
|
||||
});
|
||||
let multibuffer_snapshot = multibuffer.update(cx_a, |mb, cx| mb.snapshot(cx));
|
||||
let snapshot = buffer.update(cx_a, |buffer, _| buffer.snapshot());
|
||||
let editor: Entity<Editor> = cx_a.new_window_entity(|window, cx| {
|
||||
Editor::for_multibuffer(
|
||||
|
|
@ -2205,7 +2206,13 @@ async fn test_following_after_replacement(cx_a: &mut TestAppContext, cx_b: &mut
|
|||
editor
|
||||
.selections
|
||||
.disjoint_anchor_ranges()
|
||||
.map(|range| range.start.text_anchor.to_point(&snapshot))
|
||||
.map(|range| {
|
||||
multibuffer_snapshot
|
||||
.anchor_to_buffer_anchor(range.start)
|
||||
.unwrap()
|
||||
.0
|
||||
.to_point(&snapshot)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
multibuffer.update(cx_a, |multibuffer, cx| {
|
||||
|
|
@ -2232,7 +2239,13 @@ async fn test_following_after_replacement(cx_a: &mut TestAppContext, cx_b: &mut
|
|||
editor
|
||||
.selections
|
||||
.disjoint_anchor_ranges()
|
||||
.map(|range| range.start.text_anchor.to_point(&snapshot))
|
||||
.map(|range| {
|
||||
multibuffer_snapshot
|
||||
.anchor_to_buffer_anchor(range.start)
|
||||
.unwrap()
|
||||
.0
|
||||
.to_point(&snapshot)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
assert_eq!(positions, new_positions);
|
||||
|
|
|
|||
|
|
@ -1166,7 +1166,7 @@ impl CollabPanel {
|
|||
"Failed to join project",
|
||||
window,
|
||||
cx,
|
||||
|_, _, _| None,
|
||||
|error, _, _| Some(format!("{error:#}")),
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
|
|
@ -1729,7 +1729,7 @@ impl CollabPanel {
|
|||
"Failed to join project",
|
||||
window,
|
||||
cx,
|
||||
|_, _, _| None,
|
||||
|error, _, _| Some(format!("{error:#}")),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -161,9 +161,7 @@ impl CsvPreviewView {
|
|||
editor,
|
||||
|this: &mut CsvPreviewView, _editor, event: &EditorEvent, cx| {
|
||||
match event {
|
||||
EditorEvent::Edited { .. }
|
||||
| EditorEvent::DirtyChanged
|
||||
| EditorEvent::ExcerptsEdited { .. } => {
|
||||
EditorEvent::Edited { .. } | EditorEvent::DirtyChanged => {
|
||||
this.parse_csv_from_active_editor(true, cx);
|
||||
}
|
||||
_ => {}
|
||||
|
|
|
|||
|
|
@ -299,7 +299,7 @@ pub fn init(cx: &mut App) {
|
|||
return;
|
||||
}
|
||||
maybe!({
|
||||
let (buffer, position, _) = editor
|
||||
let (buffer, position) = editor
|
||||
.update(cx, |editor, cx| {
|
||||
let cursor_point: language::Point = editor
|
||||
.selections
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ use anyhow::Result;
|
|||
use collections::HashMap;
|
||||
use dap::{CompletionItem, CompletionItemType, OutputEvent};
|
||||
use editor::{
|
||||
Bias, CompletionProvider, Editor, EditorElement, EditorMode, EditorStyle, ExcerptId,
|
||||
HighlightKey, MultiBufferOffset, SizingBehavior,
|
||||
Bias, CompletionProvider, Editor, EditorElement, EditorMode, EditorStyle, HighlightKey,
|
||||
MultiBufferOffset, SizingBehavior,
|
||||
};
|
||||
use fuzzy::StringMatchCandidate;
|
||||
use gpui::{
|
||||
|
|
@ -528,7 +528,6 @@ struct ConsoleQueryBarCompletionProvider(WeakEntity<Console>);
|
|||
impl CompletionProvider for ConsoleQueryBarCompletionProvider {
|
||||
fn completions(
|
||||
&self,
|
||||
_excerpt_id: ExcerptId,
|
||||
buffer: &Entity<Buffer>,
|
||||
buffer_position: language::Anchor,
|
||||
_trigger: editor::CompletionContext,
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ use settings::Settings;
|
|||
use std::{
|
||||
any::{Any, TypeId},
|
||||
cmp::{self, Ordering},
|
||||
ops::Range,
|
||||
sync::Arc,
|
||||
};
|
||||
use text::{Anchor, BufferSnapshot, OffsetRangeExt};
|
||||
|
|
@ -480,25 +481,35 @@ impl BufferDiagnosticsEditor {
|
|||
})
|
||||
});
|
||||
|
||||
let (anchor_ranges, _) =
|
||||
buffer_diagnostics_editor
|
||||
.multibuffer
|
||||
.update(cx, |multibuffer, cx| {
|
||||
let excerpt_ranges = excerpt_ranges
|
||||
.into_iter()
|
||||
.map(|range| ExcerptRange {
|
||||
context: range.context.to_point(&buffer_snapshot),
|
||||
primary: range.primary.to_point(&buffer_snapshot),
|
||||
})
|
||||
.collect();
|
||||
multibuffer.set_excerpt_ranges_for_path(
|
||||
PathKey::for_buffer(&buffer, cx),
|
||||
buffer.clone(),
|
||||
&buffer_snapshot,
|
||||
excerpt_ranges,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let excerpt_ranges: Vec<_> = excerpt_ranges
|
||||
.into_iter()
|
||||
.map(|range| ExcerptRange {
|
||||
context: range.context.to_point(&buffer_snapshot),
|
||||
primary: range.primary.to_point(&buffer_snapshot),
|
||||
})
|
||||
.collect();
|
||||
buffer_diagnostics_editor
|
||||
.multibuffer
|
||||
.update(cx, |multibuffer, cx| {
|
||||
multibuffer.set_excerpt_ranges_for_path(
|
||||
PathKey::for_buffer(&buffer, cx),
|
||||
buffer.clone(),
|
||||
&buffer_snapshot,
|
||||
excerpt_ranges.clone(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let multibuffer_snapshot =
|
||||
buffer_diagnostics_editor.multibuffer.read(cx).snapshot(cx);
|
||||
let anchor_ranges: Vec<Range<editor::Anchor>> = excerpt_ranges
|
||||
.into_iter()
|
||||
.filter_map(|range| {
|
||||
let text_range = buffer_snapshot.anchor_range_inside(range.primary);
|
||||
let start = multibuffer_snapshot.anchor_in_buffer(text_range.start)?;
|
||||
let end = multibuffer_snapshot.anchor_in_buffer(text_range.end)?;
|
||||
Some(start..end)
|
||||
})
|
||||
.collect();
|
||||
|
||||
if was_empty {
|
||||
if let Some(anchor_range) = anchor_ranges.first() {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use language::{BufferId, Diagnostic, DiagnosticEntryRef, LanguageRegistry};
|
|||
use lsp::DiagnosticSeverity;
|
||||
use markdown::{CopyButtonVisibility, Markdown, MarkdownElement};
|
||||
use settings::Settings;
|
||||
use text::{AnchorRangeExt, Point};
|
||||
use text::Point;
|
||||
use theme_settings::ThemeSettings;
|
||||
use ui::{CopyButton, prelude::*};
|
||||
use util::maybe;
|
||||
|
|
@ -289,23 +289,12 @@ impl DiagnosticBlock {
|
|||
.nth(ix)
|
||||
{
|
||||
let multibuffer = editor.buffer().read(cx);
|
||||
let Some(snapshot) = multibuffer
|
||||
.buffer(buffer_id)
|
||||
.map(|entity| entity.read(cx).snapshot())
|
||||
else {
|
||||
if let Some(anchor_range) = multibuffer
|
||||
.snapshot(cx)
|
||||
.buffer_anchor_range_to_anchor_range(diagnostic.range)
|
||||
{
|
||||
Self::jump_to(editor, anchor_range, window, cx);
|
||||
return;
|
||||
};
|
||||
|
||||
for (excerpt_id, _, range) in multibuffer.excerpts_for_buffer(buffer_id, cx) {
|
||||
if range.context.overlaps(&diagnostic.range, &snapshot) {
|
||||
Self::jump_to(
|
||||
editor,
|
||||
Anchor::range_in_buffer(excerpt_id, diagnostic.range),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let Some(diagnostic) = editor
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ use buffer_diagnostics::BufferDiagnosticsEditor;
|
|||
use collections::{BTreeSet, HashMap, HashSet};
|
||||
use diagnostic_renderer::DiagnosticBlock;
|
||||
use editor::{
|
||||
Editor, EditorEvent, ExcerptRange, MultiBuffer, PathKey,
|
||||
Anchor, Editor, EditorEvent, ExcerptRange, MultiBuffer, PathKey,
|
||||
display_map::{BlockPlacement, BlockProperties, BlockStyle, CustomBlockId},
|
||||
multibuffer_context_lines,
|
||||
};
|
||||
|
|
@ -301,17 +301,21 @@ impl ProjectDiagnosticsEditor {
|
|||
let snapshot = self
|
||||
.editor
|
||||
.update(cx, |editor, cx| editor.display_snapshot(cx));
|
||||
let buffer = self.multibuffer.read(cx);
|
||||
let buffer_ids = buffer.all_buffer_ids();
|
||||
let selected_buffers = self.editor.update(cx, |editor, _| {
|
||||
editor
|
||||
.selections
|
||||
.all_anchors(&snapshot)
|
||||
.iter()
|
||||
.filter_map(|anchor| anchor.start.text_anchor.buffer_id)
|
||||
.filter_map(|anchor| {
|
||||
Some(snapshot.anchor_to_buffer_anchor(anchor.start)?.0.buffer_id)
|
||||
})
|
||||
.collect::<HashSet<_>>()
|
||||
});
|
||||
for buffer_id in buffer_ids {
|
||||
for buffer_id in snapshot
|
||||
.excerpts()
|
||||
.map(|excerpt| excerpt.context.start.buffer_id)
|
||||
.dedup()
|
||||
{
|
||||
if retain_selections && selected_buffers.contains(&buffer_id) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -329,7 +333,7 @@ impl ProjectDiagnosticsEditor {
|
|||
continue;
|
||||
}
|
||||
self.multibuffer.update(cx, |b, cx| {
|
||||
b.remove_excerpts_for_path(PathKey::for_buffer(&buffer, cx), cx);
|
||||
b.remove_excerpts(PathKey::for_buffer(&buffer, cx), cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -581,9 +585,8 @@ impl ProjectDiagnosticsEditor {
|
|||
match retain_excerpts {
|
||||
RetainExcerpts::Dirty if !is_dirty => Vec::new(),
|
||||
RetainExcerpts::All | RetainExcerpts::Dirty => multi_buffer
|
||||
.excerpts_for_buffer(buffer_id, cx)
|
||||
.into_iter()
|
||||
.map(|(_, _, range)| range)
|
||||
.snapshot(cx)
|
||||
.excerpts_for_buffer(buffer_id)
|
||||
.sorted_by(|a, b| cmp_excerpts(&buffer_snapshot, a, b))
|
||||
.collect(),
|
||||
}
|
||||
|
|
@ -621,22 +624,33 @@ impl ProjectDiagnosticsEditor {
|
|||
});
|
||||
})
|
||||
}
|
||||
let (anchor_ranges, _) = this.multibuffer.update(cx, |multi_buffer, cx| {
|
||||
let excerpt_ranges = excerpt_ranges
|
||||
.into_iter()
|
||||
.map(|range| ExcerptRange {
|
||||
context: range.context.to_point(&buffer_snapshot),
|
||||
primary: range.primary.to_point(&buffer_snapshot),
|
||||
})
|
||||
.collect();
|
||||
let excerpt_ranges: Vec<_> = excerpt_ranges
|
||||
.into_iter()
|
||||
.map(|range| ExcerptRange {
|
||||
context: range.context.to_point(&buffer_snapshot),
|
||||
primary: range.primary.to_point(&buffer_snapshot),
|
||||
})
|
||||
.collect();
|
||||
// TODO(cole): maybe should use the nonshrinking API?
|
||||
this.multibuffer.update(cx, |multi_buffer, cx| {
|
||||
multi_buffer.set_excerpt_ranges_for_path(
|
||||
PathKey::for_buffer(&buffer, cx),
|
||||
buffer.clone(),
|
||||
&buffer_snapshot,
|
||||
excerpt_ranges,
|
||||
excerpt_ranges.clone(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let multibuffer_snapshot = this.multibuffer.read(cx).snapshot(cx);
|
||||
let anchor_ranges: Vec<Range<Anchor>> = excerpt_ranges
|
||||
.into_iter()
|
||||
.filter_map(|range| {
|
||||
let text_range = buffer_snapshot.anchor_range_inside(range.primary);
|
||||
let start = multibuffer_snapshot.anchor_in_buffer(text_range.start)?;
|
||||
let end = multibuffer_snapshot.anchor_in_buffer(text_range.end)?;
|
||||
Some(start..end)
|
||||
})
|
||||
.collect();
|
||||
#[cfg(test)]
|
||||
let cloned_blocks = result_blocks.clone();
|
||||
|
||||
|
|
|
|||
|
|
@ -414,7 +414,7 @@ mod tests {
|
|||
capture_example(
|
||||
project.clone(),
|
||||
buffer.clone(),
|
||||
Anchor::MIN,
|
||||
Anchor::min_for_buffer(buffer.read(cx).remote_id()),
|
||||
events,
|
||||
true,
|
||||
cx,
|
||||
|
|
|
|||
|
|
@ -1676,7 +1676,7 @@ impl EditPredictionStore {
|
|||
buffer.pending_predictions.push(PendingSettledPrediction {
|
||||
request_id: request_id,
|
||||
editable_anchor_range: edited_buffer_snapshot
|
||||
.anchor_range_around(editable_offset_range),
|
||||
.anchor_range_inside(editable_offset_range),
|
||||
example,
|
||||
e2e_latency,
|
||||
enqueued_at: now,
|
||||
|
|
@ -2351,7 +2351,10 @@ impl EditPredictionStore {
|
|||
cx: &mut AsyncApp,
|
||||
) -> Result<Option<(Entity<Buffer>, language::Anchor)>> {
|
||||
let collaborator_cursor_rows: Vec<u32> = active_buffer_snapshot
|
||||
.selections_in_range(Anchor::MIN..Anchor::MAX, false)
|
||||
.selections_in_range(
|
||||
Anchor::min_max_range_for_buffer(active_buffer_snapshot.remote_id()),
|
||||
false,
|
||||
)
|
||||
.flat_map(|(_, _, _, selections)| {
|
||||
selections.map(|s| s.head().to_point(active_buffer_snapshot).row)
|
||||
})
|
||||
|
|
@ -2427,7 +2430,10 @@ impl EditPredictionStore {
|
|||
candidate_buffer.read_with(cx, |buffer, _cx| {
|
||||
let snapshot = buffer.snapshot();
|
||||
let has_collaborators = snapshot
|
||||
.selections_in_range(Anchor::MIN..Anchor::MAX, false)
|
||||
.selections_in_range(
|
||||
Anchor::min_max_range_for_buffer(snapshot.remote_id()),
|
||||
false,
|
||||
)
|
||||
.next()
|
||||
.is_some();
|
||||
let position = buffer
|
||||
|
|
@ -2761,7 +2767,7 @@ fn collaborator_edit_overlaps_locality_region(
|
|||
(position..position).to_point(snapshot),
|
||||
COLLABORATOR_EDIT_LOCALITY_CONTEXT_TOKENS,
|
||||
);
|
||||
let locality_anchor_range = snapshot.anchor_range_around(locality_point_range);
|
||||
let locality_anchor_range = snapshot.anchor_range_inside(locality_point_range);
|
||||
|
||||
edit_range.overlaps(&locality_anchor_range, snapshot)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,6 @@ pub async fn apply_diff(
|
|||
|
||||
let mut included_files: HashMap<String, Entity<Buffer>> = HashMap::default();
|
||||
|
||||
let ranges = [Anchor::MIN..Anchor::MAX];
|
||||
let mut diff = DiffParser::new(diff_str);
|
||||
let mut current_file = None;
|
||||
let mut edits: Vec<(std::ops::Range<Anchor>, Arc<str>)> = vec![];
|
||||
|
|
@ -115,7 +114,7 @@ pub async fn apply_diff(
|
|||
edits.extend(resolve_hunk_edits_in_buffer(
|
||||
hunk,
|
||||
buffer,
|
||||
ranges.as_slice(),
|
||||
&[Anchor::min_max_range_for_buffer(buffer.remote_id())],
|
||||
status,
|
||||
)?);
|
||||
anyhow::Ok(())
|
||||
|
|
|
|||
|
|
@ -201,10 +201,14 @@ impl EditPredictionContextView {
|
|||
multibuffer.clear(cx);
|
||||
|
||||
for (path, buffer, ranges, orders, _) in paths {
|
||||
let (anchor_ranges, _) =
|
||||
multibuffer.set_excerpts_for_path(path, buffer, ranges, 0, cx);
|
||||
for (anchor_range, order) in anchor_ranges.into_iter().zip(orders) {
|
||||
excerpt_anchors_with_orders.push((anchor_range.start, order));
|
||||
multibuffer.set_excerpts_for_path(path, buffer.clone(), ranges.clone(), 0, cx);
|
||||
let snapshot = multibuffer.snapshot(cx);
|
||||
let buffer_snapshot = buffer.read(cx).snapshot();
|
||||
for (range, order) in ranges.into_iter().zip(orders) {
|
||||
let text_anchor = buffer_snapshot.anchor_range_inside(range);
|
||||
if let Some(start) = snapshot.anchor_in_buffer(text_anchor.start) {
|
||||
excerpt_anchors_with_orders.push((start, order));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -357,35 +357,26 @@ impl RatePredictionsModal {
|
|||
});
|
||||
|
||||
editor.disable_header_for_buffer(new_buffer_id, cx);
|
||||
let excerpt_id = editor.buffer().update(cx, |multibuffer, cx| {
|
||||
editor.buffer().update(cx, |multibuffer, cx| {
|
||||
multibuffer.clear(cx);
|
||||
multibuffer.set_excerpts_for_buffer(new_buffer, [start..end], 0, cx);
|
||||
multibuffer.set_excerpts_for_buffer(new_buffer.clone(), [start..end], 0, cx);
|
||||
multibuffer.add_diff(diff, cx);
|
||||
multibuffer.excerpt_ids().into_iter().next()
|
||||
});
|
||||
|
||||
if let Some((excerpt_id, cursor_position)) =
|
||||
excerpt_id.zip(prediction.cursor_position.as_ref())
|
||||
{
|
||||
if let Some(cursor_position) = prediction.cursor_position.as_ref() {
|
||||
let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
if let Some(buffer_snapshot) =
|
||||
multibuffer_snapshot.buffer_for_excerpt(excerpt_id)
|
||||
{
|
||||
let cursor_offset = prediction
|
||||
.edit_preview
|
||||
.anchor_to_offset_in_result(cursor_position.anchor)
|
||||
+ cursor_position.offset;
|
||||
let cursor_anchor = buffer_snapshot.anchor_after(cursor_offset);
|
||||
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(excerpt_id, cursor_anchor)
|
||||
{
|
||||
editor.splice_inlays(
|
||||
&[InlayId::EditPrediction(0)],
|
||||
vec![Inlay::edit_prediction(0, anchor, "▏")],
|
||||
cx,
|
||||
);
|
||||
}
|
||||
if let Some(anchor) = multibuffer_snapshot.anchor_in_excerpt(cursor_anchor) {
|
||||
editor.splice_inlays(
|
||||
&[InlayId::EditPrediction(0)],
|
||||
vec![Inlay::edit_prediction(0, anchor, "▏")],
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -991,7 +982,6 @@ impl FeedbackCompletionProvider {
|
|||
impl editor::CompletionProvider for FeedbackCompletionProvider {
|
||||
fn completions(
|
||||
&self,
|
||||
_excerpt_id: editor::ExcerptId,
|
||||
buffer: &Entity<Buffer>,
|
||||
buffer_position: language::Anchor,
|
||||
_trigger: editor::CompletionContext,
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ use std::ops::Range;
|
|||
use crate::{Editor, HighlightKey};
|
||||
use collections::{HashMap, HashSet};
|
||||
use gpui::{AppContext as _, Context, HighlightStyle};
|
||||
use itertools::Itertools;
|
||||
use language::{BufferRow, BufferSnapshot, language_settings::LanguageSettings};
|
||||
use multi_buffer::{Anchor, ExcerptId};
|
||||
use multi_buffer::{Anchor, BufferOffset, ExcerptRange, MultiBufferSnapshot};
|
||||
use text::OffsetRangeExt as _;
|
||||
use ui::{ActiveTheme, utils::ensure_minimum_contrast};
|
||||
|
||||
impl Editor {
|
||||
|
|
@ -25,55 +25,49 @@ impl Editor {
|
|||
let accents_count = cx.theme().accents().0.len();
|
||||
let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
|
||||
|
||||
let visible_excerpts = self.visible_excerpts(false, cx);
|
||||
let excerpt_data: Vec<(ExcerptId, BufferSnapshot, Range<usize>)> = visible_excerpts
|
||||
let visible_excerpts = self.visible_buffer_ranges(cx);
|
||||
let excerpt_data: Vec<(
|
||||
BufferSnapshot,
|
||||
Range<BufferOffset>,
|
||||
ExcerptRange<text::Anchor>,
|
||||
)> = visible_excerpts
|
||||
.into_iter()
|
||||
.filter_map(|(excerpt_id, (buffer, _, buffer_range))| {
|
||||
let buffer = buffer.read(cx);
|
||||
let buffer_snapshot = buffer.snapshot();
|
||||
if LanguageSettings::for_buffer(&buffer, cx).colorize_brackets {
|
||||
Some((excerpt_id, buffer_snapshot, buffer_range))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
.filter(|(buffer_snapshot, _, _)| {
|
||||
let Some(buffer) = self.buffer().read(cx).buffer(buffer_snapshot.remote_id())
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
LanguageSettings::for_buffer(buffer.read(cx), cx).colorize_brackets
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut fetched_tree_sitter_chunks = excerpt_data
|
||||
.iter()
|
||||
.filter_map(|(excerpt_id, ..)| {
|
||||
.filter_map(|(_, _, excerpt_range)| {
|
||||
let key = excerpt_range.context.clone();
|
||||
Some((
|
||||
*excerpt_id,
|
||||
self.bracket_fetched_tree_sitter_chunks
|
||||
.get(excerpt_id)
|
||||
.cloned()?,
|
||||
key.clone(),
|
||||
self.bracket_fetched_tree_sitter_chunks.get(&key).cloned()?,
|
||||
))
|
||||
})
|
||||
.collect::<HashMap<ExcerptId, HashSet<Range<BufferRow>>>>();
|
||||
.collect::<HashMap<Range<text::Anchor>, HashSet<Range<BufferRow>>>>();
|
||||
|
||||
let bracket_matches_by_accent = cx.background_spawn(async move {
|
||||
let anchors_in_multi_buffer = |current_excerpt: ExcerptId,
|
||||
text_anchors: [text::Anchor; 4]|
|
||||
-> Option<[Option<_>; 4]> {
|
||||
multi_buffer_snapshot
|
||||
.anchors_in_excerpt(current_excerpt, text_anchors)?
|
||||
.collect_array()
|
||||
};
|
||||
|
||||
let bracket_matches_by_accent: HashMap<usize, Vec<Range<Anchor>>> =
|
||||
excerpt_data.into_iter().fold(
|
||||
HashMap::default(),
|
||||
|mut acc, (excerpt_id, buffer_snapshot, buffer_range)| {
|
||||
let fetched_chunks =
|
||||
fetched_tree_sitter_chunks.entry(excerpt_id).or_default();
|
||||
|mut acc, (buffer_snapshot, buffer_range, excerpt_range)| {
|
||||
let fetched_chunks = fetched_tree_sitter_chunks
|
||||
.entry(excerpt_range.context.clone())
|
||||
.or_default();
|
||||
|
||||
let brackets_by_accent = compute_bracket_ranges(
|
||||
&multi_buffer_snapshot,
|
||||
&buffer_snapshot,
|
||||
buffer_range,
|
||||
excerpt_range,
|
||||
fetched_chunks,
|
||||
excerpt_id,
|
||||
accents_count,
|
||||
&anchors_in_multi_buffer,
|
||||
);
|
||||
|
||||
for (accent_number, new_ranges) in brackets_by_accent {
|
||||
|
|
@ -144,15 +138,20 @@ impl Editor {
|
|||
}
|
||||
|
||||
fn compute_bracket_ranges(
|
||||
multi_buffer_snapshot: &MultiBufferSnapshot,
|
||||
buffer_snapshot: &BufferSnapshot,
|
||||
buffer_range: Range<usize>,
|
||||
buffer_range: Range<BufferOffset>,
|
||||
excerpt_range: ExcerptRange<text::Anchor>,
|
||||
fetched_chunks: &mut HashSet<Range<BufferRow>>,
|
||||
excerpt_id: ExcerptId,
|
||||
accents_count: usize,
|
||||
anchors_in_multi_buffer: &impl Fn(ExcerptId, [text::Anchor; 4]) -> Option<[Option<Anchor>; 4]>,
|
||||
) -> Vec<(usize, Vec<Range<Anchor>>)> {
|
||||
let context = excerpt_range.context.to_offset(buffer_snapshot);
|
||||
|
||||
buffer_snapshot
|
||||
.fetch_bracket_ranges(buffer_range.start..buffer_range.end, Some(fetched_chunks))
|
||||
.fetch_bracket_ranges(
|
||||
buffer_range.start.0..buffer_range.end.0,
|
||||
Some(fetched_chunks),
|
||||
)
|
||||
.into_iter()
|
||||
.flat_map(|(chunk_range, pairs)| {
|
||||
if fetched_chunks.insert(chunk_range) {
|
||||
|
|
@ -164,37 +163,25 @@ fn compute_bracket_ranges(
|
|||
.filter_map(|pair| {
|
||||
let color_index = pair.color_index?;
|
||||
|
||||
let buffer_open_range = buffer_snapshot.anchor_range_around(pair.open_range);
|
||||
let buffer_close_range = buffer_snapshot.anchor_range_around(pair.close_range);
|
||||
let [
|
||||
buffer_open_range_start,
|
||||
buffer_open_range_end,
|
||||
buffer_close_range_start,
|
||||
buffer_close_range_end,
|
||||
] = anchors_in_multi_buffer(
|
||||
excerpt_id,
|
||||
[
|
||||
buffer_open_range.start,
|
||||
buffer_open_range.end,
|
||||
buffer_close_range.start,
|
||||
buffer_close_range.end,
|
||||
],
|
||||
)?;
|
||||
let multi_buffer_open_range = buffer_open_range_start.zip(buffer_open_range_end);
|
||||
let multi_buffer_close_range = buffer_close_range_start.zip(buffer_close_range_end);
|
||||
let mut ranges = Vec::new();
|
||||
|
||||
let mut ranges = Vec::with_capacity(2);
|
||||
if let Some((open_start, open_end)) = multi_buffer_open_range {
|
||||
ranges.push(open_start..open_end);
|
||||
}
|
||||
if let Some((close_start, close_end)) = multi_buffer_close_range {
|
||||
ranges.push(close_start..close_end);
|
||||
}
|
||||
if ranges.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some((color_index % accents_count, ranges))
|
||||
}
|
||||
if context.start <= pair.open_range.start && pair.open_range.end <= context.end {
|
||||
let anchors = buffer_snapshot.anchor_range_inside(pair.open_range);
|
||||
ranges.push(
|
||||
multi_buffer_snapshot.anchor_in_buffer(anchors.start)?
|
||||
..multi_buffer_snapshot.anchor_in_buffer(anchors.end)?,
|
||||
);
|
||||
};
|
||||
|
||||
if context.start <= pair.close_range.start && pair.close_range.end <= context.end {
|
||||
let anchors = buffer_snapshot.anchor_range_inside(pair.close_range);
|
||||
ranges.push(
|
||||
multi_buffer_snapshot.anchor_in_buffer(anchors.start)?
|
||||
..multi_buffer_snapshot.anchor_in_buffer(anchors.end)?,
|
||||
);
|
||||
};
|
||||
|
||||
Some((color_index % accents_count, ranges))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
|
@ -1197,7 +1184,7 @@ mod foo «1{
|
|||
);
|
||||
}
|
||||
|
||||
let buffer_snapshot = snapshot.buffer().as_singleton().unwrap().2;
|
||||
let buffer_snapshot = snapshot.buffer().as_singleton().unwrap();
|
||||
for bracket_match in buffer_snapshot
|
||||
.fetch_bracket_ranges(
|
||||
snapshot
|
||||
|
|
@ -1464,6 +1451,101 @@ mod foo «1{
|
|||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_multi_buffer_close_excerpts(cx: &mut gpui::TestAppContext) {
|
||||
let comment_lines = 5;
|
||||
|
||||
init_test(cx, |language_settings| {
|
||||
language_settings.defaults.colorize_brackets = Some(true);
|
||||
});
|
||||
let fs = FakeFs::new(cx.background_executor.clone());
|
||||
fs.insert_tree(
|
||||
path!("/a"),
|
||||
json!({
|
||||
"lib.rs": separate_with_comment_lines(
|
||||
indoc! {r#"
|
||||
fn process_data_1() {
|
||||
let map: Option<Vec<()>> = None;
|
||||
}
|
||||
"#},
|
||||
indoc! {r#"
|
||||
fn process_data_2() {
|
||||
let other_map: Option<Vec<()>> = None;
|
||||
}
|
||||
"#},
|
||||
comment_lines,
|
||||
)
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
language_registry.add(rust_lang());
|
||||
|
||||
let buffer_1 = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_local_buffer(path!("/a/lib.rs"), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let second_excerpt_start = buffer_1.read_with(cx, |buffer, _| {
|
||||
let text = buffer.text();
|
||||
text.lines()
|
||||
.enumerate()
|
||||
.find(|(_, line)| line.contains("process_data_2"))
|
||||
.map(|(row, _)| row as u32)
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
let multi_buffer = cx.new(|cx| {
|
||||
let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
|
||||
multi_buffer.set_excerpts_for_path(
|
||||
PathKey::sorted(0),
|
||||
buffer_1.clone(),
|
||||
[
|
||||
Point::new(0, 0)..Point::new(3, 0),
|
||||
Point::new(second_excerpt_start, 0)..Point::new(second_excerpt_start + 3, 0),
|
||||
],
|
||||
0,
|
||||
cx,
|
||||
);
|
||||
multi_buffer
|
||||
});
|
||||
|
||||
let editor = cx.add_window(|window, cx| {
|
||||
Editor::for_multibuffer(multi_buffer, Some(project.clone()), window, cx)
|
||||
});
|
||||
cx.executor().advance_clock(Duration::from_millis(100));
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
let editor_snapshot = editor
|
||||
.update(cx, |editor, window, cx| editor.snapshot(window, cx))
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
concat!(
|
||||
"\n",
|
||||
"\n",
|
||||
"fn process_data_1\u{00ab}1()1\u{00bb} \u{00ab}1{\n",
|
||||
" let map: Option\u{00ab}2<Vec\u{00ab}3<\u{00ab}4()4\u{00bb}>3\u{00bb}>2\u{00bb} = None;\n",
|
||||
"}1\u{00bb}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"fn process_data_2\u{00ab}1()1\u{00bb} \u{00ab}1{\n",
|
||||
" let other_map: Option\u{00ab}2<Vec\u{00ab}3<\u{00ab}4()4\u{00bb}>3\u{00bb}>2\u{00bb} = None;\n",
|
||||
"}1\u{00bb}\n",
|
||||
"\n",
|
||||
"1 hsla(207.80, 16.20%, 69.19%, 1.00)\n",
|
||||
"2 hsla(29.00, 54.00%, 65.88%, 1.00)\n",
|
||||
"3 hsla(286.00, 51.00%, 75.25%, 1.00)\n",
|
||||
"4 hsla(187.00, 47.00%, 59.22%, 1.00)\n",
|
||||
),
|
||||
&editor_bracket_colors_markup(&editor_snapshot),
|
||||
"Two close excerpts from the same buffer (within same tree-sitter chunk) should both have bracket colors"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
// reproduction of #47846
|
||||
async fn test_bracket_colorization_with_folds(cx: &mut gpui::TestAppContext) {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use project::{Completion, CompletionSource};
|
|||
use settings::SnippetSortOrder;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use text::Anchor;
|
||||
use text::{Anchor, BufferId};
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_sort_kind(cx: &mut TestAppContext) {
|
||||
|
|
@ -393,7 +393,7 @@ impl CompletionBuilder {
|
|||
kind: Option<CompletionItemKind>,
|
||||
) -> Completion {
|
||||
Completion {
|
||||
replace_range: Anchor::MIN..Anchor::MAX,
|
||||
replace_range: Anchor::min_max_range_for_buffer(BufferId::new(1).unwrap()),
|
||||
new_text: label.to_string(),
|
||||
label: CodeLabel::plain(label.to_string(), filter_text),
|
||||
documentation: None,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use language::CodeLabel;
|
|||
use language::{Buffer, LanguageName, LanguageRegistry};
|
||||
use lsp::CompletionItemTag;
|
||||
use markdown::{CopyButtonVisibility, Markdown, MarkdownElement};
|
||||
use multi_buffer::{Anchor, ExcerptId};
|
||||
use multi_buffer::Anchor;
|
||||
use ordered_float::OrderedFloat;
|
||||
use project::lsp_store::CompletionDocumentation;
|
||||
use project::{CodeAction, Completion, TaskSourceKind};
|
||||
|
|
@ -357,7 +357,8 @@ impl CompletionsMenu {
|
|||
id: CompletionId,
|
||||
sort_completions: bool,
|
||||
choices: &Vec<String>,
|
||||
selection: Range<Anchor>,
|
||||
initial_position: Anchor,
|
||||
selection: Range<text::Anchor>,
|
||||
buffer: Entity<Buffer>,
|
||||
scroll_handle: Option<UniformListScrollHandle>,
|
||||
snippet_sort_order: SnippetSortOrder,
|
||||
|
|
@ -365,7 +366,7 @@ impl CompletionsMenu {
|
|||
let completions = choices
|
||||
.iter()
|
||||
.map(|choice| Completion {
|
||||
replace_range: selection.start.text_anchor..selection.end.text_anchor,
|
||||
replace_range: selection.clone(),
|
||||
new_text: choice.to_string(),
|
||||
label: CodeLabel::plain(choice.to_string(), None),
|
||||
match_start: None,
|
||||
|
|
@ -400,7 +401,7 @@ impl CompletionsMenu {
|
|||
id,
|
||||
source: CompletionsMenuSource::SnippetChoices,
|
||||
sort_completions,
|
||||
initial_position: selection.start,
|
||||
initial_position,
|
||||
initial_query: None,
|
||||
is_incomplete: false,
|
||||
buffer,
|
||||
|
|
@ -1380,7 +1381,6 @@ impl CompletionsMenu {
|
|||
|
||||
#[derive(Clone)]
|
||||
pub struct AvailableCodeAction {
|
||||
pub excerpt_id: ExcerptId,
|
||||
pub action: CodeAction,
|
||||
pub provider: Rc<dyn CodeActionProvider>,
|
||||
}
|
||||
|
|
@ -1433,7 +1433,6 @@ impl CodeActionContents {
|
|||
})
|
||||
.chain(self.actions.iter().flat_map(|actions| {
|
||||
actions.iter().map(|available| CodeActionsItem::CodeAction {
|
||||
excerpt_id: available.excerpt_id,
|
||||
action: available.action.clone(),
|
||||
provider: available.provider.clone(),
|
||||
})
|
||||
|
|
@ -1457,7 +1456,6 @@ impl CodeActionContents {
|
|||
if let Some(actions) = &self.actions {
|
||||
if let Some(available) = actions.get(index) {
|
||||
return Some(CodeActionsItem::CodeAction {
|
||||
excerpt_id: available.excerpt_id,
|
||||
action: available.action.clone(),
|
||||
provider: available.provider.clone(),
|
||||
});
|
||||
|
|
@ -1477,7 +1475,6 @@ impl CodeActionContents {
|
|||
pub enum CodeActionsItem {
|
||||
Task(TaskSourceKind, ResolvedTask),
|
||||
CodeAction {
|
||||
excerpt_id: ExcerptId,
|
||||
action: CodeAction,
|
||||
provider: Rc<dyn CodeActionProvider>,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ use language::{
|
|||
};
|
||||
|
||||
use multi_buffer::{
|
||||
Anchor, AnchorRangeExt, ExcerptId, MultiBuffer, MultiBufferOffset, MultiBufferOffsetUtf16,
|
||||
Anchor, AnchorRangeExt, MultiBuffer, MultiBufferOffset, MultiBufferOffsetUtf16,
|
||||
MultiBufferPoint, MultiBufferRow, MultiBufferSnapshot, RowInfo, ToOffset, ToPoint,
|
||||
};
|
||||
use project::project_settings::DiagnosticSeverity;
|
||||
|
|
@ -125,7 +125,7 @@ use std::{
|
|||
fmt::Debug,
|
||||
iter,
|
||||
num::NonZeroU32,
|
||||
ops::{self, Add, Bound, Range, Sub},
|
||||
ops::{self, Add, Range, Sub},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
|
|
@ -195,10 +195,9 @@ pub struct CompanionExcerptPatch {
|
|||
}
|
||||
|
||||
pub type ConvertMultiBufferRows = fn(
|
||||
&HashMap<ExcerptId, ExcerptId>,
|
||||
&MultiBufferSnapshot,
|
||||
&MultiBufferSnapshot,
|
||||
(Bound<MultiBufferPoint>, Bound<MultiBufferPoint>),
|
||||
Range<MultiBufferPoint>,
|
||||
) -> Vec<CompanionExcerptPatch>;
|
||||
|
||||
/// Decides how text in a [`MultiBuffer`] should be displayed in a buffer, handling inlay hints,
|
||||
|
|
@ -240,8 +239,6 @@ pub(crate) struct Companion {
|
|||
rhs_display_map_id: EntityId,
|
||||
rhs_buffer_to_lhs_buffer: HashMap<BufferId, BufferId>,
|
||||
lhs_buffer_to_rhs_buffer: HashMap<BufferId, BufferId>,
|
||||
rhs_excerpt_to_lhs_excerpt: HashMap<ExcerptId, ExcerptId>,
|
||||
lhs_excerpt_to_rhs_excerpt: HashMap<ExcerptId, ExcerptId>,
|
||||
rhs_rows_to_lhs_rows: ConvertMultiBufferRows,
|
||||
lhs_rows_to_rhs_rows: ConvertMultiBufferRows,
|
||||
rhs_custom_block_to_balancing_block: RefCell<HashMap<CustomBlockId, CustomBlockId>>,
|
||||
|
|
@ -258,8 +255,6 @@ impl Companion {
|
|||
rhs_display_map_id,
|
||||
rhs_buffer_to_lhs_buffer: Default::default(),
|
||||
lhs_buffer_to_rhs_buffer: Default::default(),
|
||||
rhs_excerpt_to_lhs_excerpt: Default::default(),
|
||||
lhs_excerpt_to_rhs_excerpt: Default::default(),
|
||||
rhs_rows_to_lhs_rows,
|
||||
lhs_rows_to_rhs_rows,
|
||||
rhs_custom_block_to_balancing_block: Default::default(),
|
||||
|
|
@ -287,14 +282,14 @@ impl Companion {
|
|||
display_map_id: EntityId,
|
||||
companion_snapshot: &MultiBufferSnapshot,
|
||||
our_snapshot: &MultiBufferSnapshot,
|
||||
bounds: (Bound<MultiBufferPoint>, Bound<MultiBufferPoint>),
|
||||
bounds: Range<MultiBufferPoint>,
|
||||
) -> Vec<CompanionExcerptPatch> {
|
||||
let (excerpt_map, convert_fn) = if self.is_rhs(display_map_id) {
|
||||
(&self.rhs_excerpt_to_lhs_excerpt, self.rhs_rows_to_lhs_rows)
|
||||
let convert_fn = if self.is_rhs(display_map_id) {
|
||||
self.rhs_rows_to_lhs_rows
|
||||
} else {
|
||||
(&self.lhs_excerpt_to_rhs_excerpt, self.lhs_rows_to_rhs_rows)
|
||||
self.lhs_rows_to_rhs_rows
|
||||
};
|
||||
convert_fn(excerpt_map, companion_snapshot, our_snapshot, bounds)
|
||||
convert_fn(companion_snapshot, our_snapshot, bounds)
|
||||
}
|
||||
|
||||
pub(crate) fn convert_point_from_companion(
|
||||
|
|
@ -304,20 +299,15 @@ impl Companion {
|
|||
companion_snapshot: &MultiBufferSnapshot,
|
||||
point: MultiBufferPoint,
|
||||
) -> Range<MultiBufferPoint> {
|
||||
let (excerpt_map, convert_fn) = if self.is_rhs(display_map_id) {
|
||||
(&self.lhs_excerpt_to_rhs_excerpt, self.lhs_rows_to_rhs_rows)
|
||||
let convert_fn = if self.is_rhs(display_map_id) {
|
||||
self.lhs_rows_to_rhs_rows
|
||||
} else {
|
||||
(&self.rhs_excerpt_to_lhs_excerpt, self.rhs_rows_to_lhs_rows)
|
||||
self.rhs_rows_to_lhs_rows
|
||||
};
|
||||
|
||||
let excerpt = convert_fn(
|
||||
excerpt_map,
|
||||
our_snapshot,
|
||||
companion_snapshot,
|
||||
(Bound::Included(point), Bound::Included(point)),
|
||||
)
|
||||
.into_iter()
|
||||
.next();
|
||||
let excerpt = convert_fn(our_snapshot, companion_snapshot, point..point)
|
||||
.into_iter()
|
||||
.next();
|
||||
|
||||
let Some(excerpt) = excerpt else {
|
||||
return Point::zero()..our_snapshot.max_point();
|
||||
|
|
@ -332,20 +322,15 @@ impl Companion {
|
|||
companion_snapshot: &MultiBufferSnapshot,
|
||||
point: MultiBufferPoint,
|
||||
) -> Range<MultiBufferPoint> {
|
||||
let (excerpt_map, convert_fn) = if self.is_rhs(display_map_id) {
|
||||
(&self.rhs_excerpt_to_lhs_excerpt, self.rhs_rows_to_lhs_rows)
|
||||
let convert_fn = if self.is_rhs(display_map_id) {
|
||||
self.rhs_rows_to_lhs_rows
|
||||
} else {
|
||||
(&self.lhs_excerpt_to_rhs_excerpt, self.lhs_rows_to_rhs_rows)
|
||||
self.lhs_rows_to_rhs_rows
|
||||
};
|
||||
|
||||
let excerpt = convert_fn(
|
||||
excerpt_map,
|
||||
companion_snapshot,
|
||||
our_snapshot,
|
||||
(Bound::Included(point), Bound::Included(point)),
|
||||
)
|
||||
.into_iter()
|
||||
.next();
|
||||
let excerpt = convert_fn(companion_snapshot, our_snapshot, point..point)
|
||||
.into_iter()
|
||||
.next();
|
||||
|
||||
let Some(excerpt) = excerpt else {
|
||||
return Point::zero()..companion_snapshot.max_point();
|
||||
|
|
@ -353,30 +338,6 @@ impl Companion {
|
|||
excerpt.patch.edit_for_old_position(point).new
|
||||
}
|
||||
|
||||
pub(crate) fn companion_excerpt_to_excerpt(
|
||||
&self,
|
||||
display_map_id: EntityId,
|
||||
) -> &HashMap<ExcerptId, ExcerptId> {
|
||||
if self.is_rhs(display_map_id) {
|
||||
&self.lhs_excerpt_to_rhs_excerpt
|
||||
} else {
|
||||
&self.rhs_excerpt_to_lhs_excerpt
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn excerpt_mappings(
|
||||
&self,
|
||||
) -> (
|
||||
&HashMap<ExcerptId, ExcerptId>,
|
||||
&HashMap<ExcerptId, ExcerptId>,
|
||||
) {
|
||||
(
|
||||
&self.lhs_excerpt_to_rhs_excerpt,
|
||||
&self.rhs_excerpt_to_lhs_excerpt,
|
||||
)
|
||||
}
|
||||
|
||||
fn buffer_to_companion_buffer(&self, display_map_id: EntityId) -> &HashMap<BufferId, BufferId> {
|
||||
if self.is_rhs(display_map_id) {
|
||||
&self.rhs_buffer_to_lhs_buffer
|
||||
|
|
@ -385,24 +346,6 @@ impl Companion {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn add_excerpt_mapping(&mut self, lhs_id: ExcerptId, rhs_id: ExcerptId) {
|
||||
self.lhs_excerpt_to_rhs_excerpt.insert(lhs_id, rhs_id);
|
||||
self.rhs_excerpt_to_lhs_excerpt.insert(rhs_id, lhs_id);
|
||||
}
|
||||
|
||||
pub(crate) fn remove_excerpt_mappings(
|
||||
&mut self,
|
||||
lhs_ids: impl IntoIterator<Item = ExcerptId>,
|
||||
rhs_ids: impl IntoIterator<Item = ExcerptId>,
|
||||
) {
|
||||
for id in lhs_ids {
|
||||
self.lhs_excerpt_to_rhs_excerpt.remove(&id);
|
||||
}
|
||||
for id in rhs_ids {
|
||||
self.rhs_excerpt_to_lhs_excerpt.remove(&id);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn lhs_to_rhs_buffer(&self, lhs_buffer_id: BufferId) -> Option<BufferId> {
|
||||
self.lhs_buffer_to_rhs_buffer.get(&lhs_buffer_id).copied()
|
||||
}
|
||||
|
|
@ -540,8 +483,7 @@ impl DisplayMap {
|
|||
.wrap_map
|
||||
.update(cx, |wrap_map, cx| wrap_map.sync(snapshot, edits, cx));
|
||||
|
||||
let (snapshot, edits) =
|
||||
writer.unfold_intersecting([Anchor::min()..Anchor::max()], true);
|
||||
let (snapshot, edits) = writer.unfold_intersecting([Anchor::Min..Anchor::Max], true);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
|
||||
let (snapshot, _edits) = self
|
||||
.wrap_map
|
||||
|
|
@ -632,18 +574,6 @@ impl DisplayMap {
|
|||
self.companion.as_ref().map(|(_, c)| c)
|
||||
}
|
||||
|
||||
pub(crate) fn companion_excerpt_to_my_excerpt(
|
||||
&self,
|
||||
their_id: ExcerptId,
|
||||
cx: &App,
|
||||
) -> Option<ExcerptId> {
|
||||
let (_, companion) = self.companion.as_ref()?;
|
||||
let c = companion.read(cx);
|
||||
c.companion_excerpt_to_excerpt(self.entity_id)
|
||||
.get(&their_id)
|
||||
.copied()
|
||||
}
|
||||
|
||||
fn sync_through_wrap(&mut self, cx: &mut App) -> (WrapSnapshot, WrapPatch) {
|
||||
let tab_size = Self::tab_size(&self.buffer, cx);
|
||||
let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
|
|
@ -1054,17 +984,10 @@ impl DisplayMap {
|
|||
return;
|
||||
}
|
||||
|
||||
let excerpt_ids = snapshot
|
||||
.excerpts()
|
||||
.filter(|(_, buf, _)| buf.remote_id() == buffer_id)
|
||||
.map(|(id, _, _)| id)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let base_placeholder = self.fold_placeholder.clone();
|
||||
let creases = ranges.into_iter().filter_map(|folding_range| {
|
||||
let mb_range = excerpt_ids.iter().find_map(|&id| {
|
||||
snapshot.anchor_range_in_excerpt(id, folding_range.range.clone())
|
||||
})?;
|
||||
let mb_range =
|
||||
snapshot.buffer_anchor_range_to_anchor_range(folding_range.range.clone())?;
|
||||
let placeholder = if let Some(collapsed_text) = folding_range.collapsed_text {
|
||||
FoldPlaceholder {
|
||||
render: Arc::new({
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ use collections::{Bound, HashMap, HashSet};
|
|||
use gpui::{AnyElement, App, EntityId, Pixels, Window};
|
||||
use language::{Patch, Point};
|
||||
use multi_buffer::{
|
||||
Anchor, ExcerptId, ExcerptInfo, MultiBuffer, MultiBufferOffset, MultiBufferPoint,
|
||||
MultiBufferRow, MultiBufferSnapshot, RowInfo, ToOffset, ToPoint as _,
|
||||
Anchor, ExcerptBoundaryInfo, MultiBuffer, MultiBufferOffset, MultiBufferPoint, MultiBufferRow,
|
||||
MultiBufferSnapshot, RowInfo, ToOffset, ToPoint as _,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use std::{
|
||||
|
|
@ -298,10 +298,10 @@ pub struct BlockContext<'a, 'b> {
|
|||
pub indent_guide_padding: Pixels,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum BlockId {
|
||||
ExcerptBoundary(ExcerptId),
|
||||
FoldedBuffer(ExcerptId),
|
||||
ExcerptBoundary(Anchor),
|
||||
FoldedBuffer(BufferId),
|
||||
Custom(CustomBlockId),
|
||||
Spacer(SpacerId),
|
||||
}
|
||||
|
|
@ -310,10 +310,8 @@ impl From<BlockId> for ElementId {
|
|||
fn from(value: BlockId) -> Self {
|
||||
match value {
|
||||
BlockId::Custom(CustomBlockId(id)) => ("Block", id).into(),
|
||||
BlockId::ExcerptBoundary(excerpt_id) => {
|
||||
("ExcerptBoundary", EntityId::from(excerpt_id)).into()
|
||||
}
|
||||
BlockId::FoldedBuffer(id) => ("FoldedBuffer", EntityId::from(id)).into(),
|
||||
BlockId::ExcerptBoundary(anchor) => anchor.opaque_id().unwrap().into(),
|
||||
BlockId::FoldedBuffer(id) => ("FoldedBuffer", EntityId::from(id.to_proto())).into(),
|
||||
BlockId::Spacer(SpacerId(id)) => ("Spacer", id).into(),
|
||||
}
|
||||
}
|
||||
|
|
@ -323,7 +321,7 @@ impl std::fmt::Display for BlockId {
|
|||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Custom(id) => write!(f, "Block({id:?})"),
|
||||
Self::ExcerptBoundary(id) => write!(f, "ExcerptHeader({id:?})"),
|
||||
Self::ExcerptBoundary(id) => write!(f, "ExcerptBoundary({id:?})"),
|
||||
Self::FoldedBuffer(id) => write!(f, "FoldedBuffer({id:?})"),
|
||||
Self::Spacer(id) => write!(f, "Spacer({id:?})"),
|
||||
}
|
||||
|
|
@ -340,15 +338,15 @@ struct Transform {
|
|||
pub enum Block {
|
||||
Custom(Arc<CustomBlock>),
|
||||
FoldedBuffer {
|
||||
first_excerpt: ExcerptInfo,
|
||||
first_excerpt: ExcerptBoundaryInfo,
|
||||
height: u32,
|
||||
},
|
||||
ExcerptBoundary {
|
||||
excerpt: ExcerptInfo,
|
||||
excerpt: ExcerptBoundaryInfo,
|
||||
height: u32,
|
||||
},
|
||||
BufferHeader {
|
||||
excerpt: ExcerptInfo,
|
||||
excerpt: ExcerptBoundaryInfo,
|
||||
height: u32,
|
||||
},
|
||||
Spacer {
|
||||
|
|
@ -365,12 +363,14 @@ impl Block {
|
|||
Block::ExcerptBoundary {
|
||||
excerpt: next_excerpt,
|
||||
..
|
||||
} => BlockId::ExcerptBoundary(next_excerpt.id),
|
||||
Block::FoldedBuffer { first_excerpt, .. } => BlockId::FoldedBuffer(first_excerpt.id),
|
||||
} => BlockId::ExcerptBoundary(next_excerpt.start_anchor),
|
||||
Block::FoldedBuffer { first_excerpt, .. } => {
|
||||
BlockId::FoldedBuffer(first_excerpt.buffer_id())
|
||||
}
|
||||
Block::BufferHeader {
|
||||
excerpt: next_excerpt,
|
||||
..
|
||||
} => BlockId::ExcerptBoundary(next_excerpt.id),
|
||||
} => BlockId::ExcerptBoundary(next_excerpt.start_anchor),
|
||||
Block::Spacer { id, .. } => BlockId::Spacer(*id),
|
||||
}
|
||||
}
|
||||
|
|
@ -1174,10 +1174,10 @@ impl BlockMap {
|
|||
let wrap_row = wrap_row_for(Point::new(excerpt_boundary.row.0, 0), Bias::Left);
|
||||
|
||||
let new_buffer_id = match (&excerpt_boundary.prev, &excerpt_boundary.next) {
|
||||
(None, next) => Some(next.buffer_id),
|
||||
(None, next) => Some(next.buffer_id()),
|
||||
(Some(prev), next) => {
|
||||
if prev.buffer_id != next.buffer_id {
|
||||
Some(next.buffer_id)
|
||||
if prev.buffer_id() != next.buffer_id() {
|
||||
Some(next.buffer_id())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
|
@ -1195,7 +1195,7 @@ impl BlockMap {
|
|||
let mut last_excerpt_end_row = first_excerpt.end_row;
|
||||
|
||||
while let Some(next_boundary) = boundaries.peek() {
|
||||
if next_boundary.next.buffer_id == new_buffer_id {
|
||||
if next_boundary.next.buffer_id() == new_buffer_id {
|
||||
last_excerpt_end_row = next_boundary.next.end_row;
|
||||
} else {
|
||||
break;
|
||||
|
|
@ -1254,12 +1254,24 @@ impl BlockMap {
|
|||
let our_buffer = wrap_snapshot.buffer_snapshot();
|
||||
let companion_buffer = companion_snapshot.buffer_snapshot();
|
||||
|
||||
let patches = companion.convert_rows_to_companion(
|
||||
let range = match bounds {
|
||||
(Bound::Included(start), Bound::Excluded(end)) => start..end,
|
||||
(Bound::Included(start), Bound::Unbounded) => start..wrap_snapshot.buffer().max_point(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let mut patches = companion.convert_rows_to_companion(
|
||||
display_map_id,
|
||||
companion_buffer,
|
||||
our_buffer,
|
||||
bounds,
|
||||
range,
|
||||
);
|
||||
if let Some(patch) = patches.last()
|
||||
&& let Bound::Excluded(end) = bounds.1
|
||||
&& end == wrap_snapshot.buffer().max_point()
|
||||
&& patch.source_excerpt_range.is_empty()
|
||||
{
|
||||
patches.pop();
|
||||
}
|
||||
|
||||
let mut our_inlay_point_cursor = wrap_snapshot.inlay_point_cursor();
|
||||
let mut our_fold_point_cursor = wrap_snapshot.fold_point_cursor();
|
||||
|
|
@ -1391,18 +1403,15 @@ impl BlockMap {
|
|||
}
|
||||
}
|
||||
|
||||
// Main loop: process one hunk/group at a time, possibly inserting spacers before and after.
|
||||
while let Some(source_point) = source_points.next() {
|
||||
let mut current_boundary = source_point;
|
||||
let current_range = excerpt.patch.edit_for_old_position(current_boundary).new;
|
||||
|
||||
// This can only occur at the end of an excerpt.
|
||||
if current_boundary.column > 0 {
|
||||
debug_assert_eq!(current_boundary, excerpt.source_excerpt_range.end);
|
||||
break;
|
||||
}
|
||||
|
||||
// Align the two sides at the start of this group.
|
||||
let (delta_at_start, mut spacer_at_start) = determine_spacer(
|
||||
&mut our_wrapper,
|
||||
&mut companion_wrapper,
|
||||
|
|
@ -1434,7 +1443,6 @@ impl BlockMap {
|
|||
source_points.next();
|
||||
}
|
||||
|
||||
// This can only occur at the end of an excerpt.
|
||||
if current_boundary.column > 0 {
|
||||
debug_assert_eq!(current_boundary, excerpt.source_excerpt_range.end);
|
||||
break;
|
||||
|
|
@ -1538,7 +1546,8 @@ impl BlockMap {
|
|||
| Block::BufferHeader {
|
||||
excerpt: excerpt_b, ..
|
||||
},
|
||||
) => Some(excerpt_a.id).cmp(&Some(excerpt_b.id)),
|
||||
) => Some(excerpt_a.start_text_anchor().opaque_id())
|
||||
.cmp(&Some(excerpt_b.start_text_anchor().opaque_id())),
|
||||
(
|
||||
Block::ExcerptBoundary { .. } | Block::BufferHeader { .. },
|
||||
Block::Spacer { .. } | Block::Custom(_),
|
||||
|
|
@ -2042,7 +2051,7 @@ impl BlockMapWriter<'_> {
|
|||
} else {
|
||||
self.block_map.folded_buffers.remove(&buffer_id);
|
||||
}
|
||||
ranges.extend(multi_buffer.excerpt_ranges_for_buffer(buffer_id, cx));
|
||||
ranges.extend(multi_buffer.range_for_buffer(buffer_id, cx));
|
||||
if let Some(companion) = &self.companion
|
||||
&& companion.inverse.is_some()
|
||||
{
|
||||
|
|
@ -2268,14 +2277,16 @@ impl BlockSnapshot {
|
|||
let custom_block = self.custom_blocks_by_id.get(&custom_block_id)?;
|
||||
return Some(Block::Custom(custom_block.clone()));
|
||||
}
|
||||
BlockId::ExcerptBoundary(next_excerpt_id) => {
|
||||
let excerpt_range = buffer.range_for_excerpt(next_excerpt_id)?;
|
||||
self.wrap_snapshot
|
||||
.make_wrap_point(excerpt_range.start, Bias::Left)
|
||||
BlockId::ExcerptBoundary(start_anchor) => {
|
||||
let start_point = start_anchor.to_point(&buffer);
|
||||
self.wrap_snapshot.make_wrap_point(start_point, Bias::Left)
|
||||
}
|
||||
BlockId::FoldedBuffer(excerpt_id) => self
|
||||
.wrap_snapshot
|
||||
.make_wrap_point(buffer.range_for_excerpt(excerpt_id)?.start, Bias::Left),
|
||||
BlockId::FoldedBuffer(buffer_id) => self.wrap_snapshot.make_wrap_point(
|
||||
buffer
|
||||
.anchor_in_excerpt(buffer.excerpts_for_buffer(buffer_id).next()?.context.start)?
|
||||
.to_point(buffer),
|
||||
Bias::Left,
|
||||
),
|
||||
BlockId::Spacer(_) => return None,
|
||||
};
|
||||
let wrap_row = wrap_point.row();
|
||||
|
|
@ -2571,7 +2582,7 @@ impl BlockChunks<'_> {
|
|||
}
|
||||
|
||||
pub struct StickyHeaderExcerpt<'a> {
|
||||
pub excerpt: &'a ExcerptInfo,
|
||||
pub excerpt: &'a ExcerptBoundaryInfo,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for BlockChunks<'a> {
|
||||
|
|
@ -3096,7 +3107,13 @@ mod tests {
|
|||
);
|
||||
multi_buffer
|
||||
});
|
||||
let excerpt_ids = multi_buffer.read_with(cx, |mb, _| mb.excerpt_ids());
|
||||
let excerpt_start_anchors = multi_buffer.read_with(cx, |mb, _| {
|
||||
let snapshot = mb.snapshot(cx);
|
||||
snapshot
|
||||
.excerpts()
|
||||
.map(|e| snapshot.anchor_in_excerpt(e.context.start).unwrap())
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
let font = test_font();
|
||||
let font_size = px(14.);
|
||||
|
|
@ -3129,9 +3146,9 @@ mod tests {
|
|||
assert_eq!(
|
||||
blocks,
|
||||
vec![
|
||||
(0..1, BlockId::ExcerptBoundary(excerpt_ids[0])), // path, header
|
||||
(3..4, BlockId::ExcerptBoundary(excerpt_ids[1])), // path, header
|
||||
(6..7, BlockId::ExcerptBoundary(excerpt_ids[2])), // path, header
|
||||
(0..1, BlockId::ExcerptBoundary(excerpt_start_anchors[0])), // path, header
|
||||
(3..4, BlockId::ExcerptBoundary(excerpt_start_anchors[1])), // path, header
|
||||
(6..7, BlockId::ExcerptBoundary(excerpt_start_anchors[2])), // path, header
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
@ -3447,13 +3464,13 @@ mod tests {
|
|||
],
|
||||
cx,
|
||||
);
|
||||
assert_eq!(multibuffer.read(cx).excerpt_ids().len(), 6);
|
||||
assert_eq!(multibuffer.read(cx).snapshot(cx).excerpts().count(), 6);
|
||||
multibuffer
|
||||
});
|
||||
let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
|
||||
let buffer_ids = buffer_snapshot
|
||||
.excerpts()
|
||||
.map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
|
||||
.map(|excerpt| excerpt.context.start.buffer_id)
|
||||
.dedup()
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(buffer_ids.len(), 3);
|
||||
|
|
@ -3800,7 +3817,7 @@ mod tests {
|
|||
let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
|
||||
let buffer_ids = buffer_snapshot
|
||||
.excerpts()
|
||||
.map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
|
||||
.map(|excerpt| excerpt.context.start.buffer_id)
|
||||
.dedup()
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(buffer_ids.len(), 1);
|
||||
|
|
@ -4008,17 +4025,16 @@ mod tests {
|
|||
wrap_map.sync(tab_snapshot, tab_edits, cx)
|
||||
});
|
||||
let mut block_map = block_map.write(wraps_snapshot, wrap_edits, None);
|
||||
let (unfolded_buffers, folded_buffers) = buffer.read_with(cx, |buffer, _| {
|
||||
let folded_buffers: Vec<_> =
|
||||
block_map.block_map.folded_buffers.iter().cloned().collect();
|
||||
let mut unfolded_buffers = buffer.excerpt_buffer_ids();
|
||||
unfolded_buffers.dedup();
|
||||
log::debug!("All buffers {unfolded_buffers:?}");
|
||||
log::debug!("Folded buffers {folded_buffers:?}");
|
||||
unfolded_buffers.retain(|buffer_id| {
|
||||
!block_map.block_map.folded_buffers.contains(buffer_id)
|
||||
});
|
||||
(unfolded_buffers, folded_buffers)
|
||||
let folded_buffers: Vec<_> =
|
||||
block_map.block_map.folded_buffers.iter().cloned().collect();
|
||||
let mut unfolded_buffers = buffer_snapshot
|
||||
.buffer_ids_for_range(Anchor::Min..Anchor::Max)
|
||||
.collect::<Vec<_>>();
|
||||
unfolded_buffers.dedup();
|
||||
log::debug!("All buffers {unfolded_buffers:?}");
|
||||
log::debug!("Folded buffers {folded_buffers:?}");
|
||||
unfolded_buffers.retain(|buffer_id| {
|
||||
!block_map.block_map.folded_buffers.contains(buffer_id)
|
||||
});
|
||||
let mut folded_count = folded_buffers.len();
|
||||
let mut unfolded_count = unfolded_buffers.len();
|
||||
|
|
@ -4039,12 +4055,14 @@ mod tests {
|
|||
log::info!("Folding {buffer_to_fold:?}");
|
||||
let related_excerpts = buffer_snapshot
|
||||
.excerpts()
|
||||
.filter_map(|(excerpt_id, buffer, range)| {
|
||||
if buffer.remote_id() == buffer_to_fold {
|
||||
.filter_map(|excerpt| {
|
||||
if excerpt.context.start.buffer_id == buffer_to_fold {
|
||||
Some((
|
||||
excerpt_id,
|
||||
buffer
|
||||
.text_for_range(range.context)
|
||||
excerpt.context.start,
|
||||
buffer_snapshot
|
||||
.buffer_for_id(buffer_to_fold)
|
||||
.unwrap()
|
||||
.text_for_range(excerpt.context)
|
||||
.collect::<String>(),
|
||||
))
|
||||
} else {
|
||||
|
|
@ -4518,7 +4536,7 @@ mod tests {
|
|||
let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
|
||||
let buffer_ids = buffer_snapshot
|
||||
.excerpts()
|
||||
.map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
|
||||
.map(|excerpt| excerpt.context.start.buffer_id)
|
||||
.dedup()
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(buffer_ids.len(), 1);
|
||||
|
|
@ -4563,7 +4581,7 @@ mod tests {
|
|||
let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
|
||||
let buffer_ids = buffer_snapshot
|
||||
.excerpts()
|
||||
.map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
|
||||
.map(|excerpt| excerpt.context.start.buffer_id)
|
||||
.dedup()
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(buffer_ids.len(), 1);
|
||||
|
|
@ -4635,11 +4653,6 @@ mod tests {
|
|||
let subscription =
|
||||
rhs_multibuffer.update(cx, |rhs_multibuffer, _| rhs_multibuffer.subscribe());
|
||||
|
||||
let lhs_excerpt_id =
|
||||
lhs_multibuffer.read_with(cx, |mb, cx| mb.snapshot(cx).excerpts().next().unwrap().0);
|
||||
let rhs_excerpt_id =
|
||||
rhs_multibuffer.read_with(cx, |mb, cx| mb.snapshot(cx).excerpts().next().unwrap().0);
|
||||
|
||||
let lhs_buffer_snapshot = cx.update(|cx| lhs_multibuffer.read(cx).snapshot(cx));
|
||||
let (mut _lhs_inlay_map, lhs_inlay_snapshot) = InlayMap::new(lhs_buffer_snapshot);
|
||||
let (mut _lhs_fold_map, lhs_fold_snapshot) = FoldMap::new(lhs_inlay_snapshot);
|
||||
|
|
@ -4661,13 +4674,11 @@ mod tests {
|
|||
let rhs_entity_id = rhs_multibuffer.entity_id();
|
||||
|
||||
let companion = cx.new(|_| {
|
||||
let mut c = Companion::new(
|
||||
Companion::new(
|
||||
rhs_entity_id,
|
||||
convert_rhs_rows_to_lhs,
|
||||
convert_lhs_rows_to_rhs,
|
||||
);
|
||||
c.add_excerpt_mapping(lhs_excerpt_id, rhs_excerpt_id);
|
||||
c
|
||||
)
|
||||
});
|
||||
|
||||
let rhs_edits = Patch::new(vec![text::Edit {
|
||||
|
|
|
|||
|
|
@ -363,7 +363,7 @@ pub struct ItemSummary {
|
|||
impl Default for ItemSummary {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
range: Anchor::min()..Anchor::min(),
|
||||
range: Anchor::Min..Anchor::Min,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -185,16 +185,18 @@ impl FoldMapWriter<'_> {
|
|||
continue;
|
||||
}
|
||||
|
||||
let fold_range = buffer.anchor_after(range.start)..buffer.anchor_before(range.end);
|
||||
// For now, ignore any ranges that span an excerpt boundary.
|
||||
let fold_range =
|
||||
FoldRange(buffer.anchor_after(range.start)..buffer.anchor_before(range.end));
|
||||
if fold_range.0.start.excerpt_id != fold_range.0.end.excerpt_id {
|
||||
if buffer
|
||||
.anchor_range_to_buffer_anchor_range(fold_range.clone())
|
||||
.is_none()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
folds.push(Fold {
|
||||
id: FoldId(post_inc(&mut self.0.next_fold_id.0)),
|
||||
range: fold_range,
|
||||
range: FoldRange(fold_range),
|
||||
placeholder: fold_text,
|
||||
});
|
||||
|
||||
|
|
@ -510,7 +512,7 @@ impl FoldMap {
|
|||
.snapshot
|
||||
.folds
|
||||
.cursor::<FoldRange>(&inlay_snapshot.buffer);
|
||||
folds_cursor.seek(&FoldRange(anchor..Anchor::max()), Bias::Left);
|
||||
folds_cursor.seek(&FoldRange(anchor..Anchor::Max), Bias::Left);
|
||||
|
||||
let mut folds = iter::from_fn({
|
||||
let inlay_snapshot = &inlay_snapshot;
|
||||
|
|
@ -1226,7 +1228,7 @@ impl DerefMut for FoldRange {
|
|||
|
||||
impl Default for FoldRange {
|
||||
fn default() -> Self {
|
||||
Self(Anchor::min()..Anchor::max())
|
||||
Self(Anchor::Min..Anchor::Max)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1262,10 +1264,10 @@ pub struct FoldSummary {
|
|||
impl Default for FoldSummary {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
start: Anchor::min(),
|
||||
end: Anchor::max(),
|
||||
min_start: Anchor::max(),
|
||||
max_end: Anchor::min(),
|
||||
start: Anchor::Min,
|
||||
end: Anchor::Max,
|
||||
min_start: Anchor::Max,
|
||||
max_end: Anchor::Min,
|
||||
count: 0,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1342,7 +1342,7 @@ mod tests {
|
|||
use settings::SettingsStore;
|
||||
use std::{cmp::Reverse, env, sync::Arc};
|
||||
use sum_tree::TreeMap;
|
||||
use text::{Patch, Rope};
|
||||
use text::{BufferId, Patch, Rope};
|
||||
use util::RandomCharIter;
|
||||
use util::post_inc;
|
||||
|
||||
|
|
@ -1351,10 +1351,10 @@ mod tests {
|
|||
assert_eq!(
|
||||
Inlay::hint(
|
||||
InlayId::Hint(0),
|
||||
Anchor::min(),
|
||||
Anchor::Min,
|
||||
&InlayHint {
|
||||
label: InlayHintLabel::String("a".to_string()),
|
||||
position: text::Anchor::MIN,
|
||||
position: text::Anchor::min_for_buffer(BufferId::new(1).unwrap()),
|
||||
padding_left: false,
|
||||
padding_right: false,
|
||||
tooltip: None,
|
||||
|
|
@ -1371,10 +1371,10 @@ mod tests {
|
|||
assert_eq!(
|
||||
Inlay::hint(
|
||||
InlayId::Hint(0),
|
||||
Anchor::min(),
|
||||
Anchor::Min,
|
||||
&InlayHint {
|
||||
label: InlayHintLabel::String("a".to_string()),
|
||||
position: text::Anchor::MIN,
|
||||
position: text::Anchor::min_for_buffer(BufferId::new(1).unwrap()),
|
||||
padding_left: true,
|
||||
padding_right: true,
|
||||
tooltip: None,
|
||||
|
|
@ -1391,10 +1391,10 @@ mod tests {
|
|||
assert_eq!(
|
||||
Inlay::hint(
|
||||
InlayId::Hint(0),
|
||||
Anchor::min(),
|
||||
Anchor::Min,
|
||||
&InlayHint {
|
||||
label: InlayHintLabel::String(" a ".to_string()),
|
||||
position: text::Anchor::MIN,
|
||||
position: text::Anchor::min_for_buffer(BufferId::new(1).unwrap()),
|
||||
padding_left: false,
|
||||
padding_right: false,
|
||||
tooltip: None,
|
||||
|
|
@ -1411,10 +1411,10 @@ mod tests {
|
|||
assert_eq!(
|
||||
Inlay::hint(
|
||||
InlayId::Hint(0),
|
||||
Anchor::min(),
|
||||
Anchor::Min,
|
||||
&InlayHint {
|
||||
label: InlayHintLabel::String(" a ".to_string()),
|
||||
position: text::Anchor::MIN,
|
||||
position: text::Anchor::min_for_buffer(BufferId::new(1).unwrap()),
|
||||
padding_left: true,
|
||||
padding_right: true,
|
||||
tooltip: None,
|
||||
|
|
@ -1434,10 +1434,10 @@ mod tests {
|
|||
assert_eq!(
|
||||
Inlay::hint(
|
||||
InlayId::Hint(0),
|
||||
Anchor::min(),
|
||||
Anchor::Min,
|
||||
&InlayHint {
|
||||
label: InlayHintLabel::String("🎨".to_string()),
|
||||
position: text::Anchor::MIN,
|
||||
position: text::Anchor::min_for_buffer(BufferId::new(1).unwrap()),
|
||||
padding_left: true,
|
||||
padding_right: true,
|
||||
tooltip: None,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use language::point_from_lsp;
|
|||
use multi_buffer::Anchor;
|
||||
use project::{DocumentColor, InlayId};
|
||||
use settings::Settings as _;
|
||||
use text::{Bias, BufferId, OffsetRangeExt as _};
|
||||
use text::{Bias, BufferId};
|
||||
use ui::{App, Context, Window};
|
||||
use util::post_inc;
|
||||
|
||||
|
|
@ -160,9 +160,9 @@ impl Editor {
|
|||
}
|
||||
|
||||
let buffers_to_query = self
|
||||
.visible_excerpts(true, cx)
|
||||
.into_values()
|
||||
.map(|(buffer, ..)| buffer)
|
||||
.visible_buffers(cx)
|
||||
.into_iter()
|
||||
.filter(|buffer| self.is_lsp_relevant(buffer.read(cx).file(), cx))
|
||||
.chain(buffer_id.and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id)))
|
||||
.filter(|editor_buffer| {
|
||||
let editor_buffer_id = editor_buffer.read(cx).remote_id();
|
||||
|
|
@ -184,9 +184,9 @@ impl Editor {
|
|||
buffers_to_query
|
||||
.into_iter()
|
||||
.filter_map(|buffer| {
|
||||
let buffer_id = buffer.read(cx).remote_id();
|
||||
let buffer_snapshot = buffer.read(cx).snapshot();
|
||||
let colors_task = lsp_store.document_colors(buffer, cx)?;
|
||||
Some(async move { (buffer_id, colors_task.await) })
|
||||
Some(async move { (buffer_snapshot, colors_task.await) })
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
|
|
@ -200,40 +200,21 @@ impl Editor {
|
|||
if all_colors.is_empty() {
|
||||
return;
|
||||
}
|
||||
let Ok((multi_buffer_snapshot, editor_excerpts)) = editor.update(cx, |editor, cx| {
|
||||
let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
let editor_excerpts = multi_buffer_snapshot.excerpts().fold(
|
||||
HashMap::default(),
|
||||
|mut acc, (excerpt_id, buffer_snapshot, excerpt_range)| {
|
||||
let excerpt_data = acc
|
||||
.entry(buffer_snapshot.remote_id())
|
||||
.or_insert_with(Vec::new);
|
||||
let excerpt_point_range =
|
||||
excerpt_range.context.to_point_utf16(buffer_snapshot);
|
||||
excerpt_data.push((
|
||||
excerpt_id,
|
||||
buffer_snapshot.clone(),
|
||||
excerpt_point_range,
|
||||
));
|
||||
acc
|
||||
},
|
||||
);
|
||||
(multi_buffer_snapshot, editor_excerpts)
|
||||
}) else {
|
||||
let Some(multi_buffer_snapshot) = editor
|
||||
.update(cx, |editor, cx| editor.buffer.read(cx).snapshot(cx))
|
||||
.ok()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut new_editor_colors: HashMap<BufferId, Vec<(Range<Anchor>, DocumentColor)>> =
|
||||
HashMap::default();
|
||||
for (buffer_id, colors) in all_colors {
|
||||
let Some(excerpts) = editor_excerpts.get(&buffer_id) else {
|
||||
continue;
|
||||
};
|
||||
for (buffer_snapshot, colors) in all_colors {
|
||||
match colors {
|
||||
Ok(colors) => {
|
||||
if colors.colors.is_empty() {
|
||||
new_editor_colors
|
||||
.entry(buffer_id)
|
||||
.entry(buffer_snapshot.remote_id())
|
||||
.or_insert_with(Vec::new)
|
||||
.clear();
|
||||
} else {
|
||||
|
|
@ -241,41 +222,33 @@ impl Editor {
|
|||
let color_start = point_from_lsp(color.lsp_range.start);
|
||||
let color_end = point_from_lsp(color.lsp_range.end);
|
||||
|
||||
for (excerpt_id, buffer_snapshot, excerpt_range) in excerpts {
|
||||
if !excerpt_range.contains(&color_start.0)
|
||||
|| !excerpt_range.contains(&color_end.0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let start = buffer_snapshot.anchor_before(
|
||||
buffer_snapshot.clip_point_utf16(color_start, Bias::Left),
|
||||
);
|
||||
let end = buffer_snapshot.anchor_after(
|
||||
buffer_snapshot.clip_point_utf16(color_end, Bias::Right),
|
||||
);
|
||||
let Some(range) = multi_buffer_snapshot
|
||||
.anchor_range_in_excerpt(*excerpt_id, start..end)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let 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_id).or_insert_with(Vec::new);
|
||||
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));
|
||||
break;
|
||||
}
|
||||
let (Ok(i) | Err(i)) =
|
||||
new_buffer_colors.binary_search_by(|(probe, _)| {
|
||||
probe
|
||||
.start
|
||||
.cmp(&range.start, &multi_buffer_snapshot)
|
||||
.then_with(|| {
|
||||
probe.end.cmp(&range.end, &multi_buffer_snapshot)
|
||||
})
|
||||
});
|
||||
new_buffer_colors.insert(i, (range, color));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,10 +62,10 @@ impl Editor {
|
|||
multi_buffer_snapshot: &MultiBufferSnapshot,
|
||||
cx: &Context<Self>,
|
||||
) -> bool {
|
||||
let Some(excerpt) = multi_buffer_snapshot.excerpt_containing(cursor..cursor) else {
|
||||
let Some((anchor, _)) = multi_buffer_snapshot.anchor_to_buffer_anchor(cursor) else {
|
||||
return false;
|
||||
};
|
||||
let Some(buffer) = self.buffer.read(cx).buffer(excerpt.buffer_id()) else {
|
||||
let Some(buffer) = self.buffer.read(cx).buffer(anchor.buffer_id) else {
|
||||
return false;
|
||||
};
|
||||
lsp_symbols_enabled(buffer.read(cx), cx)
|
||||
|
|
@ -77,19 +77,12 @@ impl Editor {
|
|||
&self,
|
||||
cursor: Anchor,
|
||||
multi_buffer_snapshot: &MultiBufferSnapshot,
|
||||
cx: &Context<Self>,
|
||||
_cx: &Context<Self>,
|
||||
) -> Option<(BufferId, Vec<OutlineItem<Anchor>>)> {
|
||||
let excerpt = multi_buffer_snapshot.excerpt_containing(cursor..cursor)?;
|
||||
let excerpt_id = excerpt.id();
|
||||
let buffer_id = excerpt.buffer_id();
|
||||
if Some(buffer_id) != cursor.text_anchor.buffer_id {
|
||||
return None;
|
||||
}
|
||||
let buffer = self.buffer.read(cx).buffer(buffer_id)?;
|
||||
let buffer_snapshot = buffer.read(cx).snapshot();
|
||||
let cursor_text_anchor = cursor.text_anchor;
|
||||
|
||||
let all_items = self.lsp_document_symbols.get(&buffer_id)?;
|
||||
let (cursor_text_anchor, buffer) = multi_buffer_snapshot.anchor_to_buffer_anchor(cursor)?;
|
||||
let all_items = self
|
||||
.lsp_document_symbols
|
||||
.get(&cursor_text_anchor.buffer_id)?;
|
||||
if all_items.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
|
@ -97,34 +90,36 @@ impl Editor {
|
|||
let mut symbols = all_items
|
||||
.iter()
|
||||
.filter(|item| {
|
||||
item.range
|
||||
.start
|
||||
.cmp(&cursor_text_anchor, &buffer_snapshot)
|
||||
.is_le()
|
||||
&& item
|
||||
.range
|
||||
.end
|
||||
.cmp(&cursor_text_anchor, &buffer_snapshot)
|
||||
.is_ge()
|
||||
item.range.start.cmp(&cursor_text_anchor, buffer).is_le()
|
||||
&& item.range.end.cmp(&cursor_text_anchor, buffer).is_ge()
|
||||
})
|
||||
.map(|item| OutlineItem {
|
||||
depth: item.depth,
|
||||
range: Anchor::range_in_buffer(excerpt_id, item.range.clone()),
|
||||
source_range_for_text: Anchor::range_in_buffer(
|
||||
excerpt_id,
|
||||
item.source_range_for_text.clone(),
|
||||
),
|
||||
text: item.text.clone(),
|
||||
highlight_ranges: item.highlight_ranges.clone(),
|
||||
name_ranges: item.name_ranges.clone(),
|
||||
body_range: item
|
||||
.body_range
|
||||
.as_ref()
|
||||
.map(|r| Anchor::range_in_buffer(excerpt_id, r.clone())),
|
||||
annotation_range: item
|
||||
.annotation_range
|
||||
.as_ref()
|
||||
.map(|r| Anchor::range_in_buffer(excerpt_id, r.clone())),
|
||||
.filter_map(|item| {
|
||||
let range_start = multi_buffer_snapshot.anchor_in_buffer(item.range.start)?;
|
||||
let range_end = multi_buffer_snapshot.anchor_in_buffer(item.range.end)?;
|
||||
let source_range_for_text_start =
|
||||
multi_buffer_snapshot.anchor_in_buffer(item.source_range_for_text.start)?;
|
||||
let source_range_for_text_end =
|
||||
multi_buffer_snapshot.anchor_in_buffer(item.source_range_for_text.end)?;
|
||||
Some(OutlineItem {
|
||||
depth: item.depth,
|
||||
range: range_start..range_end,
|
||||
source_range_for_text: source_range_for_text_start..source_range_for_text_end,
|
||||
text: item.text.clone(),
|
||||
highlight_ranges: item.highlight_ranges.clone(),
|
||||
name_ranges: item.name_ranges.clone(),
|
||||
body_range: item.body_range.as_ref().and_then(|r| {
|
||||
Some(
|
||||
multi_buffer_snapshot.anchor_in_buffer(r.start)?
|
||||
..multi_buffer_snapshot.anchor_in_buffer(r.end)?,
|
||||
)
|
||||
}),
|
||||
annotation_range: item.annotation_range.as_ref().and_then(|r| {
|
||||
Some(
|
||||
multi_buffer_snapshot.anchor_in_buffer(r.start)?
|
||||
..multi_buffer_snapshot.anchor_in_buffer(r.end)?,
|
||||
)
|
||||
}),
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
|
|
@ -135,7 +130,7 @@ impl Editor {
|
|||
retain
|
||||
});
|
||||
|
||||
Some((buffer_id, symbols))
|
||||
Some((buffer.remote_id(), symbols))
|
||||
}
|
||||
|
||||
/// Fetches document symbols from the LSP for buffers that have the setting
|
||||
|
|
@ -155,9 +150,10 @@ impl Editor {
|
|||
};
|
||||
|
||||
let buffers_to_query = self
|
||||
.visible_excerpts(true, cx)
|
||||
.visible_buffers(cx)
|
||||
.into_iter()
|
||||
.filter_map(|(_, (buffer, _, _))| {
|
||||
.filter(|buffer| self.is_lsp_relevant(buffer.read(cx).file(), cx))
|
||||
.filter_map(|buffer| {
|
||||
let id = buffer.read(cx).remote_id();
|
||||
if for_buffer.is_none_or(|target| target == id)
|
||||
&& lsp_symbols_enabled(buffer.read(cx), cx)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use gpui::{
|
|||
use indoc::indoc;
|
||||
use language::EditPredictionsMode;
|
||||
use language::{Buffer, CodeLabel};
|
||||
use multi_buffer::{Anchor, ExcerptId, MultiBufferSnapshot, ToPoint};
|
||||
use multi_buffer::{Anchor, MultiBufferSnapshot, ToPoint};
|
||||
use project::{Completion, CompletionResponse, CompletionSource};
|
||||
use std::{
|
||||
ops::Range,
|
||||
|
|
@ -1242,15 +1242,14 @@ struct FakeCompletionMenuProvider;
|
|||
impl CompletionProvider for FakeCompletionMenuProvider {
|
||||
fn completions(
|
||||
&self,
|
||||
_excerpt_id: ExcerptId,
|
||||
_buffer: &Entity<Buffer>,
|
||||
buffer: &Entity<Buffer>,
|
||||
_buffer_position: text::Anchor,
|
||||
_trigger: CompletionContext,
|
||||
_window: &mut Window,
|
||||
_cx: &mut Context<crate::Editor>,
|
||||
cx: &mut Context<crate::Editor>,
|
||||
) -> Task<anyhow::Result<Vec<CompletionResponse>>> {
|
||||
let completion = Completion {
|
||||
replace_range: text::Anchor::MIN..text::Anchor::MAX,
|
||||
replace_range: text::Anchor::min_max_range_for_buffer(buffer.read(cx).remote_id()),
|
||||
new_text: "fake_completion".to_string(),
|
||||
label: CodeLabel::plain("fake_completion".to_string(), None),
|
||||
documentation: None,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -59,7 +59,6 @@ use std::{
|
|||
sync::atomic::{self, AtomicUsize},
|
||||
};
|
||||
use test::build_editor_with_project;
|
||||
use text::ToPoint as _;
|
||||
use unindent::Unindent;
|
||||
use util::{
|
||||
assert_set_eq, path,
|
||||
|
|
@ -1030,12 +1029,13 @@ async fn test_navigation_history(cx: &mut TestAppContext) {
|
|||
original_scroll_position
|
||||
);
|
||||
|
||||
let other_buffer =
|
||||
cx.new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local("test", cx)), cx));
|
||||
|
||||
// Ensure we don't panic when navigation data contains invalid anchors *and* points.
|
||||
let mut invalid_anchor = editor
|
||||
.scroll_manager
|
||||
.native_anchor(&editor.display_snapshot(cx), cx)
|
||||
.anchor;
|
||||
invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
|
||||
let invalid_anchor = other_buffer.update(cx, |buffer, cx| {
|
||||
buffer.snapshot(cx).anchor_after(MultiBufferOffset(3))
|
||||
});
|
||||
let invalid_point = Point::new(9999, 0);
|
||||
editor.navigate(
|
||||
Arc::new(NavigationData {
|
||||
|
|
@ -13836,7 +13836,7 @@ async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
|
|||
0,
|
||||
cx,
|
||||
);
|
||||
assert_eq!(multi_buffer.excerpt_ids().len(), 9);
|
||||
assert_eq!(multi_buffer.read(cx).excerpts().count(), 9);
|
||||
multi_buffer
|
||||
});
|
||||
let multi_buffer_editor = cx.new_window_entity(|window, cx| {
|
||||
|
|
@ -18946,157 +18946,6 @@ fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
|
|||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_refresh_selections(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let buffer = cx.new(|cx| Buffer::local(sample_text(5, 4, 'a'), cx));
|
||||
let multibuffer = cx.new(|cx| {
|
||||
let mut multibuffer = MultiBuffer::new(ReadWrite);
|
||||
multibuffer.set_excerpts_for_path(
|
||||
PathKey::sorted(0),
|
||||
buffer.clone(),
|
||||
[
|
||||
Point::new(0, 0)..Point::new(1, 4),
|
||||
Point::new(3, 0)..Point::new(4, 4),
|
||||
],
|
||||
0,
|
||||
cx,
|
||||
);
|
||||
multibuffer
|
||||
});
|
||||
|
||||
let editor = cx.add_window(|window, cx| {
|
||||
let mut editor = build_editor(multibuffer.clone(), window, cx);
|
||||
let snapshot = editor.snapshot(window, cx);
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
|
||||
});
|
||||
editor.begin_selection(
|
||||
Point::new(2, 1).to_display_point(&snapshot),
|
||||
true,
|
||||
1,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
assert_eq!(
|
||||
editor.selections.ranges(&editor.display_snapshot(cx)),
|
||||
[
|
||||
Point::new(1, 3)..Point::new(1, 3),
|
||||
Point::new(2, 1)..Point::new(2, 1),
|
||||
]
|
||||
);
|
||||
editor
|
||||
});
|
||||
|
||||
// Refreshing selections is a no-op when excerpts haven't changed.
|
||||
_ = editor.update(cx, |editor, window, cx| {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
|
||||
assert_eq!(
|
||||
editor.selections.ranges(&editor.display_snapshot(cx)),
|
||||
[
|
||||
Point::new(1, 3)..Point::new(1, 3),
|
||||
Point::new(2, 1)..Point::new(2, 1),
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
multibuffer.update(cx, |multibuffer, cx| {
|
||||
multibuffer.set_excerpts_for_path(
|
||||
PathKey::sorted(0),
|
||||
buffer.clone(),
|
||||
[Point::new(3, 0)..Point::new(4, 4)],
|
||||
0,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
_ = editor.update(cx, |editor, window, cx| {
|
||||
// Removing an excerpt causes the first selection to become degenerate.
|
||||
assert_eq!(
|
||||
editor.selections.ranges(&editor.display_snapshot(cx)),
|
||||
[
|
||||
Point::new(0, 0)..Point::new(0, 0),
|
||||
Point::new(0, 1)..Point::new(0, 1)
|
||||
]
|
||||
);
|
||||
|
||||
// Refreshing selections will relocate the first selection to the original buffer
|
||||
// location.
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
|
||||
assert_eq!(
|
||||
editor.selections.ranges(&editor.display_snapshot(cx)),
|
||||
[
|
||||
Point::new(0, 0)..Point::new(0, 0),
|
||||
Point::new(0, 1)..Point::new(0, 1),
|
||||
]
|
||||
);
|
||||
assert!(editor.selections.pending_anchor().is_some());
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let buffer = cx.new(|cx| Buffer::local(sample_text(5, 4, 'a'), cx));
|
||||
let multibuffer = cx.new(|cx| {
|
||||
let mut multibuffer = MultiBuffer::new(ReadWrite);
|
||||
multibuffer.set_excerpts_for_path(
|
||||
PathKey::sorted(0),
|
||||
buffer.clone(),
|
||||
[
|
||||
Point::new(0, 0)..Point::new(1, 4),
|
||||
Point::new(3, 0)..Point::new(4, 4),
|
||||
],
|
||||
0,
|
||||
cx,
|
||||
);
|
||||
assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\ndddd\neeee");
|
||||
multibuffer
|
||||
});
|
||||
|
||||
let editor = cx.add_window(|window, cx| {
|
||||
let mut editor = build_editor(multibuffer.clone(), window, cx);
|
||||
let snapshot = editor.snapshot(window, cx);
|
||||
editor.begin_selection(
|
||||
Point::new(1, 3).to_display_point(&snapshot),
|
||||
false,
|
||||
1,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
assert_eq!(
|
||||
editor.selections.ranges(&editor.display_snapshot(cx)),
|
||||
[Point::new(1, 3)..Point::new(1, 3)]
|
||||
);
|
||||
editor
|
||||
});
|
||||
|
||||
multibuffer.update(cx, |multibuffer, cx| {
|
||||
multibuffer.set_excerpts_for_path(
|
||||
PathKey::sorted(0),
|
||||
buffer.clone(),
|
||||
[Point::new(3, 0)..Point::new(4, 4)],
|
||||
0,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
_ = editor.update(cx, |editor, window, cx| {
|
||||
assert_eq!(
|
||||
editor.selections.ranges(&editor.display_snapshot(cx)),
|
||||
[Point::new(0, 0)..Point::new(0, 0)]
|
||||
);
|
||||
|
||||
// Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
|
||||
assert_eq!(
|
||||
editor.selections.ranges(&editor.display_snapshot(cx)),
|
||||
[Point::new(0, 0)..Point::new(0, 0)]
|
||||
);
|
||||
assert!(editor.selections.pending_anchor().is_some());
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
|
@ -19738,8 +19587,8 @@ async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
|
|||
|
||||
let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
|
||||
(
|
||||
project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
|
||||
project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
|
||||
project.create_local_buffer("abc\ndef\nghi\njkl\nmno\npqr\nstu\nvwx\nyza\nbcd\nefg\nhij\nklm\nnop\nqrs\ntuv\nwxy\nzab\ncde\nfgh\n", None, false, cx),
|
||||
project.create_local_buffer("aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\n", None, false, cx),
|
||||
)
|
||||
});
|
||||
|
||||
|
|
@ -19814,7 +19663,7 @@ async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
|
|||
// Remove some excerpts.
|
||||
leader.update(cx, |leader, cx| {
|
||||
leader.buffer.update(cx, |multibuffer, cx| {
|
||||
multibuffer.remove_excerpts_for_path(
|
||||
multibuffer.remove_excerpts(
|
||||
PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
|
||||
cx,
|
||||
);
|
||||
|
|
@ -23318,7 +23167,7 @@ async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
|
|||
0,
|
||||
cx,
|
||||
);
|
||||
assert_eq!(multibuffer.excerpt_ids().len(), 9);
|
||||
assert_eq!(multibuffer.read(cx).excerpts().count(), 9);
|
||||
multibuffer
|
||||
});
|
||||
|
||||
|
|
@ -23422,7 +23271,7 @@ async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
|
|||
0,
|
||||
cx,
|
||||
);
|
||||
assert_eq!(multibuffer.excerpt_ids().len(), 3);
|
||||
assert_eq!(multibuffer.read(cx).excerpts().count(), 3);
|
||||
multibuffer
|
||||
});
|
||||
|
||||
|
|
@ -24191,9 +24040,13 @@ async fn setup_indent_guides_editor(
|
|||
|
||||
let buffer_id = cx.update_editor(|editor, window, cx| {
|
||||
editor.set_text(text, window, cx);
|
||||
let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
|
||||
|
||||
buffer_ids[0]
|
||||
editor
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.as_singleton()
|
||||
.unwrap()
|
||||
.read(cx)
|
||||
.remote_id()
|
||||
});
|
||||
|
||||
(buffer_id, cx)
|
||||
|
|
@ -24902,7 +24755,7 @@ async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
|
|||
editor
|
||||
.snapshot(window, cx)
|
||||
.buffer_snapshot()
|
||||
.indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
|
||||
.indent_guides_in_range(Anchor::Min..Anchor::Max, false, cx)
|
||||
.map(|guide| (guide.start_row..=guide.end_row, guide.depth))
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
|
|
@ -24957,12 +24810,19 @@ async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestApp
|
|||
let hunk_ranges = cx.update_editor(|editor, window, cx| {
|
||||
let snapshot = editor.snapshot(window, cx);
|
||||
let hunks = editor
|
||||
.diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
|
||||
.diff_hunks_in_ranges(&[Anchor::Min..Anchor::Max], &snapshot.buffer_snapshot())
|
||||
.collect::<Vec<_>>();
|
||||
let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
|
||||
let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
|
||||
hunks
|
||||
.into_iter()
|
||||
.map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
|
||||
.map(|hunk| {
|
||||
multibuffer_snapshot
|
||||
.anchor_in_excerpt(hunk.buffer_range.start)
|
||||
.unwrap()
|
||||
..multibuffer_snapshot
|
||||
.anchor_in_excerpt(hunk.buffer_range.end)
|
||||
.unwrap()
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
assert_eq!(hunk_ranges.len(), 2);
|
||||
|
|
@ -25047,12 +24907,19 @@ async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestApp
|
|||
let hunk_ranges = cx.update_editor(|editor, window, cx| {
|
||||
let snapshot = editor.snapshot(window, cx);
|
||||
let hunks = editor
|
||||
.diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
|
||||
.diff_hunks_in_ranges(&[Anchor::Min..Anchor::Max], &snapshot.buffer_snapshot())
|
||||
.collect::<Vec<_>>();
|
||||
let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
|
||||
let multibuffer_snapshot = snapshot.buffer_snapshot();
|
||||
hunks
|
||||
.into_iter()
|
||||
.map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
|
||||
.map(|hunk| {
|
||||
multibuffer_snapshot
|
||||
.anchor_in_excerpt(hunk.buffer_range.start)
|
||||
.unwrap()
|
||||
..multibuffer_snapshot
|
||||
.anchor_in_excerpt(hunk.buffer_range.end)
|
||||
.unwrap()
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
assert_eq!(hunk_ranges.len(), 2);
|
||||
|
|
@ -25112,12 +24979,19 @@ async fn test_toggle_deletion_hunk_at_start_of_file(
|
|||
let hunk_ranges = cx.update_editor(|editor, window, cx| {
|
||||
let snapshot = editor.snapshot(window, cx);
|
||||
let hunks = editor
|
||||
.diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
|
||||
.diff_hunks_in_ranges(&[Anchor::Min..Anchor::Max], &snapshot.buffer_snapshot())
|
||||
.collect::<Vec<_>>();
|
||||
let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
|
||||
let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
|
||||
hunks
|
||||
.into_iter()
|
||||
.map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
|
||||
.map(|hunk| {
|
||||
multibuffer_snapshot
|
||||
.anchor_in_excerpt(hunk.buffer_range.start)
|
||||
.unwrap()
|
||||
..multibuffer_snapshot
|
||||
.anchor_in_excerpt(hunk.buffer_range.end)
|
||||
.unwrap()
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
assert_eq!(hunk_ranges.len(), 1);
|
||||
|
|
@ -25217,12 +25091,17 @@ async fn test_expand_first_line_diff_hunk_keeps_deleted_lines_visible(
|
|||
// Expanding a diff hunk at the first line inserts deleted lines above the first buffer line.
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
let snapshot = editor.snapshot(window, cx);
|
||||
let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
|
||||
let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
|
||||
let hunks = editor
|
||||
.diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
|
||||
.diff_hunks_in_ranges(&[Anchor::Min..Anchor::Max], &snapshot.buffer_snapshot())
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(hunks.len(), 1);
|
||||
let hunk_range = Anchor::range_in_buffer(excerpt_id, hunks[0].buffer_range.clone());
|
||||
let hunk_range = multibuffer_snapshot
|
||||
.anchor_in_excerpt(hunks[0].buffer_range.start)
|
||||
.unwrap()
|
||||
..multibuffer_snapshot
|
||||
.anchor_in_excerpt(hunks[0].buffer_range.end)
|
||||
.unwrap();
|
||||
editor.toggle_single_diff_hunk(hunk_range, cx)
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
|
@ -25279,7 +25158,7 @@ async fn test_display_diff_hunks(cx: &mut TestAppContext) {
|
|||
multibuffer.set_excerpts_for_path(
|
||||
PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
|
||||
buffer.clone(),
|
||||
vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
|
||||
vec![Point::zero()..snapshot.max_point()],
|
||||
2,
|
||||
cx,
|
||||
);
|
||||
|
|
@ -25365,7 +25244,7 @@ async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
|
|||
cx.update_editor(|editor, window, cx| {
|
||||
let snapshot = editor.snapshot(window, cx);
|
||||
let hunks = editor
|
||||
.diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
|
||||
.diff_hunks_in_ranges(&[Anchor::Min..Anchor::Max], &snapshot.buffer_snapshot())
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(hunks.len(), 1);
|
||||
assert_eq!(
|
||||
|
|
@ -26450,7 +26329,7 @@ async fn test_folded_buffers_cleared_on_excerpts_removed(cx: &mut TestAppContext
|
|||
// `multi_buffer::Event::ExcerptsRemoved` event is emitted, which should be
|
||||
// picked up by the editor and update the display map accordingly.
|
||||
multi_buffer.update(cx, |multi_buffer, cx| {
|
||||
multi_buffer.remove_excerpts_for_path(PathKey::sorted(0), cx)
|
||||
multi_buffer.remove_excerpts(PathKey::sorted(0), cx)
|
||||
});
|
||||
assert!(!editor.update(cx, |editor, cx| editor.has_any_buffer_folded(cx)));
|
||||
}
|
||||
|
|
@ -26702,7 +26581,12 @@ async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContex
|
|||
);
|
||||
let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
|
||||
|
||||
let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
|
||||
let buffer_ids = multi_buffer
|
||||
.read(cx)
|
||||
.snapshot(cx)
|
||||
.excerpts()
|
||||
.map(|excerpt| excerpt.context.start.buffer_id)
|
||||
.collect::<Vec<_>>();
|
||||
// fold all but the second buffer, so that we test navigating between two
|
||||
// adjacent folded buffers, as well as folded buffers at the start and
|
||||
// end the multibuffer
|
||||
|
|
@ -27038,7 +26922,12 @@ async fn assert_highlighted_edits(
|
|||
let text_anchor_edits = edits
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit.into()))
|
||||
.map(|(range, edit)| {
|
||||
(
|
||||
range.start.expect_text_anchor()..range.end.expect_text_anchor(),
|
||||
edit.into(),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let edit_preview = window
|
||||
|
|
@ -27055,10 +26944,11 @@ async fn assert_highlighted_edits(
|
|||
|
||||
cx.update(|_window, cx| {
|
||||
let highlighted_edits = edit_prediction_edit_text(
|
||||
snapshot.as_singleton().unwrap().2,
|
||||
snapshot.as_singleton().unwrap(),
|
||||
&edits,
|
||||
&edit_preview,
|
||||
include_deletions,
|
||||
&snapshot,
|
||||
cx,
|
||||
);
|
||||
assertion_fn(highlighted_edits, cx)
|
||||
|
|
@ -31479,12 +31369,8 @@ async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_mult
|
|||
Point::new(1, 21)..Point::new(1, 25),
|
||||
])
|
||||
});
|
||||
let first_buffer_id = multi_buffer
|
||||
.read(cx)
|
||||
.excerpt_buffer_ids()
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap();
|
||||
let snapshot = multi_buffer.read(cx).snapshot(cx);
|
||||
let first_buffer_id = snapshot.all_buffer_ids().next().unwrap();
|
||||
let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
|
||||
first_buffer.update(cx, |buffer, cx| {
|
||||
buffer.set_language(Some(markdown_language.clone()), cx);
|
||||
|
|
@ -32530,7 +32416,12 @@ async fn test_multibuffer_selections_with_folding(cx: &mut TestAppContext) {
|
|||
});
|
||||
|
||||
let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
|
||||
let buffer_ids = cx.multibuffer(|mb, _| mb.excerpt_buffer_ids());
|
||||
let buffer_ids = cx.multibuffer(|mb, cx| {
|
||||
mb.snapshot(cx)
|
||||
.excerpts()
|
||||
.map(|excerpt| excerpt.context.start.buffer_id)
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
cx.assert_excerpts_with_selections(indoc! {"
|
||||
[EXCERPT]
|
||||
|
|
@ -33770,7 +33661,7 @@ async fn test_diff_review_button_shown_when_ai_enabled(cx: &mut TestAppContext)
|
|||
}
|
||||
|
||||
/// Helper function to create a DiffHunkKey for testing.
|
||||
/// Uses Anchor::min() as a placeholder anchor since these tests don't need
|
||||
/// Uses Anchor::Min as a placeholder anchor since these tests don't need
|
||||
/// real buffer positioning.
|
||||
fn test_hunk_key(file_path: &str) -> DiffHunkKey {
|
||||
DiffHunkKey {
|
||||
|
|
@ -33779,7 +33670,7 @@ fn test_hunk_key(file_path: &str) -> DiffHunkKey {
|
|||
} else {
|
||||
Arc::from(util::rel_path::RelPath::unix(file_path).unwrap())
|
||||
},
|
||||
hunk_start_anchor: Anchor::min(),
|
||||
hunk_start_anchor: Anchor::Min,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -33802,7 +33693,7 @@ fn add_test_comment(
|
|||
comment: &str,
|
||||
cx: &mut Context<Editor>,
|
||||
) -> usize {
|
||||
editor.add_review_comment(key, comment.to_string(), Anchor::min()..Anchor::max(), cx)
|
||||
editor.add_review_comment(key, comment.to_string(), Anchor::Min..Anchor::Max, cx)
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ use itertools::Itertools;
|
|||
use language::{HighlightedText, IndentGuideSettings, language_settings::ShowWhitespaceSetting};
|
||||
use markdown::Markdown;
|
||||
use multi_buffer::{
|
||||
Anchor, ExcerptId, ExcerptInfo, ExpandExcerptDirection, ExpandInfo, MultiBufferPoint,
|
||||
Anchor, ExcerptBoundaryInfo, ExpandExcerptDirection, ExpandInfo, MultiBufferPoint,
|
||||
MultiBufferRow, RowInfo,
|
||||
};
|
||||
|
||||
|
|
@ -1390,13 +1390,13 @@ impl EditorElement {
|
|||
.snapshot
|
||||
.display_point_to_anchor(valid_point, Bias::Left);
|
||||
|
||||
if let Some((buffer_snapshot, file)) = position_map
|
||||
if let Some((buffer_anchor, buffer_snapshot)) = position_map
|
||||
.snapshot
|
||||
.buffer_snapshot()
|
||||
.buffer_for_excerpt(buffer_anchor.excerpt_id)
|
||||
.and_then(|buffer| buffer.file().map(|file| (buffer, file)))
|
||||
.anchor_to_buffer_anchor(buffer_anchor)
|
||||
&& let Some(file) = buffer_snapshot.file()
|
||||
{
|
||||
let as_point = text::ToPoint::to_point(&buffer_anchor.text_anchor, buffer_snapshot);
|
||||
let as_point = text::ToPoint::to_point(&buffer_anchor, buffer_snapshot);
|
||||
|
||||
let is_visible = editor
|
||||
.gutter_breakpoint_indicator
|
||||
|
|
@ -1752,7 +1752,7 @@ impl EditorElement {
|
|||
// Remote cursors
|
||||
if let Some(collaboration_hub) = &editor.collaboration_hub {
|
||||
for remote_selection in snapshot.remote_selections_in_range(
|
||||
&(Anchor::min()..Anchor::max()),
|
||||
&(Anchor::Min..Anchor::Max),
|
||||
collaboration_hub.deref(),
|
||||
cx,
|
||||
) {
|
||||
|
|
@ -2589,12 +2589,6 @@ impl EditorElement {
|
|||
const INLINE_SLOT_CHAR_LIMIT: u32 = 4;
|
||||
const MAX_ALTERNATE_DISTANCE: u32 = 8;
|
||||
|
||||
let excerpt_id = snapshot
|
||||
.display_snapshot
|
||||
.buffer_snapshot()
|
||||
.excerpt_containing(buffer_point..buffer_point)
|
||||
.map(|excerpt| excerpt.id());
|
||||
|
||||
let is_valid_row = |row_candidate: u32| -> bool {
|
||||
// move to other row if folded row
|
||||
if snapshot.is_line_folded(MultiBufferRow(row_candidate)) {
|
||||
|
|
@ -2610,13 +2604,18 @@ impl EditorElement {
|
|||
row: row_candidate,
|
||||
column: 0,
|
||||
};
|
||||
let candidate_excerpt_id = snapshot
|
||||
// move to other row if different excerpt
|
||||
let range = if candidate_point < buffer_point {
|
||||
candidate_point..buffer_point
|
||||
} else {
|
||||
buffer_point..candidate_point
|
||||
};
|
||||
if snapshot
|
||||
.display_snapshot
|
||||
.buffer_snapshot()
|
||||
.excerpt_containing(candidate_point..candidate_point)
|
||||
.map(|excerpt| excerpt.id());
|
||||
// move to other row if different excerpt
|
||||
if excerpt_id != candidate_excerpt_id {
|
||||
.excerpt_containing(range)
|
||||
.is_none()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -2796,7 +2795,7 @@ impl EditorElement {
|
|||
.newest::<language::Point>(&editor_snapshot.display_snapshot)
|
||||
.head();
|
||||
|
||||
let Some((buffer, buffer_point, _)) = editor_snapshot
|
||||
let Some((buffer, buffer_point)) = editor_snapshot
|
||||
.buffer_snapshot()
|
||||
.point_to_buffer_point(cursor_point)
|
||||
else {
|
||||
|
|
@ -3389,8 +3388,8 @@ impl EditorElement {
|
|||
.enumerate()
|
||||
.map(|(ix, row_info)| {
|
||||
let ExpandInfo {
|
||||
excerpt_id,
|
||||
direction,
|
||||
start_anchor,
|
||||
} = row_info.expand_info?;
|
||||
|
||||
let icon_name = match direction {
|
||||
|
|
@ -3419,7 +3418,7 @@ impl EditorElement {
|
|||
.width(width)
|
||||
.on_click(move |_, window, cx| {
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.expand_excerpt(excerpt_id, direction, window, cx);
|
||||
editor.expand_excerpt(start_anchor, direction, window, cx);
|
||||
});
|
||||
})
|
||||
.tooltip(Tooltip::for_action_title(
|
||||
|
|
@ -3886,7 +3885,7 @@ impl EditorElement {
|
|||
selected_buffer_ids: &Vec<BufferId>,
|
||||
latest_selection_anchors: &HashMap<BufferId, Anchor>,
|
||||
is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
|
||||
sticky_header_excerpt_id: Option<ExcerptId>,
|
||||
sticky_header_excerpt_id: Option<BufferId>,
|
||||
indent_guides: &Option<Vec<IndentGuideLayout>>,
|
||||
block_resize_offset: &mut i32,
|
||||
window: &mut Window,
|
||||
|
|
@ -3974,7 +3973,7 @@ impl EditorElement {
|
|||
let mut result = v_flex().id(block_id).w_full().pr(editor_margins.right);
|
||||
|
||||
if self.should_show_buffer_headers() {
|
||||
let selected = selected_buffer_ids.contains(&first_excerpt.buffer_id);
|
||||
let selected = selected_buffer_ids.contains(&first_excerpt.buffer_id());
|
||||
let jump_data = header_jump_data(
|
||||
snapshot,
|
||||
block_row_start,
|
||||
|
|
@ -4029,8 +4028,8 @@ impl EditorElement {
|
|||
latest_selection_anchors,
|
||||
);
|
||||
|
||||
if sticky_header_excerpt_id != Some(excerpt.id) {
|
||||
let selected = selected_buffer_ids.contains(&excerpt.buffer_id);
|
||||
if sticky_header_excerpt_id != Some(excerpt.buffer_id()) {
|
||||
let selected = selected_buffer_ids.contains(&excerpt.buffer_id());
|
||||
|
||||
result = result.child(div().pr(editor_margins.right).child(
|
||||
self.render_buffer_header(
|
||||
|
|
@ -4190,7 +4189,7 @@ impl EditorElement {
|
|||
|
||||
fn render_buffer_header(
|
||||
&self,
|
||||
for_excerpt: &ExcerptInfo,
|
||||
for_excerpt: &ExcerptBoundaryInfo,
|
||||
is_folded: bool,
|
||||
is_selected: bool,
|
||||
is_sticky: bool,
|
||||
|
|
@ -4227,7 +4226,7 @@ impl EditorElement {
|
|||
selected_buffer_ids: &Vec<BufferId>,
|
||||
latest_selection_anchors: &HashMap<BufferId, Anchor>,
|
||||
is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
|
||||
sticky_header_excerpt_id: Option<ExcerptId>,
|
||||
sticky_header_excerpt_id: Option<BufferId>,
|
||||
indent_guides: &Option<Vec<IndentGuideLayout>>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
|
|
@ -4520,7 +4519,7 @@ impl EditorElement {
|
|||
|
||||
let editor_bg_color = cx.theme().colors().editor_background;
|
||||
|
||||
let selected = selected_buffer_ids.contains(&excerpt.buffer_id);
|
||||
let selected = selected_buffer_ids.contains(&excerpt.buffer_id());
|
||||
|
||||
let available_width = hitbox.bounds.size.width - right_margin;
|
||||
|
||||
|
|
@ -7894,23 +7893,26 @@ impl EditorElement {
|
|||
return;
|
||||
}
|
||||
let buffer_snapshot = &display_snapshot.buffer_snapshot();
|
||||
for (buffer, buffer_range, excerpt_id) in
|
||||
buffer_snapshot.range_to_buffer_ranges(anchor_range.start..=anchor_range.end)
|
||||
for (excerpt_buffer_snapshot, buffer_range, _) in
|
||||
buffer_snapshot.range_to_buffer_ranges(anchor_range.start..anchor_range.end)
|
||||
{
|
||||
let buffer_range =
|
||||
buffer.anchor_after(buffer_range.start)..buffer.anchor_before(buffer_range.end);
|
||||
let buffer_range = excerpt_buffer_snapshot.anchor_after(buffer_range.start)
|
||||
..excerpt_buffer_snapshot.anchor_before(buffer_range.end);
|
||||
selections.extend(debug_ranges.ranges.iter().flat_map(|debug_range| {
|
||||
let player_color = theme
|
||||
.players()
|
||||
.color_for_participant(debug_range.occurrence_index as u32 + 1);
|
||||
debug_range.ranges.iter().filter_map(move |range| {
|
||||
if range.start.buffer_id != Some(buffer.remote_id()) {
|
||||
debug_range.ranges.iter().filter_map(|range| {
|
||||
let player_color = theme
|
||||
.players()
|
||||
.color_for_participant(debug_range.occurrence_index as u32 + 1);
|
||||
if range.start.buffer_id != excerpt_buffer_snapshot.remote_id() {
|
||||
return None;
|
||||
}
|
||||
let clipped_start = range.start.max(&buffer_range.start, buffer);
|
||||
let clipped_end = range.end.min(&buffer_range.end, buffer);
|
||||
let clipped_start = range
|
||||
.start
|
||||
.max(&buffer_range.start, &excerpt_buffer_snapshot);
|
||||
let clipped_end =
|
||||
range.end.min(&buffer_range.end, &excerpt_buffer_snapshot);
|
||||
let range = buffer_snapshot
|
||||
.anchor_range_in_excerpt(excerpt_id, *clipped_start..*clipped_end)?;
|
||||
.buffer_anchor_range_to_anchor_range(*clipped_start..*clipped_end)?;
|
||||
let start = range.start.to_display_point(display_snapshot);
|
||||
let end = range.end.to_display_point(display_snapshot);
|
||||
let selection_layout = SelectionLayout {
|
||||
|
|
@ -8150,49 +8152,23 @@ pub(crate) fn header_jump_data(
|
|||
editor_snapshot: &EditorSnapshot,
|
||||
block_row_start: DisplayRow,
|
||||
height: u32,
|
||||
first_excerpt: &ExcerptInfo,
|
||||
first_excerpt: &ExcerptBoundaryInfo,
|
||||
latest_selection_anchors: &HashMap<BufferId, Anchor>,
|
||||
) -> JumpData {
|
||||
let jump_target = if let Some(anchor) = latest_selection_anchors.get(&first_excerpt.buffer_id)
|
||||
&& let Some(range) = editor_snapshot.context_range_for_excerpt(anchor.excerpt_id)
|
||||
&& let Some(buffer) = editor_snapshot
|
||||
.buffer_snapshot()
|
||||
.buffer_for_excerpt(anchor.excerpt_id)
|
||||
let multibuffer_snapshot = editor_snapshot.buffer_snapshot();
|
||||
let buffer = first_excerpt.buffer(multibuffer_snapshot);
|
||||
let (jump_anchor, jump_buffer) = if let Some(anchor) =
|
||||
latest_selection_anchors.get(&first_excerpt.buffer_id())
|
||||
&& let Some((jump_anchor, selection_buffer)) =
|
||||
multibuffer_snapshot.anchor_to_buffer_anchor(*anchor)
|
||||
{
|
||||
JumpTargetInExcerptInput {
|
||||
id: anchor.excerpt_id,
|
||||
buffer,
|
||||
excerpt_start_anchor: range.start,
|
||||
jump_anchor: anchor.text_anchor,
|
||||
}
|
||||
(jump_anchor, selection_buffer)
|
||||
} else {
|
||||
JumpTargetInExcerptInput {
|
||||
id: first_excerpt.id,
|
||||
buffer: &first_excerpt.buffer,
|
||||
excerpt_start_anchor: first_excerpt.range.context.start,
|
||||
jump_anchor: first_excerpt.range.primary.start,
|
||||
}
|
||||
(first_excerpt.range.primary.start, buffer)
|
||||
};
|
||||
header_jump_data_inner(editor_snapshot, block_row_start, height, &jump_target)
|
||||
}
|
||||
|
||||
struct JumpTargetInExcerptInput<'a> {
|
||||
id: ExcerptId,
|
||||
buffer: &'a language::BufferSnapshot,
|
||||
excerpt_start_anchor: text::Anchor,
|
||||
jump_anchor: text::Anchor,
|
||||
}
|
||||
|
||||
fn header_jump_data_inner(
|
||||
snapshot: &EditorSnapshot,
|
||||
block_row_start: DisplayRow,
|
||||
height: u32,
|
||||
for_excerpt: &JumpTargetInExcerptInput,
|
||||
) -> JumpData {
|
||||
let buffer = &for_excerpt.buffer;
|
||||
let jump_position = language::ToPoint::to_point(&for_excerpt.jump_anchor, buffer);
|
||||
let excerpt_start = for_excerpt.excerpt_start_anchor;
|
||||
let rows_from_excerpt_start = if for_excerpt.jump_anchor == excerpt_start {
|
||||
let excerpt_start = first_excerpt.range.context.start;
|
||||
let jump_position = language::ToPoint::to_point(&jump_anchor, jump_buffer);
|
||||
let rows_from_excerpt_start = if jump_anchor == excerpt_start {
|
||||
0
|
||||
} else {
|
||||
let excerpt_start_point = language::ToPoint::to_point(&excerpt_start, buffer);
|
||||
|
|
@ -8201,15 +8177,14 @@ fn header_jump_data_inner(
|
|||
|
||||
let line_offset_from_top = (block_row_start.0 + height + rows_from_excerpt_start)
|
||||
.saturating_sub(
|
||||
snapshot
|
||||
editor_snapshot
|
||||
.scroll_anchor
|
||||
.scroll_position(&snapshot.display_snapshot)
|
||||
.scroll_position(&editor_snapshot.display_snapshot)
|
||||
.y as u32,
|
||||
);
|
||||
|
||||
JumpData::MultiBufferPoint {
|
||||
excerpt_id: for_excerpt.id,
|
||||
anchor: for_excerpt.jump_anchor,
|
||||
anchor: jump_anchor,
|
||||
position: jump_position,
|
||||
line_offset_from_top,
|
||||
}
|
||||
|
|
@ -8217,7 +8192,7 @@ fn header_jump_data_inner(
|
|||
|
||||
pub(crate) fn render_buffer_header(
|
||||
editor: &Entity<Editor>,
|
||||
for_excerpt: &ExcerptInfo,
|
||||
for_excerpt: &ExcerptBoundaryInfo,
|
||||
is_folded: bool,
|
||||
is_selected: bool,
|
||||
is_sticky: bool,
|
||||
|
|
@ -8229,6 +8204,8 @@ pub(crate) fn render_buffer_header(
|
|||
let multi_buffer = editor_read.buffer.read(cx);
|
||||
let is_read_only = editor_read.read_only(cx);
|
||||
let editor_handle: &dyn ItemHandle = editor;
|
||||
let multibuffer_snapshot = multi_buffer.snapshot(cx);
|
||||
let buffer = for_excerpt.buffer(&multibuffer_snapshot);
|
||||
|
||||
let breadcrumbs = if is_selected {
|
||||
editor_read.breadcrumbs_inner(cx)
|
||||
|
|
@ -8236,31 +8213,30 @@ pub(crate) fn render_buffer_header(
|
|||
None
|
||||
};
|
||||
|
||||
let buffer_id = for_excerpt.buffer_id();
|
||||
let file_status = multi_buffer
|
||||
.all_diff_hunks_expanded()
|
||||
.then(|| editor_read.status_for_buffer_id(for_excerpt.buffer_id, cx))
|
||||
.then(|| editor_read.status_for_buffer_id(buffer_id, cx))
|
||||
.flatten();
|
||||
let indicator = multi_buffer
|
||||
.buffer(for_excerpt.buffer_id)
|
||||
.and_then(|buffer| {
|
||||
let buffer = buffer.read(cx);
|
||||
let indicator_color = match (buffer.has_conflict(), buffer.is_dirty()) {
|
||||
(true, _) => Some(Color::Warning),
|
||||
(_, true) => Some(Color::Accent),
|
||||
(false, false) => None,
|
||||
};
|
||||
indicator_color.map(|indicator_color| Indicator::dot().color(indicator_color))
|
||||
});
|
||||
let indicator = multi_buffer.buffer(buffer_id).and_then(|buffer| {
|
||||
let buffer = buffer.read(cx);
|
||||
let indicator_color = match (buffer.has_conflict(), buffer.is_dirty()) {
|
||||
(true, _) => Some(Color::Warning),
|
||||
(_, true) => Some(Color::Accent),
|
||||
(false, false) => None,
|
||||
};
|
||||
indicator_color.map(|indicator_color| Indicator::dot().color(indicator_color))
|
||||
});
|
||||
|
||||
let include_root = editor_read
|
||||
.project
|
||||
.as_ref()
|
||||
.map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
|
||||
.unwrap_or_default();
|
||||
let file = for_excerpt.buffer.file();
|
||||
let file = buffer.file();
|
||||
let can_open_excerpts = file.is_none_or(|file| file.can_open());
|
||||
let path_style = file.map(|file| file.path_style(cx));
|
||||
let relative_path = for_excerpt.buffer.resolve_file_path(include_root, cx);
|
||||
let relative_path = buffer.resolve_file_path(include_root, cx);
|
||||
let (parent_path, filename) = if let Some(path) = &relative_path {
|
||||
if let Some(path_style) = path_style {
|
||||
let (dir, file_name) = path_style.split(path);
|
||||
|
|
@ -8275,7 +8251,7 @@ pub(crate) fn render_buffer_header(
|
|||
let colors = cx.theme().colors();
|
||||
|
||||
let header = div()
|
||||
.id(("buffer-header", for_excerpt.buffer_id.to_proto()))
|
||||
.id(("buffer-header", buffer_id.to_proto()))
|
||||
.p(BUFFER_HEADER_PADDING)
|
||||
.w_full()
|
||||
.h(FILE_HEADER_HEIGHT as f32 * window.line_height())
|
||||
|
|
@ -8303,7 +8279,7 @@ pub(crate) fn render_buffer_header(
|
|||
.hover(|style| style.bg(colors.element_hover))
|
||||
.map(|header| {
|
||||
let editor = editor.clone();
|
||||
let buffer_id = for_excerpt.buffer_id;
|
||||
let buffer_id = for_excerpt.buffer_id();
|
||||
let toggle_chevron_icon =
|
||||
FileIcons::get_chevron_icon(!is_folded, cx).map(Icon::from_path);
|
||||
let button_size = rems_from_px(28.);
|
||||
|
|
@ -8367,7 +8343,7 @@ pub(crate) fn render_buffer_header(
|
|||
.addons
|
||||
.values()
|
||||
.filter_map(|addon| {
|
||||
addon.render_buffer_header_controls(for_excerpt, window, cx)
|
||||
addon.render_buffer_header_controls(for_excerpt, buffer, window, cx)
|
||||
})
|
||||
.take(1),
|
||||
)
|
||||
|
|
@ -8460,7 +8436,7 @@ pub(crate) fn render_buffer_header(
|
|||
),
|
||||
)
|
||||
})
|
||||
.when(!for_excerpt.buffer.capability.editable(), |el| {
|
||||
.when(!buffer.capability.editable(), |el| {
|
||||
el.child(Icon::new(IconName::FileLock).color(Color::Muted))
|
||||
})
|
||||
.when_some(breadcrumbs, |then, breadcrumbs| {
|
||||
|
|
@ -8511,7 +8487,7 @@ pub(crate) fn render_buffer_header(
|
|||
})
|
||||
.on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
|
||||
.on_click(window.listener_for(editor, {
|
||||
let buffer_id = for_excerpt.buffer_id;
|
||||
let buffer_id = for_excerpt.buffer_id();
|
||||
move |editor, e: &ClickEvent, window, cx| {
|
||||
if e.modifiers().alt {
|
||||
editor.open_excerpts_common(
|
||||
|
|
@ -8533,7 +8509,7 @@ pub(crate) fn render_buffer_header(
|
|||
),
|
||||
);
|
||||
|
||||
let file = for_excerpt.buffer.file().cloned();
|
||||
let file = buffer.file().cloned();
|
||||
let editor = editor.clone();
|
||||
|
||||
right_click_menu("buffer-header-context-menu")
|
||||
|
|
@ -9855,14 +9831,14 @@ impl Element for EditorElement {
|
|||
};
|
||||
|
||||
let start_anchor = if start_row == Default::default() {
|
||||
Anchor::min()
|
||||
Anchor::Min
|
||||
} else {
|
||||
snapshot.buffer_snapshot().anchor_before(
|
||||
DisplayPoint::new(start_row, 0).to_offset(&snapshot, Bias::Left),
|
||||
)
|
||||
};
|
||||
let end_anchor = if end_row > max_row {
|
||||
Anchor::max()
|
||||
Anchor::Max
|
||||
} else {
|
||||
snapshot.buffer_snapshot().anchor_before(
|
||||
DisplayPoint::new(end_row, 0).to_offset(&snapshot, Bias::Right),
|
||||
|
|
@ -9888,7 +9864,7 @@ impl Element for EditorElement {
|
|||
editor.update(cx, |editor, cx| {
|
||||
let snapshot = editor.snapshot(window, cx);
|
||||
let start_anchor = if start_row == Default::default() {
|
||||
Anchor::min()
|
||||
Anchor::Min
|
||||
} else {
|
||||
snapshot.buffer_snapshot().anchor_before(
|
||||
DisplayPoint::new(start_row, 0)
|
||||
|
|
@ -9896,7 +9872,7 @@ impl Element for EditorElement {
|
|||
)
|
||||
};
|
||||
let end_anchor = if end_row > max_row {
|
||||
Anchor::max()
|
||||
Anchor::Max
|
||||
} else {
|
||||
snapshot.buffer_snapshot().anchor_before(
|
||||
DisplayPoint::new(end_row, 0)
|
||||
|
|
@ -10052,9 +10028,11 @@ impl Element for EditorElement {
|
|||
HashMap::default();
|
||||
for selection in all_anchor_selections.iter() {
|
||||
let head = selection.head();
|
||||
if let Some(buffer_id) = head.text_anchor.buffer_id {
|
||||
if let Some((text_anchor, _)) =
|
||||
snapshot.buffer_snapshot().anchor_to_buffer_anchor(head)
|
||||
{
|
||||
anchors_by_buffer
|
||||
.entry(buffer_id)
|
||||
.entry(text_anchor.buffer_id)
|
||||
.and_modify(|(latest_id, latest_anchor)| {
|
||||
if selection.id > *latest_id {
|
||||
*latest_id = selection.id;
|
||||
|
|
@ -10322,8 +10300,9 @@ impl Element for EditorElement {
|
|||
} else {
|
||||
None
|
||||
};
|
||||
let sticky_header_excerpt_id =
|
||||
sticky_header_excerpt.as_ref().map(|top| top.excerpt.id);
|
||||
let sticky_header_excerpt_id = sticky_header_excerpt
|
||||
.as_ref()
|
||||
.map(|top| top.excerpt.buffer_id());
|
||||
|
||||
let buffer = snapshot.buffer_snapshot();
|
||||
let start_buffer_row = MultiBufferRow(start_anchor.to_point(&buffer).row);
|
||||
|
|
@ -12968,7 +12947,7 @@ mod tests {
|
|||
editor.insert_blocks(
|
||||
[BlockProperties {
|
||||
style: BlockStyle::Fixed,
|
||||
placement: BlockPlacement::Above(Anchor::min()),
|
||||
placement: BlockPlacement::Above(Anchor::Min),
|
||||
height: Some(3),
|
||||
render: Arc::new(|cx| div().h(3. * cx.window.line_height()).into_any()),
|
||||
priority: 0,
|
||||
|
|
|
|||
|
|
@ -21,9 +21,9 @@ impl Editor {
|
|||
};
|
||||
|
||||
let buffers_to_query = self
|
||||
.visible_excerpts(true, cx)
|
||||
.into_values()
|
||||
.map(|(buffer, ..)| buffer)
|
||||
.visible_buffers(cx)
|
||||
.into_iter()
|
||||
.filter(|buffer| self.is_lsp_relevant(buffer.read(cx).file(), cx))
|
||||
.chain(for_buffer.and_then(|id| self.buffer.read(cx).buffer(id)))
|
||||
.filter(|buffer| {
|
||||
let id = buffer.read(cx).remote_id();
|
||||
|
|
|
|||
|
|
@ -204,8 +204,8 @@ impl GitBlame {
|
|||
git_blame.generate(cx);
|
||||
}
|
||||
}
|
||||
multi_buffer::Event::ExcerptsAdded { .. }
|
||||
| multi_buffer::Event::ExcerptsEdited { .. } => git_blame.regenerate_on_edit(cx),
|
||||
multi_buffer::Event::BufferRangesUpdated { .. }
|
||||
| multi_buffer::Event::BuffersEdited { .. } => git_blame.regenerate_on_edit(cx),
|
||||
_ => {}
|
||||
},
|
||||
);
|
||||
|
|
@ -346,11 +346,10 @@ impl GitBlame {
|
|||
let Some(multi_buffer) = self.multi_buffer.upgrade() else {
|
||||
return;
|
||||
};
|
||||
multi_buffer
|
||||
.read(cx)
|
||||
.excerpt_buffer_ids()
|
||||
.into_iter()
|
||||
.for_each(|id| self.sync(cx, id));
|
||||
let snapshot = multi_buffer.read(cx).snapshot(cx);
|
||||
for id in snapshot.all_buffer_ids() {
|
||||
self.sync(cx, id)
|
||||
}
|
||||
}
|
||||
|
||||
fn sync(&mut self, cx: &mut App, buffer_id: BufferId) {
|
||||
|
|
@ -497,10 +496,10 @@ impl GitBlame {
|
|||
}
|
||||
let buffers_to_blame = self
|
||||
.multi_buffer
|
||||
.update(cx, |multi_buffer, _| {
|
||||
multi_buffer
|
||||
.update(cx, |multi_buffer, cx| {
|
||||
let snapshot = multi_buffer.snapshot(cx);
|
||||
snapshot
|
||||
.all_buffer_ids()
|
||||
.into_iter()
|
||||
.filter_map(|id| Some(multi_buffer.buffer(id)?.downgrade()))
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -237,7 +237,8 @@ impl Editor {
|
|||
let Some(mb_anchor) = self
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.buffer_anchor_to_anchor(&buffer, anchor, cx)
|
||||
.snapshot(cx)
|
||||
.anchor_in_excerpt(anchor)
|
||||
else {
|
||||
return Task::ready(Ok(Navigated::No));
|
||||
};
|
||||
|
|
@ -324,16 +325,13 @@ pub fn show_link_definition(
|
|||
return;
|
||||
}
|
||||
|
||||
let trigger_anchor = trigger_point.anchor();
|
||||
let anchor = snapshot.buffer_snapshot().anchor_before(*trigger_anchor);
|
||||
let Some(buffer) = editor.buffer().read(cx).buffer_for_anchor(anchor, cx) else {
|
||||
let anchor = trigger_point.anchor().bias_left(snapshot.buffer_snapshot());
|
||||
let Some((anchor, _)) = snapshot.buffer_snapshot().anchor_to_buffer_anchor(anchor) else {
|
||||
return;
|
||||
};
|
||||
let Some(buffer) = editor.buffer.read(cx).buffer(anchor.buffer_id) else {
|
||||
return;
|
||||
};
|
||||
let Anchor {
|
||||
excerpt_id,
|
||||
text_anchor,
|
||||
..
|
||||
} = anchor;
|
||||
let same_kind = hovered_link_state.preferred_kind == preferred_kind
|
||||
|| hovered_link_state
|
||||
.links
|
||||
|
|
@ -363,39 +361,39 @@ pub fn show_link_definition(
|
|||
async move {
|
||||
let result = match &trigger_point {
|
||||
TriggerPoint::Text(_) => {
|
||||
if let Some((url_range, url)) = find_url(&buffer, text_anchor, cx.clone()) {
|
||||
if let Some((url_range, url)) = find_url(&buffer, anchor, cx.clone()) {
|
||||
this.read_with(cx, |_, _| {
|
||||
let range = maybe!({
|
||||
let range =
|
||||
snapshot.anchor_range_in_excerpt(excerpt_id, url_range)?;
|
||||
snapshot.buffer_anchor_range_to_anchor_range(url_range)?;
|
||||
Some(RangeInEditor::Text(range))
|
||||
});
|
||||
(range, vec![HoverLink::Url(url)])
|
||||
})
|
||||
.ok()
|
||||
} else if let Some((filename_range, filename)) =
|
||||
find_file(&buffer, project.clone(), text_anchor, cx).await
|
||||
find_file(&buffer, project.clone(), anchor, cx).await
|
||||
{
|
||||
let range = maybe!({
|
||||
let range =
|
||||
snapshot.anchor_range_in_excerpt(excerpt_id, filename_range)?;
|
||||
snapshot.buffer_anchor_range_to_anchor_range(filename_range)?;
|
||||
Some(RangeInEditor::Text(range))
|
||||
});
|
||||
|
||||
Some((range, vec![HoverLink::File(filename)]))
|
||||
} else if let Some(provider) = provider {
|
||||
let task = cx.update(|_, cx| {
|
||||
provider.definitions(&buffer, text_anchor, preferred_kind, cx)
|
||||
provider.definitions(&buffer, anchor, preferred_kind, cx)
|
||||
})?;
|
||||
if let Some(task) = task {
|
||||
task.await.ok().flatten().map(|definition_result| {
|
||||
(
|
||||
definition_result.iter().find_map(|link| {
|
||||
link.origin.as_ref().and_then(|origin| {
|
||||
let range = snapshot.anchor_range_in_excerpt(
|
||||
excerpt_id,
|
||||
origin.range.clone(),
|
||||
)?;
|
||||
let range = snapshot
|
||||
.buffer_anchor_range_to_anchor_range(
|
||||
origin.range.clone(),
|
||||
)?;
|
||||
Some(RangeInEditor::Text(range))
|
||||
})
|
||||
}),
|
||||
|
|
@ -1602,7 +1600,11 @@ mod tests {
|
|||
cx.set_state(input);
|
||||
|
||||
let (position, snapshot) = cx.editor(|editor, _, cx| {
|
||||
let positions = editor.selections.newest_anchor().head().text_anchor;
|
||||
let positions = editor
|
||||
.selections
|
||||
.newest_anchor()
|
||||
.head()
|
||||
.expect_text_anchor();
|
||||
let snapshot = editor
|
||||
.buffer()
|
||||
.clone()
|
||||
|
|
|
|||
|
|
@ -275,12 +275,12 @@ fn show_hover(
|
|||
|
||||
let snapshot = editor.snapshot(window, cx);
|
||||
|
||||
let (buffer, buffer_position) = editor
|
||||
let (buffer_position, _) = editor
|
||||
.buffer
|
||||
.read(cx)
|
||||
.text_anchor_for_position(anchor, cx)?;
|
||||
|
||||
let (excerpt_id, _, _) = editor.buffer().read(cx).excerpt_containing(anchor, cx)?;
|
||||
.snapshot(cx)
|
||||
.anchor_to_buffer_anchor(anchor)?;
|
||||
let buffer = editor.buffer.read(cx).buffer(buffer_position.buffer_id)?;
|
||||
|
||||
let language_registry = editor
|
||||
.project()
|
||||
|
|
@ -515,7 +515,7 @@ fn show_hover(
|
|||
.and_then(|range| {
|
||||
let range = snapshot
|
||||
.buffer_snapshot()
|
||||
.anchor_range_in_excerpt(excerpt_id, range)?;
|
||||
.buffer_anchor_range_to_anchor_range(range)?;
|
||||
Some(range)
|
||||
})
|
||||
.or_else(|| {
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ impl InlaySplice {
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct Inlay {
|
||||
pub id: InlayId,
|
||||
// TODO this could be an ExcerptAnchor
|
||||
pub position: Anchor,
|
||||
pub content: InlayContent,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ use language::{
|
|||
language_settings::{InlayHintKind, InlayHintSettings},
|
||||
};
|
||||
use lsp::LanguageServerId;
|
||||
use multi_buffer::{Anchor, ExcerptId, MultiBufferSnapshot};
|
||||
use multi_buffer::{Anchor, MultiBufferSnapshot};
|
||||
use project::{
|
||||
HoverBlock, HoverBlockKind, InlayHintLabel, InlayHintLabelPartTooltip, InlayHintTooltip,
|
||||
InvalidationStrategy, ResolveState,
|
||||
|
|
@ -110,14 +110,15 @@ impl LspInlayHintData {
|
|||
&mut self,
|
||||
buffer_ids: &HashSet<BufferId>,
|
||||
current_hints: impl IntoIterator<Item = Inlay>,
|
||||
snapshot: &MultiBufferSnapshot,
|
||||
) {
|
||||
for buffer_id in buffer_ids {
|
||||
self.hint_refresh_tasks.remove(buffer_id);
|
||||
self.hint_chunk_fetching.remove(buffer_id);
|
||||
}
|
||||
for hint in current_hints {
|
||||
if let Some(buffer_id) = hint.position.text_anchor.buffer_id {
|
||||
if buffer_ids.contains(&buffer_id) {
|
||||
if let Some((text_anchor, _)) = snapshot.anchor_to_buffer_anchor(hint.position) {
|
||||
if buffer_ids.contains(&text_anchor.buffer_id) {
|
||||
self.added_hints.remove(&hint.id);
|
||||
}
|
||||
}
|
||||
|
|
@ -237,7 +238,7 @@ pub enum InlayHintRefreshReason {
|
|||
server_id: LanguageServerId,
|
||||
request_id: Option<usize>,
|
||||
},
|
||||
ExcerptsRemoved(Vec<ExcerptId>),
|
||||
BuffersRemoved(Vec<BufferId>),
|
||||
}
|
||||
|
||||
impl Editor {
|
||||
|
|
@ -303,7 +304,7 @@ impl Editor {
|
|||
let debounce = match &reason {
|
||||
InlayHintRefreshReason::SettingsChange(_)
|
||||
| InlayHintRefreshReason::Toggle(_)
|
||||
| InlayHintRefreshReason::ExcerptsRemoved(_)
|
||||
| InlayHintRefreshReason::BuffersRemoved(_)
|
||||
| InlayHintRefreshReason::ModifiersChanged(_) => None,
|
||||
_may_need_lsp_call => self.inlay_hints.as_ref().and_then(|inlay_hints| {
|
||||
if invalidate_cache.should_invalidate() {
|
||||
|
|
@ -314,7 +315,8 @@ impl Editor {
|
|||
}),
|
||||
};
|
||||
|
||||
let mut visible_excerpts = self.visible_excerpts(true, cx);
|
||||
let mut visible_excerpts = self.visible_buffer_ranges(cx);
|
||||
visible_excerpts.retain(|(snapshot, _, _)| self.is_lsp_relevant(snapshot.file(), cx));
|
||||
|
||||
let mut invalidate_hints_for_buffers = HashSet::default();
|
||||
let ignore_previous_fetches = match reason {
|
||||
|
|
@ -324,7 +326,7 @@ impl Editor {
|
|||
| InlayHintRefreshReason::ServerRemoved => true,
|
||||
InlayHintRefreshReason::NewLinesShown
|
||||
| InlayHintRefreshReason::RefreshRequested { .. }
|
||||
| InlayHintRefreshReason::ExcerptsRemoved(_) => false,
|
||||
| InlayHintRefreshReason::BuffersRemoved(_) => false,
|
||||
InlayHintRefreshReason::BufferEdited(buffer_id) => {
|
||||
let Some(affected_language) = self
|
||||
.buffer()
|
||||
|
|
@ -351,8 +353,8 @@ impl Editor {
|
|||
);
|
||||
|
||||
semantics_provider.invalidate_inlay_hints(&invalidate_hints_for_buffers, cx);
|
||||
visible_excerpts.retain(|_, (visible_buffer, _, _)| {
|
||||
visible_buffer.read(cx).language() == Some(&affected_language)
|
||||
visible_excerpts.retain(|(buffer_snapshot, _, _)| {
|
||||
buffer_snapshot.language() == Some(&affected_language)
|
||||
});
|
||||
false
|
||||
}
|
||||
|
|
@ -371,6 +373,7 @@ impl Editor {
|
|||
inlay_hints.clear_for_buffers(
|
||||
&invalidate_hints_for_buffers,
|
||||
Self::visible_inlay_hints(self.display_map.read(cx)),
|
||||
&multi_buffer.read(cx).snapshot(cx),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -379,14 +382,18 @@ impl Editor {
|
|||
.extend(invalidate_hints_for_buffers);
|
||||
|
||||
let mut buffers_to_query = HashMap::default();
|
||||
for (_, (buffer, buffer_version, visible_range)) in visible_excerpts {
|
||||
let buffer_id = buffer.read(cx).remote_id();
|
||||
for (buffer_snapshot, visible_range, _) in visible_excerpts {
|
||||
let buffer_id = buffer_snapshot.remote_id();
|
||||
|
||||
if !self.registered_buffers.contains_key(&buffer_id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let buffer_snapshot = buffer.read(cx).snapshot();
|
||||
let Some(buffer) = multi_buffer.read(cx).buffer(buffer_id) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let buffer_version = buffer_snapshot.version().clone();
|
||||
let buffer_anchor_range = buffer_snapshot.anchor_before(visible_range.start)
|
||||
..buffer_snapshot.anchor_after(visible_range.end);
|
||||
|
||||
|
|
@ -514,13 +521,14 @@ impl Editor {
|
|||
}
|
||||
}
|
||||
}
|
||||
InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
|
||||
InlayHintRefreshReason::BuffersRemoved(buffers_removed) => {
|
||||
let to_remove = self
|
||||
.display_map
|
||||
.read(cx)
|
||||
.current_inlays()
|
||||
.filter_map(|inlay| {
|
||||
if excerpts_removed.contains(&inlay.position.excerpt_id) {
|
||||
let anchor = inlay.position.raw_text_anchor()?;
|
||||
if buffers_removed.contains(&anchor.buffer_id) {
|
||||
Some(inlay.id)
|
||||
} else {
|
||||
None
|
||||
|
|
@ -610,13 +618,11 @@ impl Editor {
|
|||
})
|
||||
.max_by_key(|hint| hint.id)
|
||||
{
|
||||
if let Some(ResolvedHint::Resolved(cached_hint)) = hovered_hint
|
||||
.position
|
||||
.text_anchor
|
||||
.buffer_id
|
||||
.and_then(|buffer_id| {
|
||||
if let Some(ResolvedHint::Resolved(cached_hint)) = buffer_snapshot
|
||||
.anchor_to_buffer_anchor(hovered_hint.position)
|
||||
.and_then(|(anchor, _)| {
|
||||
lsp_store.update(cx, |lsp_store, cx| {
|
||||
lsp_store.resolved_hint(buffer_id, hovered_hint.id, cx)
|
||||
lsp_store.resolved_hint(anchor.buffer_id, hovered_hint.id, cx)
|
||||
})
|
||||
})
|
||||
{
|
||||
|
|
@ -787,15 +793,19 @@ impl Editor {
|
|||
new_hints: Vec<(Range<BufferRow>, anyhow::Result<CacheInlayHints>)>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let visible_inlay_hint_ids = Self::visible_inlay_hints(self.display_map.read(cx))
|
||||
.filter(|inlay| inlay.position.text_anchor.buffer_id == Some(buffer_id))
|
||||
.filter(|inlay| {
|
||||
multi_buffer_snapshot
|
||||
.anchor_to_buffer_anchor(inlay.position)
|
||||
.map(|(anchor, _)| anchor.buffer_id)
|
||||
== Some(buffer_id)
|
||||
})
|
||||
.map(|inlay| inlay.id)
|
||||
.collect::<Vec<_>>();
|
||||
let Some(inlay_hints) = &mut self.inlay_hints else {
|
||||
return;
|
||||
};
|
||||
|
||||
let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let Some(buffer_snapshot) = self
|
||||
.buffer
|
||||
.read(cx)
|
||||
|
|
@ -910,12 +920,10 @@ impl Editor {
|
|||
hints_to_remove.extend(
|
||||
Self::visible_inlay_hints(self.display_map.read(cx))
|
||||
.filter(|inlay| {
|
||||
inlay
|
||||
.position
|
||||
.text_anchor
|
||||
.buffer_id
|
||||
.is_none_or(|buffer_id| {
|
||||
invalidate_hints_for_buffers.contains(&buffer_id)
|
||||
multi_buffer_snapshot
|
||||
.anchor_to_buffer_anchor(inlay.position)
|
||||
.is_none_or(|(anchor, _)| {
|
||||
invalidate_hints_for_buffers.contains(&anchor.buffer_id)
|
||||
})
|
||||
})
|
||||
.map(|inlay| inlay.id),
|
||||
|
|
@ -2285,17 +2293,15 @@ pub mod tests {
|
|||
cx: &mut gpui::TestAppContext,
|
||||
) -> Range<Point> {
|
||||
let ranges = editor
|
||||
.update(cx, |editor, _window, cx| editor.visible_excerpts(true, cx))
|
||||
.update(cx, |editor, _window, cx| editor.visible_buffer_ranges(cx))
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
ranges.len(),
|
||||
1,
|
||||
"Single buffer should produce a single excerpt with visible range"
|
||||
);
|
||||
let (_, (excerpt_buffer, _, excerpt_visible_range)) = ranges.into_iter().next().unwrap();
|
||||
excerpt_buffer.read_with(cx, |buffer, _| {
|
||||
excerpt_visible_range.to_point(&buffer.snapshot())
|
||||
})
|
||||
let (buffer_snapshot, visible_range, _) = ranges.into_iter().next().unwrap();
|
||||
visible_range.to_point(&buffer_snapshot)
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
|
|
@ -2968,7 +2974,7 @@ let c = 3;"#
|
|||
.await
|
||||
.unwrap();
|
||||
let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
|
||||
let (buffer_1_excerpts, buffer_2_excerpts) = multibuffer.update(cx, |multibuffer, cx| {
|
||||
multibuffer.update(cx, |multibuffer, cx| {
|
||||
multibuffer.set_excerpts_for_path(
|
||||
PathKey::sorted(0),
|
||||
buffer_1.clone(),
|
||||
|
|
@ -2983,15 +2989,8 @@ let c = 3;"#
|
|||
0,
|
||||
cx,
|
||||
);
|
||||
let excerpt_ids = multibuffer.excerpt_ids();
|
||||
let buffer_1_excerpts = vec![excerpt_ids[0]];
|
||||
let buffer_2_excerpts = vec![excerpt_ids[1]];
|
||||
(buffer_1_excerpts, buffer_2_excerpts)
|
||||
});
|
||||
|
||||
assert!(!buffer_1_excerpts.is_empty());
|
||||
assert!(!buffer_2_excerpts.is_empty());
|
||||
|
||||
cx.executor().run_until_parked();
|
||||
let editor = cx.add_window(|window, cx| {
|
||||
Editor::for_multibuffer(multibuffer, Some(project.clone()), window, cx)
|
||||
|
|
@ -3092,7 +3091,7 @@ let c = 3;"#
|
|||
editor
|
||||
.update(cx, |editor, _, cx| {
|
||||
editor.buffer().update(cx, |multibuffer, cx| {
|
||||
multibuffer.remove_excerpts_for_path(PathKey::sorted(1), cx);
|
||||
multibuffer.remove_excerpts(PathKey::sorted(1), cx);
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{
|
||||
ActiveDebugLine, Anchor, Autoscroll, BufferSerialization, Capability, Editor, EditorEvent,
|
||||
EditorSettings, ExcerptId, ExcerptRange, FormatTarget, MultiBuffer, MultiBufferSnapshot,
|
||||
NavigationData, ReportEditorEvent, SelectionEffects, ToPoint as _,
|
||||
EditorSettings, ExcerptRange, FormatTarget, MultiBuffer, MultiBufferSnapshot, NavigationData,
|
||||
ReportEditorEvent, SelectionEffects, ToPoint as _,
|
||||
display_map::HighlightKey,
|
||||
editor_settings::SeedQuerySetting,
|
||||
persistence::{EditorDb, SerializedEditor},
|
||||
|
|
@ -22,7 +22,7 @@ use language::{
|
|||
SelectionGoal, proto::serialize_anchor as serialize_text_anchor,
|
||||
};
|
||||
use lsp::DiagnosticSeverity;
|
||||
use multi_buffer::MultiBufferOffset;
|
||||
use multi_buffer::{MultiBufferOffset, PathKey};
|
||||
use project::{
|
||||
File, Project, ProjectItem as _, ProjectPath, lsp_store::FormatTrigger,
|
||||
project_settings::ProjectSettings, search::SearchQuery,
|
||||
|
|
@ -33,14 +33,13 @@ use std::{
|
|||
any::{Any, TypeId},
|
||||
borrow::Cow,
|
||||
cmp::{self, Ordering},
|
||||
iter,
|
||||
ops::Range,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use text::{BufferId, BufferSnapshot, Selection};
|
||||
use ui::{IconDecorationKind, prelude::*};
|
||||
use util::{ResultExt, TryFutureExt, paths::PathExt};
|
||||
use util::{ResultExt, TryFutureExt, paths::PathExt, rel_path::RelPath};
|
||||
use workspace::item::{Dedup, ItemSettings, SerializableItem, TabContentParams};
|
||||
use workspace::{
|
||||
CollaboratorId, ItemId, ItemNavHistory, ToolbarItemLocation, ViewId, Workspace, WorkspaceId,
|
||||
|
|
@ -83,10 +82,11 @@ impl FollowableItem for Editor {
|
|||
};
|
||||
|
||||
let buffer_ids = state
|
||||
.excerpts
|
||||
.path_excerpts
|
||||
.iter()
|
||||
.map(|excerpt| excerpt.buffer_id)
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
let buffers = project.update(cx, |project, cx| {
|
||||
buffer_ids
|
||||
.iter()
|
||||
|
|
@ -106,38 +106,32 @@ impl FollowableItem for Editor {
|
|||
multibuffer = MultiBuffer::singleton(buffers.pop().unwrap(), cx)
|
||||
} else {
|
||||
multibuffer = MultiBuffer::new(project.read(cx).capability());
|
||||
let mut sorted_excerpts = state.excerpts.clone();
|
||||
sorted_excerpts.sort_by_key(|e| e.id);
|
||||
let sorted_excerpts = sorted_excerpts.into_iter().peekable();
|
||||
|
||||
for excerpt in sorted_excerpts {
|
||||
let Ok(buffer_id) = BufferId::new(excerpt.buffer_id) else {
|
||||
for path_with_ranges in state.path_excerpts {
|
||||
let Some(path_key) =
|
||||
path_with_ranges.path_key.and_then(deserialize_path_key)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let mut insert_position = ExcerptId::min();
|
||||
for e in &state.excerpts {
|
||||
if e.id == excerpt.id {
|
||||
break;
|
||||
}
|
||||
if e.id < excerpt.id {
|
||||
insert_position = ExcerptId::from_proto(e.id);
|
||||
}
|
||||
}
|
||||
|
||||
let buffer =
|
||||
buffers.iter().find(|b| b.read(cx).remote_id() == buffer_id);
|
||||
|
||||
let Some(excerpt) = deserialize_excerpt_range(excerpt) else {
|
||||
let Some(buffer_id) = BufferId::new(path_with_ranges.buffer_id).ok()
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let Some(buffer) = buffer else { continue };
|
||||
|
||||
multibuffer.insert_excerpts_with_ids_after(
|
||||
insert_position,
|
||||
let Some(buffer) =
|
||||
buffers.iter().find(|b| b.read(cx).remote_id() == buffer_id)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let buffer_snapshot = buffer.read(cx).snapshot();
|
||||
let ranges = path_with_ranges
|
||||
.ranges
|
||||
.into_iter()
|
||||
.filter_map(deserialize_excerpt_range)
|
||||
.collect::<Vec<_>>();
|
||||
multibuffer.update_path_excerpts(
|
||||
path_key,
|
||||
buffer.clone(),
|
||||
[excerpt],
|
||||
&buffer_snapshot,
|
||||
&ranges,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
|
@ -158,6 +152,7 @@ impl FollowableItem for Editor {
|
|||
})
|
||||
})?;
|
||||
|
||||
editor.update(cx, |editor, cx| editor.text(cx));
|
||||
update_editor_from_message(
|
||||
editor.downgrade(),
|
||||
project,
|
||||
|
|
@ -215,38 +210,43 @@ impl FollowableItem for Editor {
|
|||
let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let scroll_anchor = self.scroll_manager.native_anchor(&display_snapshot, cx);
|
||||
let buffer = self.buffer.read(cx);
|
||||
let excerpts = buffer
|
||||
.read(cx)
|
||||
.excerpts()
|
||||
.map(|(id, buffer, range)| proto::Excerpt {
|
||||
id: id.to_proto(),
|
||||
buffer_id: buffer.remote_id().into(),
|
||||
context_start: Some(serialize_text_anchor(&range.context.start)),
|
||||
context_end: Some(serialize_text_anchor(&range.context.end)),
|
||||
primary_start: Some(serialize_text_anchor(&range.primary.start)),
|
||||
primary_end: Some(serialize_text_anchor(&range.primary.end)),
|
||||
})
|
||||
.collect();
|
||||
let snapshot = buffer.snapshot(cx);
|
||||
let mut path_excerpts: Vec<proto::PathExcerpts> = Vec::new();
|
||||
for excerpt in snapshot.excerpts() {
|
||||
if let Some(prev_entry) = path_excerpts.last_mut()
|
||||
&& prev_entry.buffer_id == excerpt.context.start.buffer_id.to_proto()
|
||||
{
|
||||
prev_entry.ranges.push(serialize_excerpt_range(excerpt));
|
||||
} else if let Some(path_key) = snapshot.path_for_buffer(excerpt.context.start.buffer_id)
|
||||
{
|
||||
path_excerpts.push(proto::PathExcerpts {
|
||||
path_key: Some(serialize_path_key(path_key)),
|
||||
buffer_id: excerpt.context.start.buffer_id.to_proto(),
|
||||
ranges: vec![serialize_excerpt_range(excerpt)],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Some(proto::view::Variant::Editor(proto::view::Editor {
|
||||
singleton: buffer.is_singleton(),
|
||||
title: buffer.explicit_title().map(ToOwned::to_owned),
|
||||
excerpts,
|
||||
scroll_top_anchor: Some(serialize_anchor(&scroll_anchor.anchor, &snapshot)),
|
||||
excerpts: Vec::new(),
|
||||
scroll_top_anchor: Some(serialize_anchor(&scroll_anchor.anchor)),
|
||||
scroll_x: scroll_anchor.offset.x,
|
||||
scroll_y: scroll_anchor.offset.y,
|
||||
selections: self
|
||||
.selections
|
||||
.disjoint_anchors_arc()
|
||||
.iter()
|
||||
.map(|s| serialize_selection(s, &snapshot))
|
||||
.map(serialize_selection)
|
||||
.collect(),
|
||||
pending_selection: self
|
||||
.selections
|
||||
.pending_anchor()
|
||||
.as_ref()
|
||||
.map(|s| serialize_selection(s, &snapshot)),
|
||||
.copied()
|
||||
.map(serialize_selection),
|
||||
path_excerpts,
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
@ -277,56 +277,52 @@ impl FollowableItem for Editor {
|
|||
|
||||
match update {
|
||||
proto::update_view::Variant::Editor(update) => match event {
|
||||
EditorEvent::ExcerptsAdded {
|
||||
EditorEvent::BufferRangesUpdated {
|
||||
buffer,
|
||||
predecessor,
|
||||
excerpts,
|
||||
path_key,
|
||||
ranges,
|
||||
} => {
|
||||
let buffer_id = buffer.read(cx).remote_id();
|
||||
let mut excerpts = excerpts.iter();
|
||||
if let Some((id, range)) = excerpts.next() {
|
||||
update.inserted_excerpts.push(proto::ExcerptInsertion {
|
||||
previous_excerpt_id: Some(predecessor.to_proto()),
|
||||
excerpt: serialize_excerpt(buffer_id, id, range),
|
||||
});
|
||||
update.inserted_excerpts.extend(excerpts.map(|(id, range)| {
|
||||
proto::ExcerptInsertion {
|
||||
previous_excerpt_id: None,
|
||||
excerpt: serialize_excerpt(buffer_id, id, range),
|
||||
}
|
||||
}))
|
||||
}
|
||||
let buffer_id = buffer.read(cx).remote_id().to_proto();
|
||||
let path_key = serialize_path_key(path_key);
|
||||
let ranges = ranges
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(serialize_excerpt_range)
|
||||
.collect::<Vec<_>>();
|
||||
update.updated_paths.push(proto::PathExcerpts {
|
||||
path_key: Some(path_key),
|
||||
buffer_id,
|
||||
ranges,
|
||||
});
|
||||
true
|
||||
}
|
||||
EditorEvent::ExcerptsRemoved { ids, .. } => {
|
||||
EditorEvent::BuffersRemoved { removed_buffer_ids } => {
|
||||
update
|
||||
.deleted_excerpts
|
||||
.extend(ids.iter().copied().map(ExcerptId::to_proto));
|
||||
.deleted_buffers
|
||||
.extend(removed_buffer_ids.iter().copied().map(BufferId::to_proto));
|
||||
true
|
||||
}
|
||||
EditorEvent::ScrollPositionChanged { autoscroll, .. } if !autoscroll => {
|
||||
let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let scroll_anchor = self.scroll_manager.native_anchor(&display_snapshot, cx);
|
||||
update.scroll_top_anchor =
|
||||
Some(serialize_anchor(&scroll_anchor.anchor, &snapshot));
|
||||
update.scroll_top_anchor = Some(serialize_anchor(&scroll_anchor.anchor));
|
||||
update.scroll_x = scroll_anchor.offset.x;
|
||||
update.scroll_y = scroll_anchor.offset.y;
|
||||
true
|
||||
}
|
||||
EditorEvent::SelectionsChanged { .. } => {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
update.selections = self
|
||||
.selections
|
||||
.disjoint_anchors_arc()
|
||||
.iter()
|
||||
.map(|s| serialize_selection(s, &snapshot))
|
||||
.map(serialize_selection)
|
||||
.collect();
|
||||
update.pending_selection = self
|
||||
.selections
|
||||
.pending_anchor()
|
||||
.as_ref()
|
||||
.map(|s| serialize_selection(s, &snapshot));
|
||||
.copied()
|
||||
.map(serialize_selection);
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
|
|
@ -370,7 +366,7 @@ impl FollowableItem for Editor {
|
|||
) {
|
||||
let buffer = self.buffer.read(cx);
|
||||
let buffer = buffer.read(cx);
|
||||
let Some(position) = buffer.as_singleton_anchor(location) else {
|
||||
let Some(position) = buffer.anchor_in_excerpt(location) else {
|
||||
return;
|
||||
};
|
||||
let selection = Selection {
|
||||
|
|
@ -394,9 +390,9 @@ async fn update_editor_from_message(
|
|||
) -> Result<()> {
|
||||
// Open all of the buffers of which excerpts were added to the editor.
|
||||
let inserted_excerpt_buffer_ids = message
|
||||
.inserted_excerpts
|
||||
.updated_paths
|
||||
.iter()
|
||||
.filter_map(|insertion| Some(insertion.excerpt.as_ref()?.buffer_id))
|
||||
.map(|insertion| insertion.buffer_id)
|
||||
.collect::<HashSet<_>>();
|
||||
let inserted_excerpt_buffers = project.update(cx, |project, cx| {
|
||||
inserted_excerpt_buffer_ids
|
||||
|
|
@ -407,66 +403,53 @@ async fn update_editor_from_message(
|
|||
let _inserted_excerpt_buffers = try_join_all(inserted_excerpt_buffers).await?;
|
||||
|
||||
// Update the editor's excerpts.
|
||||
this.update(cx, |editor, cx| {
|
||||
let buffer_snapshot = this.update(cx, |editor, cx| {
|
||||
editor.buffer.update(cx, |multibuffer, cx| {
|
||||
let mut removed_excerpt_ids = message
|
||||
.deleted_excerpts
|
||||
.into_iter()
|
||||
.map(ExcerptId::from_proto)
|
||||
.collect::<Vec<_>>();
|
||||
removed_excerpt_ids.sort_by({
|
||||
let multibuffer = multibuffer.read(cx);
|
||||
move |a, b| a.cmp(b, &multibuffer)
|
||||
});
|
||||
|
||||
let mut insertions = message.inserted_excerpts.into_iter().peekable();
|
||||
while let Some(insertion) = insertions.next() {
|
||||
let Some(excerpt) = insertion.excerpt else {
|
||||
for path_with_excerpts in message.updated_paths {
|
||||
let Some(path_key) = path_with_excerpts.path_key.and_then(deserialize_path_key)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let Some(previous_excerpt_id) = insertion.previous_excerpt_id else {
|
||||
continue;
|
||||
};
|
||||
let buffer_id = BufferId::new(excerpt.buffer_id)?;
|
||||
let Some(buffer) = project.read(cx).buffer_for_id(buffer_id, cx) else {
|
||||
let ranges = path_with_excerpts
|
||||
.ranges
|
||||
.into_iter()
|
||||
.filter_map(deserialize_excerpt_range)
|
||||
.collect::<Vec<_>>();
|
||||
let Some(buffer) = BufferId::new(path_with_excerpts.buffer_id)
|
||||
.ok()
|
||||
.and_then(|buffer_id| project.read(cx).buffer_for_id(buffer_id, cx))
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let adjacent_excerpts = iter::from_fn(|| {
|
||||
let insertion = insertions.peek()?;
|
||||
if insertion.previous_excerpt_id.is_none()
|
||||
&& insertion.excerpt.as_ref()?.buffer_id == u64::from(buffer_id)
|
||||
{
|
||||
insertions.next()?.excerpt
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
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,
|
||||
);
|
||||
let buffer_snapshot = buffer.read(cx).snapshot();
|
||||
multibuffer.update_path_excerpts(path_key, buffer, &buffer_snapshot, &ranges, cx);
|
||||
}
|
||||
|
||||
multibuffer.remove_excerpts(removed_excerpt_ids, cx);
|
||||
anyhow::Ok(())
|
||||
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.snapshot(cx)
|
||||
})
|
||||
})??;
|
||||
})?;
|
||||
|
||||
// Deserialize the editor state.
|
||||
let selections = message
|
||||
.selections
|
||||
.into_iter()
|
||||
.filter_map(deserialize_selection)
|
||||
.filter_map(|selection| deserialize_selection(selection, &buffer_snapshot))
|
||||
.collect::<Vec<_>>();
|
||||
let pending_selection = message.pending_selection.and_then(deserialize_selection);
|
||||
let scroll_top_anchor = message.scroll_top_anchor.and_then(deserialize_anchor);
|
||||
let pending_selection = message
|
||||
.pending_selection
|
||||
.and_then(|selection| deserialize_selection(selection, &buffer_snapshot));
|
||||
let scroll_top_anchor = message
|
||||
.scroll_top_anchor
|
||||
.and_then(|selection| deserialize_anchor(selection, &buffer_snapshot));
|
||||
|
||||
// Wait until the buffer has received all of the operations referenced by
|
||||
// the editor's new state.
|
||||
|
|
@ -503,79 +486,103 @@ async fn update_editor_from_message(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_excerpt(
|
||||
buffer_id: BufferId,
|
||||
id: &ExcerptId,
|
||||
range: &ExcerptRange<language::Anchor>,
|
||||
) -> Option<proto::Excerpt> {
|
||||
Some(proto::Excerpt {
|
||||
id: id.to_proto(),
|
||||
buffer_id: buffer_id.into(),
|
||||
context_start: Some(serialize_text_anchor(&range.context.start)),
|
||||
context_end: Some(serialize_text_anchor(&range.context.end)),
|
||||
primary_start: Some(serialize_text_anchor(&range.primary.start)),
|
||||
primary_end: Some(serialize_text_anchor(&range.primary.end)),
|
||||
})
|
||||
}
|
||||
|
||||
fn serialize_selection(
|
||||
selection: &Selection<Anchor>,
|
||||
buffer: &MultiBufferSnapshot,
|
||||
) -> proto::Selection {
|
||||
fn serialize_selection(selection: &Selection<Anchor>) -> proto::Selection {
|
||||
proto::Selection {
|
||||
id: selection.id as u64,
|
||||
start: Some(serialize_anchor(&selection.start, buffer)),
|
||||
end: Some(serialize_anchor(&selection.end, buffer)),
|
||||
start: Some(serialize_anchor(&selection.start)),
|
||||
end: Some(serialize_anchor(&selection.end)),
|
||||
reversed: selection.reversed,
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_anchor(anchor: &Anchor, buffer: &MultiBufferSnapshot) -> proto::EditorAnchor {
|
||||
proto::EditorAnchor {
|
||||
excerpt_id: buffer.latest_excerpt_id(anchor.excerpt_id).to_proto(),
|
||||
anchor: Some(serialize_text_anchor(&anchor.text_anchor)),
|
||||
fn serialize_anchor(anchor: &Anchor) -> proto::EditorAnchor {
|
||||
match anchor {
|
||||
Anchor::Min => proto::EditorAnchor {
|
||||
excerpt_id: None,
|
||||
anchor: Some(proto::Anchor {
|
||||
replica_id: 0,
|
||||
timestamp: 0,
|
||||
offset: 0,
|
||||
bias: proto::Bias::Left as i32,
|
||||
buffer_id: None,
|
||||
}),
|
||||
},
|
||||
Anchor::Excerpt(_) => proto::EditorAnchor {
|
||||
excerpt_id: None,
|
||||
anchor: anchor.raw_text_anchor().map(|a| serialize_text_anchor(&a)),
|
||||
},
|
||||
Anchor::Max => proto::EditorAnchor {
|
||||
excerpt_id: None,
|
||||
anchor: Some(proto::Anchor {
|
||||
replica_id: u32::MAX,
|
||||
timestamp: u32::MAX,
|
||||
offset: u64::MAX,
|
||||
bias: proto::Bias::Right as i32,
|
||||
buffer_id: None,
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_excerpt_range(range: ExcerptRange<language::Anchor>) -> proto::ExcerptRange {
|
||||
let context_start = language::proto::serialize_anchor(&range.context.start);
|
||||
let context_end = language::proto::serialize_anchor(&range.context.end);
|
||||
let primary_start = language::proto::serialize_anchor(&range.primary.start);
|
||||
let primary_end = language::proto::serialize_anchor(&range.primary.end);
|
||||
proto::ExcerptRange {
|
||||
context_start: Some(context_start),
|
||||
context_end: Some(context_end),
|
||||
primary_start: Some(primary_start),
|
||||
primary_end: Some(primary_end),
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_excerpt_range(
|
||||
excerpt: proto::Excerpt,
|
||||
) -> Option<(ExcerptId, ExcerptRange<language::Anchor>)> {
|
||||
excerpt_range: proto::ExcerptRange,
|
||||
) -> Option<ExcerptRange<language::Anchor>> {
|
||||
let context = {
|
||||
let start = language::proto::deserialize_anchor(excerpt.context_start?)?;
|
||||
let end = language::proto::deserialize_anchor(excerpt.context_end?)?;
|
||||
let start = language::proto::deserialize_anchor(excerpt_range.context_start?)?;
|
||||
let end = language::proto::deserialize_anchor(excerpt_range.context_end?)?;
|
||||
start..end
|
||||
};
|
||||
let primary = excerpt
|
||||
let primary = excerpt_range
|
||||
.primary_start
|
||||
.zip(excerpt.primary_end)
|
||||
.zip(excerpt_range.primary_end)
|
||||
.and_then(|(start, end)| {
|
||||
let start = language::proto::deserialize_anchor(start)?;
|
||||
let end = language::proto::deserialize_anchor(end)?;
|
||||
Some(start..end)
|
||||
})
|
||||
.unwrap_or_else(|| context.clone());
|
||||
Some((
|
||||
ExcerptId::from_proto(excerpt.id),
|
||||
ExcerptRange { context, primary },
|
||||
))
|
||||
Some(ExcerptRange { context, primary })
|
||||
}
|
||||
|
||||
fn deserialize_selection(selection: proto::Selection) -> Option<Selection<Anchor>> {
|
||||
fn deserialize_selection(
|
||||
selection: proto::Selection,
|
||||
buffer: &MultiBufferSnapshot,
|
||||
) -> Option<Selection<Anchor>> {
|
||||
Some(Selection {
|
||||
id: selection.id as usize,
|
||||
start: deserialize_anchor(selection.start?)?,
|
||||
end: deserialize_anchor(selection.end?)?,
|
||||
start: deserialize_anchor(selection.start?, buffer)?,
|
||||
end: deserialize_anchor(selection.end?, buffer)?,
|
||||
reversed: selection.reversed,
|
||||
goal: SelectionGoal::None,
|
||||
})
|
||||
}
|
||||
|
||||
fn deserialize_anchor(anchor: proto::EditorAnchor) -> Option<Anchor> {
|
||||
let excerpt_id = ExcerptId::from_proto(anchor.excerpt_id);
|
||||
Some(Anchor::in_buffer(
|
||||
excerpt_id,
|
||||
language::proto::deserialize_anchor(anchor.anchor?)?,
|
||||
))
|
||||
fn deserialize_anchor(anchor: proto::EditorAnchor, buffer: &MultiBufferSnapshot) -> Option<Anchor> {
|
||||
let anchor = anchor.anchor?;
|
||||
if let Some(buffer_id) = anchor.buffer_id
|
||||
&& BufferId::new(buffer_id).is_ok()
|
||||
{
|
||||
let text_anchor = language::proto::deserialize_anchor(anchor)?;
|
||||
buffer.anchor_in_buffer(text_anchor)
|
||||
} else {
|
||||
match proto::Bias::from_i32(anchor.bias)? {
|
||||
proto::Bias::Left => Some(Anchor::Min),
|
||||
proto::Bias::Right => Some(Anchor::Max),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Item for Editor {
|
||||
|
|
@ -1071,7 +1078,7 @@ impl Item for Editor {
|
|||
f(ItemEvent::UpdateBreadcrumbs);
|
||||
}
|
||||
|
||||
EditorEvent::ExcerptsAdded { .. } | EditorEvent::ExcerptsRemoved { .. } => {
|
||||
EditorEvent::BufferRangesUpdated { .. } | EditorEvent::BuffersRemoved { .. } => {
|
||||
f(ItemEvent::Edit);
|
||||
}
|
||||
|
||||
|
|
@ -1434,9 +1441,9 @@ impl ProjectItem for Editor {
|
|||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let mut editor = Self::for_buffer(buffer.clone(), Some(project), window, cx);
|
||||
let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
|
||||
if let Some((excerpt_id, _, snapshot)) =
|
||||
editor.buffer().read(cx).snapshot(cx).as_singleton()
|
||||
if let Some(buffer_snapshot) = editor.buffer().read(cx).snapshot(cx).as_singleton()
|
||||
&& WorkspaceSettings::get(None, cx).restore_on_file_reopen
|
||||
&& let Some(restoration_data) = Self::project_item_kind()
|
||||
.and_then(|kind| pane.as_ref()?.project_item_restoration_data.get(&kind))
|
||||
|
|
@ -1448,7 +1455,7 @@ impl ProjectItem for Editor {
|
|||
{
|
||||
if !restoration_data.folds.is_empty() {
|
||||
editor.fold_ranges(
|
||||
clip_ranges(&restoration_data.folds, snapshot),
|
||||
clip_ranges(&restoration_data.folds, buffer_snapshot),
|
||||
false,
|
||||
window,
|
||||
cx,
|
||||
|
|
@ -1456,12 +1463,11 @@ impl ProjectItem for Editor {
|
|||
}
|
||||
if !restoration_data.selections.is_empty() {
|
||||
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
|
||||
s.select_ranges(clip_ranges(&restoration_data.selections, snapshot));
|
||||
s.select_ranges(clip_ranges(&restoration_data.selections, buffer_snapshot));
|
||||
});
|
||||
}
|
||||
let (top_row, offset) = restoration_data.scroll_position;
|
||||
let anchor =
|
||||
Anchor::in_buffer(excerpt_id, snapshot.anchor_before(Point::new(top_row, 0)));
|
||||
let anchor = multibuffer_snapshot.anchor_before(Point::new(top_row, 0));
|
||||
editor.set_scroll_anchor(ScrollAnchor { anchor, offset }, window, cx);
|
||||
}
|
||||
|
||||
|
|
@ -1838,7 +1844,7 @@ impl SearchableItem for Editor {
|
|||
};
|
||||
|
||||
for range in search_within_ranges {
|
||||
for (search_buffer, search_range, excerpt_id, deleted_hunk_anchor) in
|
||||
for (search_buffer, search_range, deleted_hunk_anchor) in
|
||||
buffer.range_to_buffer_ranges_with_deleted_hunks(range)
|
||||
{
|
||||
ranges.extend(
|
||||
|
|
@ -1849,20 +1855,22 @@ impl SearchableItem for Editor {
|
|||
)
|
||||
.await
|
||||
.into_iter()
|
||||
.map(|match_range| {
|
||||
.filter_map(|match_range| {
|
||||
if let Some(deleted_hunk_anchor) = deleted_hunk_anchor {
|
||||
let start = search_buffer
|
||||
.anchor_after(search_range.start + match_range.start);
|
||||
let end = search_buffer
|
||||
.anchor_before(search_range.start + match_range.end);
|
||||
deleted_hunk_anchor.with_diff_base_anchor(start)
|
||||
..deleted_hunk_anchor.with_diff_base_anchor(end)
|
||||
Some(
|
||||
deleted_hunk_anchor.with_diff_base_anchor(start)
|
||||
..deleted_hunk_anchor.with_diff_base_anchor(end),
|
||||
)
|
||||
} else {
|
||||
let start = search_buffer
|
||||
.anchor_after(search_range.start + match_range.start);
|
||||
let end = search_buffer
|
||||
.anchor_before(search_range.start + match_range.end);
|
||||
Anchor::range_in_buffer(excerpt_id, start..end)
|
||||
buffer.buffer_anchor_range_to_anchor_range(start..end)
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
|
@ -2050,6 +2058,20 @@ fn restore_serialized_buffer_contents(
|
|||
}
|
||||
}
|
||||
|
||||
fn serialize_path_key(path_key: &PathKey) -> proto::PathKey {
|
||||
proto::PathKey {
|
||||
sort_prefix: path_key.sort_prefix,
|
||||
path: path_key.path.to_proto(),
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_path_key(path_key: proto::PathKey) -> Option<PathKey> {
|
||||
Some(PathKey {
|
||||
sort_prefix: path_key.sort_prefix,
|
||||
path: RelPath::from_proto(&path_key.path).ok()?,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::editor_tests::init_test;
|
||||
|
|
|
|||
|
|
@ -352,11 +352,12 @@ pub(crate) fn construct_initial_buffer_versions_map<
|
|||
}
|
||||
|
||||
for (edit_range, _) in edits {
|
||||
let edit_range_buffer = editor
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.excerpt_containing(edit_range.end, cx)
|
||||
.map(|e| e.1);
|
||||
let multibuffer = editor.buffer.read(cx);
|
||||
let snapshot = multibuffer.snapshot(cx);
|
||||
let anchor = snapshot.anchor_before(edit_range.end);
|
||||
let edit_range_buffer = snapshot
|
||||
.anchor_to_buffer_anchor(anchor)
|
||||
.and_then(|(text_anchor, _)| multibuffer.buffer(text_anchor.buffer_id));
|
||||
if let Some(buffer) = edit_range_buffer {
|
||||
let (buffer_id, buffer_version) =
|
||||
buffer.read_with(cx, |buffer, _| (buffer.remote_id(), buffer.version.clone()));
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ use collections::HashMap;
|
|||
use gpui::{AppContext, Context, Entity, Window};
|
||||
use itertools::Itertools;
|
||||
use language::Buffer;
|
||||
use multi_buffer::MultiBufferOffset;
|
||||
use std::{ops::Range, sync::Arc, time::Duration};
|
||||
use text::{Anchor, AnchorRangeExt, Bias, BufferId, ToOffset, ToPoint};
|
||||
use util::ResultExt;
|
||||
|
|
@ -62,27 +61,15 @@ pub(super) fn refresh_linked_ranges(
|
|||
editor
|
||||
.update(cx, |editor, cx| {
|
||||
let display_snapshot = editor.display_snapshot(cx);
|
||||
let selections = editor
|
||||
.selections
|
||||
.all::<MultiBufferOffset>(&display_snapshot);
|
||||
let selections = editor.selections.all_anchors(&display_snapshot);
|
||||
let snapshot = display_snapshot.buffer_snapshot();
|
||||
let buffer = editor.buffer.read(cx);
|
||||
for selection in selections {
|
||||
let cursor_position = selection.head();
|
||||
let start_position = snapshot.anchor_before(cursor_position);
|
||||
let end_position = snapshot.anchor_after(selection.tail());
|
||||
if start_position.text_anchor.buffer_id != end_position.text_anchor.buffer_id
|
||||
|| end_position.text_anchor.buffer_id.is_none()
|
||||
for selection in selections.iter() {
|
||||
if let Some((_, range)) =
|
||||
snapshot.anchor_range_to_buffer_anchor_range(selection.range())
|
||||
&& let Some(buffer) = buffer.buffer(range.start.buffer_id)
|
||||
{
|
||||
// Throw away selections spanning multiple buffers.
|
||||
continue;
|
||||
}
|
||||
if let Some(buffer) = buffer.buffer_for_anchor(end_position, cx) {
|
||||
applicable_selections.push((
|
||||
buffer,
|
||||
start_position.text_anchor,
|
||||
end_position.text_anchor,
|
||||
));
|
||||
applicable_selections.push((buffer, range.start, range.end));
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ use language::Buffer;
|
|||
use language::Language;
|
||||
use lsp::LanguageServerId;
|
||||
use lsp::LanguageServerName;
|
||||
use multi_buffer::Anchor;
|
||||
use project::LanguageServerToQuery;
|
||||
use project::LocationLink;
|
||||
use project::Project;
|
||||
|
|
@ -27,7 +26,12 @@ pub(crate) fn find_specific_language_server_in_selection<F>(
|
|||
cx: &mut App,
|
||||
filter_language: F,
|
||||
language_server_name: LanguageServerName,
|
||||
) -> Option<(Anchor, Arc<Language>, LanguageServerId, Entity<Buffer>)>
|
||||
) -> Option<(
|
||||
text::Anchor,
|
||||
Arc<Language>,
|
||||
LanguageServerId,
|
||||
Entity<Buffer>,
|
||||
)>
|
||||
where
|
||||
F: Fn(&Language) -> bool,
|
||||
{
|
||||
|
|
@ -40,19 +44,15 @@ where
|
|||
.iter()
|
||||
.find_map(|selection| {
|
||||
let multi_buffer = multi_buffer.read(cx);
|
||||
let (position, buffer) = multi_buffer
|
||||
.buffer_for_anchor(selection.head(), cx)
|
||||
.map(|buffer| (selection.head(), buffer))
|
||||
.or_else(|| {
|
||||
multi_buffer
|
||||
.buffer_for_anchor(selection.tail(), cx)
|
||||
.map(|buffer| (selection.tail(), buffer))
|
||||
})?;
|
||||
let multi_buffer_snapshot = multi_buffer.snapshot(cx);
|
||||
let (position, buffer) = multi_buffer_snapshot
|
||||
.anchor_to_buffer_anchor(selection.head())
|
||||
.and_then(|(anchor, _)| Some((anchor, multi_buffer.buffer(anchor.buffer_id)?)))?;
|
||||
if !seen_buffer_ids.insert(buffer.read(cx).remote_id()) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let language = buffer.read(cx).language_at(position.text_anchor)?;
|
||||
let language = buffer.read(cx).language_at(position)?;
|
||||
if filter_language(&language) {
|
||||
let server_id = buffer.update(cx, |buffer, cx| {
|
||||
project
|
||||
|
|
@ -108,7 +108,7 @@ pub fn lsp_tasks(
|
|||
let buffers = buffer_ids
|
||||
.iter()
|
||||
.filter(|&&buffer_id| match for_position {
|
||||
Some(for_position) => for_position.buffer_id == Some(buffer_id),
|
||||
Some(for_position) => for_position.buffer_id == buffer_id,
|
||||
None => true,
|
||||
})
|
||||
.filter_map(|&buffer_id| project.read(cx).buffer_for_id(buffer_id, cx))
|
||||
|
|
@ -194,7 +194,7 @@ mod tests {
|
|||
use language::{FakeLspAdapter, Language};
|
||||
use languages::rust_lang;
|
||||
use lsp::{LanguageServerId, LanguageServerName};
|
||||
use multi_buffer::{Anchor, MultiBuffer};
|
||||
use multi_buffer::MultiBuffer;
|
||||
use project::{FakeFs, Project};
|
||||
use util::path;
|
||||
|
||||
|
|
@ -236,7 +236,7 @@ mod tests {
|
|||
let filter = |language: &Language| language.name().as_ref() == "Rust";
|
||||
|
||||
let assert_result = |result: Option<(
|
||||
Anchor,
|
||||
text::Anchor,
|
||||
Arc<Language>,
|
||||
LanguageServerId,
|
||||
Entity<language::Buffer>,
|
||||
|
|
|
|||
|
|
@ -205,16 +205,17 @@ pub fn deploy_context_menu(
|
|||
.all::<PointUtf16>(&display_map)
|
||||
.into_iter()
|
||||
.any(|s| !s.is_empty());
|
||||
let has_git_repo = buffer
|
||||
.buffer_id_for_anchor(anchor)
|
||||
.is_some_and(|buffer_id| {
|
||||
project
|
||||
.read(cx)
|
||||
.git_store()
|
||||
.read(cx)
|
||||
.repository_and_path_for_buffer_id(buffer_id, cx)
|
||||
.is_some()
|
||||
});
|
||||
let has_git_repo =
|
||||
buffer
|
||||
.anchor_to_buffer_anchor(anchor)
|
||||
.is_some_and(|(buffer_anchor, _)| {
|
||||
project
|
||||
.read(cx)
|
||||
.git_store()
|
||||
.read(cx)
|
||||
.repository_and_path_for_buffer_id(buffer_anchor.buffer_id, cx)
|
||||
.is_some()
|
||||
});
|
||||
|
||||
let evaluate_selection = window.is_action_available(&EvaluateSelectedText, cx);
|
||||
let run_to_cursor = window.is_action_available(&RunToCursor, cx);
|
||||
|
|
|
|||
|
|
@ -588,22 +588,30 @@ pub fn start_of_excerpt(
|
|||
direction: Direction,
|
||||
) -> DisplayPoint {
|
||||
let point = map.display_point_to_point(display_point, Bias::Left);
|
||||
let Some(excerpt) = map.buffer_snapshot().excerpt_containing(point..point) else {
|
||||
let Some((_, excerpt_range)) = map.buffer_snapshot().excerpt_containing(point..point) else {
|
||||
return display_point;
|
||||
};
|
||||
match direction {
|
||||
Direction::Prev => {
|
||||
let mut start = excerpt.start_anchor().to_display_point(map);
|
||||
let Some(start_anchor) = map.anchor_in_excerpt(excerpt_range.context.start) else {
|
||||
return display_point;
|
||||
};
|
||||
let mut start = start_anchor.to_display_point(map);
|
||||
if start >= display_point && start.row() > DisplayRow(0) {
|
||||
let Some(excerpt) = map.buffer_snapshot().excerpt_before(excerpt.id()) else {
|
||||
let Some(excerpt) = map.buffer_snapshot().excerpt_before(start_anchor) else {
|
||||
return display_point;
|
||||
};
|
||||
start = excerpt.start_anchor().to_display_point(map);
|
||||
if let Some(start_anchor) = map.anchor_in_excerpt(excerpt.context.start) {
|
||||
start = start_anchor.to_display_point(map);
|
||||
}
|
||||
}
|
||||
start
|
||||
}
|
||||
Direction::Next => {
|
||||
let mut end = excerpt.end_anchor().to_display_point(map);
|
||||
let Some(end_anchor) = map.anchor_in_excerpt(excerpt_range.context.end) else {
|
||||
return display_point;
|
||||
};
|
||||
let mut end = end_anchor.to_display_point(map);
|
||||
*end.row_mut() += 1;
|
||||
map.clip_point(end, Bias::Right)
|
||||
}
|
||||
|
|
@ -616,12 +624,15 @@ pub fn end_of_excerpt(
|
|||
direction: Direction,
|
||||
) -> DisplayPoint {
|
||||
let point = map.display_point_to_point(display_point, Bias::Left);
|
||||
let Some(excerpt) = map.buffer_snapshot().excerpt_containing(point..point) else {
|
||||
let Some((_, excerpt_range)) = map.buffer_snapshot().excerpt_containing(point..point) else {
|
||||
return display_point;
|
||||
};
|
||||
match direction {
|
||||
Direction::Prev => {
|
||||
let mut start = excerpt.start_anchor().to_display_point(map);
|
||||
let Some(start_anchor) = map.anchor_in_excerpt(excerpt_range.context.start) else {
|
||||
return display_point;
|
||||
};
|
||||
let mut start = start_anchor.to_display_point(map);
|
||||
if start.row() > DisplayRow(0) {
|
||||
*start.row_mut() -= 1;
|
||||
}
|
||||
|
|
@ -630,18 +641,23 @@ pub fn end_of_excerpt(
|
|||
start
|
||||
}
|
||||
Direction::Next => {
|
||||
let mut end = excerpt.end_anchor().to_display_point(map);
|
||||
let Some(end_anchor) = map.anchor_in_excerpt(excerpt_range.context.end) else {
|
||||
return display_point;
|
||||
};
|
||||
let mut end = end_anchor.to_display_point(map);
|
||||
*end.column_mut() = 0;
|
||||
if end <= display_point {
|
||||
*end.row_mut() += 1;
|
||||
let point_end = map.display_point_to_point(end, Bias::Right);
|
||||
let Some(excerpt) = map
|
||||
let Some((_, excerpt_range)) = map
|
||||
.buffer_snapshot()
|
||||
.excerpt_containing(point_end..point_end)
|
||||
else {
|
||||
return display_point;
|
||||
};
|
||||
end = excerpt.end_anchor().to_display_point(map);
|
||||
if let Some(end_anchor) = map.anchor_in_excerpt(excerpt_range.context.end) {
|
||||
end = end_anchor.to_display_point(map);
|
||||
}
|
||||
*end.column_mut() = 0;
|
||||
}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,9 +8,7 @@ use gpui::{
|
|||
};
|
||||
use language::{Buffer, BufferRow, Runnable};
|
||||
use lsp::LanguageServerName;
|
||||
use multi_buffer::{
|
||||
Anchor, BufferOffset, MultiBufferOffset, MultiBufferRow, MultiBufferSnapshot, ToPoint as _,
|
||||
};
|
||||
use multi_buffer::{Anchor, BufferOffset, MultiBufferRow, MultiBufferSnapshot, ToPoint as _};
|
||||
use project::{
|
||||
Location, Project, TaskSourceKind,
|
||||
debugger::breakpoint_store::{Breakpoint, BreakpointSessionState},
|
||||
|
|
@ -165,7 +163,7 @@ impl Editor {
|
|||
.update(cx, |editor, cx| {
|
||||
let multi_buffer = editor.buffer().read(cx);
|
||||
if multi_buffer.is_singleton() {
|
||||
Some((multi_buffer.snapshot(cx), Anchor::min()..Anchor::max()))
|
||||
Some((multi_buffer.snapshot(cx), Anchor::Min..Anchor::Max))
|
||||
} else {
|
||||
let display_snapshot =
|
||||
editor.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
|
|
@ -209,16 +207,8 @@ impl Editor {
|
|||
.fold(HashMap::default(), |mut acc, (kind, location, task)| {
|
||||
let buffer = location.target.buffer;
|
||||
let buffer_snapshot = buffer.read(cx).snapshot();
|
||||
let offset = multi_buffer_snapshot.excerpts().find_map(
|
||||
|(excerpt_id, snapshot, _)| {
|
||||
if snapshot.remote_id() == buffer_snapshot.remote_id() {
|
||||
multi_buffer_snapshot
|
||||
.anchor_in_excerpt(excerpt_id, location.target.range.start)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
);
|
||||
let offset =
|
||||
multi_buffer_snapshot.anchor_in_excerpt(location.target.range.start);
|
||||
if let Some(offset) = offset {
|
||||
let task_buffer_range =
|
||||
location.target.range.to_point(&buffer_snapshot);
|
||||
|
|
@ -369,20 +359,23 @@ impl Editor {
|
|||
(selection, buffer, snapshot)
|
||||
};
|
||||
let selection_range = selection.range();
|
||||
let start = editor_snapshot
|
||||
let Some((_, range)) = editor_snapshot
|
||||
.display_snapshot
|
||||
.buffer_snapshot()
|
||||
.anchor_after(selection_range.start)
|
||||
.text_anchor;
|
||||
let end = editor_snapshot
|
||||
.display_snapshot
|
||||
.buffer_snapshot()
|
||||
.anchor_after(selection_range.end)
|
||||
.text_anchor;
|
||||
let location = Location {
|
||||
buffer,
|
||||
range: start..end,
|
||||
.anchor_range_to_buffer_anchor_range(
|
||||
editor_snapshot
|
||||
.display_snapshot
|
||||
.buffer_snapshot()
|
||||
.anchor_after(selection_range.start)
|
||||
..editor_snapshot
|
||||
.display_snapshot
|
||||
.buffer_snapshot()
|
||||
.anchor_before(selection_range.end),
|
||||
)
|
||||
else {
|
||||
return Task::ready(None);
|
||||
};
|
||||
let location = Location { buffer, range };
|
||||
let captured_variables = {
|
||||
let mut variables = TaskVariables::default();
|
||||
let buffer = location.buffer.read(cx);
|
||||
|
|
@ -430,9 +423,9 @@ impl Editor {
|
|||
return HashMap::default();
|
||||
}
|
||||
let buffers = if visible_only {
|
||||
self.visible_excerpts(true, cx)
|
||||
.into_values()
|
||||
.map(|(buffer, _, _)| buffer)
|
||||
self.visible_buffers(cx)
|
||||
.into_iter()
|
||||
.filter(|buffer| self.is_lsp_relevant(buffer.read(cx).file(), cx))
|
||||
.collect()
|
||||
} else {
|
||||
self.buffer().read(cx).all_buffers()
|
||||
|
|
@ -482,19 +475,15 @@ impl Editor {
|
|||
cx: &mut Context<Self>,
|
||||
) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let offset = self
|
||||
.selections
|
||||
.newest::<MultiBufferOffset>(&self.display_snapshot(cx))
|
||||
.head();
|
||||
let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
|
||||
let offset = excerpt.map_offset_to_buffer(offset);
|
||||
let buffer_id = excerpt.buffer().remote_id();
|
||||
let anchor = self.selections.newest_anchor().head();
|
||||
let (anchor, buffer_snapshot) = snapshot.anchor_to_buffer_anchor(anchor)?;
|
||||
let offset = anchor.to_offset(buffer_snapshot);
|
||||
|
||||
let layer = excerpt.buffer().syntax_layer_at(offset)?;
|
||||
let layer = buffer_snapshot.syntax_layer_at(offset)?;
|
||||
let mut cursor = layer.node().walk();
|
||||
|
||||
while cursor.goto_first_child_for_byte(offset.0).is_some() {
|
||||
if cursor.node().end_byte() == offset.0 {
|
||||
while cursor.goto_first_child_for_byte(offset).is_some() {
|
||||
if cursor.node().end_byte() == offset {
|
||||
cursor.goto_next_sibling();
|
||||
}
|
||||
}
|
||||
|
|
@ -503,18 +492,18 @@ impl Editor {
|
|||
loop {
|
||||
let node = cursor.node();
|
||||
let node_range = node.byte_range();
|
||||
let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
|
||||
let symbol_start_row = buffer_snapshot.offset_to_point(node.start_byte()).row;
|
||||
|
||||
// Check if this node contains our offset
|
||||
if node_range.start <= offset.0 && node_range.end >= offset.0 {
|
||||
if node_range.start <= offset && node_range.end >= offset {
|
||||
// If it contains offset, check for task
|
||||
if let Some(tasks) = self
|
||||
.runnables
|
||||
.runnables
|
||||
.get(&buffer_id)
|
||||
.get(&buffer_snapshot.remote_id())
|
||||
.and_then(|(_, tasks)| tasks.get(&symbol_start_row))
|
||||
{
|
||||
let buffer = self.buffer.read(cx).buffer(buffer_id)?;
|
||||
let buffer = self.buffer.read(cx).buffer(buffer_snapshot.remote_id())?;
|
||||
return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ pub fn go_to_parent_module(
|
|||
let request = proto::LspExtGoToParentModule {
|
||||
project_id,
|
||||
buffer_id: buffer_id.to_proto(),
|
||||
position: Some(serialize_anchor(&trigger_anchor.text_anchor)),
|
||||
position: Some(serialize_anchor(&trigger_anchor)),
|
||||
};
|
||||
let response = client
|
||||
.request(request)
|
||||
|
|
@ -106,7 +106,7 @@ pub fn go_to_parent_module(
|
|||
.context("go to parent module via collab")?
|
||||
} else {
|
||||
let buffer_snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot());
|
||||
let position = trigger_anchor.text_anchor.to_point_utf16(&buffer_snapshot);
|
||||
let position = trigger_anchor.to_point_utf16(&buffer_snapshot);
|
||||
project
|
||||
.update(cx, |project, cx| {
|
||||
project.request_lsp(
|
||||
|
|
@ -168,7 +168,7 @@ pub fn expand_macro_recursively(
|
|||
let request = proto::LspExtExpandMacro {
|
||||
project_id,
|
||||
buffer_id: buffer_id.to_proto(),
|
||||
position: Some(serialize_anchor(&trigger_anchor.text_anchor)),
|
||||
position: Some(serialize_anchor(&trigger_anchor)),
|
||||
};
|
||||
let response = client
|
||||
.request(request)
|
||||
|
|
@ -180,7 +180,7 @@ pub fn expand_macro_recursively(
|
|||
}
|
||||
} else {
|
||||
let buffer_snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot());
|
||||
let position = trigger_anchor.text_anchor.to_point_utf16(&buffer_snapshot);
|
||||
let position = trigger_anchor.to_point_utf16(&buffer_snapshot);
|
||||
project
|
||||
.update(cx, |project, cx| {
|
||||
project.request_lsp(
|
||||
|
|
@ -195,10 +195,7 @@ pub fn expand_macro_recursively(
|
|||
};
|
||||
|
||||
if macro_expansion.is_empty() {
|
||||
log::info!(
|
||||
"Empty macro expansion for position {:?}",
|
||||
trigger_anchor.text_anchor
|
||||
);
|
||||
log::info!("Empty macro expansion for position {:?}", trigger_anchor);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
|
@ -260,7 +257,7 @@ pub fn open_docs(editor: &mut Editor, _: &OpenDocs, window: &mut Window, cx: &mu
|
|||
let request = proto::LspExtOpenDocs {
|
||||
project_id,
|
||||
buffer_id: buffer_id.to_proto(),
|
||||
position: Some(serialize_anchor(&trigger_anchor.text_anchor)),
|
||||
position: Some(serialize_anchor(&trigger_anchor)),
|
||||
};
|
||||
let response = client
|
||||
.request(request)
|
||||
|
|
@ -272,7 +269,7 @@ pub fn open_docs(editor: &mut Editor, _: &OpenDocs, window: &mut Window, cx: &mu
|
|||
}
|
||||
} else {
|
||||
let buffer_snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot());
|
||||
let position = trigger_anchor.text_anchor.to_point_utf16(&buffer_snapshot);
|
||||
let position = trigger_anchor.to_point_utf16(&buffer_snapshot);
|
||||
project
|
||||
.update(cx, |project, cx| {
|
||||
project.request_lsp(
|
||||
|
|
@ -287,10 +284,7 @@ pub fn open_docs(editor: &mut Editor, _: &OpenDocs, window: &mut Window, cx: &mu
|
|||
};
|
||||
|
||||
if docs_urls.is_empty() {
|
||||
log::debug!(
|
||||
"Empty docs urls for position {:?}",
|
||||
trigger_anchor.text_anchor
|
||||
);
|
||||
log::debug!("Empty docs urls for position {:?}", trigger_anchor);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
|
@ -322,16 +316,18 @@ fn cancel_flycheck_action(
|
|||
let Some(project) = &editor.project else {
|
||||
return;
|
||||
};
|
||||
let multibuffer_snapshot = editor
|
||||
.buffer
|
||||
.read_with(cx, |buffer, cx| buffer.snapshot(cx));
|
||||
let buffer_id = editor
|
||||
.selections
|
||||
.disjoint_anchors_arc()
|
||||
.iter()
|
||||
.find_map(|selection| {
|
||||
let buffer_id = selection
|
||||
.start
|
||||
.text_anchor
|
||||
.buffer_id
|
||||
.or(selection.end.text_anchor.buffer_id)?;
|
||||
let buffer_id = multibuffer_snapshot
|
||||
.anchor_to_buffer_anchor(selection.start)?
|
||||
.0
|
||||
.buffer_id;
|
||||
let project = project.read(cx);
|
||||
let entry_id = project
|
||||
.buffer_for_id(buffer_id, cx)?
|
||||
|
|
@ -351,16 +347,18 @@ fn run_flycheck_action(
|
|||
let Some(project) = &editor.project else {
|
||||
return;
|
||||
};
|
||||
let multibuffer_snapshot = editor
|
||||
.buffer
|
||||
.read_with(cx, |buffer, cx| buffer.snapshot(cx));
|
||||
let buffer_id = editor
|
||||
.selections
|
||||
.disjoint_anchors_arc()
|
||||
.iter()
|
||||
.find_map(|selection| {
|
||||
let buffer_id = selection
|
||||
.start
|
||||
.text_anchor
|
||||
.buffer_id
|
||||
.or(selection.end.text_anchor.buffer_id)?;
|
||||
let buffer_id = multibuffer_snapshot
|
||||
.anchor_to_buffer_anchor(selection.head())?
|
||||
.0
|
||||
.buffer_id;
|
||||
let project = project.read(cx);
|
||||
let entry_id = project
|
||||
.buffer_for_id(buffer_id, cx)?
|
||||
|
|
@ -380,16 +378,18 @@ fn clear_flycheck_action(
|
|||
let Some(project) = &editor.project else {
|
||||
return;
|
||||
};
|
||||
let multibuffer_snapshot = editor
|
||||
.buffer
|
||||
.read_with(cx, |buffer, cx| buffer.snapshot(cx));
|
||||
let buffer_id = editor
|
||||
.selections
|
||||
.disjoint_anchors_arc()
|
||||
.iter()
|
||||
.find_map(|selection| {
|
||||
let buffer_id = selection
|
||||
.start
|
||||
.text_anchor
|
||||
.buffer_id
|
||||
.or(selection.end.text_anchor.buffer_id)?;
|
||||
let buffer_id = multibuffer_snapshot
|
||||
.anchor_to_buffer_anchor(selection.head())?
|
||||
.0
|
||||
.buffer_id;
|
||||
let project = project.read(cx);
|
||||
let entry_id = project
|
||||
.buffer_for_id(buffer_id, cx)?
|
||||
|
|
|
|||
|
|
@ -44,13 +44,13 @@ impl ScrollAnchor {
|
|||
pub(super) fn new() -> Self {
|
||||
Self {
|
||||
offset: gpui::Point::default(),
|
||||
anchor: Anchor::min(),
|
||||
anchor: Anchor::Min,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> gpui::Point<ScrollOffset> {
|
||||
self.offset.apply_along(Axis::Vertical, |offset| {
|
||||
if self.anchor == Anchor::min() {
|
||||
if self.anchor == Anchor::Min {
|
||||
0.
|
||||
} else {
|
||||
let scroll_top = self.anchor.to_display_point(snapshot).row().as_f64();
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ impl Editor {
|
|||
let selection_head = self.selections.newest_display(&display_snapshot).head();
|
||||
|
||||
let sticky_headers_len = if EditorSettings::get_global(cx).sticky_scroll.enabled
|
||||
&& let Some((_, _, buffer_snapshot)) = display_snapshot.buffer_snapshot().as_singleton()
|
||||
&& let Some(buffer_snapshot) = display_snapshot.buffer_snapshot().as_singleton()
|
||||
{
|
||||
let select_head_point =
|
||||
rope::Point::new(selection_head.to_point(&display_snapshot).row, 0);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ use std::{
|
|||
sync::Arc,
|
||||
};
|
||||
|
||||
use collections::HashMap;
|
||||
use gpui::Pixels;
|
||||
use itertools::Itertools as _;
|
||||
use language::{Bias, Point, PointUtf16, Selection, SelectionGoal};
|
||||
|
|
@ -12,7 +11,7 @@ use multi_buffer::{MultiBufferDimension, MultiBufferOffset};
|
|||
use util::post_inc;
|
||||
|
||||
use crate::{
|
||||
Anchor, DisplayPoint, DisplayRow, ExcerptId, MultiBufferSnapshot, SelectMode, ToOffset,
|
||||
Anchor, DisplayPoint, DisplayRow, MultiBufferSnapshot, SelectMode, ToOffset,
|
||||
display_map::{DisplaySnapshot, ToDisplayPoint},
|
||||
movement::TextLayoutDetails,
|
||||
};
|
||||
|
|
@ -45,8 +44,8 @@ impl SelectionsCollection {
|
|||
pending: Some(PendingSelection {
|
||||
selection: Selection {
|
||||
id: 0,
|
||||
start: Anchor::min(),
|
||||
end: Anchor::min(),
|
||||
start: Anchor::Min,
|
||||
end: Anchor::Min,
|
||||
reversed: false,
|
||||
goal: SelectionGoal::None,
|
||||
},
|
||||
|
|
@ -547,13 +546,11 @@ impl SelectionsCollection {
|
|||
);
|
||||
assert!(
|
||||
snapshot.can_resolve(&selection.start),
|
||||
"disjoint selection start is not resolvable for the given snapshot:\n{selection:?}, {excerpt:?}",
|
||||
excerpt = snapshot.buffer_for_excerpt(selection.start.excerpt_id).map(|snapshot| snapshot.remote_id()),
|
||||
"disjoint selection start is not resolvable for the given snapshot:\n{selection:?}",
|
||||
);
|
||||
assert!(
|
||||
snapshot.can_resolve(&selection.end),
|
||||
"disjoint selection end is not resolvable for the given snapshot: {selection:?}, {excerpt:?}",
|
||||
excerpt = snapshot.buffer_for_excerpt(selection.end.excerpt_id).map(|snapshot| snapshot.remote_id()),
|
||||
"disjoint selection start is not resolvable for the given snapshot:\n{selection:?}",
|
||||
);
|
||||
});
|
||||
assert!(
|
||||
|
|
@ -572,17 +569,11 @@ impl SelectionsCollection {
|
|||
);
|
||||
assert!(
|
||||
snapshot.can_resolve(&selection.start),
|
||||
"pending selection start is not resolvable for the given snapshot: {pending:?}, {excerpt:?}",
|
||||
excerpt = snapshot
|
||||
.buffer_for_excerpt(selection.start.excerpt_id)
|
||||
.map(|snapshot| snapshot.remote_id()),
|
||||
"pending selection start is not resolvable for the given snapshot: {pending:?}",
|
||||
);
|
||||
assert!(
|
||||
snapshot.can_resolve(&selection.end),
|
||||
"pending selection end is not resolvable for the given snapshot: {pending:?}, {excerpt:?}",
|
||||
excerpt = snapshot
|
||||
.buffer_for_excerpt(selection.end.excerpt_id)
|
||||
.map(|snapshot| snapshot.remote_id()),
|
||||
"pending selection end is not resolvable for the given snapshot: {pending:?}",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -665,10 +656,10 @@ impl<'snap, 'a> MutableSelectionsCollection<'snap, 'a> {
|
|||
self.disjoint
|
||||
.iter()
|
||||
.filter(|selection| {
|
||||
if let Some(selection_buffer_id) =
|
||||
self.snapshot.buffer_id_for_anchor(selection.start)
|
||||
if let Some((selection_buffer_anchor, _)) =
|
||||
self.snapshot.anchor_to_buffer_anchor(selection.start)
|
||||
{
|
||||
let should_remove = selection_buffer_id == buffer_id;
|
||||
let should_remove = selection_buffer_anchor.buffer_id == buffer_id;
|
||||
changed |= should_remove;
|
||||
!should_remove
|
||||
} else {
|
||||
|
|
@ -683,10 +674,8 @@ impl<'snap, 'a> MutableSelectionsCollection<'snap, 'a> {
|
|||
let buffer_snapshot = self.snapshot.buffer_snapshot();
|
||||
let anchor = buffer_snapshot
|
||||
.excerpts()
|
||||
.find(|(_, buffer, _)| buffer.remote_id() == buffer_id)
|
||||
.and_then(|(excerpt_id, _, range)| {
|
||||
buffer_snapshot.anchor_in_excerpt(excerpt_id, range.context.start)
|
||||
})
|
||||
.find(|excerpt| excerpt.context.start.buffer_id == buffer_id)
|
||||
.and_then(|excerpt| buffer_snapshot.anchor_in_excerpt(excerpt.context.start))
|
||||
.unwrap_or_else(|| self.snapshot.anchor_before(MultiBufferOffset(0)));
|
||||
self.collection.disjoint = Arc::from([Selection {
|
||||
id: post_inc(&mut self.collection.next_selection_id),
|
||||
|
|
@ -1077,80 +1066,6 @@ impl<'snap, 'a> MutableSelectionsCollection<'snap, 'a> {
|
|||
self.selections_changed = true;
|
||||
self.pending.as_mut().map(|pending| &mut pending.selection)
|
||||
}
|
||||
|
||||
/// Compute new ranges for any selections that were located in excerpts that have
|
||||
/// since been removed.
|
||||
///
|
||||
/// Returns a `HashMap` indicating which selections whose former head position
|
||||
/// was no longer present. The keys of the map are selection ids. The values are
|
||||
/// the id of the new excerpt where the head of the selection has been moved.
|
||||
pub fn refresh(&mut self) -> HashMap<usize, ExcerptId> {
|
||||
let mut pending = self.collection.pending.take();
|
||||
let mut selections_with_lost_position = HashMap::default();
|
||||
|
||||
let anchors_with_status = {
|
||||
let disjoint_anchors = self
|
||||
.disjoint
|
||||
.iter()
|
||||
.flat_map(|selection| [&selection.start, &selection.end]);
|
||||
self.snapshot.refresh_anchors(disjoint_anchors)
|
||||
};
|
||||
let adjusted_disjoint: Vec<_> = anchors_with_status
|
||||
.chunks(2)
|
||||
.map(|selection_anchors| {
|
||||
let (anchor_ix, start, kept_start) = selection_anchors[0];
|
||||
let (_, end, kept_end) = selection_anchors[1];
|
||||
let selection = &self.disjoint[anchor_ix / 2];
|
||||
let kept_head = if selection.reversed {
|
||||
kept_start
|
||||
} else {
|
||||
kept_end
|
||||
};
|
||||
if !kept_head {
|
||||
selections_with_lost_position.insert(selection.id, selection.head().excerpt_id);
|
||||
}
|
||||
|
||||
Selection {
|
||||
id: selection.id,
|
||||
start,
|
||||
end,
|
||||
reversed: selection.reversed,
|
||||
goal: selection.goal,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
if !adjusted_disjoint.is_empty() {
|
||||
let map = self.display_snapshot();
|
||||
let resolved_selections =
|
||||
resolve_selections_wrapping_blocks(adjusted_disjoint.iter(), &map).collect();
|
||||
self.select::<MultiBufferOffset>(resolved_selections);
|
||||
}
|
||||
|
||||
if let Some(pending) = pending.as_mut() {
|
||||
let anchors = self
|
||||
.snapshot
|
||||
.refresh_anchors([&pending.selection.start, &pending.selection.end]);
|
||||
let (_, start, kept_start) = anchors[0];
|
||||
let (_, end, kept_end) = anchors[1];
|
||||
let kept_head = if pending.selection.reversed {
|
||||
kept_start
|
||||
} else {
|
||||
kept_end
|
||||
};
|
||||
if !kept_head {
|
||||
selections_with_lost_position
|
||||
.insert(pending.selection.id, pending.selection.head().excerpt_id);
|
||||
}
|
||||
|
||||
pending.selection.start = start;
|
||||
pending.selection.end = end;
|
||||
}
|
||||
self.collection.pending = pending;
|
||||
self.selections_changed = true;
|
||||
|
||||
selections_with_lost_position
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for MutableSelectionsCollection<'_, '_> {
|
||||
|
|
|
|||
|
|
@ -148,9 +148,9 @@ impl Editor {
|
|||
};
|
||||
|
||||
let buffers_to_query = self
|
||||
.visible_excerpts(true, cx)
|
||||
.into_values()
|
||||
.map(|(buffer, ..)| buffer)
|
||||
.visible_buffers(cx)
|
||||
.into_iter()
|
||||
.filter(|buffer| self.is_lsp_relevant(buffer.read(cx).file(), cx))
|
||||
.chain(buffer_id.and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id)))
|
||||
.filter_map(|editor_buffer| {
|
||||
let editor_buffer_id = editor_buffer.read(cx).remote_id();
|
||||
|
|
@ -1214,11 +1214,19 @@ mod tests {
|
|||
);
|
||||
|
||||
// Get the excerpt id for the TOML excerpt and expand it down by 2 lines.
|
||||
let toml_excerpt_id =
|
||||
editor.read_with(cx, |editor, cx| editor.buffer().read(cx).excerpt_ids()[0]);
|
||||
let toml_anchor = editor.read_with(cx, |editor, cx| {
|
||||
editor
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.snapshot(cx)
|
||||
.anchor_in_excerpt(text::Anchor::min_for_buffer(
|
||||
toml_buffer.read(cx).remote_id(),
|
||||
))
|
||||
.unwrap()
|
||||
});
|
||||
editor.update_in(cx, |editor, _, cx| {
|
||||
editor.buffer().update(cx, |buffer, cx| {
|
||||
buffer.expand_excerpts([toml_excerpt_id], 2, ExpandExcerptDirection::Down, cx);
|
||||
buffer.expand_excerpts([toml_anchor], 2, ExpandExcerptDirection::Down, cx);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -7,7 +7,7 @@ use gpui::{
|
|||
ParentElement, Pixels, StatefulInteractiveElement, Styled, TextStyleRefinement, Window, div,
|
||||
linear_color_stop, linear_gradient, point, px, size,
|
||||
};
|
||||
use multi_buffer::{Anchor, ExcerptId};
|
||||
use multi_buffer::{Anchor, ExcerptBoundaryInfo};
|
||||
use settings::Settings;
|
||||
use smallvec::smallvec;
|
||||
use text::BufferId;
|
||||
|
|
@ -429,7 +429,7 @@ impl SplitBufferHeadersElement {
|
|||
|
||||
let sticky_header_excerpt_id = snapshot
|
||||
.sticky_header_excerpt(scroll_position.y)
|
||||
.map(|e| e.excerpt.id);
|
||||
.map(|e| e.excerpt);
|
||||
|
||||
let non_sticky_headers = self.build_non_sticky_headers(
|
||||
&snapshot,
|
||||
|
|
@ -476,9 +476,10 @@ impl SplitBufferHeadersElement {
|
|||
let mut anchors_by_buffer: HashMap<BufferId, (usize, Anchor)> = HashMap::default();
|
||||
for selection in all_anchor_selections.iter() {
|
||||
let head = selection.head();
|
||||
if let Some(buffer_id) = head.text_anchor.buffer_id {
|
||||
if let Some((text_anchor, _)) = snapshot.buffer_snapshot().anchor_to_buffer_anchor(head)
|
||||
{
|
||||
anchors_by_buffer
|
||||
.entry(buffer_id)
|
||||
.entry(text_anchor.buffer_id)
|
||||
.and_modify(|(latest_id, latest_anchor)| {
|
||||
if selection.id > *latest_id {
|
||||
*latest_id = selection.id;
|
||||
|
|
@ -520,7 +521,7 @@ impl SplitBufferHeadersElement {
|
|||
);
|
||||
|
||||
let editor_bg_color = cx.theme().colors().editor_background;
|
||||
let selected = selected_buffer_ids.contains(&excerpt.buffer_id);
|
||||
let selected = selected_buffer_ids.contains(&excerpt.buffer_id());
|
||||
|
||||
let mut header = v_flex()
|
||||
.id("sticky-buffer-header")
|
||||
|
|
@ -594,7 +595,7 @@ impl SplitBufferHeadersElement {
|
|||
end_row: DisplayRow,
|
||||
selected_buffer_ids: &HashSet<BufferId>,
|
||||
latest_selection_anchors: &HashMap<BufferId, Anchor>,
|
||||
sticky_header_excerpt_id: Option<ExcerptId>,
|
||||
sticky_header: Option<&ExcerptBoundaryInfo>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Vec<BufferHeaderLayout> {
|
||||
|
|
@ -603,7 +604,7 @@ impl SplitBufferHeadersElement {
|
|||
for (block_row, block) in snapshot.blocks_in_range(start_row..end_row) {
|
||||
let (excerpt, is_folded) = match block {
|
||||
Block::BufferHeader { excerpt, .. } => {
|
||||
if sticky_header_excerpt_id == Some(excerpt.id) {
|
||||
if sticky_header == Some(excerpt) {
|
||||
continue;
|
||||
}
|
||||
(excerpt, false)
|
||||
|
|
@ -613,7 +614,7 @@ impl SplitBufferHeadersElement {
|
|||
Block::ExcerptBoundary { .. } | Block::Custom(_) | Block::Spacer { .. } => continue,
|
||||
};
|
||||
|
||||
let selected = selected_buffer_ids.contains(&excerpt.buffer_id);
|
||||
let selected = selected_buffer_ids.contains(&excerpt.buffer_id());
|
||||
let jump_data = header_jump_data(
|
||||
snapshot,
|
||||
block_row,
|
||||
|
|
|
|||
101
crates/editor/src/tasks.rs
Normal file
101
crates/editor/src/tasks.rs
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
use crate::Editor;
|
||||
|
||||
use collections::HashMap;
|
||||
use gpui::{App, Task, Window};
|
||||
use lsp::LanguageServerName;
|
||||
use project::{Location, project_settings::ProjectSettings};
|
||||
use settings::Settings as _;
|
||||
use task::{TaskContext, TaskVariables, VariableName};
|
||||
use text::{BufferId, ToOffset, ToPoint};
|
||||
|
||||
impl Editor {
|
||||
pub fn task_context(&self, window: &mut Window, cx: &mut App) -> Task<Option<TaskContext>> {
|
||||
let Some(project) = self.project.clone() else {
|
||||
return Task::ready(None);
|
||||
};
|
||||
let display_snapshot = self.display_snapshot(cx);
|
||||
let selection = self.selections.newest_adjusted(&display_snapshot);
|
||||
let start = display_snapshot
|
||||
.buffer_snapshot()
|
||||
.anchor_after(selection.start);
|
||||
let end = display_snapshot
|
||||
.buffer_snapshot()
|
||||
.anchor_after(selection.end);
|
||||
let Some((buffer_snapshot, range)) = display_snapshot
|
||||
.buffer_snapshot()
|
||||
.anchor_range_to_buffer_anchor_range(start..end)
|
||||
else {
|
||||
return Task::ready(None);
|
||||
};
|
||||
let Some(buffer) = self.buffer.read(cx).buffer(buffer_snapshot.remote_id()) else {
|
||||
return Task::ready(None);
|
||||
};
|
||||
let location = Location { buffer, range };
|
||||
let captured_variables = {
|
||||
let mut variables = TaskVariables::default();
|
||||
let buffer = location.buffer.read(cx);
|
||||
let buffer_id = buffer.remote_id();
|
||||
let snapshot = buffer.snapshot();
|
||||
let starting_point = location.range.start.to_point(&snapshot);
|
||||
let starting_offset = starting_point.to_offset(&snapshot);
|
||||
for (_, tasks) in self
|
||||
.tasks
|
||||
.range((buffer_id, 0)..(buffer_id, starting_point.row + 1))
|
||||
{
|
||||
if !tasks
|
||||
.context_range
|
||||
.contains(&crate::BufferOffset(starting_offset))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (capture_name, value) in tasks.extra_variables.iter() {
|
||||
variables.insert(
|
||||
VariableName::Custom(capture_name.to_owned().into()),
|
||||
value.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
variables
|
||||
};
|
||||
|
||||
project.update(cx, |project, cx| {
|
||||
project.task_store().update(cx, |task_store, cx| {
|
||||
task_store.task_context_for_location(captured_variables, location, cx)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn lsp_task_sources(&self, cx: &App) -> HashMap<LanguageServerName, Vec<BufferId>> {
|
||||
let lsp_settings = &ProjectSettings::get_global(cx).lsp;
|
||||
|
||||
self.buffer()
|
||||
.read(cx)
|
||||
.all_buffers()
|
||||
.into_iter()
|
||||
.filter_map(|buffer| {
|
||||
let lsp_tasks_source = buffer
|
||||
.read(cx)
|
||||
.language()?
|
||||
.context_provider()?
|
||||
.lsp_task_source()?;
|
||||
if lsp_settings
|
||||
.get(&lsp_tasks_source)
|
||||
.is_none_or(|s| s.enable_lsp_tasks)
|
||||
{
|
||||
let buffer_id = buffer.read(cx).remote_id();
|
||||
Some((lsp_tasks_source, buffer_id))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.fold(
|
||||
HashMap::default(),
|
||||
|mut acc, (lsp_task_source, buffer_id)| {
|
||||
acc.entry(lsp_task_source)
|
||||
.or_insert_with(Vec::new)
|
||||
.push(buffer_id);
|
||||
acc
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -245,7 +245,7 @@ pub fn editor_content_with_blocks_and_size(
|
|||
format!(
|
||||
"§ {}",
|
||||
first_excerpt
|
||||
.buffer
|
||||
.buffer(snapshot.buffer_snapshot())
|
||||
.file()
|
||||
.map(|file| file.file_name(cx))
|
||||
.unwrap_or("<no file>")
|
||||
|
|
@ -274,7 +274,7 @@ pub fn editor_content_with_blocks_and_size(
|
|||
format!(
|
||||
"§ {}",
|
||||
excerpt
|
||||
.buffer
|
||||
.buffer(snapshot.buffer_snapshot())
|
||||
.file()
|
||||
.map(|file| file.file_name(cx))
|
||||
.unwrap_or("<no file>")
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::{
|
||||
AnchorRangeExt, DisplayPoint, Editor, ExcerptId, MultiBuffer, MultiBufferSnapshot, RowExt,
|
||||
DisplayPoint, Editor, MultiBuffer, MultiBufferSnapshot, RowExt,
|
||||
display_map::{HighlightKey, ToDisplayPoint},
|
||||
};
|
||||
use buffer_diff::DiffHunkStatusKind;
|
||||
|
|
@ -13,7 +13,9 @@ use gpui::{
|
|||
};
|
||||
use itertools::Itertools;
|
||||
use language::{Buffer, BufferSnapshot, LanguageRegistry};
|
||||
use multi_buffer::{Anchor, ExcerptRange, MultiBufferOffset, MultiBufferRow, PathKey};
|
||||
use multi_buffer::{
|
||||
Anchor, AnchorRangeExt, ExcerptRange, MultiBufferOffset, MultiBufferRow, PathKey,
|
||||
};
|
||||
use parking_lot::RwLock;
|
||||
use project::{FakeFs, Project};
|
||||
use std::{
|
||||
|
|
@ -464,7 +466,21 @@ impl EditorTestContext {
|
|||
let selections = editor.selections.disjoint_anchors_arc();
|
||||
let excerpts = multibuffer_snapshot
|
||||
.excerpts()
|
||||
.map(|(e_id, snapshot, range)| (e_id, snapshot.clone(), range))
|
||||
.map(|info| {
|
||||
(
|
||||
multibuffer_snapshot
|
||||
.buffer_for_id(info.context.start.buffer_id)
|
||||
.cloned()
|
||||
.unwrap(),
|
||||
multibuffer_snapshot
|
||||
.anchor_in_excerpt(info.context.start)
|
||||
.unwrap()
|
||||
..multibuffer_snapshot
|
||||
.anchor_in_excerpt(info.context.end)
|
||||
.unwrap(),
|
||||
info,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
(multibuffer_snapshot, selections, excerpts)
|
||||
|
|
@ -478,14 +494,23 @@ impl EditorTestContext {
|
|||
fmt_additional_notes(),
|
||||
);
|
||||
|
||||
for (ix, (excerpt_id, snapshot, range)) in excerpts.into_iter().enumerate() {
|
||||
for (ix, (snapshot, multibuffer_range, excerpt_range)) in excerpts.into_iter().enumerate() {
|
||||
let is_folded = self
|
||||
.update_editor(|editor, _, cx| editor.is_buffer_folded(snapshot.remote_id(), cx));
|
||||
let (expected_text, expected_selections) =
|
||||
marked_text_ranges(expected_excerpts[ix], true);
|
||||
if expected_text == "[FOLDED]\n" {
|
||||
assert!(is_folded, "excerpt {} should be folded", ix);
|
||||
let is_selected = selections.iter().any(|s| s.head().excerpt_id == excerpt_id);
|
||||
let is_selected = selections.iter().any(|s| {
|
||||
multibuffer_range
|
||||
.start
|
||||
.cmp(&s.head(), &multibuffer_snapshot)
|
||||
.is_le()
|
||||
&& multibuffer_range
|
||||
.end
|
||||
.cmp(&s.head(), &multibuffer_snapshot)
|
||||
.is_ge()
|
||||
});
|
||||
if !expected_selections.is_empty() {
|
||||
assert!(
|
||||
is_selected,
|
||||
|
|
@ -510,7 +535,7 @@ impl EditorTestContext {
|
|||
);
|
||||
assert_eq!(
|
||||
multibuffer_snapshot
|
||||
.text_for_range(Anchor::range_in_buffer(excerpt_id, range.context.clone()))
|
||||
.text_for_range(multibuffer_range.clone())
|
||||
.collect::<String>(),
|
||||
expected_text,
|
||||
"{}",
|
||||
|
|
@ -519,13 +544,24 @@ impl EditorTestContext {
|
|||
|
||||
let selections = selections
|
||||
.iter()
|
||||
.filter(|s| s.head().excerpt_id == excerpt_id)
|
||||
.map(|s| {
|
||||
let head = text::ToOffset::to_offset(&s.head().text_anchor, &snapshot)
|
||||
- text::ToOffset::to_offset(&range.context.start, &snapshot);
|
||||
let tail = text::ToOffset::to_offset(&s.head().text_anchor, &snapshot)
|
||||
- text::ToOffset::to_offset(&range.context.start, &snapshot);
|
||||
tail..head
|
||||
.filter(|s| {
|
||||
multibuffer_range
|
||||
.start
|
||||
.cmp(&s.head(), &multibuffer_snapshot)
|
||||
.is_le()
|
||||
&& multibuffer_range
|
||||
.end
|
||||
.cmp(&s.head(), &multibuffer_snapshot)
|
||||
.is_ge()
|
||||
})
|
||||
.filter_map(|s| {
|
||||
let (head_anchor, buffer_snapshot) =
|
||||
multibuffer_snapshot.anchor_to_buffer_anchor(s.head())?;
|
||||
let head = text::ToOffset::to_offset(&head_anchor, buffer_snapshot)
|
||||
- text::ToOffset::to_offset(&excerpt_range.context.start, buffer_snapshot);
|
||||
let tail = text::ToOffset::to_offset(&head_anchor, buffer_snapshot)
|
||||
- text::ToOffset::to_offset(&excerpt_range.context.start, buffer_snapshot);
|
||||
Some(tail..head)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
// todo: selections that cross excerpt boundaries..
|
||||
|
|
@ -546,9 +582,12 @@ impl EditorTestContext {
|
|||
let selections = editor.selections.disjoint_anchors_arc().to_vec();
|
||||
let excerpts = multibuffer_snapshot
|
||||
.excerpts()
|
||||
.map(|(e_id, snapshot, range)| {
|
||||
let is_folded = editor.is_buffer_folded(snapshot.remote_id(), cx);
|
||||
(e_id, snapshot.clone(), range, is_folded)
|
||||
.map(|info| {
|
||||
let buffer_snapshot = multibuffer_snapshot
|
||||
.buffer_for_id(info.context.start.buffer_id)
|
||||
.unwrap();
|
||||
let is_folded = editor.is_buffer_folded(buffer_snapshot.remote_id(), cx);
|
||||
(buffer_snapshot.clone(), info, is_folded)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
|
|
@ -673,7 +712,7 @@ impl EditorTestContext {
|
|||
struct FormatMultiBufferAsMarkedText {
|
||||
multibuffer_snapshot: MultiBufferSnapshot,
|
||||
selections: Vec<Selection<Anchor>>,
|
||||
excerpts: Vec<(ExcerptId, BufferSnapshot, ExcerptRange<text::Anchor>, bool)>,
|
||||
excerpts: Vec<(BufferSnapshot, ExcerptRange<text::Anchor>, bool)>,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for FormatMultiBufferAsMarkedText {
|
||||
|
|
@ -684,25 +723,40 @@ impl std::fmt::Display for FormatMultiBufferAsMarkedText {
|
|||
excerpts,
|
||||
} = self;
|
||||
|
||||
for (excerpt_id, snapshot, range, is_folded) in excerpts.into_iter() {
|
||||
for (_snapshot, range, is_folded) in excerpts.into_iter() {
|
||||
write!(f, "[EXCERPT]\n")?;
|
||||
if *is_folded {
|
||||
write!(f, "[FOLDED]\n")?;
|
||||
}
|
||||
|
||||
let multibuffer_range = multibuffer_snapshot
|
||||
.buffer_anchor_range_to_anchor_range(range.context.clone())
|
||||
.unwrap();
|
||||
|
||||
let mut text = multibuffer_snapshot
|
||||
.text_for_range(Anchor::range_in_buffer(*excerpt_id, range.context.clone()))
|
||||
.text_for_range(multibuffer_range.clone())
|
||||
.collect::<String>();
|
||||
|
||||
let selections = selections
|
||||
.iter()
|
||||
.filter(|&s| s.head().excerpt_id == *excerpt_id)
|
||||
.map(|s| {
|
||||
let head = text::ToOffset::to_offset(&s.head().text_anchor, &snapshot)
|
||||
- text::ToOffset::to_offset(&range.context.start, &snapshot);
|
||||
let tail = text::ToOffset::to_offset(&s.head().text_anchor, &snapshot)
|
||||
- text::ToOffset::to_offset(&range.context.start, &snapshot);
|
||||
tail..head
|
||||
.filter(|&s| {
|
||||
multibuffer_range
|
||||
.start
|
||||
.cmp(&s.head(), multibuffer_snapshot)
|
||||
.is_le()
|
||||
&& multibuffer_range
|
||||
.end
|
||||
.cmp(&s.head(), multibuffer_snapshot)
|
||||
.is_ge()
|
||||
})
|
||||
.filter_map(|s| {
|
||||
let (head_anchor, buffer_snapshot) =
|
||||
multibuffer_snapshot.anchor_to_buffer_anchor(s.head())?;
|
||||
let head = text::ToOffset::to_offset(&head_anchor, buffer_snapshot)
|
||||
- text::ToOffset::to_offset(&range.context.start, buffer_snapshot);
|
||||
let tail = text::ToOffset::to_offset(&head_anchor, buffer_snapshot)
|
||||
- text::ToOffset::to_offset(&range.context.start, buffer_snapshot);
|
||||
Some(tail..head)
|
||||
})
|
||||
.rev()
|
||||
.collect::<Vec<_>>();
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ impl ActiveBufferEncoding {
|
|||
self.is_shared = project.is_shared();
|
||||
self.is_via_remote_server = project.is_via_remote_server();
|
||||
|
||||
if let Some((_, buffer, _)) = editor.read(cx).active_excerpt(cx) {
|
||||
if let Some(buffer) = editor.read(cx).active_buffer(cx) {
|
||||
let buffer = buffer.read(cx);
|
||||
self.active_encoding = Some(buffer.encoding());
|
||||
self.has_bom = buffer.has_bom();
|
||||
|
|
|
|||
|
|
@ -47,11 +47,11 @@ impl EncodingSelector {
|
|||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) -> Option<()> {
|
||||
let (_, buffer, _) = workspace
|
||||
let buffer = workspace
|
||||
.active_item(cx)?
|
||||
.act_as::<Editor>(cx)?
|
||||
.read(cx)
|
||||
.active_excerpt(cx)?;
|
||||
.active_buffer(cx)?;
|
||||
|
||||
let buffer_handle = buffer.read(cx);
|
||||
let project = workspace.project().read(cx);
|
||||
|
|
|
|||
|
|
@ -212,7 +212,7 @@ impl CommitView {
|
|||
|
||||
editor.insert_blocks(
|
||||
[BlockProperties {
|
||||
placement: BlockPlacement::Above(editor::Anchor::min()),
|
||||
placement: BlockPlacement::Above(editor::Anchor::Min),
|
||||
height: Some(1),
|
||||
style: BlockStyle::Sticky,
|
||||
render: Arc::new(|_| gpui::Empty.into_any_element()),
|
||||
|
|
@ -223,7 +223,10 @@ impl CommitView {
|
|||
editor
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.buffer_anchor_to_anchor(&message_buffer, Anchor::MAX, cx)
|
||||
.snapshot(cx)
|
||||
.anchor_in_buffer(Anchor::max_for_buffer(
|
||||
message_buffer.read(cx).remote_id(),
|
||||
))
|
||||
.map(|anchor| BlockProperties {
|
||||
placement: BlockPlacement::Below(anchor),
|
||||
height: Some(1),
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use agent_settings::AgentSettings;
|
|||
use collections::{HashMap, HashSet};
|
||||
use editor::{
|
||||
ConflictsOurs, ConflictsOursMarker, ConflictsOuter, ConflictsTheirs, ConflictsTheirsMarker,
|
||||
Editor, EditorEvent, ExcerptId, MultiBuffer, RowHighlightOptions,
|
||||
Editor, EditorEvent, MultiBuffer, RowHighlightOptions,
|
||||
display_map::{BlockContext, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId},
|
||||
};
|
||||
use gpui::{
|
||||
|
|
@ -67,62 +67,22 @@ pub fn register_editor(editor: &mut Editor, buffer: Entity<MultiBuffer>, cx: &mu
|
|||
|
||||
let buffers = buffer.read(cx).all_buffers();
|
||||
for buffer in buffers {
|
||||
buffer_added(editor, buffer, cx);
|
||||
buffer_ranges_updated(editor, buffer, cx);
|
||||
}
|
||||
|
||||
cx.subscribe(&cx.entity(), |editor, _, event, cx| match event {
|
||||
EditorEvent::ExcerptsAdded { buffer, .. } => buffer_added(editor, buffer.clone(), cx),
|
||||
EditorEvent::ExcerptsExpanded { ids } => {
|
||||
let multibuffer = editor.buffer().read(cx).snapshot(cx);
|
||||
for excerpt_id in ids {
|
||||
let Some(buffer) = multibuffer.buffer_for_excerpt(*excerpt_id) else {
|
||||
continue;
|
||||
};
|
||||
let addon = editor.addon::<ConflictAddon>().unwrap();
|
||||
let Some(conflict_set) = addon.conflict_set(buffer.remote_id()).clone() else {
|
||||
return;
|
||||
};
|
||||
excerpt_for_buffer_updated(editor, conflict_set, cx);
|
||||
}
|
||||
EditorEvent::BufferRangesUpdated { buffer, .. } => {
|
||||
buffer_ranges_updated(editor, buffer.clone(), cx)
|
||||
}
|
||||
EditorEvent::BuffersRemoved { removed_buffer_ids } => {
|
||||
buffers_removed(editor, removed_buffer_ids, cx)
|
||||
}
|
||||
EditorEvent::ExcerptsRemoved {
|
||||
removed_buffer_ids, ..
|
||||
} => buffers_removed(editor, removed_buffer_ids, cx),
|
||||
_ => {}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn excerpt_for_buffer_updated(
|
||||
editor: &mut Editor,
|
||||
conflict_set: Entity<ConflictSet>,
|
||||
cx: &mut Context<Editor>,
|
||||
) {
|
||||
let conflicts_len = conflict_set.read(cx).snapshot().conflicts.len();
|
||||
let buffer_id = conflict_set.read(cx).snapshot().buffer_id;
|
||||
let Some(buffer_conflicts) = editor
|
||||
.addon_mut::<ConflictAddon>()
|
||||
.unwrap()
|
||||
.buffers
|
||||
.get(&buffer_id)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let addon_conflicts_len = buffer_conflicts.block_ids.len();
|
||||
conflicts_updated(
|
||||
editor,
|
||||
conflict_set,
|
||||
&ConflictSetUpdate {
|
||||
buffer_range: None,
|
||||
old_range: 0..addon_conflicts_len,
|
||||
new_range: 0..conflicts_len,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
#[ztracing::instrument(skip_all)]
|
||||
fn buffer_added(editor: &mut Editor, buffer: Entity<Buffer>, cx: &mut Context<Editor>) {
|
||||
fn buffer_ranges_updated(editor: &mut Editor, buffer: Entity<Buffer>, cx: &mut Context<Editor>) {
|
||||
let Some(project) = editor.project() else {
|
||||
return;
|
||||
};
|
||||
|
|
@ -188,14 +148,6 @@ fn conflicts_updated(
|
|||
let conflict_set = conflict_set.read(cx).snapshot();
|
||||
let multibuffer = editor.buffer().read(cx);
|
||||
let snapshot = multibuffer.snapshot(cx);
|
||||
let excerpts = multibuffer.excerpts_for_buffer(buffer_id, cx);
|
||||
let Some(buffer_snapshot) = excerpts
|
||||
.first()
|
||||
.and_then(|(excerpt_id, _, _)| snapshot.buffer_for_excerpt(*excerpt_id))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let old_range = maybe!({
|
||||
let conflict_addon = editor.addon_mut::<ConflictAddon>().unwrap();
|
||||
let buffer_conflicts = conflict_addon.buffers.get(&buffer_id)?;
|
||||
|
|
@ -230,23 +182,7 @@ fn conflicts_updated(
|
|||
let mut removed_highlighted_ranges = Vec::new();
|
||||
let mut removed_block_ids = HashSet::default();
|
||||
for (conflict_range, block_id) in old_conflicts {
|
||||
let Some((excerpt_id, _, _)) = excerpts.iter().find(|(_, _, range)| {
|
||||
let precedes_start = range
|
||||
.context
|
||||
.start
|
||||
.cmp(&conflict_range.start, buffer_snapshot)
|
||||
.is_le();
|
||||
let follows_end = range
|
||||
.context
|
||||
.end
|
||||
.cmp(&conflict_range.start, buffer_snapshot)
|
||||
.is_ge();
|
||||
precedes_start && follows_end
|
||||
}) else {
|
||||
continue;
|
||||
};
|
||||
let excerpt_id = *excerpt_id;
|
||||
let Some(range) = snapshot.anchor_range_in_excerpt(excerpt_id, conflict_range) else {
|
||||
let Some(range) = snapshot.buffer_anchor_range_to_anchor_range(conflict_range) else {
|
||||
continue;
|
||||
};
|
||||
removed_highlighted_ranges.push(range.clone());
|
||||
|
|
@ -272,26 +208,9 @@ fn conflicts_updated(
|
|||
let new_conflicts = &conflict_set.conflicts[event.new_range.clone()];
|
||||
let mut blocks = Vec::new();
|
||||
for conflict in new_conflicts {
|
||||
let Some((excerpt_id, _, _)) = excerpts.iter().find(|(_, _, range)| {
|
||||
let precedes_start = range
|
||||
.context
|
||||
.start
|
||||
.cmp(&conflict.range.start, buffer_snapshot)
|
||||
.is_le();
|
||||
let follows_end = range
|
||||
.context
|
||||
.end
|
||||
.cmp(&conflict.range.start, buffer_snapshot)
|
||||
.is_ge();
|
||||
precedes_start && follows_end
|
||||
}) else {
|
||||
continue;
|
||||
};
|
||||
let excerpt_id = *excerpt_id;
|
||||
update_conflict_highlighting(editor, conflict, &snapshot, cx);
|
||||
|
||||
update_conflict_highlighting(editor, conflict, &snapshot, excerpt_id, cx);
|
||||
|
||||
let Some(anchor) = snapshot.anchor_in_excerpt(excerpt_id, conflict.range.start) else {
|
||||
let Some(anchor) = snapshot.anchor_in_excerpt(conflict.range.start) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
|
|
@ -302,7 +221,7 @@ fn conflicts_updated(
|
|||
style: BlockStyle::Sticky,
|
||||
render: Arc::new({
|
||||
let conflict = conflict.clone();
|
||||
move |cx| render_conflict_buttons(&conflict, excerpt_id, editor_handle.clone(), cx)
|
||||
move |cx| render_conflict_buttons(&conflict, editor_handle.clone(), cx)
|
||||
}),
|
||||
priority: 0,
|
||||
})
|
||||
|
|
@ -328,14 +247,13 @@ fn update_conflict_highlighting(
|
|||
editor: &mut Editor,
|
||||
conflict: &ConflictRegion,
|
||||
buffer: &editor::MultiBufferSnapshot,
|
||||
excerpt_id: editor::ExcerptId,
|
||||
cx: &mut Context<Editor>,
|
||||
) -> Option<()> {
|
||||
log::debug!("update conflict highlighting for {conflict:?}");
|
||||
|
||||
let outer = buffer.anchor_range_in_excerpt(excerpt_id, conflict.range.clone())?;
|
||||
let ours = buffer.anchor_range_in_excerpt(excerpt_id, conflict.ours.clone())?;
|
||||
let theirs = buffer.anchor_range_in_excerpt(excerpt_id, conflict.theirs.clone())?;
|
||||
let outer = buffer.buffer_anchor_range_to_anchor_range(conflict.range.clone())?;
|
||||
let ours = buffer.buffer_anchor_range_to_anchor_range(conflict.ours.clone())?;
|
||||
let theirs = buffer.buffer_anchor_range_to_anchor_range(conflict.theirs.clone())?;
|
||||
|
||||
let ours_background = cx.theme().colors().version_control_conflict_marker_ours;
|
||||
let theirs_background = cx.theme().colors().version_control_conflict_marker_theirs;
|
||||
|
|
@ -373,7 +291,6 @@ fn update_conflict_highlighting(
|
|||
|
||||
fn render_conflict_buttons(
|
||||
conflict: &ConflictRegion,
|
||||
excerpt_id: ExcerptId,
|
||||
editor: WeakEntity<Editor>,
|
||||
cx: &mut BlockContext,
|
||||
) -> AnyElement {
|
||||
|
|
@ -395,7 +312,6 @@ fn render_conflict_buttons(
|
|||
move |_, window, cx| {
|
||||
resolve_conflict(
|
||||
editor.clone(),
|
||||
excerpt_id,
|
||||
conflict.clone(),
|
||||
vec![ours.clone()],
|
||||
window,
|
||||
|
|
@ -415,7 +331,6 @@ fn render_conflict_buttons(
|
|||
move |_, window, cx| {
|
||||
resolve_conflict(
|
||||
editor.clone(),
|
||||
excerpt_id,
|
||||
conflict.clone(),
|
||||
vec![theirs.clone()],
|
||||
window,
|
||||
|
|
@ -436,7 +351,6 @@ fn render_conflict_buttons(
|
|||
move |_, window, cx| {
|
||||
resolve_conflict(
|
||||
editor.clone(),
|
||||
excerpt_id,
|
||||
conflict.clone(),
|
||||
vec![ours.clone(), theirs.clone()],
|
||||
window,
|
||||
|
|
@ -461,7 +375,7 @@ fn render_conflict_buttons(
|
|||
let content = editor
|
||||
.update(cx, |editor, cx| {
|
||||
let multibuffer = editor.buffer().read(cx);
|
||||
let buffer_id = conflict.ours.end.buffer_id?;
|
||||
let buffer_id = conflict.ours.end.buffer_id;
|
||||
let buffer = multibuffer.buffer(buffer_id)?;
|
||||
let buffer_read = buffer.read(cx);
|
||||
let snapshot = buffer_read.snapshot();
|
||||
|
|
@ -589,7 +503,6 @@ pub(crate) fn register_conflict_notification(
|
|||
|
||||
pub(crate) fn resolve_conflict(
|
||||
editor: WeakEntity<Editor>,
|
||||
excerpt_id: ExcerptId,
|
||||
resolved_conflict: ConflictRegion,
|
||||
ranges: Vec<Range<Anchor>>,
|
||||
window: &mut Window,
|
||||
|
|
@ -601,7 +514,7 @@ pub(crate) fn resolve_conflict(
|
|||
let workspace = editor.workspace()?;
|
||||
let project = editor.project()?.clone();
|
||||
let multibuffer = editor.buffer().clone();
|
||||
let buffer_id = resolved_conflict.ours.end.buffer_id?;
|
||||
let buffer_id = resolved_conflict.ours.end.buffer_id;
|
||||
let buffer = multibuffer.read(cx).buffer(buffer_id)?;
|
||||
resolved_conflict.resolve(buffer.clone(), &ranges, cx);
|
||||
let conflict_addon = editor.addon_mut::<ConflictAddon>().unwrap();
|
||||
|
|
@ -620,7 +533,7 @@ pub(crate) fn resolve_conflict(
|
|||
.ok()?;
|
||||
let &(_, block_id) = &state.block_ids[ix];
|
||||
let range =
|
||||
snapshot.anchor_range_in_excerpt(excerpt_id, resolved_conflict.range)?;
|
||||
snapshot.buffer_anchor_range_to_anchor_range(resolved_conflict.range)?;
|
||||
|
||||
editor.remove_gutter_highlights::<ConflictsOuter>(vec![range.clone()], cx);
|
||||
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ use language_model::{
|
|||
LanguageModelRequestMessage, Role,
|
||||
};
|
||||
use menu;
|
||||
use multi_buffer::ExcerptInfo;
|
||||
use multi_buffer::ExcerptBoundaryInfo;
|
||||
use notifications::status_toast::{StatusToast, ToastIcon};
|
||||
use panel::{PanelHeader, panel_button, panel_filled_button, panel_icon_button};
|
||||
use project::{
|
||||
|
|
@ -5754,11 +5754,12 @@ impl editor::Addon for GitPanelAddon {
|
|||
|
||||
fn render_buffer_header_controls(
|
||||
&self,
|
||||
excerpt_info: &ExcerptInfo,
|
||||
_excerpt_info: &ExcerptBoundaryInfo,
|
||||
buffer: &language::BufferSnapshot,
|
||||
window: &Window,
|
||||
cx: &App,
|
||||
) -> Option<AnyElement> {
|
||||
let file = excerpt_info.buffer.file()?;
|
||||
let file = buffer.file()?;
|
||||
let git_panel = self.workspace.upgrade()?.read(cx).panel::<GitPanel>(cx)?;
|
||||
|
||||
git_panel
|
||||
|
|
|
|||
|
|
@ -501,9 +501,11 @@ impl ProjectDiff {
|
|||
|
||||
pub fn active_path(&self, cx: &App) -> Option<ProjectPath> {
|
||||
let editor = self.editor.read(cx).focused_editor().read(cx);
|
||||
let multibuffer = editor.buffer().read(cx);
|
||||
let position = editor.selections.newest_anchor().head();
|
||||
let multi_buffer = editor.buffer().read(cx);
|
||||
let (_, buffer, _) = multi_buffer.excerpt_containing(position, cx)?;
|
||||
let snapshot = multibuffer.snapshot(cx);
|
||||
let (text_anchor, _) = snapshot.anchor_to_buffer_anchor(position)?;
|
||||
let buffer = multibuffer.buffer(text_anchor.buffer_id)?;
|
||||
|
||||
let file = buffer.read(cx).file()?;
|
||||
Some(ProjectPath {
|
||||
|
|
@ -516,9 +518,7 @@ impl ProjectDiff {
|
|||
self.editor.update(cx, |editor, cx| {
|
||||
editor.rhs_editor().update(cx, |editor, cx| {
|
||||
editor.change_selections(Default::default(), window, cx, |s| {
|
||||
s.select_ranges(vec![
|
||||
multi_buffer::Anchor::min()..multi_buffer::Anchor::min(),
|
||||
]);
|
||||
s.select_ranges(vec![multi_buffer::Anchor::Min..multi_buffer::Anchor::Min]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -569,17 +569,17 @@ impl ProjectDiff {
|
|||
.collect::<Vec<_>>();
|
||||
if !ranges.iter().any(|range| range.start != range.end) {
|
||||
selection = false;
|
||||
if let Some((excerpt_id, _, range)) = self
|
||||
.editor
|
||||
.read(cx)
|
||||
.rhs_editor()
|
||||
.read(cx)
|
||||
.active_excerpt(cx)
|
||||
let anchor = editor.selections.newest_anchor().head();
|
||||
if let Some((_, excerpt_range)) = snapshot.excerpt_containing(anchor..anchor)
|
||||
&& let Some(range) = snapshot
|
||||
.anchor_in_buffer(excerpt_range.context.start)
|
||||
.zip(snapshot.anchor_in_buffer(excerpt_range.context.end))
|
||||
.map(|(start, end)| start..end)
|
||||
{
|
||||
ranges = vec![multi_buffer::Anchor::range_in_buffer(excerpt_id, range)];
|
||||
ranges = vec![range];
|
||||
} else {
|
||||
ranges = Vec::default();
|
||||
}
|
||||
};
|
||||
}
|
||||
let mut has_staged_hunks = false;
|
||||
let mut has_unstaged_hunks = false;
|
||||
|
|
@ -715,7 +715,7 @@ impl ProjectDiff {
|
|||
|
||||
let (was_empty, is_excerpt_newly_added) = self.editor.update(cx, |editor, cx| {
|
||||
let was_empty = editor.rhs_editor().read(cx).buffer().read(cx).is_empty();
|
||||
let (_, is_newly_added) = editor.set_excerpts_for_path(
|
||||
let is_newly_added = editor.update_excerpts_for_path(
|
||||
path_key.clone(),
|
||||
buffer,
|
||||
excerpt_ranges,
|
||||
|
|
@ -735,7 +735,7 @@ impl ProjectDiff {
|
|||
cx,
|
||||
|selections| {
|
||||
selections.select_ranges([
|
||||
multi_buffer::Anchor::min()..multi_buffer::Anchor::min()
|
||||
multi_buffer::Anchor::Min..multi_buffer::Anchor::Min
|
||||
])
|
||||
},
|
||||
);
|
||||
|
|
@ -785,8 +785,9 @@ impl ProjectDiff {
|
|||
let mut previous_paths = this
|
||||
.multibuffer
|
||||
.read(cx)
|
||||
.paths()
|
||||
.cloned()
|
||||
.snapshot(cx)
|
||||
.buffers_with_paths()
|
||||
.map(|(_, path_key)| path_key.clone())
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
if let Some(repo) = repo {
|
||||
|
|
@ -877,10 +878,23 @@ impl ProjectDiff {
|
|||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn excerpt_paths(&self, cx: &App) -> Vec<std::sync::Arc<util::rel_path::RelPath>> {
|
||||
self.multibuffer
|
||||
let snapshot = self
|
||||
.editor()
|
||||
.read(cx)
|
||||
.paths()
|
||||
.map(|key| key.path.clone())
|
||||
.rhs_editor()
|
||||
.read(cx)
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.snapshot(cx);
|
||||
snapshot
|
||||
.excerpts()
|
||||
.map(|excerpt| {
|
||||
snapshot
|
||||
.path_for_buffer(excerpt.context.start.buffer_id)
|
||||
.unwrap()
|
||||
.path
|
||||
.clone()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
|
@ -1937,7 +1951,7 @@ mod tests {
|
|||
let snapshot = buffer_editor.snapshot(window, cx);
|
||||
let snapshot = &snapshot.buffer_snapshot();
|
||||
let prev_buffer_hunks = buffer_editor
|
||||
.diff_hunks_in_ranges(&[editor::Anchor::min()..editor::Anchor::max()], snapshot)
|
||||
.diff_hunks_in_ranges(&[editor::Anchor::Min..editor::Anchor::Max], snapshot)
|
||||
.collect::<Vec<_>>();
|
||||
buffer_editor.git_restore(&Default::default(), window, cx);
|
||||
prev_buffer_hunks
|
||||
|
|
@ -1950,7 +1964,7 @@ mod tests {
|
|||
let snapshot = buffer_editor.snapshot(window, cx);
|
||||
let snapshot = &snapshot.buffer_snapshot();
|
||||
buffer_editor
|
||||
.diff_hunks_in_ranges(&[editor::Anchor::min()..editor::Anchor::max()], snapshot)
|
||||
.diff_hunks_in_ranges(&[editor::Anchor::Min..editor::Anchor::Max], snapshot)
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
assert_eq!(new_buffer_hunks.as_slice(), &[]);
|
||||
|
|
@ -2209,9 +2223,14 @@ mod tests {
|
|||
|
||||
cx.update(|window, cx| {
|
||||
let editor = diff.read(cx).editor.read(cx).rhs_editor().clone();
|
||||
let excerpt_ids = editor.read(cx).buffer().read(cx).excerpt_ids();
|
||||
assert_eq!(excerpt_ids.len(), 1);
|
||||
let excerpt_id = excerpt_ids[0];
|
||||
let excerpts = editor
|
||||
.read(cx)
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.snapshot(cx)
|
||||
.excerpts()
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(excerpts.len(), 1);
|
||||
let buffer = editor
|
||||
.read(cx)
|
||||
.buffer()
|
||||
|
|
@ -2239,7 +2258,6 @@ mod tests {
|
|||
|
||||
resolve_conflict(
|
||||
editor.downgrade(),
|
||||
excerpt_id,
|
||||
snapshot.conflicts[0].clone(),
|
||||
vec![ours_range],
|
||||
window,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use gpui::{
|
|||
AnyElement, App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, FocusHandle,
|
||||
Focusable, IntoElement, Render, Task, Window,
|
||||
};
|
||||
use language::{self, Buffer, Point};
|
||||
use language::{self, Buffer, OffsetRangeExt, Point};
|
||||
use project::Project;
|
||||
use settings::Settings;
|
||||
use std::{
|
||||
|
|
@ -52,36 +52,26 @@ impl TextDiffView {
|
|||
|
||||
let selection_data = source_editor.update(cx, |editor, cx| {
|
||||
let multibuffer = editor.buffer();
|
||||
let selections = editor.selections.all::<Point>(&editor.display_snapshot(cx));
|
||||
let first_selection = selections.first()?;
|
||||
let multibuffer_snapshot = multibuffer.read(cx).snapshot(cx);
|
||||
let first_selection = editor.selections.newest_anchor();
|
||||
|
||||
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 (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())?;
|
||||
|
||||
let buffer_snapshot = source_buffer.read(cx);
|
||||
let max_point = buffer_snapshot.max_point();
|
||||
|
||||
if first_selection.is_empty() {
|
||||
if buffer_range.is_empty() {
|
||||
let full_range = Point::new(0, 0)..max_point;
|
||||
return Some((source_buffer, full_range));
|
||||
}
|
||||
|
||||
let expanded_start = Point::new(buffer_start.row, 0);
|
||||
let expanded_end = if buffer_end.column > 0 {
|
||||
let next_row = buffer_end.row + 1;
|
||||
let expanded_start = Point::new(buffer_range.start.row, 0);
|
||||
let expanded_end = if buffer_range.end.column > 0 {
|
||||
let next_row = buffer_range.end.row + 1;
|
||||
cmp::min(max_point, Point::new(next_row, 0))
|
||||
} else {
|
||||
buffer_end
|
||||
buffer_range.end
|
||||
};
|
||||
Some((source_buffer, expanded_start..expanded_end))
|
||||
});
|
||||
|
|
|
|||
|
|
@ -42,23 +42,22 @@ impl UserCaretPosition {
|
|||
snapshot: &MultiBufferSnapshot,
|
||||
) -> Self {
|
||||
let selection_end = selection.head();
|
||||
let (line, character) = if let Some((buffer_snapshot, point, _)) =
|
||||
snapshot.point_to_buffer_point(selection_end)
|
||||
{
|
||||
let line_start = Point::new(point.row, 0);
|
||||
let (line, character) =
|
||||
if let Some((buffer_snapshot, point)) = snapshot.point_to_buffer_point(selection_end) {
|
||||
let line_start = Point::new(point.row, 0);
|
||||
|
||||
let chars_to_last_position = buffer_snapshot
|
||||
.text_summary_for_range::<text::TextSummary, _>(line_start..point)
|
||||
.chars as u32;
|
||||
(line_start.row, chars_to_last_position)
|
||||
} else {
|
||||
let line_start = Point::new(selection_end.row, 0);
|
||||
let chars_to_last_position = buffer_snapshot
|
||||
.text_summary_for_range::<text::TextSummary, _>(line_start..point)
|
||||
.chars as u32;
|
||||
(line_start.row, chars_to_last_position)
|
||||
} else {
|
||||
let line_start = Point::new(selection_end.row, 0);
|
||||
|
||||
let chars_to_last_position = snapshot
|
||||
.text_summary_for_range::<MBTextSummary, _>(line_start..selection_end)
|
||||
.chars as u32;
|
||||
(selection_end.row, chars_to_last_position)
|
||||
};
|
||||
let chars_to_last_position = snapshot
|
||||
.text_summary_for_range::<MBTextSummary, _>(line_start..selection_end)
|
||||
.chars as u32;
|
||||
(selection_end.row, chars_to_last_position)
|
||||
};
|
||||
|
||||
Self {
|
||||
line: NonZeroU32::new(line + 1).expect("added 1"),
|
||||
|
|
@ -232,7 +231,7 @@ impl Render for CursorPosition {
|
|||
if let Some(editor) = workspace
|
||||
.active_item(cx)
|
||||
.and_then(|item| item.act_as::<Editor>(cx))
|
||||
&& let Some((_, buffer, _)) = editor.read(cx).active_excerpt(cx)
|
||||
&& let Some(buffer) = editor.read(cx).active_buffer(cx)
|
||||
{
|
||||
workspace.toggle_modal(window, cx, |window, cx| {
|
||||
crate::GoToLine::new(editor, buffer, window, cx)
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ impl GoToLine {
|
|||
return;
|
||||
};
|
||||
let editor = editor_handle.read(cx);
|
||||
let Some((_, buffer, _)) = editor.active_excerpt(cx) else {
|
||||
let Some(buffer) = editor.active_buffer(cx) else {
|
||||
return;
|
||||
};
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
|
|
@ -93,11 +93,9 @@ impl GoToLine {
|
|||
let last_line = editor
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.excerpts_for_buffer(snapshot.remote_id(), cx)
|
||||
.into_iter()
|
||||
.map(move |(_, _, range)| {
|
||||
text::ToPoint::to_point(&range.context.end, &snapshot).row
|
||||
})
|
||||
.snapshot(cx)
|
||||
.excerpts_for_buffer(snapshot.remote_id())
|
||||
.map(move |range| text::ToPoint::to_point(&range.context.end, &snapshot).row)
|
||||
.max()
|
||||
.unwrap_or(0);
|
||||
|
||||
|
|
@ -230,7 +228,7 @@ impl GoToLine {
|
|||
let character = query_char.unwrap_or(0).saturating_sub(1);
|
||||
|
||||
let target_multi_buffer_row = MultiBufferRow(row);
|
||||
let (buffer_snapshot, target_in_buffer, _) = snapshot.point_to_buffer_point(Point::new(
|
||||
let (buffer_snapshot, target_in_buffer) = snapshot.point_to_buffer_point(Point::new(
|
||||
target_multi_buffer_row.min(snapshot.max_row()).0,
|
||||
0,
|
||||
))?;
|
||||
|
|
|
|||
|
|
@ -5496,6 +5496,8 @@ pub enum ElementId {
|
|||
CodeLocation(core::panic::Location<'static>),
|
||||
/// A labeled child of an element.
|
||||
NamedChild(Arc<ElementId>, SharedString),
|
||||
/// A byte array ID (used for text-anchors)
|
||||
OpaqueId([u8; 20]),
|
||||
}
|
||||
|
||||
impl ElementId {
|
||||
|
|
@ -5517,6 +5519,7 @@ impl Display for ElementId {
|
|||
ElementId::Path(path) => write!(f, "{}", path.display())?,
|
||||
ElementId::CodeLocation(location) => write!(f, "{}", location)?,
|
||||
ElementId::NamedChild(id, name) => write!(f, "{}-{}", id, name)?,
|
||||
ElementId::OpaqueId(opaque_id) => write!(f, "{:x?}", opaque_id)?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
@ -5631,6 +5634,12 @@ impl From<&'static core::panic::Location<'static>> for ElementId {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<[u8; 20]> for ElementId {
|
||||
fn from(opaque_id: [u8; 20]) -> Self {
|
||||
ElementId::OpaqueId(opaque_id)
|
||||
}
|
||||
}
|
||||
|
||||
/// A rectangle to be rendered in the window at the given position and size.
|
||||
/// Passed as an argument [`Window::paint_quad`].
|
||||
#[derive(Clone)]
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
use anyhow::{Result, anyhow};
|
||||
use editor::{
|
||||
Bias, CompletionProvider, Editor, EditorEvent, EditorMode, ExcerptId, MinimapVisibility,
|
||||
MultiBuffer,
|
||||
Bias, CompletionProvider, Editor, EditorEvent, EditorMode, MinimapVisibility, MultiBuffer,
|
||||
};
|
||||
use fuzzy::StringMatch;
|
||||
use gpui::{
|
||||
|
|
@ -641,7 +640,6 @@ struct RustStyleCompletionProvider {
|
|||
impl CompletionProvider for RustStyleCompletionProvider {
|
||||
fn completions(
|
||||
&self,
|
||||
_excerpt_id: ExcerptId,
|
||||
buffer: &Entity<Buffer>,
|
||||
position: Anchor,
|
||||
_: editor::CompletionContext,
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ impl ActionCompletionProvider {
|
|||
impl CompletionProvider for ActionCompletionProvider {
|
||||
fn completions(
|
||||
&self,
|
||||
_excerpt_id: editor::ExcerptId,
|
||||
buffer: &Entity<language::Buffer>,
|
||||
buffer_position: language::Anchor,
|
||||
_trigger: editor::CompletionContext,
|
||||
|
|
|
|||
|
|
@ -3480,7 +3480,6 @@ struct KeyContextCompletionProvider {
|
|||
impl CompletionProvider for KeyContextCompletionProvider {
|
||||
fn completions(
|
||||
&self,
|
||||
_excerpt_id: editor::ExcerptId,
|
||||
buffer: &Entity<language::Buffer>,
|
||||
buffer_position: language::Anchor,
|
||||
_trigger: editor::CompletionContext,
|
||||
|
|
|
|||
|
|
@ -326,23 +326,17 @@ impl DiagnosticEntry<Anchor> {
|
|||
}
|
||||
}
|
||||
|
||||
impl Default for Summary {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
start: Anchor::MIN,
|
||||
end: Anchor::MAX,
|
||||
min_start: Anchor::MAX,
|
||||
max_end: Anchor::MIN,
|
||||
count: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl sum_tree::Summary for Summary {
|
||||
type Context<'a> = &'a text::BufferSnapshot;
|
||||
|
||||
fn zero(_cx: Self::Context<'_>) -> Self {
|
||||
Default::default()
|
||||
fn zero(buffer: &text::BufferSnapshot) -> Self {
|
||||
Self {
|
||||
start: Anchor::min_for_buffer(buffer.remote_id()),
|
||||
end: Anchor::max_for_buffer(buffer.remote_id()),
|
||||
min_start: Anchor::max_for_buffer(buffer.remote_id()),
|
||||
max_end: Anchor::min_for_buffer(buffer.remote_id()),
|
||||
count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, other: &Self, buffer: Self::Context<'_>) {
|
||||
|
|
|
|||
|
|
@ -174,11 +174,11 @@ pub fn serialize_selection(selection: &Selection<Anchor>) -> proto::Selection {
|
|||
id: selection.id as u64,
|
||||
start: Some(proto::EditorAnchor {
|
||||
anchor: Some(serialize_anchor(&selection.start)),
|
||||
excerpt_id: 0,
|
||||
excerpt_id: None,
|
||||
}),
|
||||
end: Some(proto::EditorAnchor {
|
||||
anchor: Some(serialize_anchor(&selection.end)),
|
||||
excerpt_id: 0,
|
||||
excerpt_id: None,
|
||||
}),
|
||||
reversed: selection.reversed,
|
||||
}
|
||||
|
|
@ -260,7 +260,7 @@ pub fn serialize_anchor(anchor: &Anchor) -> proto::Anchor {
|
|||
Bias::Left => proto::Bias::Left as i32,
|
||||
Bias::Right => proto::Bias::Right as i32,
|
||||
},
|
||||
buffer_id: anchor.buffer_id.map(Into::into),
|
||||
buffer_id: Some(anchor.buffer_id.into()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -498,7 +498,7 @@ pub fn deserialize_anchor(anchor: proto::Anchor) -> Option<Anchor> {
|
|||
timestamp,
|
||||
anchor.offset as u32,
|
||||
bias,
|
||||
buffer_id,
|
||||
buffer_id?,
|
||||
))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ use std::{
|
|||
};
|
||||
use streaming_iterator::StreamingIterator;
|
||||
use sum_tree::{Bias, Dimensions, SeekTarget, SumTree};
|
||||
use text::{Anchor, BufferSnapshot, OffsetRangeExt, Point, Rope, ToOffset, ToPoint};
|
||||
use text::{Anchor, BufferId, BufferSnapshot, OffsetRangeExt, Point, Rope, ToOffset, ToPoint};
|
||||
use tree_sitter::{
|
||||
Node, Query, QueryCapture, QueryCaptures, QueryCursor, QueryMatch, QueryMatches,
|
||||
QueryPredicateArg,
|
||||
|
|
@ -56,7 +56,15 @@ impl Drop for SyntaxSnapshot {
|
|||
// This does allocate a new Arc, but it's cheap and avoids blocking the main thread without needing to use an `Option` or `MaybeUninit`.
|
||||
let _ = DROP_TX.send(std::mem::replace(
|
||||
&mut self.layers,
|
||||
SumTree::from_summary(Default::default()),
|
||||
SumTree::from_summary(SyntaxLayerSummary {
|
||||
min_depth: Default::default(),
|
||||
max_depth: Default::default(),
|
||||
// Deliberately bogus anchors, doesn't matter in this context
|
||||
range: Anchor::min_min_range_for_buffer(BufferId::new(1).unwrap()),
|
||||
last_layer_range: Anchor::min_min_range_for_buffer(BufferId::new(1).unwrap()),
|
||||
last_layer_language: Default::default(),
|
||||
contains_unknown_injections: Default::default(),
|
||||
}),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
@ -588,7 +596,7 @@ impl SyntaxSnapshot {
|
|||
|
||||
let bounded_position = SyntaxLayerPositionBeforeChange {
|
||||
position: position.clone(),
|
||||
change: changed_regions.start_position(),
|
||||
change: changed_regions.start_position(text.remote_id()),
|
||||
};
|
||||
if bounded_position.cmp(cursor.start(), text).is_gt() {
|
||||
let slice = cursor.slice(&bounded_position, Bias::Left);
|
||||
|
|
@ -1946,11 +1954,11 @@ impl ChangedRegion {
|
|||
}
|
||||
|
||||
impl ChangeRegionSet {
|
||||
fn start_position(&self) -> ChangeStartPosition {
|
||||
fn start_position(&self, buffer_id: BufferId) -> ChangeStartPosition {
|
||||
self.0.first().map_or(
|
||||
ChangeStartPosition {
|
||||
depth: usize::MAX,
|
||||
position: Anchor::MAX,
|
||||
position: Anchor::max_for_buffer(buffer_id),
|
||||
},
|
||||
|region| ChangeStartPosition {
|
||||
depth: region.depth,
|
||||
|
|
@ -1999,24 +2007,20 @@ impl ChangeRegionSet {
|
|||
}
|
||||
}
|
||||
|
||||
impl Default for SyntaxLayerSummary {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
max_depth: 0,
|
||||
min_depth: 0,
|
||||
range: Anchor::MAX..Anchor::MIN,
|
||||
last_layer_range: Anchor::MIN..Anchor::MAX,
|
||||
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 zero(buffer: &BufferSnapshot) -> Self {
|
||||
Self {
|
||||
max_depth: 0,
|
||||
min_depth: 0,
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
fn add_summary(&mut self, other: &Self, buffer: Self::Context<'_>) {
|
||||
|
|
@ -2024,7 +2028,7 @@ impl sum_tree::Summary for SyntaxLayerSummary {
|
|||
self.max_depth = other.max_depth;
|
||||
self.range = other.range.clone();
|
||||
} else {
|
||||
if self.range == (Anchor::MAX..Anchor::MAX) {
|
||||
if self.range.start.is_max() && self.range.end.is_max() {
|
||||
self.range.start = other.range.start;
|
||||
}
|
||||
if other.range.end.cmp(&self.range.end, buffer).is_gt() {
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ impl ActiveBufferLanguage {
|
|||
self.active_language = Some(None);
|
||||
|
||||
let editor = editor.read(cx);
|
||||
if let Some((_, buffer, _)) = editor.active_excerpt(cx)
|
||||
if let Some(buffer) = editor.active_buffer(cx)
|
||||
&& let Some(language) = buffer.read(cx).language()
|
||||
{
|
||||
self.active_language = Some(Some(language.name()));
|
||||
|
|
|
|||
|
|
@ -51,11 +51,11 @@ impl LanguageSelector {
|
|||
cx: &mut Context<Workspace>,
|
||||
) -> Option<()> {
|
||||
let registry = workspace.app_state().languages.clone();
|
||||
let (_, buffer, _) = workspace
|
||||
let buffer = workspace
|
||||
.active_item(cx)?
|
||||
.act_as::<Editor>(cx)?
|
||||
.read(cx)
|
||||
.active_excerpt(cx)?;
|
||||
.active_buffer(cx)?;
|
||||
let project = workspace.project().clone();
|
||||
|
||||
workspace.toggle_modal(window, cx, move |window, cx| {
|
||||
|
|
@ -414,10 +414,10 @@ mod tests {
|
|||
) -> Entity<Editor> {
|
||||
let editor = open_new_buffer_editor(workspace, project, cx).await;
|
||||
// Ensure the buffer has no language after the editor is created
|
||||
let (_, buffer, _) = editor.read_with(cx, |editor, cx| {
|
||||
let buffer = editor.read_with(cx, |editor, cx| {
|
||||
editor
|
||||
.active_excerpt(cx)
|
||||
.expect("editor should have an active excerpt")
|
||||
.active_buffer(cx)
|
||||
.expect("editor should have an active buffer")
|
||||
});
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.set_language(None, cx);
|
||||
|
|
@ -454,8 +454,8 @@ mod tests {
|
|||
.await
|
||||
.expect("language should exist in registry");
|
||||
editor.update(cx, move |editor, cx| {
|
||||
let (_, buffer, _) = editor
|
||||
.active_excerpt(cx)
|
||||
let buffer = editor
|
||||
.active_buffer(cx)
|
||||
.expect("editor should have an active excerpt");
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.set_language(Some(language), cx);
|
||||
|
|
@ -578,6 +578,15 @@ mod tests {
|
|||
|
||||
assert_selected_language_for_editor(&workspace, &rust_editor, Some("Rust"), cx);
|
||||
assert_selected_language_for_editor(&workspace, &typescript_editor, Some("TypeScript"), cx);
|
||||
// Ensure the empty editor's buffer has no language before asserting
|
||||
let buffer = empty_editor.read_with(cx, |editor, cx| {
|
||||
editor
|
||||
.active_buffer(cx)
|
||||
.expect("editor should have an active excerpt")
|
||||
});
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.set_language(None, cx);
|
||||
});
|
||||
assert_selected_language_for_editor(&workspace, &empty_editor, None, cx);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use editor::{
|
||||
Anchor, Editor, ExcerptId, HighlightKey, MultiBufferSnapshot, SelectionEffects, ToPoint,
|
||||
Anchor, Editor, HighlightKey, MultiBufferSnapshot, SelectionEffects, ToPoint,
|
||||
scroll::Autoscroll,
|
||||
};
|
||||
use gpui::{
|
||||
|
|
@ -8,8 +8,7 @@ use gpui::{
|
|||
MouseDownEvent, MouseMoveEvent, ParentElement, Render, ScrollStrategy, SharedString, Styled,
|
||||
Task, UniformListScrollHandle, WeakEntity, Window, actions, div, rems, uniform_list,
|
||||
};
|
||||
use language::ToOffset;
|
||||
|
||||
use language::{BufferId, Point, ToOffset};
|
||||
use menu::{SelectNext, SelectPrevious};
|
||||
use std::{mem, ops::Range};
|
||||
use theme::ActiveTheme;
|
||||
|
|
@ -114,12 +113,12 @@ impl HighlightCategory {
|
|||
|
||||
#[derive(Debug, Clone)]
|
||||
struct HighlightEntry {
|
||||
excerpt_id: ExcerptId,
|
||||
range: Range<Anchor>,
|
||||
buffer_id: BufferId,
|
||||
buffer_point_range: Range<Point>,
|
||||
range_display: SharedString,
|
||||
style: HighlightStyle,
|
||||
category: HighlightCategory,
|
||||
sort_key: (ExcerptId, u32, u32, u32, u32),
|
||||
}
|
||||
|
||||
/// An item in the display list: either a separator between excerpts or a highlight entry.
|
||||
|
|
@ -319,20 +318,18 @@ impl HighlightsTreeView {
|
|||
display_map.update(cx, |display_map, cx| {
|
||||
for (key, text_highlights) in display_map.all_text_highlights() {
|
||||
for range in &text_highlights.1 {
|
||||
let excerpt_id = range.start.excerpt_id;
|
||||
let (range_display, sort_key) = format_anchor_range(
|
||||
range,
|
||||
excerpt_id,
|
||||
&multi_buffer_snapshot,
|
||||
is_singleton,
|
||||
);
|
||||
let Some((range_display, buffer_id, buffer_point_range)) =
|
||||
format_anchor_range(range, &multi_buffer_snapshot)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
entries.push(HighlightEntry {
|
||||
excerpt_id,
|
||||
range: range.clone(),
|
||||
buffer_id,
|
||||
range_display,
|
||||
style: text_highlights.0,
|
||||
category: HighlightCategory::Text(*key),
|
||||
sort_key,
|
||||
buffer_point_range,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -345,13 +342,11 @@ impl HighlightsTreeView {
|
|||
.and_then(|buf| buf.read(cx).language().map(|l| l.name()));
|
||||
for token in tokens.iter() {
|
||||
let range = token.range.start..token.range.end;
|
||||
let excerpt_id = range.start.excerpt_id;
|
||||
let (range_display, sort_key) = format_anchor_range(
|
||||
&range,
|
||||
excerpt_id,
|
||||
&multi_buffer_snapshot,
|
||||
is_singleton,
|
||||
);
|
||||
let Some((range_display, entry_buffer_id, buffer_point_range)) =
|
||||
format_anchor_range(&range, &multi_buffer_snapshot)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let Some(stylizer) = lsp_store.get_or_create_token_stylizer(
|
||||
token.server_id,
|
||||
language_name.as_ref(),
|
||||
|
|
@ -388,8 +383,8 @@ impl HighlightsTreeView {
|
|||
});
|
||||
|
||||
entries.push(HighlightEntry {
|
||||
excerpt_id,
|
||||
range,
|
||||
buffer_id: entry_buffer_id,
|
||||
range_display,
|
||||
style: interner[token.style],
|
||||
category: HighlightCategory::SemanticToken {
|
||||
|
|
@ -399,7 +394,7 @@ impl HighlightsTreeView {
|
|||
.map(SharedString::from),
|
||||
theme_key,
|
||||
},
|
||||
sort_key,
|
||||
buffer_point_range,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -407,7 +402,13 @@ impl HighlightsTreeView {
|
|||
});
|
||||
|
||||
let syntax_theme = cx.theme().syntax().clone();
|
||||
for (excerpt_id, buffer_snapshot, excerpt_range) in multi_buffer_snapshot.excerpts() {
|
||||
for excerpt_range in multi_buffer_snapshot.excerpts() {
|
||||
let Some(buffer_snapshot) =
|
||||
multi_buffer_snapshot.buffer_for_id(excerpt_range.context.start.buffer_id)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let start_offset = excerpt_range.context.start.to_offset(buffer_snapshot);
|
||||
let end_offset = excerpt_range.context.end.to_offset(buffer_snapshot);
|
||||
let range = start_offset..end_offset;
|
||||
|
|
@ -438,8 +439,8 @@ impl HighlightsTreeView {
|
|||
let start_anchor = buffer_snapshot.anchor_before(capture.node.start_byte());
|
||||
let end_anchor = buffer_snapshot.anchor_after(capture.node.end_byte());
|
||||
|
||||
let start = multi_buffer_snapshot.anchor_in_excerpt(excerpt_id, start_anchor);
|
||||
let end = multi_buffer_snapshot.anchor_in_excerpt(excerpt_id, end_anchor);
|
||||
let start = multi_buffer_snapshot.anchor_in_excerpt(start_anchor);
|
||||
let end = multi_buffer_snapshot.anchor_in_excerpt(end_anchor);
|
||||
|
||||
let (start, end) = match (start, end) {
|
||||
(Some(s), Some(e)) => (s, e),
|
||||
|
|
@ -447,29 +448,38 @@ impl HighlightsTreeView {
|
|||
};
|
||||
|
||||
let range = start..end;
|
||||
let (range_display, sort_key) =
|
||||
format_anchor_range(&range, excerpt_id, &multi_buffer_snapshot, is_singleton);
|
||||
let Some((range_display, buffer_id, buffer_point_range)) =
|
||||
format_anchor_range(&range, &multi_buffer_snapshot)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
entries.push(HighlightEntry {
|
||||
excerpt_id,
|
||||
range,
|
||||
buffer_id,
|
||||
range_display,
|
||||
style,
|
||||
category: HighlightCategory::SyntaxToken {
|
||||
capture_name,
|
||||
theme_key,
|
||||
},
|
||||
sort_key,
|
||||
buffer_point_range,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
entries.sort_by(|a, b| {
|
||||
a.sort_key
|
||||
.cmp(&b.sort_key)
|
||||
a.buffer_id
|
||||
.cmp(&b.buffer_id)
|
||||
.then_with(|| a.buffer_point_range.start.cmp(&b.buffer_point_range.start))
|
||||
.then_with(|| a.buffer_point_range.end.cmp(&b.buffer_point_range.end))
|
||||
.then_with(|| a.category.cmp(&b.category))
|
||||
});
|
||||
entries.dedup_by(|a, b| a.sort_key == b.sort_key && a.category == b.category);
|
||||
entries.dedup_by(|a, b| {
|
||||
a.buffer_id == b.buffer_id
|
||||
&& a.buffer_point_range == b.buffer_point_range
|
||||
&& a.category == b.category
|
||||
});
|
||||
|
||||
self.cached_entries = entries;
|
||||
self.rebuild_display_items(&multi_buffer_snapshot, cx);
|
||||
|
|
@ -485,7 +495,7 @@ impl HighlightsTreeView {
|
|||
fn rebuild_display_items(&mut self, snapshot: &MultiBufferSnapshot, cx: &App) {
|
||||
self.display_items.clear();
|
||||
|
||||
let mut last_excerpt_id: Option<ExcerptId> = None;
|
||||
let mut last_range_end: Option<Anchor> = None;
|
||||
|
||||
for (entry_ix, entry) in self.cached_entries.iter().enumerate() {
|
||||
if !self.should_show_entry(entry) {
|
||||
|
|
@ -493,11 +503,14 @@ impl HighlightsTreeView {
|
|||
}
|
||||
|
||||
if !self.is_singleton {
|
||||
let excerpt_changed =
|
||||
last_excerpt_id.is_none_or(|last_id| last_id != entry.excerpt_id);
|
||||
let excerpt_changed = last_range_end.is_none_or(|anchor| {
|
||||
snapshot
|
||||
.excerpt_containing(anchor..entry.range.start)
|
||||
.is_none()
|
||||
});
|
||||
if excerpt_changed {
|
||||
last_excerpt_id = Some(entry.excerpt_id);
|
||||
let label = excerpt_label_for(entry.excerpt_id, snapshot, cx);
|
||||
last_range_end = Some(entry.range.end);
|
||||
let label = excerpt_label_for(entry, snapshot, cx);
|
||||
self.display_items
|
||||
.push(DisplayItem::ExcerptSeparator { label });
|
||||
}
|
||||
|
|
@ -516,10 +529,6 @@ impl HighlightsTreeView {
|
|||
}
|
||||
|
||||
fn scroll_to_cursor_position(&mut self, cursor: &Anchor, snapshot: &MultiBufferSnapshot) {
|
||||
let cursor_point = cursor.to_point(snapshot);
|
||||
let cursor_key = (cursor_point.row, cursor_point.column);
|
||||
let cursor_excerpt = cursor.excerpt_id;
|
||||
|
||||
let best = self
|
||||
.display_items
|
||||
.iter()
|
||||
|
|
@ -532,17 +541,18 @@ impl HighlightsTreeView {
|
|||
_ => None,
|
||||
})
|
||||
.filter(|(_, _, entry)| {
|
||||
let (excerpt_id, start_row, start_col, end_row, end_col) = entry.sort_key;
|
||||
if !self.is_singleton && excerpt_id != cursor_excerpt {
|
||||
return false;
|
||||
}
|
||||
let start = (start_row, start_col);
|
||||
let end = (end_row, end_col);
|
||||
cursor_key >= start && cursor_key <= end
|
||||
entry.range.start.cmp(&cursor, snapshot).is_le()
|
||||
&& cursor.cmp(&entry.range.end, snapshot).is_lt()
|
||||
})
|
||||
.min_by_key(|(_, _, entry)| {
|
||||
let (_, start_row, start_col, end_row, end_col) = entry.sort_key;
|
||||
(end_row - start_row, end_col.saturating_sub(start_col))
|
||||
(
|
||||
entry.buffer_point_range.end.row - entry.buffer_point_range.start.row,
|
||||
entry
|
||||
.buffer_point_range
|
||||
.end
|
||||
.column
|
||||
.saturating_sub(entry.buffer_point_range.start.column),
|
||||
)
|
||||
})
|
||||
.map(|(display_ix, entry_ix, _)| (display_ix, entry_ix));
|
||||
|
||||
|
|
@ -1076,12 +1086,13 @@ impl ToolbarItemView for HighlightsTreeToolbarItemView {
|
|||
}
|
||||
|
||||
fn excerpt_label_for(
|
||||
excerpt_id: ExcerptId,
|
||||
entry: &HighlightEntry,
|
||||
snapshot: &MultiBufferSnapshot,
|
||||
cx: &App,
|
||||
) -> SharedString {
|
||||
let buffer = snapshot.buffer_for_excerpt(excerpt_id);
|
||||
let path_label = buffer
|
||||
let path_label = snapshot
|
||||
.anchor_to_buffer_anchor(entry.range.start)
|
||||
.and_then(|(anchor, _)| snapshot.buffer_for_id(anchor.buffer_id))
|
||||
.and_then(|buf| buf.file())
|
||||
.map(|file| {
|
||||
let full_path = file.full_path(cx);
|
||||
|
|
@ -1093,50 +1104,21 @@ fn excerpt_label_for(
|
|||
|
||||
fn format_anchor_range(
|
||||
range: &Range<Anchor>,
|
||||
excerpt_id: ExcerptId,
|
||||
snapshot: &MultiBufferSnapshot,
|
||||
is_singleton: bool,
|
||||
) -> (SharedString, (ExcerptId, u32, u32, u32, u32)) {
|
||||
if is_singleton {
|
||||
let start = range.start.to_point(snapshot);
|
||||
let end = range.end.to_point(snapshot);
|
||||
let display = SharedString::from(format!(
|
||||
"[{}:{} - {}:{}]",
|
||||
start.row + 1,
|
||||
start.column + 1,
|
||||
end.row + 1,
|
||||
end.column + 1,
|
||||
));
|
||||
let sort_key = (excerpt_id, start.row, start.column, end.row, end.column);
|
||||
(display, sort_key)
|
||||
} else {
|
||||
let buffer = snapshot.buffer_for_excerpt(excerpt_id);
|
||||
if let Some(buffer) = buffer {
|
||||
let start = language::ToPoint::to_point(&range.start.text_anchor, buffer);
|
||||
let end = language::ToPoint::to_point(&range.end.text_anchor, buffer);
|
||||
let display = SharedString::from(format!(
|
||||
"[{}:{} - {}:{}]",
|
||||
start.row + 1,
|
||||
start.column + 1,
|
||||
end.row + 1,
|
||||
end.column + 1,
|
||||
));
|
||||
let sort_key = (excerpt_id, start.row, start.column, end.row, end.column);
|
||||
(display, sort_key)
|
||||
} else {
|
||||
let start = range.start.to_point(snapshot);
|
||||
let end = range.end.to_point(snapshot);
|
||||
let display = SharedString::from(format!(
|
||||
"[{}:{} - {}:{}]",
|
||||
start.row + 1,
|
||||
start.column + 1,
|
||||
end.row + 1,
|
||||
end.column + 1,
|
||||
));
|
||||
let sort_key = (excerpt_id, start.row, start.column, end.row, end.column);
|
||||
(display, sort_key)
|
||||
}
|
||||
}
|
||||
) -> Option<(SharedString, BufferId, Range<Point>)> {
|
||||
let start = range.start.to_point(snapshot);
|
||||
let end = range.end.to_point(snapshot);
|
||||
let ((start_buffer, start), (_, end)) = snapshot
|
||||
.point_to_buffer_point(start)
|
||||
.zip(snapshot.point_to_buffer_point(end))?;
|
||||
let display = SharedString::from(format!(
|
||||
"[{}:{} - {}:{}]",
|
||||
start.row + 1,
|
||||
start.column + 1,
|
||||
end.row + 1,
|
||||
end.column + 1,
|
||||
));
|
||||
Some((display, start_buffer.remote_id(), start..end))
|
||||
}
|
||||
|
||||
fn render_style_preview(style: HighlightStyle, selected: bool, cx: &App) -> Div {
|
||||
|
|
|
|||
|
|
@ -1179,13 +1179,20 @@ impl StatusItemView for LspButton {
|
|||
.and_then(|active_editor| active_editor.editor.upgrade())
|
||||
.as_ref()
|
||||
{
|
||||
let editor_buffers =
|
||||
HashSet::from_iter(editor.read(cx).buffer().read(cx).excerpt_buffer_ids());
|
||||
let editor_buffers = HashSet::from_iter(
|
||||
editor
|
||||
.read(cx)
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.snapshot(cx)
|
||||
.excerpts()
|
||||
.map(|excerpt| excerpt.context.start.buffer_id),
|
||||
);
|
||||
let _editor_subscription = cx.subscribe_in(
|
||||
&editor,
|
||||
window,
|
||||
|lsp_button, _, e: &EditorEvent, window, cx| match e {
|
||||
EditorEvent::ExcerptsAdded { buffer, .. } => {
|
||||
EditorEvent::BufferRangesUpdated { buffer, .. } => {
|
||||
let updated = lsp_button.server_state.update(cx, |state, cx| {
|
||||
if let Some(active_editor) = state.active_editor.as_mut() {
|
||||
let buffer_id = buffer.read(cx).remote_id();
|
||||
|
|
@ -1198,9 +1205,7 @@ impl StatusItemView for LspButton {
|
|||
lsp_button.refresh_lsp_menu(false, window, cx);
|
||||
}
|
||||
}
|
||||
EditorEvent::ExcerptsRemoved {
|
||||
removed_buffer_ids, ..
|
||||
} => {
|
||||
EditorEvent::BuffersRemoved { removed_buffer_ids } => {
|
||||
let removed = lsp_button.server_state.update(cx, |state, _| {
|
||||
let mut removed = false;
|
||||
if let Some(active_editor) = state.active_editor.as_mut() {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
use command_palette_hooks::CommandPaletteFilter;
|
||||
use editor::{
|
||||
Anchor, Editor, ExcerptId, HighlightKey, MultiBufferOffset, SelectionEffects,
|
||||
scroll::Autoscroll,
|
||||
Anchor, Editor, HighlightKey, MultiBufferOffset, SelectionEffects, scroll::Autoscroll,
|
||||
};
|
||||
use gpui::{
|
||||
App, AppContext as _, Context, Div, Entity, EntityId, EventEmitter, FocusHandle, Focusable,
|
||||
|
|
@ -125,7 +124,6 @@ impl EditorState {
|
|||
#[derive(Clone)]
|
||||
struct BufferState {
|
||||
buffer: Entity<Buffer>,
|
||||
excerpt_id: ExcerptId,
|
||||
active_layer: Option<OwnedSyntaxLayer>,
|
||||
}
|
||||
|
||||
|
|
@ -253,18 +251,18 @@ impl SyntaxTreeView {
|
|||
let snapshot = editor_state
|
||||
.editor
|
||||
.update(cx, |editor, cx| editor.snapshot(window, cx));
|
||||
let (buffer, range, excerpt_id) = editor_state.editor.update(cx, |editor, cx| {
|
||||
let (buffer, range) = editor_state.editor.update(cx, |editor, cx| {
|
||||
let selection_range = editor
|
||||
.selections
|
||||
.last::<MultiBufferOffset>(&editor.display_snapshot(cx))
|
||||
.range();
|
||||
let multi_buffer = editor.buffer().read(cx);
|
||||
let (buffer, range, excerpt_id) = snapshot
|
||||
let (buffer, range, _) = snapshot
|
||||
.buffer_snapshot()
|
||||
.range_to_buffer_ranges(selection_range.start..=selection_range.end)
|
||||
.range_to_buffer_ranges(selection_range.start..selection_range.end)
|
||||
.pop()?;
|
||||
let buffer = multi_buffer.buffer(buffer.remote_id()).unwrap();
|
||||
Some((buffer, range, excerpt_id))
|
||||
Some((buffer, range))
|
||||
})?;
|
||||
|
||||
// If the cursor has moved into a different excerpt, retrieve a new syntax layer
|
||||
|
|
@ -273,16 +271,14 @@ impl SyntaxTreeView {
|
|||
.active_buffer
|
||||
.get_or_insert_with(|| BufferState {
|
||||
buffer: buffer.clone(),
|
||||
excerpt_id,
|
||||
active_layer: None,
|
||||
});
|
||||
let mut prev_layer = None;
|
||||
if did_reparse {
|
||||
prev_layer = buffer_state.active_layer.take();
|
||||
}
|
||||
if buffer_state.buffer != buffer || buffer_state.excerpt_id != excerpt_id {
|
||||
if buffer_state.buffer != buffer {
|
||||
buffer_state.buffer = buffer.clone();
|
||||
buffer_state.excerpt_id = excerpt_id;
|
||||
buffer_state.active_layer = None;
|
||||
}
|
||||
|
||||
|
|
@ -360,8 +356,7 @@ impl SyntaxTreeView {
|
|||
// Build a multibuffer anchor range.
|
||||
let multibuffer = editor_state.editor.read(cx).buffer();
|
||||
let multibuffer = multibuffer.read(cx).snapshot(cx);
|
||||
let excerpt_id = buffer_state.excerpt_id;
|
||||
let range = multibuffer.anchor_range_in_excerpt(excerpt_id, range)?;
|
||||
let range = multibuffer.buffer_anchor_range_to_anchor_range(range)?;
|
||||
let key = cx.entity_id().as_u64() as usize;
|
||||
|
||||
// Update the editor with the anchor range.
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ impl LineEndingIndicator {
|
|||
self.line_ending = None;
|
||||
self.active_editor = None;
|
||||
|
||||
if let Some((_, buffer, _)) = editor.read(cx).active_excerpt(cx) {
|
||||
if let Some(buffer) = editor.read(cx).active_buffer(cx) {
|
||||
let line_ending = buffer.read(cx).line_ending();
|
||||
self.line_ending = Some(line_ending);
|
||||
self.active_editor = Some(editor.downgrade());
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ impl LineEndingSelector {
|
|||
fn toggle(editor: &WeakEntity<Editor>, window: &mut Window, cx: &mut App) {
|
||||
let Some((workspace, buffer)) = editor
|
||||
.update(cx, |editor, cx| {
|
||||
Some((editor.workspace()?, editor.active_excerpt(cx)?.1))
|
||||
Some((editor.workspace()?, editor.active_buffer(cx)?))
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
|
|
|
|||
|
|
@ -295,7 +295,7 @@ impl MarkdownPreviewView {
|
|||
EditorEvent::Edited { .. }
|
||||
| EditorEvent::BufferEdited { .. }
|
||||
| EditorEvent::DirtyChanged
|
||||
| EditorEvent::ExcerptsEdited { .. } => {
|
||||
| EditorEvent::BuffersEdited { .. } => {
|
||||
this.update_markdown_from_active_editor(true, false, window, cx);
|
||||
}
|
||||
EditorEvent::SelectionsChanged { .. } => {
|
||||
|
|
|
|||
|
|
@ -1,192 +1,331 @@
|
|||
use crate::{MultiBufferDimension, MultiBufferOffset, MultiBufferOffsetUtf16};
|
||||
use crate::{
|
||||
ExcerptSummary, MultiBufferDimension, MultiBufferOffset, MultiBufferOffsetUtf16, PathKey,
|
||||
PathKeyIndex, find_diff_state,
|
||||
};
|
||||
|
||||
use super::{ExcerptId, MultiBufferSnapshot, ToOffset, ToPoint};
|
||||
use language::Point;
|
||||
use super::{MultiBufferSnapshot, ToOffset, ToPoint};
|
||||
use language::{BufferSnapshot, Point};
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
ops::{Add, AddAssign, Range, Sub},
|
||||
};
|
||||
use sum_tree::Bias;
|
||||
use text::BufferId;
|
||||
|
||||
/// A multibuffer anchor derived from an anchor into a specific excerpted buffer.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct ExcerptAnchor {
|
||||
pub(crate) text_anchor: text::Anchor,
|
||||
pub(crate) path: PathKeyIndex,
|
||||
pub(crate) diff_base_anchor: Option<text::Anchor>,
|
||||
}
|
||||
|
||||
/// A stable reference to a position within a [`MultiBuffer`](super::MultiBuffer).
|
||||
///
|
||||
/// Unlike simple offsets, anchors remain valid as the text is edited, automatically
|
||||
/// adjusting to reflect insertions and deletions around them.
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Hash)]
|
||||
pub struct Anchor {
|
||||
/// Identifies which excerpt within the multi-buffer this anchor belongs to.
|
||||
/// A multi-buffer can contain multiple excerpts from different buffers.
|
||||
pub excerpt_id: ExcerptId,
|
||||
/// The position within the excerpt's underlying buffer. This is a stable
|
||||
/// reference that remains valid as the buffer text is edited.
|
||||
pub text_anchor: text::Anchor,
|
||||
/// When present, indicates this anchor points into deleted text within an
|
||||
/// expanded diff hunk. The anchor references a position in the diff base
|
||||
/// (original) text rather than the current buffer text. This is used when
|
||||
/// displaying inline diffs where deleted lines are shown.
|
||||
pub diff_base_anchor: Option<text::Anchor>,
|
||||
pub enum Anchor {
|
||||
/// An anchor that always resolves to the start of the multibuffer.
|
||||
Min,
|
||||
/// An anchor that's attached to a specific excerpted buffer.
|
||||
Excerpt(ExcerptAnchor),
|
||||
/// An anchor that always resolves to the end of the multibuffer.
|
||||
Max,
|
||||
}
|
||||
|
||||
pub(crate) enum AnchorSeekTarget {
|
||||
Excerpt {
|
||||
path_key: PathKey,
|
||||
anchor: ExcerptAnchor,
|
||||
// None when the buffer no longer exists in the multibuffer
|
||||
snapshot: Option<BufferSnapshot>,
|
||||
},
|
||||
Empty,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for AnchorSeekTarget {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Anchor {
|
||||
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 {
|
||||
Anchor::Min => write!(f, "Anchor::Min"),
|
||||
Anchor::Max => write!(f, "Anchor::Max"),
|
||||
Anchor::Excerpt(excerpt_anchor) => write!(f, "{excerpt_anchor:?}"),
|
||||
}
|
||||
if self.is_max() {
|
||||
return write!(f, "Anchor::max({:?})", self.text_anchor.buffer_id);
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ExcerptAnchor> for Anchor {
|
||||
fn from(anchor: ExcerptAnchor) -> Self {
|
||||
Anchor::Excerpt(anchor)
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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()
|
||||
// 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub(crate) fn in_buffer(path: PathKeyIndex, text_anchor: text::Anchor) -> Self {
|
||||
ExcerptAnchor {
|
||||
path,
|
||||
diff_base_anchor: None,
|
||||
text_anchor,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_valid(&self, snapshot: &MultiBufferSnapshot) -> bool {
|
||||
let Some(target) = self.try_seek_target(snapshot) else {
|
||||
return false;
|
||||
};
|
||||
let Some(buffer_snapshot) = snapshot.buffer_for_id(self.buffer_id()) else {
|
||||
return false;
|
||||
};
|
||||
// Early check to avoid invalid comparisons when seeking
|
||||
if !buffer_snapshot.can_resolve(&self.text_anchor) {
|
||||
return false;
|
||||
}
|
||||
let mut cursor = snapshot.excerpts.cursor::<ExcerptSummary>(());
|
||||
cursor.seek(&target, Bias::Left);
|
||||
let Some(excerpt) = cursor.item() else {
|
||||
return false;
|
||||
};
|
||||
let is_valid = self.text_anchor == excerpt.range.context.start
|
||||
|| self.text_anchor == excerpt.range.context.end
|
||||
|| self.text_anchor.is_valid(&buffer_snapshot);
|
||||
is_valid
|
||||
&& excerpt
|
||||
.range
|
||||
.context
|
||||
.start
|
||||
.cmp(&self.text_anchor(), buffer_snapshot)
|
||||
.is_le()
|
||||
&& excerpt
|
||||
.range
|
||||
.context
|
||||
.end
|
||||
.cmp(&self.text_anchor(), buffer_snapshot)
|
||||
.is_ge()
|
||||
}
|
||||
|
||||
pub(crate) fn seek_target(&self, snapshot: &MultiBufferSnapshot) -> AnchorSeekTarget {
|
||||
self.try_seek_target(snapshot)
|
||||
.expect("anchor is from different multi-buffer")
|
||||
}
|
||||
|
||||
pub(crate) fn try_seek_target(
|
||||
&self,
|
||||
snapshot: &MultiBufferSnapshot,
|
||||
) -> Option<AnchorSeekTarget> {
|
||||
let path_key = snapshot.try_path_for_anchor(*self)?;
|
||||
let buffer = snapshot.buffer_for_path(&path_key).cloned();
|
||||
Some(AnchorSeekTarget::Excerpt {
|
||||
path_key,
|
||||
anchor: *self,
|
||||
snapshot: buffer,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ToOffset for ExcerptAnchor {
|
||||
fn to_offset(&self, snapshot: &MultiBufferSnapshot) -> MultiBufferOffset {
|
||||
Anchor::from(*self).to_offset(snapshot)
|
||||
}
|
||||
|
||||
fn to_offset_utf16(&self, snapshot: &MultiBufferSnapshot) -> MultiBufferOffsetUtf16 {
|
||||
Anchor::from(*self).to_offset_utf16(snapshot)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPoint for ExcerptAnchor {
|
||||
fn to_point(&self, snapshot: &MultiBufferSnapshot) -> Point {
|
||||
Anchor::from(*self).to_point(snapshot)
|
||||
}
|
||||
|
||||
fn to_point_utf16(&self, snapshot: &MultiBufferSnapshot) -> rope::PointUtf16 {
|
||||
Anchor::from(*self).to_point_utf16(snapshot)
|
||||
}
|
||||
}
|
||||
|
||||
impl Anchor {
|
||||
pub fn with_diff_base_anchor(self, diff_base_anchor: text::Anchor) -> Self {
|
||||
Self {
|
||||
diff_base_anchor: Some(diff_base_anchor),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn in_buffer(excerpt_id: ExcerptId, text_anchor: text::Anchor) -> Self {
|
||||
Self {
|
||||
excerpt_id,
|
||||
text_anchor,
|
||||
diff_base_anchor: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn range_in_buffer(excerpt_id: ExcerptId, range: Range<text::Anchor>) -> Range<Self> {
|
||||
Self::in_buffer(excerpt_id, range.start)..Self::in_buffer(excerpt_id, range.end)
|
||||
}
|
||||
|
||||
pub fn min() -> Self {
|
||||
Self {
|
||||
excerpt_id: ExcerptId::min(),
|
||||
text_anchor: text::Anchor::MIN,
|
||||
diff_base_anchor: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn max() -> Self {
|
||||
Self {
|
||||
excerpt_id: ExcerptId::max(),
|
||||
text_anchor: text::Anchor::MAX,
|
||||
diff_base_anchor: None,
|
||||
}
|
||||
}
|
||||
|
||||
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(crate) fn in_buffer(path: PathKeyIndex, text_anchor: text::Anchor) -> Self {
|
||||
Self::Excerpt(ExcerptAnchor::in_buffer(path, text_anchor))
|
||||
}
|
||||
|
||||
pub(crate) fn range_in_buffer(path: PathKeyIndex, range: Range<text::Anchor>) -> Range<Self> {
|
||||
Self::in_buffer(path, range.start)..Self::in_buffer(path, range.end)
|
||||
}
|
||||
|
||||
pub fn cmp(&self, other: &Anchor, snapshot: &MultiBufferSnapshot) -> Ordering {
|
||||
if self == other {
|
||||
return Ordering::Equal;
|
||||
}
|
||||
|
||||
let self_excerpt_id = snapshot.latest_excerpt_id(self.excerpt_id);
|
||||
let other_excerpt_id = snapshot.latest_excerpt_id(other.excerpt_id);
|
||||
|
||||
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,
|
||||
};
|
||||
match (self, other) {
|
||||
(Anchor::Min, Anchor::Min) => return Ordering::Equal,
|
||||
(Anchor::Max, Anchor::Max) => return Ordering::Equal,
|
||||
(Anchor::Min, _) => return Ordering::Less,
|
||||
(Anchor::Max, _) => return Ordering::Greater,
|
||||
(_, Anchor::Max) => return Ordering::Less,
|
||||
(_, Anchor::Min) => return Ordering::Greater,
|
||||
(Anchor::Excerpt(self_excerpt_anchor), Anchor::Excerpt(other_excerpt_anchor)) => {
|
||||
self_excerpt_anchor.cmp(other_excerpt_anchor, snapshot)
|
||||
}
|
||||
}
|
||||
Ordering::Equal
|
||||
}
|
||||
|
||||
pub fn bias(&self) -> Bias {
|
||||
self.text_anchor.bias
|
||||
match self {
|
||||
Anchor::Min => Bias::Left,
|
||||
Anchor::Max => Bias::Right,
|
||||
Anchor::Excerpt(anchor) => anchor.text_anchor.bias,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bias_left(&self, snapshot: &MultiBufferSnapshot) -> Anchor {
|
||||
if self.text_anchor.bias != Bias::Left
|
||||
&& let Some(excerpt) = snapshot.excerpt(self.excerpt_id)
|
||||
{
|
||||
return Self {
|
||||
excerpt_id: excerpt.id,
|
||||
text_anchor: self.text_anchor.bias_left(&excerpt.buffer),
|
||||
diff_base_anchor: self.diff_base_anchor.map(|a| {
|
||||
if let Some(base_text) = snapshot
|
||||
.diff_state(excerpt.buffer_id)
|
||||
.map(|diff| diff.base_text())
|
||||
&& a.is_valid(&base_text)
|
||||
{
|
||||
return a.bias_left(base_text);
|
||||
}
|
||||
a
|
||||
}),
|
||||
};
|
||||
match self {
|
||||
Anchor::Min => *self,
|
||||
Anchor::Max => snapshot.anchor_before(snapshot.max_point()),
|
||||
Anchor::Excerpt(anchor) => Anchor::Excerpt(anchor.bias_left(snapshot)),
|
||||
}
|
||||
*self
|
||||
}
|
||||
|
||||
pub fn bias_right(&self, snapshot: &MultiBufferSnapshot) -> Anchor {
|
||||
if self.text_anchor.bias != Bias::Right
|
||||
&& let Some(excerpt) = snapshot.excerpt(self.excerpt_id)
|
||||
{
|
||||
return Self {
|
||||
excerpt_id: excerpt.id,
|
||||
text_anchor: self.text_anchor.bias_right(&excerpt.buffer),
|
||||
diff_base_anchor: self.diff_base_anchor.map(|a| {
|
||||
if let Some(base_text) = snapshot
|
||||
.diff_state(excerpt.buffer_id)
|
||||
.map(|diff| diff.base_text())
|
||||
&& a.is_valid(&base_text)
|
||||
{
|
||||
return a.bias_right(base_text);
|
||||
}
|
||||
a
|
||||
}),
|
||||
};
|
||||
match self {
|
||||
Anchor::Max => *self,
|
||||
Anchor::Min => snapshot.anchor_after(Point::zero()),
|
||||
Anchor::Excerpt(anchor) => Anchor::Excerpt(anchor.bias_right(snapshot)),
|
||||
}
|
||||
*self
|
||||
}
|
||||
|
||||
pub fn summary<D>(&self, snapshot: &MultiBufferSnapshot) -> D
|
||||
|
|
@ -203,17 +342,112 @@ impl Anchor {
|
|||
}
|
||||
|
||||
pub fn is_valid(&self, snapshot: &MultiBufferSnapshot) -> bool {
|
||||
if self.is_min() || self.is_max() {
|
||||
true
|
||||
} else if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) {
|
||||
(self.text_anchor == excerpt.range.context.start
|
||||
|| self.text_anchor == excerpt.range.context.end
|
||||
|| self.text_anchor.is_valid(&excerpt.buffer))
|
||||
&& excerpt.contains(self)
|
||||
} else {
|
||||
false
|
||||
match self {
|
||||
Anchor::Min | Anchor::Max => true,
|
||||
Anchor::Excerpt(excerpt_anchor) => excerpt_anchor.is_valid(snapshot),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_excerpt_anchor(&self, snapshot: &MultiBufferSnapshot) -> Option<ExcerptAnchor> {
|
||||
match self {
|
||||
Anchor::Min => {
|
||||
let excerpt = snapshot.excerpts.first()?;
|
||||
|
||||
Some(ExcerptAnchor {
|
||||
text_anchor: excerpt.range.context.start,
|
||||
path: excerpt.path_key_index,
|
||||
diff_base_anchor: None,
|
||||
})
|
||||
}
|
||||
Anchor::Excerpt(excerpt_anchor) => Some(*excerpt_anchor),
|
||||
Anchor::Max => {
|
||||
let excerpt = snapshot.excerpts.last()?;
|
||||
|
||||
Some(ExcerptAnchor {
|
||||
text_anchor: excerpt.range.context.end,
|
||||
path: excerpt.path_key_index,
|
||||
diff_base_anchor: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn seek_target(&self, snapshot: &MultiBufferSnapshot) -> AnchorSeekTarget {
|
||||
let Some(excerpt_anchor) = self.to_excerpt_anchor(snapshot) else {
|
||||
return AnchorSeekTarget::Empty;
|
||||
};
|
||||
|
||||
excerpt_anchor.seek_target(snapshot)
|
||||
}
|
||||
|
||||
pub(crate) fn excerpt_anchor(&self) -> Option<ExcerptAnchor> {
|
||||
match self {
|
||||
Anchor::Min | Anchor::Max => None,
|
||||
Anchor::Excerpt(excerpt_anchor) => Some(*excerpt_anchor),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn text_anchor(&self) -> Option<text::Anchor> {
|
||||
match self {
|
||||
Anchor::Min | Anchor::Max => None,
|
||||
Anchor::Excerpt(excerpt_anchor) => Some(excerpt_anchor.text_anchor()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn opaque_id(&self) -> Option<[u8; 20]> {
|
||||
self.text_anchor().map(|a| a.opaque_id())
|
||||
}
|
||||
|
||||
/// Note: anchor_to_buffer_anchor is probably what you want
|
||||
pub fn raw_text_anchor(&self) -> Option<text::Anchor> {
|
||||
match self {
|
||||
Anchor::Min | Anchor::Max => None,
|
||||
Anchor::Excerpt(excerpt_anchor) => Some(excerpt_anchor.text_anchor),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn try_seek_target(
|
||||
&self,
|
||||
snapshot: &MultiBufferSnapshot,
|
||||
) -> Option<AnchorSeekTarget> {
|
||||
let Some(excerpt_anchor) = self.to_excerpt_anchor(snapshot) else {
|
||||
return Some(AnchorSeekTarget::Empty);
|
||||
};
|
||||
excerpt_anchor.try_seek_target(snapshot)
|
||||
}
|
||||
|
||||
/// Returns the text anchor for this anchor.
|
||||
/// Panics if the anchor is from a different buffer.
|
||||
pub fn text_anchor_in(&self, buffer: &BufferSnapshot) -> text::Anchor {
|
||||
match self {
|
||||
Anchor::Min => text::Anchor::min_for_buffer(buffer.remote_id()),
|
||||
Anchor::Excerpt(excerpt_anchor) => {
|
||||
let text_anchor = excerpt_anchor.text_anchor;
|
||||
assert_eq!(text_anchor.buffer_id, buffer.remote_id());
|
||||
text_anchor
|
||||
}
|
||||
Anchor::Max => text::Anchor::max_for_buffer(buffer.remote_id()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn diff_base_anchor(&self) -> Option<text::Anchor> {
|
||||
self.excerpt_anchor()?.diff_base_anchor
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn expect_text_anchor(&self) -> text::Anchor {
|
||||
self.excerpt_anchor().unwrap().text_anchor
|
||||
}
|
||||
|
||||
pub fn with_diff_base_anchor(mut self, diff_base_anchor: text::Anchor) -> Self {
|
||||
match &mut self {
|
||||
Anchor::Min | Anchor::Max => {}
|
||||
Anchor::Excerpt(excerpt_anchor) => {
|
||||
excerpt_anchor.diff_base_anchor = Some(diff_base_anchor);
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ToOffset for Anchor {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue