1093 lines · 39.1 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, MultiBufferOffset, MultiBufferRow, MultiBufferSnapshot, ToPoint as _,};use project::{    Location, Project, TaskSourceKind,    debugger::breakpoint_store::{Breakpoint, BreakpointSessionState},    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::DisplayRow,}; #[derive(Debug)]pub(super) struct RunnableData {    runnables: HashMap<BufferId, (Global, BTreeMap<BufferRow, RunnableTasks>)>,    invalidate_buffer_data: HashSet<BufferId>,    runnables_update_task: Task<()>,} impl RunnableData {    pub fn new() -> Self {        Self {            runnables: 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 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, 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_id = buffer.read(cx).remote_id();            if invalidate_buffer_data != Some(buffer_id)                && self                    .runnables                    .has_cached(buffer_id, &buffer.read(cx).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.excerpts().find_map(                            |(excerpt_id, snapshot, _)| {                                if snapshot.remote_id() == buffer_snapshot.remote_id() {                                    multi_buffer_snapshot                                        .anchor_in_excerpt(excerpt_id, location.target.range.start)                                } else {                                    None                                }                            },                        );                        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 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?;            let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;             let resolved = &mut resolved_task.resolved;            resolved.reveal = reveal_strategy;             workspace                .update_in(cx, |workspace, window, cx| {                    workspace.schedule_resolved_task(                        task_source_kind,                        resolved_task,                        false,                        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);        } else {            self.runnables.runnables.clear();        }        self.runnables.invalidate_buffer_data.clear();        self.runnables.runnables_update_task = Task::ready(());    }     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 start = editor_snapshot            .display_snapshot            .buffer_snapshot()            .anchor_after(selection_range.start)            .text_anchor;        let end = editor_snapshot            .display_snapshot            .buffer_snapshot()            .anchor_after(selection_range.end)            .text_anchor;        let location = Location {            buffer,            range: start..end,        };        let captured_variables = {            let mut variables = TaskVariables::default();            let buffer = location.buffer.read(cx);            let buffer_id = buffer.remote_id();            let snapshot = buffer.snapshot();            let starting_point = location.range.start.to_point(&snapshot);            let starting_offset = starting_point.to_offset(&snapshot);            for (_, tasks) in self                .runnables                .runnables                .get(&buffer_id)                .into_iter()                .flat_map(|(_, tasks)| tasks.range(0..starting_point.row + 1))            {                if !tasks                    .context_range                    .contains(&crate::BufferOffset(starting_offset))                {                    continue;                }                for (capture_name, value) in tasks.extra_variables.iter() {                    variables.insert(                        VariableName::Custom(capture_name.to_owned().into()),                        value.clone(),                    );                }            }            variables        };         project.update(cx, |project, cx| {            project.task_store().update(cx, |task_store, cx| {                task_store.task_context_for_location(captured_variables, location, cx)            })        })    }     pub fn lsp_task_sources(        &self,        visible_only: bool,        skip_cached: bool,        cx: &mut Context<Self>,    ) -> HashMap<LanguageServerName, Vec<BufferId>> {        if !self.lsp_data_enabled() {            return HashMap::default();        }        let buffers = if visible_only {            self.visible_excerpts(true, cx)                .into_values()                .map(|(buffer, _, _)| buffer)                .collect()        } else {            self.buffer().read(cx).all_buffers()        };         let lsp_settings = &ProjectSettings::get_global(cx).lsp;         buffers            .into_iter()            .filter_map(|buffer| {                let lsp_tasks_source = buffer                    .read(cx)                    .language()?                    .context_provider()?                    .lsp_task_source()?;                if lsp_settings                    .get(&lsp_tasks_source)                    .is_none_or(|s| s.enable_lsp_tasks)                {                    let buffer_id = buffer.read(cx).remote_id();                    if skip_cached                        && self                            .runnables                            .has_cached(buffer_id, &buffer.read(cx).version())                    {                        None                    } else {                        Some((lsp_tasks_source, buffer_id))                    }                } else {                    None                }            })            .fold(                HashMap::default(),                |mut acc, (lsp_task_source, buffer_id)| {                    acc.entry(lsp_task_source)                        .or_insert_with(Vec::new)                        .push(buffer_id);                    acc                },            )    }     pub fn find_enclosing_node_task(        &mut self,        cx: &mut Context<Self>,    ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {        let snapshot = self.buffer.read(cx).snapshot(cx);        let offset = self            .selections            .newest::<MultiBufferOffset>(&self.display_snapshot(cx))            .head();        let mut excerpt = snapshot.excerpt_containing(offset..offset)?;        let offset = excerpt.map_offset_to_buffer(offset);        let buffer_id = excerpt.buffer().remote_id();         let layer = excerpt.buffer().syntax_layer_at(offset)?;        let mut cursor = layer.node().walk();         while cursor.goto_first_child_for_byte(offset.0).is_some() {            if cursor.node().end_byte() == offset.0 {                cursor.goto_next_sibling();            }        }

Lines 1–500 of 1093.