Detailed changes
@@ -3425,16 +3425,16 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
assert_eq!(
entries,
vec![
- Some(blame_entry("1b1b1b", 0..1)),
- Some(blame_entry("0d0d0d", 1..2)),
- Some(blame_entry("3a3a3a", 2..3)),
- Some(blame_entry("4c4c4c", 3..4)),
+ Some((buffer_id_b, blame_entry("1b1b1b", 0..1))),
+ Some((buffer_id_b, blame_entry("0d0d0d", 1..2))),
+ Some((buffer_id_b, blame_entry("3a3a3a", 2..3))),
+ Some((buffer_id_b, blame_entry("4c4c4c", 3..4))),
]
);
blame.update(cx, |blame, _| {
- for (idx, entry) in entries.iter().flatten().enumerate() {
- let details = blame.details_for_entry(entry).unwrap();
+ for (idx, (buffer, entry)) in entries.iter().flatten().enumerate() {
+ let details = blame.details_for_entry(*buffer, entry).unwrap();
assert_eq!(details.message, format!("message for idx-{}", idx));
assert_eq!(
details.permalink.unwrap().to_string(),
@@ -3474,9 +3474,9 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
entries,
vec![
None,
- Some(blame_entry("0d0d0d", 1..2)),
- Some(blame_entry("3a3a3a", 2..3)),
- Some(blame_entry("4c4c4c", 3..4)),
+ Some((buffer_id_b, blame_entry("0d0d0d", 1..2))),
+ Some((buffer_id_b, blame_entry("3a3a3a", 2..3))),
+ Some((buffer_id_b, blame_entry("4c4c4c", 3..4))),
]
);
});
@@ -3511,8 +3511,8 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
vec![
None,
None,
- Some(blame_entry("3a3a3a", 2..3)),
- Some(blame_entry("4c4c4c", 3..4)),
+ Some((buffer_id_b, blame_entry("3a3a3a", 2..3))),
+ Some((buffer_id_b, blame_entry("4c4c4c", 3..4))),
]
);
});
@@ -190,7 +190,6 @@ use std::{
sync::Arc,
time::{Duration, Instant},
};
-use sum_tree::TreeMap;
use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
use theme::{
@@ -227,7 +226,7 @@ const MAX_SELECTION_HISTORY_LEN: usize = 1024;
pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
#[doc(hidden)]
pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
-const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
+pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
@@ -1060,8 +1059,8 @@ pub struct Editor {
placeholder_text: Option<Arc<str>>,
highlight_order: usize,
highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
- background_highlights: TreeMap<HighlightKey, BackgroundHighlight>,
- gutter_highlights: TreeMap<TypeId, GutterHighlight>,
+ background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
+ gutter_highlights: HashMap<TypeId, GutterHighlight>,
scrollbar_marker_state: ScrollbarMarkerState,
active_indent_guides_state: ActiveIndentGuidesState,
nav_history: Option<ItemNavHistory>,
@@ -2112,8 +2111,8 @@ impl Editor {
placeholder_text: None,
highlight_order: 0,
highlighted_rows: HashMap::default(),
- background_highlights: TreeMap::default(),
- gutter_highlights: TreeMap::default(),
+ background_highlights: HashMap::default(),
+ gutter_highlights: HashMap::default(),
scrollbar_marker_state: ScrollbarMarkerState::default(),
active_indent_guides_state: ActiveIndentGuidesState::default(),
nav_history: None,
@@ -6630,7 +6629,7 @@ impl Editor {
buffer_row: Some(point.row),
..Default::default()
};
- let Some(blame_entry) = blame
+ let Some((buffer, blame_entry)) = blame
.update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
.flatten()
else {
@@ -6640,12 +6639,19 @@ impl Editor {
let anchor = self.selections.newest_anchor().head();
let position = self.to_pixel_point(anchor, &snapshot, window);
if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
- self.show_blame_popover(&blame_entry, position + last_bounds.origin, true, cx);
+ self.show_blame_popover(
+ buffer,
+ &blame_entry,
+ position + last_bounds.origin,
+ true,
+ cx,
+ );
};
}
fn show_blame_popover(
&mut self,
+ buffer: BufferId,
blame_entry: &BlameEntry,
position: gpui::Point<Pixels>,
ignore_timeout: bool,
@@ -6669,7 +6675,7 @@ impl Editor {
return;
};
let blame = blame.read(cx);
- let details = blame.details_for_entry(&blame_entry);
+ let details = blame.details_for_entry(buffer, &blame_entry);
let markdown = cx.new(|cx| {
Markdown::new(
details
@@ -19071,7 +19077,7 @@ impl Editor {
let snapshot = self.snapshot(window, cx);
let cursor = self.selections.newest::<Point>(cx).head();
let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
- let blame_entry = blame
+ let (_, blame_entry) = blame
.update(cx, |blame, cx| {
blame
.blame_for_rows(
@@ -19086,7 +19092,7 @@ impl Editor {
})
.flatten()?;
let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
- let repo = blame.read(cx).repository(cx)?;
+ let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
let workspace = self.workspace()?.downgrade();
renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
None
@@ -19122,18 +19128,17 @@ impl Editor {
cx: &mut Context<Self>,
) {
if let Some(project) = self.project() {
- let Some(buffer) = self.buffer().read(cx).as_singleton() else {
- return;
- };
-
- if buffer.read(cx).file().is_none() {
+ if let Some(buffer) = self.buffer().read(cx).as_singleton()
+ && buffer.read(cx).file().is_none()
+ {
return;
}
let focused = self.focus_handle(cx).contains_focused(window, cx);
let project = project.clone();
- let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
+ let blame = cx
+ .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
self.blame_subscription =
Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
self.blame = Some(blame);
@@ -19783,7 +19788,24 @@ impl Editor {
let buffer = &snapshot.buffer_snapshot;
let start = buffer.anchor_before(0);
let end = buffer.anchor_after(buffer.len());
- self.background_highlights_in_range(start..end, &snapshot, cx.theme())
+ self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
+ }
+
+ #[cfg(any(test, feature = "test-support"))]
+ pub fn sorted_background_highlights_in_range(
+ &self,
+ search_range: Range<Anchor>,
+ display_snapshot: &DisplaySnapshot,
+ theme: &Theme,
+ ) -> Vec<(Range<DisplayPoint>, Hsla)> {
+ let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
+ res.sort_by(|a, b| {
+ a.0.start
+ .cmp(&b.0.start)
+ .then_with(|| a.0.end.cmp(&b.0.end))
+ .then_with(|| a.1.cmp(&b.1))
+ });
+ res
}
#[cfg(feature = "test-support")]
@@ -19848,6 +19870,9 @@ impl Editor {
.is_some_and(|(_, highlights)| !highlights.is_empty())
}
+ /// Returns all background highlights for a given range.
+ ///
+ /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
pub fn background_highlights_in_range(
&self,
search_range: Range<Anchor>,
@@ -19886,84 +19911,6 @@ impl Editor {
results
}
- pub fn background_highlight_row_ranges<T: 'static>(
- &self,
- search_range: Range<Anchor>,
- display_snapshot: &DisplaySnapshot,
- count: usize,
- ) -> Vec<RangeInclusive<DisplayPoint>> {
- let mut results = Vec::new();
- let Some((_, ranges)) = self
- .background_highlights
- .get(&HighlightKey::Type(TypeId::of::<T>()))
- else {
- return vec![];
- };
-
- let start_ix = match ranges.binary_search_by(|probe| {
- let cmp = probe
- .end
- .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
- if cmp.is_gt() {
- Ordering::Greater
- } else {
- Ordering::Less
- }
- }) {
- Ok(i) | Err(i) => i,
- };
- let mut push_region = |start: Option<Point>, end: Option<Point>| {
- if let (Some(start_display), Some(end_display)) = (start, end) {
- results.push(
- start_display.to_display_point(display_snapshot)
- ..=end_display.to_display_point(display_snapshot),
- );
- }
- };
- let mut start_row: Option<Point> = None;
- let mut end_row: Option<Point> = None;
- if ranges.len() > count {
- return Vec::new();
- }
- for range in &ranges[start_ix..] {
- if range
- .start
- .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
- .is_ge()
- {
- break;
- }
- let end = range.end.to_point(&display_snapshot.buffer_snapshot);
- if let Some(current_row) = &end_row
- && end.row == current_row.row
- {
- continue;
- }
- let start = range.start.to_point(&display_snapshot.buffer_snapshot);
- if start_row.is_none() {
- assert_eq!(end_row, None);
- start_row = Some(start);
- end_row = Some(end);
- continue;
- }
- if let Some(current_end) = end_row.as_mut() {
- if start.row > current_end.row + 1 {
- push_region(start_row, end_row);
- start_row = Some(start);
- end_row = Some(end);
- } else {
- // Merge two hunks.
- *current_end = end;
- }
- } else {
- unreachable!();
- }
- }
- // We might still have a hunk that was not rendered (if there was a search hit on the last line)
- push_region(start_row, end_row);
- results
- }
-
pub fn gutter_highlights_in_range(
&self,
search_range: Range<Anchor>,
@@ -15453,37 +15453,34 @@ fn test_highlighted_ranges(cx: &mut TestAppContext) {
);
let snapshot = editor.snapshot(window, cx);
- let mut highlighted_ranges = editor.background_highlights_in_range(
+ let highlighted_ranges = editor.sorted_background_highlights_in_range(
anchor_range(Point::new(3, 4)..Point::new(7, 4)),
&snapshot,
cx.theme(),
);
- // Enforce a consistent ordering based on color without relying on the ordering of the
- // highlight's `TypeId` which is non-executor.
- highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
assert_eq!(
highlighted_ranges,
&[
(
- DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
- Hsla::red(),
+ DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
+ Hsla::green(),
),
(
- DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
+ DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
Hsla::red(),
),
(
- DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
+ DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
Hsla::green(),
),
(
- DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
- Hsla::green(),
+ DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
+ Hsla::red(),
),
]
);
assert_eq!(
- editor.background_highlights_in_range(
+ editor.sorted_background_highlights_in_range(
anchor_range(Point::new(5, 6)..Point::new(6, 4)),
&snapshot,
cx.theme(),
@@ -117,6 +117,7 @@ struct SelectionLayout {
struct InlineBlameLayout {
element: AnyElement,
bounds: Bounds<Pixels>,
+ buffer_id: BufferId,
entry: BlameEntry,
}
@@ -1157,7 +1158,7 @@ impl EditorElement {
cx.notify();
}
- if let Some((bounds, blame_entry)) = &position_map.inline_blame_bounds {
+ if let Some((bounds, buffer_id, blame_entry)) = &position_map.inline_blame_bounds {
let mouse_over_inline_blame = bounds.contains(&event.position);
let mouse_over_popover = editor
.inline_blame_popover
@@ -1170,7 +1171,7 @@ impl EditorElement {
.is_some_and(|state| state.keyboard_grace);
if mouse_over_inline_blame || mouse_over_popover {
- editor.show_blame_popover(blame_entry, event.position, false, cx);
+ editor.show_blame_popover(*buffer_id, blame_entry, event.position, false, cx);
} else if !keyboard_grace {
editor.hide_blame_popover(cx);
}
@@ -2454,7 +2455,7 @@ impl EditorElement {
padding * em_width
};
- let entry = blame
+ let (buffer_id, entry) = blame
.update(cx, |blame, cx| {
blame.blame_for_rows(&[*row_info], cx).next()
})
@@ -2489,13 +2490,22 @@ impl EditorElement {
let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
let bounds = Bounds::new(absolute_offset, size);
- self.layout_blame_entry_popover(entry.clone(), blame, line_height, text_hitbox, window, cx);
+ self.layout_blame_entry_popover(
+ entry.clone(),
+ blame,
+ line_height,
+ text_hitbox,
+ row_info.buffer_id?,
+ window,
+ cx,
+ );
element.prepaint_as_root(absolute_offset, AvailableSpace::min_size(), window, cx);
Some(InlineBlameLayout {
element,
bounds,
+ buffer_id,
entry,
})
}
@@ -2506,6 +2516,7 @@ impl EditorElement {
blame: Entity<GitBlame>,
line_height: Pixels,
text_hitbox: &Hitbox,
+ buffer: BufferId,
window: &mut Window,
cx: &mut App,
) {
@@ -2530,6 +2541,7 @@ impl EditorElement {
popover_state.markdown,
workspace,
&blame,
+ buffer,
window,
cx,
)
@@ -2604,14 +2616,16 @@ impl EditorElement {
.into_iter()
.enumerate()
.flat_map(|(ix, blame_entry)| {
+ let (buffer_id, blame_entry) = blame_entry?;
let mut element = render_blame_entry(
ix,
&blame,
- blame_entry?,
+ blame_entry,
&self.style,
&mut last_used_color,
self.editor.clone(),
workspace.clone(),
+ buffer_id,
blame_renderer.clone(),
cx,
)?;
@@ -7401,12 +7415,13 @@ fn render_blame_entry_popover(
markdown: Entity<Markdown>,
workspace: WeakEntity<Workspace>,
blame: &Entity<GitBlame>,
+ buffer: BufferId,
window: &mut Window,
cx: &mut App,
) -> Option<AnyElement> {
let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
let blame = blame.read(cx);
- let repository = blame.repository(cx)?;
+ let repository = blame.repository(cx, buffer)?;
renderer.render_blame_entry_popover(
blame_entry,
scroll_handle,
@@ -7427,6 +7442,7 @@ fn render_blame_entry(
last_used_color: &mut Option<(PlayerColor, Oid)>,
editor: Entity<Editor>,
workspace: Entity<Workspace>,
+ buffer: BufferId,
renderer: Arc<dyn BlameRenderer>,
cx: &mut App,
) -> Option<AnyElement> {
@@ -7447,8 +7463,8 @@ fn render_blame_entry(
last_used_color.replace((sha_color, blame_entry.sha));
let blame = blame.read(cx);
- let details = blame.details_for_entry(&blame_entry);
- let repository = blame.repository(cx)?;
+ let details = blame.details_for_entry(buffer, &blame_entry);
+ let repository = blame.repository(cx, buffer)?;
renderer.render_blame_entry(
&style.text,
blame_entry,
@@ -8755,7 +8771,7 @@ impl Element for EditorElement {
return None;
}
let blame = editor.blame.as_ref()?;
- let blame_entry = blame
+ let (_, blame_entry) = blame
.update(cx, |blame, cx| {
let row_infos =
snapshot.row_infos(snapshot.longest_row()).next()?;
@@ -9305,7 +9321,7 @@ impl Element for EditorElement {
text_hitbox: text_hitbox.clone(),
inline_blame_bounds: inline_blame_layout
.as_ref()
- .map(|layout| (layout.bounds, layout.entry.clone())),
+ .map(|layout| (layout.bounds, layout.buffer_id, layout.entry.clone())),
display_hunks: display_hunks.clone(),
diff_hunk_control_bounds,
});
@@ -9969,7 +9985,7 @@ pub(crate) struct PositionMap {
pub snapshot: EditorSnapshot,
pub text_hitbox: Hitbox,
pub gutter_hitbox: Hitbox,
- pub inline_blame_bounds: Option<(Bounds<Pixels>, BlameEntry)>,
+ pub inline_blame_bounds: Option<(Bounds<Pixels>, BufferId, BlameEntry)>,
pub display_hunks: Vec<(DisplayDiffHunk, Option<Hitbox>)>,
pub diff_hunk_control_bounds: Vec<(DisplayRow, Bounds<Pixels>)>,
}
@@ -10,16 +10,18 @@ use gpui::{
AnyElement, App, AppContext as _, Context, Entity, Hsla, ScrollHandle, Subscription, Task,
TextStyle, WeakEntity, Window,
};
-use language::{Bias, Buffer, BufferSnapshot, Edit};
+use itertools::Itertools;
+use language::{Bias, BufferSnapshot, Edit};
use markdown::Markdown;
-use multi_buffer::RowInfo;
+use multi_buffer::{MultiBuffer, RowInfo};
use project::{
- Project, ProjectItem,
+ Project, ProjectItem as _,
git_store::{GitStoreEvent, Repository, RepositoryEvent},
};
use smallvec::SmallVec;
use std::{sync::Arc, time::Duration};
use sum_tree::SumTree;
+use text::BufferId;
use workspace::Workspace;
#[derive(Clone, Debug, Default)]
@@ -63,16 +65,19 @@ impl<'a> sum_tree::Dimension<'a, GitBlameEntrySummary> for u32 {
}
}
-pub struct GitBlame {
- project: Entity<Project>,
- buffer: Entity<Buffer>,
+struct GitBlameBuffer {
entries: SumTree<GitBlameEntry>,
- commit_details: HashMap<Oid, ParsedCommitMessage>,
buffer_snapshot: BufferSnapshot,
buffer_edits: text::Subscription,
+ commit_details: HashMap<Oid, ParsedCommitMessage>,
+}
+
+pub struct GitBlame {
+ project: Entity<Project>,
+ multi_buffer: WeakEntity<MultiBuffer>,
+ buffers: HashMap<BufferId, GitBlameBuffer>,
task: Task<Result<()>>,
focused: bool,
- generated: bool,
changed_while_blurred: bool,
user_triggered: bool,
regenerate_on_edit_task: Task<Result<()>>,
@@ -184,44 +189,44 @@ impl gpui::Global for GlobalBlameRenderer {}
impl GitBlame {
pub fn new(
- buffer: Entity<Buffer>,
+ multi_buffer: Entity<MultiBuffer>,
project: Entity<Project>,
user_triggered: bool,
focused: bool,
cx: &mut Context<Self>,
) -> Self {
- let entries = SumTree::from_item(
- GitBlameEntry {
- rows: buffer.read(cx).max_point().row + 1,
- blame: None,
+ let multi_buffer_subscription = cx.subscribe(
+ &multi_buffer,
+ |git_blame, multi_buffer, event, cx| match event {
+ multi_buffer::Event::DirtyChanged => {
+ if !multi_buffer.read(cx).is_dirty(cx) {
+ git_blame.generate(cx);
+ }
+ }
+ multi_buffer::Event::ExcerptsAdded { .. }
+ | multi_buffer::Event::ExcerptsEdited { .. } => git_blame.regenerate_on_edit(cx),
+ _ => {}
},
- &(),
);
- let buffer_subscriptions = cx.subscribe(&buffer, |this, buffer, event, cx| match event {
- language::BufferEvent::DirtyChanged => {
- if !buffer.read(cx).is_dirty() {
- this.generate(cx);
- }
- }
- language::BufferEvent::Edited => {
- this.regenerate_on_edit(cx);
- }
- _ => {}
- });
-
let project_subscription = cx.subscribe(&project, {
- let buffer = buffer.clone();
+ let multi_buffer = multi_buffer.downgrade();
- move |this, _, event, cx| {
+ move |git_blame, _, event, cx| {
if let project::Event::WorktreeUpdatedEntries(_, updated) = event {
- let project_entry_id = buffer.read(cx).entry_id(cx);
+ let Some(multi_buffer) = multi_buffer.upgrade() else {
+ return;
+ };
+ let project_entry_id = multi_buffer
+ .read(cx)
+ .as_singleton()
+ .and_then(|it| it.read(cx).entry_id(cx));
if updated
.iter()
.any(|(_, entry_id, _)| project_entry_id == Some(*entry_id))
{
log::debug!("Updated buffers. Regenerating blame data...",);
- this.generate(cx);
+ git_blame.generate(cx);
}
}
}
@@ -239,24 +244,17 @@ impl GitBlame {
_ => {}
});
- let buffer_snapshot = buffer.read(cx).snapshot();
- let buffer_edits = buffer.update(cx, |buffer, _| buffer.subscribe());
-
let mut this = Self {
project,
- buffer,
- buffer_snapshot,
- entries,
- buffer_edits,
+ multi_buffer: multi_buffer.downgrade(),
+ buffers: HashMap::default(),
user_triggered,
focused,
changed_while_blurred: false,
- commit_details: HashMap::default(),
task: Task::ready(Ok(())),
- generated: false,
regenerate_on_edit_task: Task::ready(Ok(())),
_regenerate_subscriptions: vec![
- buffer_subscriptions,
+ multi_buffer_subscription,
project_subscription,
git_store_subscription,
],
@@ -265,56 +263,63 @@ impl GitBlame {
this
}
- pub fn repository(&self, cx: &App) -> Option<Entity<Repository>> {
+ pub fn repository(&self, cx: &App, id: BufferId) -> Option<Entity<Repository>> {
self.project
.read(cx)
.git_store()
.read(cx)
- .repository_and_path_for_buffer_id(self.buffer.read(cx).remote_id(), cx)
+ .repository_and_path_for_buffer_id(id, cx)
.map(|(repo, _)| repo)
}
pub fn has_generated_entries(&self) -> bool {
- self.generated
+ !self.buffers.is_empty()
}
- pub fn details_for_entry(&self, entry: &BlameEntry) -> Option<ParsedCommitMessage> {
- self.commit_details.get(&entry.sha).cloned()
+ pub fn details_for_entry(
+ &self,
+ buffer: BufferId,
+ entry: &BlameEntry,
+ ) -> Option<ParsedCommitMessage> {
+ self.buffers
+ .get(&buffer)?
+ .commit_details
+ .get(&entry.sha)
+ .cloned()
}
pub fn blame_for_rows<'a>(
&'a mut self,
rows: &'a [RowInfo],
- cx: &App,
- ) -> impl 'a + Iterator<Item = Option<BlameEntry>> {
- self.sync(cx);
-
- let buffer_id = self.buffer_snapshot.remote_id();
- let mut cursor = self.entries.cursor::<u32>(&());
+ cx: &'a mut App,
+ ) -> impl Iterator<Item = Option<(BufferId, BlameEntry)>> + use<'a> {
rows.iter().map(move |info| {
- let row = info
- .buffer_row
- .filter(|_| info.buffer_id == Some(buffer_id))?;
- cursor.seek_forward(&row, Bias::Right);
- cursor.item()?.blame.clone()
+ let buffer_id = info.buffer_id?;
+ self.sync(cx, buffer_id);
+
+ let buffer_row = info.buffer_row?;
+ let mut cursor = self.buffers.get(&buffer_id)?.entries.cursor::<u32>(&());
+ cursor.seek_forward(&buffer_row, Bias::Right);
+ Some((buffer_id, cursor.item()?.blame.clone()?))
})
}
- pub fn max_author_length(&mut self, cx: &App) -> usize {
- self.sync(cx);
-
+ pub fn max_author_length(&mut self, cx: &mut App) -> usize {
let mut max_author_length = 0;
-
- for entry in self.entries.iter() {
- let author_len = entry
- .blame
- .as_ref()
- .and_then(|entry| entry.author.as_ref())
- .map(|author| author.len());
- if let Some(author_len) = author_len
- && author_len > max_author_length
- {
- max_author_length = author_len;
+ self.sync_all(cx);
+
+ for buffer in self.buffers.values() {
+ for entry in buffer.entries.iter() {
+ let author_len = entry
+ .blame
+ .as_ref()
+ .and_then(|entry| entry.author.as_ref())
+ .map(|author| author.len());
+ if let Some(author_len) = author_len
+ && author_len > max_author_length
+ {
+ max_author_length = author_len;
+ }
}
}
@@ -336,22 +341,48 @@ impl GitBlame {
}
}
- fn sync(&mut self, cx: &App) {
- let edits = self.buffer_edits.consume();
- let new_snapshot = self.buffer.read(cx).snapshot();
+ fn sync_all(&mut self, cx: &mut App) {
+ 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));
+ }
+
+ fn sync(&mut self, cx: &mut App, buffer_id: BufferId) {
+ let Some(blame_buffer) = self.buffers.get_mut(&buffer_id) else {
+ return;
+ };
+ let Some(buffer) = self
+ .multi_buffer
+ .upgrade()
+ .and_then(|multi_buffer| multi_buffer.read(cx).buffer(buffer_id))
+ else {
+ return;
+ };
+ let edits = blame_buffer.buffer_edits.consume();
+ let new_snapshot = buffer.read(cx).snapshot();
let mut row_edits = edits
.into_iter()
.map(|edit| {
- let old_point_range = self.buffer_snapshot.offset_to_point(edit.old.start)
- ..self.buffer_snapshot.offset_to_point(edit.old.end);
+ let old_point_range = blame_buffer.buffer_snapshot.offset_to_point(edit.old.start)
+ ..blame_buffer.buffer_snapshot.offset_to_point(edit.old.end);
let new_point_range = new_snapshot.offset_to_point(edit.new.start)
..new_snapshot.offset_to_point(edit.new.end);
if old_point_range.start.column
- == self.buffer_snapshot.line_len(old_point_range.start.row)
+ == blame_buffer
+ .buffer_snapshot
+ .line_len(old_point_range.start.row)
&& (new_snapshot.chars_at(edit.new.start).next() == Some('\n')
- || self.buffer_snapshot.line_len(old_point_range.end.row) == 0)
+ || blame_buffer
+ .buffer_snapshot
+ .line_len(old_point_range.end.row)
+ == 0)
{
Edit {
old: old_point_range.start.row + 1..old_point_range.end.row + 1,
@@ -375,7 +406,7 @@ impl GitBlame {
.peekable();
let mut new_entries = SumTree::default();
- let mut cursor = self.entries.cursor::<u32>(&());
+ let mut cursor = blame_buffer.entries.cursor::<u32>(&());
while let Some(mut edit) = row_edits.next() {
while let Some(next_edit) = row_edits.peek() {
@@ -433,17 +464,28 @@ impl GitBlame {
new_entries.append(cursor.suffix(), &());
drop(cursor);
- self.buffer_snapshot = new_snapshot;
- self.entries = new_entries;
+ blame_buffer.buffer_snapshot = new_snapshot;
+ blame_buffer.entries = new_entries;
}
#[cfg(test)]
fn check_invariants(&mut self, cx: &mut Context<Self>) {
- self.sync(cx);
- assert_eq!(
- self.entries.summary().rows,
- self.buffer.read(cx).max_point().row + 1
- );
+ self.sync_all(cx);
+ for (&id, buffer) in &self.buffers {
+ assert_eq!(
+ buffer.entries.summary().rows,
+ self.multi_buffer
+ .upgrade()
+ .unwrap()
+ .read(cx)
+ .buffer(id)
+ .unwrap()
+ .read(cx)
+ .max_point()
+ .row
+ + 1
+ );
+ }
}
fn generate(&mut self, cx: &mut Context<Self>) {
@@ -451,62 +493,105 @@ impl GitBlame {
self.changed_while_blurred = true;
return;
}
- let buffer_edits = self.buffer.update(cx, |buffer, _| buffer.subscribe());
- let snapshot = self.buffer.read(cx).snapshot();
let blame = self.project.update(cx, |project, cx| {
- project.blame_buffer(&self.buffer, None, cx)
+ let Some(multi_buffer) = self.multi_buffer.upgrade() else {
+ return Vec::new();
+ };
+ multi_buffer
+ .read(cx)
+ .all_buffer_ids()
+ .into_iter()
+ .filter_map(|id| {
+ let buffer = multi_buffer.read(cx).buffer(id)?;
+ let snapshot = buffer.read(cx).snapshot();
+ let buffer_edits = buffer.update(cx, |buffer, _| buffer.subscribe());
+
+ let blame_buffer = project.blame_buffer(&buffer, None, cx);
+ Some((id, snapshot, buffer_edits, blame_buffer))
+ })
+ .collect::<Vec<_>>()
});
let provider_registry = GitHostingProviderRegistry::default_global(cx);
self.task = cx.spawn(async move |this, cx| {
- let result = cx
+ let (result, errors) = cx
.background_spawn({
- let snapshot = snapshot.clone();
async move {
- let Some(Blame {
- entries,
- messages,
- remote_url,
- }) = blame.await?
- else {
- return Ok(None);
- };
-
- let entries = build_blame_entry_sum_tree(entries, snapshot.max_point().row);
- let commit_details =
- parse_commit_messages(messages, remote_url, provider_registry).await;
-
- anyhow::Ok(Some((entries, commit_details)))
+ let mut res = vec![];
+ let mut errors = vec![];
+ for (id, snapshot, buffer_edits, blame) in blame {
+ match blame.await {
+ Ok(Some(Blame {
+ entries,
+ messages,
+ remote_url,
+ })) => {
+ let entries = build_blame_entry_sum_tree(
+ entries,
+ snapshot.max_point().row,
+ );
+ let commit_details = parse_commit_messages(
+ messages,
+ remote_url,
+ provider_registry.clone(),
+ )
+ .await;
+
+ res.push((
+ id,
+ snapshot,
+ buffer_edits,
+ Some(entries),
+ commit_details,
+ ));
+ }
+ Ok(None) => {
+ res.push((id, snapshot, buffer_edits, None, Default::default()))
+ }
+ Err(e) => errors.push(e),
+ }
+ }
+ (res, errors)
}
})
.await;
- this.update(cx, |this, cx| match result {
- Ok(None) => {
- // Nothing to do, e.g. no repository found
+ this.update(cx, |this, cx| {
+ this.buffers.clear();
+ for (id, snapshot, buffer_edits, entries, commit_details) in result {
+ let Some(entries) = entries else {
+ continue;
+ };
+ this.buffers.insert(
+ id,
+ GitBlameBuffer {
+ buffer_edits,
+ buffer_snapshot: snapshot,
+ entries,
+ commit_details,
+ },
+ );
}
- Ok(Some((entries, commit_details))) => {
- this.buffer_edits = buffer_edits;
- this.buffer_snapshot = snapshot;
- this.entries = entries;
- this.commit_details = commit_details;
- this.generated = true;
- cx.notify();
+ cx.notify();
+ if !errors.is_empty() {
+ this.project.update(cx, |_, cx| {
+ if this.user_triggered {
+ log::error!("failed to get git blame data: {errors:?}");
+ let notification = errors
+ .into_iter()
+ .format_with(",", |e, f| f(&format_args!("{:#}", e)))
+ .to_string();
+ cx.emit(project::Event::Toast {
+ notification_id: "git-blame".into(),
+ message: notification,
+ });
+ } else {
+ // If we weren't triggered by a user, we just log errors in the background, instead of sending
+ // notifications.
+ log::debug!("failed to get git blame data: {errors:?}");
+ }
+ })
}
- Err(error) => this.project.update(cx, |_, cx| {
- if this.user_triggered {
- log::error!("failed to get git blame data: {error:?}");
- let notification = format!("{:#}", error).trim().to_string();
- cx.emit(project::Event::Toast {
- notification_id: "git-blame".into(),
- message: notification,
- });
- } else {
- // If we weren't triggered by a user, we just log errors in the background, instead of sending
- // notifications.
- log::debug!("failed to get git blame data: {error:?}");
- }
- }),
})
});
}
@@ -520,7 +605,7 @@ impl GitBlame {
this.update(cx, |this, cx| {
this.generate(cx);
})
- })
+ });
}
}
@@ -659,6 +744,9 @@ mod tests {
)
.collect::<Vec<_>>(),
expected
+ .into_iter()
+ .map(|it| Some((buffer_id, it?)))
+ .collect::<Vec<_>>()
);
}
@@ -705,6 +793,7 @@ mod tests {
})
.await
.unwrap();
+ let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
let blame = cx.new(|cx| GitBlame::new(buffer.clone(), project.clone(), true, true, cx));
@@ -785,6 +874,7 @@ mod tests {
.await
.unwrap();
let buffer_id = buffer.read_with(cx, |buffer, _| buffer.remote_id());
+ let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
let git_blame = cx.new(|cx| GitBlame::new(buffer.clone(), project, false, true, cx));
@@ -806,14 +896,14 @@ mod tests {
)
.collect::<Vec<_>>(),
vec![
- Some(blame_entry("1b1b1b", 0..1)),
- Some(blame_entry("0d0d0d", 1..2)),
- Some(blame_entry("3a3a3a", 2..3)),
+ Some((buffer_id, blame_entry("1b1b1b", 0..1))),
+ Some((buffer_id, blame_entry("0d0d0d", 1..2))),
+ Some((buffer_id, blame_entry("3a3a3a", 2..3))),
None,
None,
- Some(blame_entry("3a3a3a", 5..6)),
- Some(blame_entry("0d0d0d", 6..7)),
- Some(blame_entry("3a3a3a", 7..8)),
+ Some((buffer_id, blame_entry("3a3a3a", 5..6))),
+ Some((buffer_id, blame_entry("0d0d0d", 6..7))),
+ Some((buffer_id, blame_entry("3a3a3a", 7..8))),
]
);
// Subset of lines
@@ -831,8 +921,8 @@ mod tests {
)
.collect::<Vec<_>>(),
vec![
- Some(blame_entry("0d0d0d", 1..2)),
- Some(blame_entry("3a3a3a", 2..3)),
+ Some((buffer_id, blame_entry("0d0d0d", 1..2))),
+ Some((buffer_id, blame_entry("3a3a3a", 2..3))),
None
]
);
@@ -852,7 +942,7 @@ mod tests {
cx
)
.collect::<Vec<_>>(),
- vec![Some(blame_entry("0d0d0d", 1..2)), None, None]
+ vec![Some((buffer_id, blame_entry("0d0d0d", 1..2))), None, None]
);
});
}
@@ -895,6 +985,7 @@ mod tests {
.await
.unwrap();
let buffer_id = buffer.read_with(cx, |buffer, _| buffer.remote_id());
+ let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
let git_blame = cx.new(|cx| GitBlame::new(buffer.clone(), project, false, true, cx));
@@ -1061,8 +1152,9 @@ mod tests {
})
.await
.unwrap();
+ let mbuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
- let git_blame = cx.new(|cx| GitBlame::new(buffer.clone(), project, false, true, cx));
+ let git_blame = cx.new(|cx| GitBlame::new(mbuffer.clone(), project, false, true, cx));
cx.executor().run_until_parked();
git_blame.update(cx, |blame, cx| blame.check_invariants(cx));
@@ -735,7 +735,7 @@ impl MultiBuffer {
pub fn as_singleton(&self) -> Option<Entity<Buffer>> {
if self.singleton {
- return Some(
+ Some(
self.buffers
.borrow()
.values()
@@ -743,7 +743,7 @@ impl MultiBuffer {
.unwrap()
.buffer
.clone(),
- );
+ )
} else {
None
}
@@ -2552,6 +2552,10 @@ impl MultiBuffer {
.collect()
}
+ pub fn all_buffer_ids(&self) -> Vec<BufferId> {
+ self.buffers.borrow().keys().copied().collect()
+ }
+
pub fn buffer(&self, buffer_id: BufferId) -> Option<Entity<Buffer>> {
self.buffers
.borrow()
@@ -5402,8 +5402,9 @@ mod tests {
init_test(cx);
let fs = FakeFs::new(cx.background_executor.clone());
- populate_with_test_ra_project(&fs, "/rust-analyzer").await;
- let project = Project::test(fs.clone(), ["/rust-analyzer".as_ref()], cx).await;
+ let root = path!("/rust-analyzer");
+ populate_with_test_ra_project(&fs, root).await;
+ let project = Project::test(fs.clone(), [Path::new(root)], cx).await;
project.read_with(cx, |project, _| {
project.languages().add(Arc::new(rust_lang()))
});
@@ -5448,15 +5449,16 @@ mod tests {
});
});
- let all_matches = r#"/rust-analyzer/
+ let all_matches = format!(
+ r#"{root}/
crates/
ide/src/
inlay_hints/
fn_lifetime_fn.rs
- search: match config.param_names_for_lifetime_elision_hints {
- search: allocated_lifetimes.push(if config.param_names_for_lifetime_elision_hints {
- search: Some(it) if config.param_names_for_lifetime_elision_hints => {
- search: InlayHintsConfig { param_names_for_lifetime_elision_hints: true, ..TEST_CONFIG },
+ search: match config.param_names_for_lifetime_elision_hints {{
+ search: allocated_lifetimes.push(if config.param_names_for_lifetime_elision_hints {{
+ search: Some(it) if config.param_names_for_lifetime_elision_hints => {{
+ search: InlayHintsConfig {{ param_names_for_lifetime_elision_hints: true, ..TEST_CONFIG }},
inlay_hints.rs
search: pub param_names_for_lifetime_elision_hints: bool,
search: param_names_for_lifetime_elision_hints: self
@@ -5467,7 +5469,9 @@ mod tests {
analysis_stats.rs
search: param_names_for_lifetime_elision_hints: true,
config.rs
- search: param_names_for_lifetime_elision_hints: self"#;
+ search: param_names_for_lifetime_elision_hints: self"#
+ );
+
let select_first_in_all_matches = |line_to_select: &str| {
assert!(all_matches.contains(line_to_select));
all_matches.replacen(
@@ -5524,7 +5528,7 @@ mod tests {
cx,
),
format!(
- r#"/rust-analyzer/
+ r#"{root}/
crates/
ide/src/
inlay_hints/
@@ -5594,7 +5598,7 @@ mod tests {
cx,
),
format!(
- r#"/rust-analyzer/
+ r#"{root}/
crates/
ide/src/{SELECTED_MARKER}
rust-analyzer/src/
@@ -5631,8 +5635,9 @@ mod tests {
init_test(cx);
let fs = FakeFs::new(cx.background_executor.clone());
- populate_with_test_ra_project(&fs, "/rust-analyzer").await;
- let project = Project::test(fs.clone(), ["/rust-analyzer".as_ref()], cx).await;
+ let root = path!("/rust-analyzer");
+ populate_with_test_ra_project(&fs, root).await;
+ let project = Project::test(fs.clone(), [Path::new(root)], cx).await;
project.read_with(cx, |project, _| {
project.languages().add(Arc::new(rust_lang()))
});
@@ -5676,15 +5681,16 @@ mod tests {
);
});
});
- let all_matches = r#"/rust-analyzer/
+ let all_matches = format!(
+ r#"{root}/
crates/
ide/src/
inlay_hints/
fn_lifetime_fn.rs
- search: match config.param_names_for_lifetime_elision_hints {
- search: allocated_lifetimes.push(if config.param_names_for_lifetime_elision_hints {
- search: Some(it) if config.param_names_for_lifetime_elision_hints => {
- search: InlayHintsConfig { param_names_for_lifetime_elision_hints: true, ..TEST_CONFIG },
+ search: match config.param_names_for_lifetime_elision_hints {{
+ search: allocated_lifetimes.push(if config.param_names_for_lifetime_elision_hints {{
+ search: Some(it) if config.param_names_for_lifetime_elision_hints => {{
+ search: InlayHintsConfig {{ param_names_for_lifetime_elision_hints: true, ..TEST_CONFIG }},
inlay_hints.rs
search: pub param_names_for_lifetime_elision_hints: bool,
search: param_names_for_lifetime_elision_hints: self
@@ -5695,7 +5701,8 @@ mod tests {
analysis_stats.rs
search: param_names_for_lifetime_elision_hints: true,
config.rs
- search: param_names_for_lifetime_elision_hints: self"#;
+ search: param_names_for_lifetime_elision_hints: self"#
+ );
cx.executor()
.advance_clock(UPDATE_DEBOUNCE + Duration::from_millis(100));
@@ -5768,8 +5775,9 @@ mod tests {
init_test(cx);
let fs = FakeFs::new(cx.background_executor.clone());
- populate_with_test_ra_project(&fs, path!("/rust-analyzer")).await;
- let project = Project::test(fs.clone(), [path!("/rust-analyzer").as_ref()], cx).await;
+ let root = path!("/rust-analyzer");
+ populate_with_test_ra_project(&fs, root).await;
+ let project = Project::test(fs.clone(), [Path::new(root)], cx).await;
project.read_with(cx, |project, _| {
project.languages().add(Arc::new(rust_lang()))
});
@@ -5813,9 +5821,8 @@ mod tests {
);
});
});
- let root_path = format!("{}/", path!("/rust-analyzer"));
let all_matches = format!(
- r#"{root_path}
+ r#"{root}/
crates/
ide/src/
inlay_hints/
@@ -5977,7 +5984,7 @@ mod tests {
let fs = FakeFs::new(cx.background_executor.clone());
fs.insert_tree(
- "/root",
+ path!("/root"),
json!({
"one": {
"a.txt": "aaa aaa"
@@ -5989,7 +5996,7 @@ mod tests {
}),
)
.await;
- let project = Project::test(fs.clone(), [Path::new("/root/one")], cx).await;
+ let project = Project::test(fs.clone(), [Path::new(path!("/root/one"))], cx).await;
let workspace = add_outline_panel(&project, cx).await;
let cx = &mut VisualTestContext::from_window(*workspace, cx);
let outline_panel = outline_panel(&workspace, cx);
@@ -6000,7 +6007,7 @@ mod tests {
let items = workspace
.update(cx, |workspace, window, cx| {
workspace.open_paths(
- vec![PathBuf::from("/root/two")],
+ vec![PathBuf::from(path!("/root/two"))],
OpenOptions {
visible: Some(OpenVisible::OnlyDirectories),
..Default::default()
@@ -6064,13 +6071,17 @@ mod tests {
outline_panel.selected_entry(),
cx,
),
- r#"/root/one/
+ format!(
+ r#"{}/
a.txt
search: aaa aaa <==== selected
search: aaa aaa
-/root/two/
+{}/
b.txt
- search: a aaa"#
+ search: a aaa"#,
+ path!("/root/one"),
+ path!("/root/two"),
+ ),
);
});
@@ -6090,11 +6101,15 @@ mod tests {
outline_panel.selected_entry(),
cx,
),
- r#"/root/one/
+ format!(
+ r#"{}/
a.txt <==== selected
-/root/two/
+{}/
b.txt
- search: a aaa"#
+ search: a aaa"#,
+ path!("/root/one"),
+ path!("/root/two"),
+ ),
);
});
@@ -6114,9 +6129,13 @@ mod tests {
outline_panel.selected_entry(),
cx,
),
- r#"/root/one/
+ format!(
+ r#"{}/
a.txt
-/root/two/ <==== selected"#
+{}/ <==== selected"#,
+ path!("/root/one"),
+ path!("/root/two"),
+ ),
);
});
@@ -6135,11 +6154,15 @@ mod tests {
outline_panel.selected_entry(),
cx,
),
- r#"/root/one/
+ format!(
+ r#"{}/
a.txt
-/root/two/ <==== selected
+{}/ <==== selected
b.txt
- search: a aaa"#
+ search: a aaa"#,
+ path!("/root/one"),
+ path!("/root/two"),
+ )
);
});
}
@@ -6165,7 +6188,7 @@ struct OutlineEntryExcerpt {
}),
)
.await;
- let project = Project::test(fs.clone(), [root.as_ref()], cx).await;
+ let project = Project::test(fs.clone(), [Path::new(root)], cx).await;
project.read_with(cx, |project, _| {
project.languages().add(Arc::new(
rust_lang()
@@ -6508,7 +6531,7 @@ outline: struct OutlineEntryExcerpt
async fn test_frontend_repo_structure(cx: &mut TestAppContext) {
init_test(cx);
- let root = "/frontend-project";
+ let root = path!("/frontend-project");
let fs = FakeFs::new(cx.background_executor.clone());
fs.insert_tree(
root,
@@ -6545,7 +6568,7 @@ outline: struct OutlineEntryExcerpt
}),
)
.await;
- let project = Project::test(fs.clone(), [root.as_ref()], cx).await;
+ let project = Project::test(fs.clone(), [Path::new(root)], cx).await;
let workspace = add_outline_panel(&project, cx).await;
let cx = &mut VisualTestContext::from_window(*workspace, cx);
let outline_panel = outline_panel(&workspace, cx);
@@ -6599,10 +6622,11 @@ outline: struct OutlineEntryExcerpt
outline_panel.selected_entry(),
cx,
),
- r#"/frontend-project/
+ format!(
+ r#"{root}/
public/lottie/
syntax-tree.json
- search: { "something": "static" } <==== selected
+ search: {{ "something": "static" }} <==== selected
src/
app/(site)/
(about)/jobs/[slug]/
@@ -6614,6 +6638,7 @@ outline: struct OutlineEntryExcerpt
components/
ErrorBoundary.tsx
search: static"#
+ )
);
});
@@ -6636,15 +6661,17 @@ outline: struct OutlineEntryExcerpt
outline_panel.selected_entry(),
cx,
),
- r#"/frontend-project/
+ format!(
+ r#"{root}/
public/lottie/
syntax-tree.json
- search: { "something": "static" }
+ search: {{ "something": "static" }}
src/
app/(site)/ <==== selected
components/
ErrorBoundary.tsx
search: static"#
+ )
);
});
@@ -6664,15 +6691,17 @@ outline: struct OutlineEntryExcerpt
outline_panel.selected_entry(),
cx,
),
- r#"/frontend-project/
+ format!(
+ r#"{root}/
public/lottie/
syntax-tree.json
- search: { "something": "static" }
+ search: {{ "something": "static" }}
src/
app/(site)/
components/
ErrorBoundary.tsx
search: static <==== selected"#
+ )
);
});
@@ -6696,14 +6725,16 @@ outline: struct OutlineEntryExcerpt
outline_panel.selected_entry(),
cx,
),
- r#"/frontend-project/
+ format!(
+ r#"{root}/
public/lottie/
syntax-tree.json
- search: { "something": "static" }
+ search: {{ "something": "static" }}
src/
app/(site)/
components/
ErrorBoundary.tsx <==== selected"#
+ )
);
});
@@ -6727,15 +6758,17 @@ outline: struct OutlineEntryExcerpt
outline_panel.selected_entry(),
cx,
),
- r#"/frontend-project/
+ format!(
+ r#"{root}/
public/lottie/
syntax-tree.json
- search: { "something": "static" }
+ search: {{ "something": "static" }}
src/
app/(site)/
components/
ErrorBoundary.tsx <==== selected
search: static"#
+ )
);
});
}
@@ -2566,10 +2566,7 @@ impl LocalLspStore {
};
let Ok(file_url) = lsp::Uri::from_file_path(old_path.as_path()) else {
- debug_panic!(
- "`{}` is not parseable as an URI",
- old_path.to_string_lossy()
- );
+ debug_panic!("{old_path:?} is not parseable as an URI");
return;
};
self.unregister_buffer_from_language_servers(buffer, &file_url, cx);
@@ -1384,6 +1384,9 @@ impl ProjectSearchView {
let match_ranges = self.entity.read(cx).match_ranges.clone();
if match_ranges.is_empty() {
self.active_match_index = None;
+ self.results_editor.update(cx, |editor, cx| {
+ editor.clear_background_highlights::<Self>(cx);
+ });
} else {
self.active_match_index = Some(0);
self.update_match_index(cx);
@@ -2338,7 +2341,7 @@ pub fn perform_project_search(
#[cfg(test)]
pub mod tests {
- use std::{ops::Deref as _, sync::Arc};
+ use std::{ops::Deref as _, sync::Arc, time::Duration};
use super::*;
use editor::{DisplayPoint, display_map::DisplayRow};
@@ -2381,6 +2384,7 @@ pub mod tests {
"\n\nconst THREE: usize = one::ONE + two::TWO;\n\n\nconst TWO: usize = one::ONE + one::ONE;"
);
let match_background_color = cx.theme().colors().search_match_background;
+ let selection_background_color = cx.theme().colors().editor_document_highlight_bracket_background;
assert_eq!(
search_view
.results_editor
@@ -2390,14 +2394,23 @@ pub mod tests {
DisplayPoint::new(DisplayRow(2), 32)..DisplayPoint::new(DisplayRow(2), 35),
match_background_color
),
+ (
+ DisplayPoint::new(DisplayRow(2), 37)..DisplayPoint::new(DisplayRow(2), 40),
+ selection_background_color
+ ),
(
DisplayPoint::new(DisplayRow(2), 37)..DisplayPoint::new(DisplayRow(2), 40),
match_background_color
),
+ (
+ DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 9),
+ selection_background_color
+ ),
(
DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 9),
match_background_color
- )
+ ),
+
]
);
assert_eq!(search_view.active_match_index, Some(0));
@@ -4156,6 +4169,10 @@ pub mod tests {
search_view.search(cx);
})
.unwrap();
+ // Ensure editor highlights appear after the search is done
+ cx.executor().advance_clock(
+ editor::SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT + Duration::from_millis(100),
+ );
cx.background_executor.run_until_parked();
}
}
@@ -2,6 +2,7 @@ use std::{cmp::Ordering, fmt::Debug};
use crate::{Bias, Dimension, Edit, Item, KeyedItem, SeekTarget, SumTree, Summary};
+/// A cheaply-clonable ordered map based on a [SumTree](crate::SumTree).
#[derive(Clone, PartialEq, Eq)]
pub struct TreeMap<K, V>(SumTree<MapEntry<K, V>>)
where