Task indicators in multibuffers (#11603)

Piotr Osiewicz created

Following #11487 the task indicators would no longer show up in
multibuffers.
Release Notes:

- N/A

Change summary

crates/editor/src/actions.rs            |  7 --
crates/editor/src/editor.rs             | 79 ++++++++++++++++----------
crates/editor/src/element.rs            | 49 +++++++++------
crates/multi_buffer/src/multi_buffer.rs |  8 +-
4 files changed, 83 insertions(+), 60 deletions(-)

Detailed changes

crates/editor/src/actions.rs 🔗

@@ -52,16 +52,11 @@ pub struct SelectToEndOfLine {
 
 #[derive(PartialEq, Clone, Deserialize, Default)]
 pub struct ToggleCodeActions {
+    // Display row from which the action was deployed.
     #[serde(default)]
     pub deployed_from_indicator: Option<u32>,
 }
 
-#[derive(PartialEq, Clone, Deserialize, Default)]
-pub struct ToggleTestRunner {
-    #[serde(default)]
-    pub deployed_from_row: Option<u32>,
-}
-
 #[derive(PartialEq, Clone, Deserialize, Default)]
 pub struct ConfirmCompletion {
     #[serde(default)]

crates/editor/src/editor.rs 🔗

@@ -504,7 +504,7 @@ pub struct Editor {
     >,
     last_bounds: Option<Bounds<Pixels>>,
     expect_bounds_change: Option<Bounds<Pixels>>,
-    tasks: HashMap<u32, RunnableTasks>,
+    tasks: HashMap<(BufferId, u32), (usize, RunnableTasks)>,
     tasks_update_task: Option<Task<()>>,
 }
 
@@ -3839,7 +3839,7 @@ impl Editor {
             }
         }
         drop(context_menu);
-
+        let snapshot = self.snapshot(cx);
         let deployed_from_indicator = action.deployed_from_indicator;
         let mut task = self.code_actions_task.take();
         let action = action.clone();
@@ -3851,11 +3851,24 @@ impl Editor {
 
             let spawned_test_task = this.update(&mut cx, |this, cx| {
                 if this.focus_handle.is_focused(cx) {
-                    let buffer_row = action
+                    let display_row = action
                         .deployed_from_indicator
+                        .map(|row| {
+                            DisplayPoint::new(row, 0)
+                                .to_point(&snapshot.display_snapshot)
+                                .row
+                        })
                         .unwrap_or_else(|| this.selections.newest::<Point>(cx).head().row);
-                    let tasks = this.tasks.get(&buffer_row).map(|t| Arc::new(t.to_owned()));
-                    let (location, code_actions) = this
+                    let (buffer, buffer_row) = snapshot
+                        .buffer_snapshot
+                        .buffer_line_for_row(display_row)
+                        .and_then(|(buffer_snapshot, range)| {
+                            this.buffer
+                                .read(cx)
+                                .buffer(buffer_snapshot.remote_id())
+                                .map(|buffer| (buffer, range.start.row))
+                        })?;
+                    let (_, code_actions) = this
                         .available_code_actions
                         .clone()
                         .and_then(|(location, code_actions)| {
@@ -3869,25 +3882,20 @@ impl Editor {
                             }
                         })
                         .unzip();
+                    let buffer_id = buffer.read(cx).remote_id();
+                    let tasks = this
+                        .tasks
+                        .get(&(buffer_id, buffer_row))
+                        .map(|t| Arc::new(t.to_owned()));
                     if tasks.is_none() && code_actions.is_none() {
                         return None;
                     }
 
-                    let buffer = location.map(|location| location.buffer).or_else(|| {
-                        let snapshot = this.snapshot(cx);
-                        let (buffer_snapshot, _) =
-                            snapshot.buffer_snapshot.buffer_line_for_row(buffer_row)?;
-                        let buffer_id = buffer_snapshot.remote_id();
-                        this.buffer().read(cx).buffer(buffer_id)
-                    });
-                    let Some(buffer) = buffer else {
-                        return None;
-                    };
                     this.completion_tasks.clear();
                     this.discard_inline_completion(cx);
                     let task_context = tasks.as_ref().zip(this.workspace.clone()).and_then(
                         |(tasks, (workspace, _))| {
-                            let position = Point::new(buffer_row, tasks.column);
+                            let position = Point::new(buffer_row, tasks.1.column);
                             let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
                             let location = Location {
                                 buffer: buffer.clone(),
@@ -3906,6 +3914,7 @@ impl Editor {
                         .map(|(tasks, task_context)| {
                             Arc::new(ResolvedTasks {
                                 templates: tasks
+                                    .1
                                     .templates
                                     .iter()
                                     .filter_map(|(kind, template)| {
@@ -3914,7 +3923,7 @@ impl Editor {
                                             .map(|task| (kind.clone(), task))
                                     })
                                     .collect(),
-                                position: Point::new(buffer_row, tasks.column),
+                                position: Point::new(buffer_row, tasks.1.column),
                             })
                         });
                     let spawn_straight_away = tasks
@@ -4505,8 +4514,8 @@ impl Editor {
         self.tasks.clear()
     }
 
-    fn insert_tasks(&mut self, row: u32, tasks: RunnableTasks) {
-        if let Some(_) = self.tasks.insert(row, tasks) {
+    fn insert_tasks(&mut self, key: (BufferId, u32), value: (usize, RunnableTasks)) {
+        if let Some(_) = self.tasks.insert(key, value) {
             // This case should hopefully be rare, but just in case...
             log::error!("multiple different run targets found on a single line, only the last target will be rendered")
         }
@@ -7726,8 +7735,8 @@ impl Editor {
 
             this.update(&mut cx, |this, _| {
                 this.clear_tasks();
-                for (row, tasks) in rows {
-                    this.insert_tasks(row, tasks);
+                for (key, value) in rows {
+                    this.insert_tasks(key, value);
                 }
             })
             .ok();
@@ -7736,19 +7745,19 @@ impl Editor {
     fn fetch_runnable_ranges(
         snapshot: &DisplaySnapshot,
         range: Range<Anchor>,
-    ) -> Vec<(Range<usize>, Runnable)> {
+    ) -> Vec<(BufferId, Range<usize>, Runnable)> {
         snapshot.buffer_snapshot.runnable_ranges(range).collect()
     }
 
     fn runnable_rows(
         project: Model<Project>,
         snapshot: DisplaySnapshot,
-        runnable_ranges: Vec<(Range<usize>, Runnable)>,
+        runnable_ranges: Vec<(BufferId, Range<usize>, Runnable)>,
         mut cx: AsyncWindowContext,
-    ) -> Vec<(u32, RunnableTasks)> {
+    ) -> Vec<((BufferId, u32), (usize, RunnableTasks))> {
         runnable_ranges
             .into_iter()
-            .filter_map(|(multi_buffer_range, mut runnable)| {
+            .filter_map(|(buffer_id, multi_buffer_range, mut runnable)| {
                 let (tasks, _) = cx
                     .update(|cx| Self::resolve_runnable(project.clone(), &mut runnable, cx))
                     .ok()?;
@@ -7756,12 +7765,21 @@ impl Editor {
                     return None;
                 }
                 let point = multi_buffer_range.start.to_point(&snapshot.buffer_snapshot);
+                let row = snapshot
+                    .buffer_snapshot
+                    .buffer_line_for_row(point.row)?
+                    .1
+                    .start
+                    .row;
                 Some((
-                    point.row,
-                    RunnableTasks {
-                        templates: tasks,
-                        column: point.column,
-                    },
+                    (buffer_id, row),
+                    (
+                        multi_buffer_range.start,
+                        RunnableTasks {
+                            templates: tasks,
+                            column: point.column,
+                        },
+                    ),
                 ))
             })
             .collect()
@@ -10102,6 +10120,7 @@ impl Editor {
                 predecessor,
                 excerpts,
             } => {
+                self.tasks_update_task = Some(self.refresh_runnables(cx));
                 cx.emit(EditorEvent::ExcerptsAdded {
                     buffer: buffer.clone(),
                     predecessor: *predecessor,

crates/editor/src/element.rs 🔗

@@ -1406,20 +1406,24 @@ impl EditorElement {
                 };
             editor
                 .tasks
-                .keys()
-                .filter_map(|row| {
+                .iter()
+                .filter_map(|((_, row), (multibuffer_offset, _))| {
                     if snapshot.is_line_folded(*row) {
                         return None;
                     }
+                    let display_row = snapshot
+                        .buffer_snapshot
+                        .offset_to_point(*multibuffer_offset)
+                        .to_display_point(&snapshot.display_snapshot)
+                        .row();
+
                     let button = editor.render_run_indicator(
                         &self.style,
                         Some(*row) == active_task_indicator_row,
-                        *row,
+                        display_row,
                         cx,
                     );
-                    let display_row = Point::new(*row, 0)
-                        .to_display_point(&snapshot.display_snapshot)
-                        .row();
+
                     let button = prepaint_gutter_button(
                         button,
                         display_row,
@@ -4035,20 +4039,25 @@ impl Element for EditorElement {
                         if gutter_settings.code_actions {
                             let newest_selection_point =
                                 newest_selection_head.to_point(&snapshot.display_snapshot);
-                            let has_test_indicator = self
-                                .editor
-                                .read(cx)
-                                .tasks
-                                .contains_key(&newest_selection_point.row);
-                            if !has_test_indicator {
-                                code_actions_indicator = self.layout_code_actions_indicator(
-                                    line_height,
-                                    newest_selection_head,
-                                    scroll_pixel_position,
-                                    &gutter_dimensions,
-                                    &gutter_hitbox,
-                                    cx,
-                                );
+                            let buffer = snapshot
+                                .buffer_snapshot
+                                .buffer_line_for_row(newest_selection_point.row);
+                            if let Some((buffer, range)) = buffer {
+                                let buffer_id = buffer.remote_id();
+                                let row = range.start.row;
+                                let has_test_indicator =
+                                    self.editor.read(cx).tasks.contains_key(&(buffer_id, row));
+
+                                if !has_test_indicator {
+                                    code_actions_indicator = self.layout_code_actions_indicator(
+                                        line_height,
+                                        newest_selection_head,
+                                        scroll_pixel_position,
+                                        &gutter_dimensions,
+                                        &gutter_hitbox,
+                                        cx,
+                                    );
+                                }
                             }
                         }
                     }

crates/multi_buffer/src/multi_buffer.rs 🔗

@@ -3168,7 +3168,7 @@ impl MultiBufferSnapshot {
     pub fn runnable_ranges(
         &self,
         range: Range<Anchor>,
-    ) -> impl Iterator<Item = (Range<usize>, Runnable)> + '_ {
+    ) -> impl Iterator<Item = (BufferId, Range<usize>, Runnable)> + '_ {
         let range = range.start.to_offset(self)..range.end.to_offset(self);
         self.excerpts_for_range(range.clone())
             .flat_map(move |(excerpt, excerpt_offset)| {
@@ -3183,10 +3183,10 @@ impl MultiBufferSnapshot {
                             excerpt_offset + (match_range.start - excerpt_buffer_start);
                         match_range.end = excerpt_offset + (match_range.end - excerpt_buffer_start);
 
-                        (match_range, runnable)
+                        (excerpt.buffer_id, match_range, runnable)
                     })
-                    .skip_while(move |(match_range, _)| match_range.end < range.start)
-                    .take_while(move |(match_range, _)| match_range.start < range.end)
+                    .skip_while(move |(_, match_range, _)| match_range.end < range.start)
+                    .take_while(move |(_, match_range, _)| match_range.start < range.end)
             })
     }