use std::{collections::BTreeMap, mem, ops::Range, sync::Arc}; use clock::Global;use collections::{HashMap, HashSet};use gpui::{ App, AppContext as _, AsyncWindowContext, ClickEvent, Context, Entity, Focusable as _, MouseButton, Task, Window,};use language::{Buffer, BufferRow, Runnable};use lsp::LanguageServerName;use multi_buffer::{Anchor, BufferOffset, MultiBufferRow, MultiBufferSnapshot, ToPoint as _};use project::{Location, Project, TaskSourceKind, project_settings::ProjectSettings};use settings::Settings as _;use smallvec::SmallVec;use task::{ResolvedTask, RunnableTag, TaskContext, TaskTemplate, TaskVariables, VariableName};use text::{BufferId, OffsetRangeExt as _, ToOffset as _, ToPoint as _};use ui::{Clickable as _, Color, IconButton, IconSize, Toggleable as _}; use crate::{ CodeActionSource, Editor, EditorSettings, EditorStyle, RangeToAnchorExt, SpawnNearestTask, ToggleCodeActions, UPDATE_DEBOUNCE, display_map::{DisplayPoint, DisplayRow},}; #[derive(Debug)]pub(super) struct RunnableData { runnables: HashMap<BufferId, (Global, BTreeMap<BufferRow, RunnableTasks>)>, task_statuses: HashMap<(BufferId, BufferRow), RunnableTaskStatus>, invalidate_buffer_data: HashSet<BufferId>, runnables_update_task: Task<()>,} impl RunnableData { pub fn new() -> Self { Self { runnables: HashMap::default(), task_statuses: HashMap::default(), invalidate_buffer_data: HashSet::default(), runnables_update_task: Task::ready(()), } } pub fn runnables( &self, (buffer_id, buffer_row): (BufferId, BufferRow), ) -> Option<&RunnableTasks> { self.runnables.get(&buffer_id)?.1.get(&buffer_row) } pub fn all_runnables(&self) -> impl Iterator<Item = &RunnableTasks> { self.runnables .values() .flat_map(|(_, tasks)| tasks.values()) } pub fn task_status( &self, (buffer_id, buffer_row): (BufferId, BufferRow), ) -> Option<RunnableTaskStatus> { self.task_statuses.get(&(buffer_id, buffer_row)).copied() } pub fn set_task_status( &mut self, (buffer_id, buffer_row): (BufferId, BufferRow), status: RunnableTaskStatus, ) { self.task_statuses.insert((buffer_id, buffer_row), status); } pub fn clear_task_status(&mut self, (buffer_id, buffer_row): (BufferId, BufferRow)) { self.task_statuses.remove(&(buffer_id, buffer_row)); } pub fn task_key_for_offset( &self, buffer_id: BufferId, offset: BufferOffset, ) -> Option<(BufferId, BufferRow)> { let (_, tasks_by_row) = self.runnables.get(&buffer_id)?; tasks_by_row .iter() .find(|(_, tasks)| tasks.context_range.contains(&offset)) .or_else(|| { tasks_by_row .iter() .find(|(_, tasks)| tasks.context_range.start >= offset) }) .map(|(row, _)| (buffer_id, *row)) } pub fn has_cached(&self, buffer_id: BufferId, version: &Global) -> bool { self.runnables .get(&buffer_id) .is_some_and(|(cached_version, _)| !version.changed_since(cached_version)) } #[cfg(test)] pub fn insert( &mut self, buffer_id: BufferId, buffer_row: BufferRow, version: Global, tasks: RunnableTasks, ) { self.runnables .entry(buffer_id) .or_insert_with(|| (version, BTreeMap::default())) .1 .insert(buffer_row, tasks); }} #[derive(Clone, Copy, Debug, PartialEq, Eq)]pub(crate) enum RunnableTaskStatus { Running, Passed, Failed,} impl From<workspace::tasks::ScheduledTaskResult> for RunnableTaskStatus { fn from(result: workspace::tasks::ScheduledTaskResult) -> Self { match result { workspace::tasks::ScheduledTaskResult::Success => Self::Passed, workspace::tasks::ScheduledTaskResult::Failure | workspace::tasks::ScheduledTaskResult::SpawnFailed | workspace::tasks::ScheduledTaskResult::Cancelled => Self::Failed, } }} #[derive(Clone, Debug)]pub struct RunnableTasks { pub templates: Vec<(TaskSourceKind, TaskTemplate)>, pub offset: multi_buffer::Anchor, // We need the column at which the task context evaluation should take place (when we're spawning it via gutter). pub column: u32, // Values of all named captures, including those starting with '_' pub extra_variables: HashMap<String, String>, // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal. pub context_range: Range<BufferOffset>,} impl RunnableTasks { pub fn resolve<'a>( &'a self, cx: &'a task::TaskContext, ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a { self.templates.iter().filter_map(|(kind, template)| { template .resolve_task(&kind.to_id_base(), cx) .map(|task| (kind.clone(), task)) }) }} #[derive(Clone)]pub struct ResolvedTasks { pub templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>, pub position: Anchor,} impl Editor { pub fn refresh_runnables( &mut self, invalidate_buffer_data: Option<BufferId>, window: &mut Window, cx: &mut Context<Self>, ) { if !self.mode().is_full() || !EditorSettings::get_global(cx).gutter.runnables || !self.enable_runnables { self.clear_runnables(None); return; } if let Some(buffer) = self.buffer().read(cx).as_singleton() { let buffer_read = buffer.read(cx); if buffer_read.file().is_none() { self.clear_runnables(None); return; } let buffer_id = buffer_read.remote_id(); if invalidate_buffer_data != Some(buffer_id) && self.runnables.has_cached(buffer_id, &buffer_read.version()) { return; } } if let Some(buffer_id) = invalidate_buffer_data { self.runnables.invalidate_buffer_data.insert(buffer_id); } let project = self.project().map(Entity::downgrade); let lsp_task_sources = self.lsp_task_sources(true, true, cx); let multi_buffer = self.buffer.downgrade(); self.runnables.runnables_update_task = cx.spawn_in(window, async move |editor, cx| { cx.background_executor().timer(UPDATE_DEBOUNCE).await; let Some(project) = project.and_then(|p| p.upgrade()) else { return; }; let hide_runnables = project.update(cx, |project, _| project.is_via_collab()); if hide_runnables { return; } let lsp_tasks = if lsp_task_sources.is_empty() { Vec::new() } else { let Ok(lsp_tasks) = cx .update(|_, cx| crate::lsp_tasks(project.clone(), &lsp_task_sources, None, cx)) else { return; }; lsp_tasks.await }; let new_rows = { let Some((multi_buffer_snapshot, multi_buffer_query_range)) = 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)) } else { let display_snapshot = editor.display_map.update(cx, |map, cx| map.snapshot(cx)); let multi_buffer_query_range = editor.multi_buffer_visible_range(&display_snapshot, cx); let multi_buffer_snapshot = display_snapshot.buffer(); Some(( multi_buffer_snapshot.clone(), multi_buffer_query_range.to_anchors(&multi_buffer_snapshot), )) } }) .ok() .flatten() else { return; }; cx.background_spawn({ async move { multi_buffer_snapshot .runnable_ranges(multi_buffer_query_range) .collect() } }) .await }; let Ok(multi_buffer_snapshot) = editor.update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx)) else { return; }; let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| { lsp_tasks .into_iter() .flat_map(|(kind, tasks)| { tasks.into_iter().filter_map(move |(location, task)| { Some((kind.clone(), location?, task)) }) }) .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.anchor_in_excerpt(location.target.range.start); if let Some(offset) = offset { let task_buffer_range = location.target.range.to_point(&buffer_snapshot); let context_buffer_range = task_buffer_range.to_offset(&buffer_snapshot); let context_range = BufferOffset(context_buffer_range.start) ..BufferOffset(context_buffer_range.end); acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row)) .or_insert_with(|| RunnableTasks { templates: Vec::new(), offset, column: task_buffer_range.start.column, extra_variables: HashMap::default(), context_range, }) .templates .push((kind, task.original_task().clone())); } acc }) }) else { return; }; let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| { buffer.language_settings(cx).tasks.prefer_lsp }) else { return; }; let rows = Self::runnable_rows( project, multi_buffer_snapshot, prefer_lsp && !lsp_tasks_by_rows.is_empty(), new_rows, cx.clone(), ) .await; editor .update(cx, |editor, cx| { for buffer_id in std::mem::take(&mut editor.runnables.invalidate_buffer_data) { editor.clear_runnables(Some(buffer_id)); } for ((buffer_id, row), mut new_tasks) in rows { let Some(buffer) = editor.buffer().read(cx).buffer(buffer_id) else { continue; }; if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&(buffer_id, row)) { new_tasks.templates.extend(lsp_tasks.templates); } editor.insert_runnables( buffer_id, buffer.read(cx).version(), row, new_tasks, ); } for ((buffer_id, row), new_tasks) in lsp_tasks_by_rows { let Some(buffer) = editor.buffer().read(cx).buffer(buffer_id) else { continue; }; editor.insert_runnables( buffer_id, buffer.read(cx).version(), row, new_tasks, ); } }) .ok(); }); } pub fn spawn_nearest_task( &mut self, action: &SpawnNearestTask, window: &mut Window, cx: &mut Context<Self>, ) { let Some((workspace, _)) = self.workspace.clone() else { return; }; let Some(project) = self.project.clone() else { return; }; // Try to find a closest, enclosing node using tree-sitter that has a task let Some((buffer, buffer_row, tasks)) = self .find_enclosing_node_task(cx) // Or find the task that's closest in row-distance. .or_else(|| self.find_closest_task(cx)) else { return; }; let buffer_id = buffer.read(cx).remote_id(); let editor = cx.weak_entity(); let reveal_strategy = action.reveal; let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx); cx.spawn_in(window, async move |_, cx| { let context = task_context.await.ok().flatten()?; let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?; let resolved = &mut resolved_task.resolved; resolved.reveal = reveal_strategy; editor .update(cx, |editor, cx| { editor.set_runnable_task_status( buffer_id, buffer_row, RunnableTaskStatus::Running, cx, ); }) .ok(); workspace .update_in(cx, |workspace, window, cx| { workspace.schedule_resolved_task_with_completion( task_source_kind, resolved_task, false, move |result, cx| { editor .update(cx, |editor, cx| { editor.set_runnable_task_status( buffer_id, buffer_row, RunnableTaskStatus::from(result), cx, ); }) .ok(); }, window, cx, ); }) .ok() }) .detach(); } pub fn clear_runnables(&mut self, for_buffer: Option<BufferId>) { if let Some(buffer_id) = for_buffer { self.runnables.runnables.remove(&buffer_id); self.runnables .task_statuses .retain(|(status_buffer_id, _), _| *status_buffer_id != buffer_id); } else { self.runnables.runnables.clear(); self.runnables.task_statuses.clear(); } self.runnables.invalidate_buffer_data.clear(); self.runnables.runnables_update_task = Task::ready(()); } pub(crate) fn runnable_task_status( &self, buffer_id: BufferId, buffer_row: BufferRow, ) -> Option<RunnableTaskStatus> { self.runnables.task_status((buffer_id, buffer_row)) } pub(crate) fn set_runnable_task_status( &mut self, buffer_id: BufferId, buffer_row: BufferRow, status: RunnableTaskStatus, cx: &mut Context<Self>, ) { self.runnables .set_task_status((buffer_id, buffer_row), status); cx.notify(); } pub(crate) fn clear_runnable_task_status( &mut self, buffer_id: BufferId, buffer_row: BufferRow, cx: &mut Context<Self>, ) { self.runnables.clear_task_status((buffer_id, buffer_row)); cx.notify(); } pub(crate) fn runnable_task_key_for_display_row( &self, display_row: DisplayRow, window: &mut Window, cx: &mut Context<Self>, ) -> Option<(BufferId, BufferRow)> { let snapshot = self.snapshot(window, cx); let multibuffer_row = MultiBufferRow(DisplayPoint::new(display_row, 0).to_point(&snapshot).row); let (buffer_snapshot, range) = snapshot .buffer_snapshot() .buffer_line_for_row(multibuffer_row)?; Some((buffer_snapshot.remote_id(), range.start.row)) } pub(crate) fn runnable_task_key_for_offset( &self, buffer_id: BufferId, offset: BufferOffset, ) -> Option<(BufferId, BufferRow)> { self.runnables.task_key_for_offset(buffer_id, offset) } 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 (selection, buffer, editor_snapshot) = { let selection = self.selections.newest_adjusted(&self.display_snapshot(cx)); let Some((buffer, _)) = self .buffer() .read(cx) .point_to_buffer_offset(selection.start, cx) else { return Task::ready(None); }; let snapshot = self.snapshot(window, cx); (selection, buffer, snapshot) }; let selection_range = selection.range(); let Some((_, range)) = editor_snapshot .display_snapshotLines 1–500 of 1479. Load more
use std::{collections::BTreeMap, mem, ops::Range, sync::Arc}; use clock::Global; use collections::{HashMap, HashSet}; use gpui::{ App, AppContext as _, AsyncWindowContext, ClickEvent, Context, Entity, Focusable as _, MouseButton, Task, Window, }; use language::{Buffer, BufferRow, Runnable}; use lsp::LanguageServerName; use multi_buffer::{Anchor, BufferOffset, MultiBufferRow, MultiBufferSnapshot, ToPoint as _}; use project::{Location, Project, TaskSourceKind, project_settings::ProjectSettings}; use settings::Settings as _; use smallvec::SmallVec; use task::{ResolvedTask, RunnableTag, TaskContext, TaskTemplate, TaskVariables, VariableName}; use text::{BufferId, OffsetRangeExt as _, ToOffset as _, ToPoint as _}; use ui::{Clickable as _, Color, IconButton, IconSize, Toggleable as _}; use crate::{ CodeActionSource, Editor, EditorSettings, EditorStyle, RangeToAnchorExt, SpawnNearestTask, ToggleCodeActions, UPDATE_DEBOUNCE, display_map::{DisplayPoint, DisplayRow}, }; #[derive(Debug)] pub(super) struct RunnableData { runnables: HashMap<BufferId, (Global, BTreeMap<BufferRow, RunnableTasks>)>, task_statuses: HashMap<(BufferId, BufferRow), RunnableTaskStatus>, invalidate_buffer_data: HashSet<BufferId>, runnables_update_task: Task<()>, } impl RunnableData { pub fn new() -> Self { Self { runnables: HashMap::default(), task_statuses: HashMap::default(), invalidate_buffer_data: HashSet::default(), runnables_update_task: Task::ready(()), } } pub fn runnables( &self, (buffer_id, buffer_row): (BufferId, BufferRow), ) -> Option<&RunnableTasks> { self.runnables.get(&buffer_id)?.1.get(&buffer_row) } pub fn all_runnables(&self) -> impl Iterator<Item = &RunnableTasks> { self.runnables .values() .flat_map(|(_, tasks)| tasks.values()) } pub fn task_status( &self, (buffer_id, buffer_row): (BufferId, BufferRow), ) -> Option<RunnableTaskStatus> { self.task_statuses.get(&(buffer_id, buffer_row)).copied() } pub fn set_task_status( &mut self, (buffer_id, buffer_row): (BufferId, BufferRow), status: RunnableTaskStatus, ) { self.task_statuses.insert((buffer_id, buffer_row), status); } pub fn clear_task_status(&mut self, (buffer_id, buffer_row): (BufferId, BufferRow)) { self.task_statuses.remove(&(buffer_id, buffer_row)); } pub fn task_key_for_offset( &self, buffer_id: BufferId, offset: BufferOffset, ) -> Option<(BufferId, BufferRow)> { let (_, tasks_by_row) = self.runnables.get(&buffer_id)?; tasks_by_row .iter() .find(|(_, tasks)| tasks.context_range.contains(&offset)) .or_else(|| { tasks_by_row .iter() .find(|(_, tasks)| tasks.context_range.start >= offset) }) .map(|(row, _)| (buffer_id, *row)) } pub fn has_cached(&self, buffer_id: BufferId, version: &Global) -> bool { self.runnables .get(&buffer_id) .is_some_and(|(cached_version, _)| !version.changed_since(cached_version)) } #[cfg(test)] pub fn insert( &mut self, buffer_id: BufferId, buffer_row: BufferRow, version: Global, tasks: RunnableTasks, ) { self.runnables .entry(buffer_id) .or_insert_with(|| (version, BTreeMap::default())) .1 .insert(buffer_row, tasks); } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub(crate) enum RunnableTaskStatus { Running, Passed, Failed, } impl From<workspace::tasks::ScheduledTaskResult> for RunnableTaskStatus { fn from(result: workspace::tasks::ScheduledTaskResult) -> Self { match result { workspace::tasks::ScheduledTaskResult::Success => Self::Passed, workspace::tasks::ScheduledTaskResult::Failure | workspace::tasks::ScheduledTaskResult::SpawnFailed | workspace::tasks::ScheduledTaskResult::Cancelled => Self::Failed, } } } #[derive(Clone, Debug)] pub struct RunnableTasks { pub templates: Vec<(TaskSourceKind, TaskTemplate)>, pub offset: multi_buffer::Anchor, // We need the column at which the task context evaluation should take place (when we're spawning it via gutter). pub column: u32, // Values of all named captures, including those starting with '_' pub extra_variables: HashMap<String, String>, // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal. pub context_range: Range<BufferOffset>, } impl RunnableTasks { pub fn resolve<'a>( &'a self, cx: &'a task::TaskContext, ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a { self.templates.iter().filter_map(|(kind, template)| { template .resolve_task(&kind.to_id_base(), cx) .map(|task| (kind.clone(), task)) }) } } #[derive(Clone)] pub struct ResolvedTasks { pub templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>, pub position: Anchor, } impl Editor { pub fn refresh_runnables( &mut self, invalidate_buffer_data: Option<BufferId>, window: &mut Window, cx: &mut Context<Self>, ) { if !self.mode().is_full() || !EditorSettings::get_global(cx).gutter.runnables || !self.enable_runnables { self.clear_runnables(None); return; } if let Some(buffer) = self.buffer().read(cx).as_singleton() { let buffer_read = buffer.read(cx); if buffer_read.file().is_none() { self.clear_runnables(None); return; } let buffer_id = buffer_read.remote_id(); if invalidate_buffer_data != Some(buffer_id) && self.runnables.has_cached(buffer_id, &buffer_read.version()) { return; } } if let Some(buffer_id) = invalidate_buffer_data { self.runnables.invalidate_buffer_data.insert(buffer_id); } let project = self.project().map(Entity::downgrade); let lsp_task_sources = self.lsp_task_sources(true, true, cx); let multi_buffer = self.buffer.downgrade(); self.runnables.runnables_update_task = cx.spawn_in(window, async move |editor, cx| { cx.background_executor().timer(UPDATE_DEBOUNCE).await; let Some(project) = project.and_then(|p| p.upgrade()) else { return; }; let hide_runnables = project.update(cx, |project, _| project.is_via_collab()); if hide_runnables { return; } let lsp_tasks = if lsp_task_sources.is_empty() { Vec::new() } else { let Ok(lsp_tasks) = cx .update(|_, cx| crate::lsp_tasks(project.clone(), &lsp_task_sources, None, cx)) else { return; }; lsp_tasks.await }; let new_rows = { let Some((multi_buffer_snapshot, multi_buffer_query_range)) = 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)) } else { let display_snapshot = editor.display_map.update(cx, |map, cx| map.snapshot(cx)); let multi_buffer_query_range = editor.multi_buffer_visible_range(&display_snapshot, cx); let multi_buffer_snapshot = display_snapshot.buffer(); Some(( multi_buffer_snapshot.clone(), multi_buffer_query_range.to_anchors(&multi_buffer_snapshot), )) } }) .ok() .flatten() else { return; }; cx.background_spawn({ async move { multi_buffer_snapshot .runnable_ranges(multi_buffer_query_range) .collect() } }) .await }; let Ok(multi_buffer_snapshot) = editor.update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx)) else { return; }; let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| { lsp_tasks .into_iter() .flat_map(|(kind, tasks)| { tasks.into_iter().filter_map(move |(location, task)| { Some((kind.clone(), location?, task)) }) }) .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.anchor_in_excerpt(location.target.range.start); if let Some(offset) = offset { let task_buffer_range = location.target.range.to_point(&buffer_snapshot); let context_buffer_range = task_buffer_range.to_offset(&buffer_snapshot); let context_range = BufferOffset(context_buffer_range.start) ..BufferOffset(context_buffer_range.end); acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row)) .or_insert_with(|| RunnableTasks { templates: Vec::new(), offset, column: task_buffer_range.start.column, extra_variables: HashMap::default(), context_range, }) .templates .push((kind, task.original_task().clone())); } acc }) }) else { return; }; let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| { buffer.language_settings(cx).tasks.prefer_lsp }) else { return; }; let rows = Self::runnable_rows( project, multi_buffer_snapshot, prefer_lsp && !lsp_tasks_by_rows.is_empty(), new_rows, cx.clone(), ) .await; editor .update(cx, |editor, cx| { for buffer_id in std::mem::take(&mut editor.runnables.invalidate_buffer_data) { editor.clear_runnables(Some(buffer_id)); } for ((buffer_id, row), mut new_tasks) in rows { let Some(buffer) = editor.buffer().read(cx).buffer(buffer_id) else { continue; }; if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&(buffer_id, row)) { new_tasks.templates.extend(lsp_tasks.templates); } editor.insert_runnables( buffer_id, buffer.read(cx).version(), row, new_tasks, ); } for ((buffer_id, row), new_tasks) in lsp_tasks_by_rows { let Some(buffer) = editor.buffer().read(cx).buffer(buffer_id) else { continue; }; editor.insert_runnables( buffer_id, buffer.read(cx).version(), row, new_tasks, ); } }) .ok(); }); } pub fn spawn_nearest_task( &mut self, action: &SpawnNearestTask, window: &mut Window, cx: &mut Context<Self>, ) { let Some((workspace, _)) = self.workspace.clone() else { return; }; let Some(project) = self.project.clone() else { return; }; // Try to find a closest, enclosing node using tree-sitter that has a task let Some((buffer, buffer_row, tasks)) = self .find_enclosing_node_task(cx) // Or find the task that's closest in row-distance. .or_else(|| self.find_closest_task(cx)) else { return; }; let buffer_id = buffer.read(cx).remote_id(); let editor = cx.weak_entity(); let reveal_strategy = action.reveal; let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx); cx.spawn_in(window, async move |_, cx| { let context = task_context.await.ok().flatten()?; let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?; let resolved = &mut resolved_task.resolved; resolved.reveal = reveal_strategy; editor .update(cx, |editor, cx| { editor.set_runnable_task_status( buffer_id, buffer_row, RunnableTaskStatus::Running, cx, ); }) .ok(); workspace .update_in(cx, |workspace, window, cx| { workspace.schedule_resolved_task_with_completion( task_source_kind, resolved_task, false, move |result, cx| { editor .update(cx, |editor, cx| { editor.set_runnable_task_status( buffer_id, buffer_row, RunnableTaskStatus::from(result), cx, ); }) .ok(); }, window, cx, ); }) .ok() }) .detach(); } pub fn clear_runnables(&mut self, for_buffer: Option<BufferId>) { if let Some(buffer_id) = for_buffer { self.runnables.runnables.remove(&buffer_id); self.runnables .task_statuses .retain(|(status_buffer_id, _), _| *status_buffer_id != buffer_id); } else { self.runnables.runnables.clear(); self.runnables.task_statuses.clear(); } self.runnables.invalidate_buffer_data.clear(); self.runnables.runnables_update_task = Task::ready(()); } pub(crate) fn runnable_task_status( &self, buffer_id: BufferId, buffer_row: BufferRow, ) -> Option<RunnableTaskStatus> { self.runnables.task_status((buffer_id, buffer_row)) } pub(crate) fn set_runnable_task_status( &mut self, buffer_id: BufferId, buffer_row: BufferRow, status: RunnableTaskStatus, cx: &mut Context<Self>, ) { self.runnables .set_task_status((buffer_id, buffer_row), status); cx.notify(); } pub(crate) fn clear_runnable_task_status( &mut self, buffer_id: BufferId, buffer_row: BufferRow, cx: &mut Context<Self>, ) { self.runnables.clear_task_status((buffer_id, buffer_row)); cx.notify(); } pub(crate) fn runnable_task_key_for_display_row( &self, display_row: DisplayRow, window: &mut Window, cx: &mut Context<Self>, ) -> Option<(BufferId, BufferRow)> { let snapshot = self.snapshot(window, cx); let multibuffer_row = MultiBufferRow(DisplayPoint::new(display_row, 0).to_point(&snapshot).row); let (buffer_snapshot, range) = snapshot .buffer_snapshot() .buffer_line_for_row(multibuffer_row)?; Some((buffer_snapshot.remote_id(), range.start.row)) } pub(crate) fn runnable_task_key_for_offset( &self, buffer_id: BufferId, offset: BufferOffset, ) -> Option<(BufferId, BufferRow)> { self.runnables.task_key_for_offset(buffer_id, offset) } 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 (selection, buffer, editor_snapshot) = { let selection = self.selections.newest_adjusted(&self.display_snapshot(cx)); let Some((buffer, _)) = self .buffer() .read(cx) .point_to_buffer_offset(selection.start, cx) else { return Task::ready(None); }; let snapshot = self.snapshot(window, cx); (selection, buffer, snapshot) }; let selection_range = selection.range(); let Some((_, range)) = editor_snapshot .display_snapshot