1479 lines · 52.7 KB · Rust
RawDownload
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

Lines 1–500 of 1479.