diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 52272253b2..fdbd4e2d4b 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -55,9 +55,7 @@ use std::{ }, }; use syntax_map::SyntaxSnapshot; -pub use task_context::{ - ContextProvider, ContextProviderWithTasks, LanguageSource, SymbolContextProvider, -}; +pub use task_context::{ContextProvider, ContextProviderWithTasks, SymbolContextProvider}; use theme::SyntaxTheme; use tree_sitter::{self, wasmtime, Query, WasmStore}; use util::http::HttpClient; diff --git a/crates/language/src/task_context.rs b/crates/language/src/task_context.rs index 6d2090b628..3106d303be 100644 --- a/crates/language/src/task_context.rs +++ b/crates/language/src/task_context.rs @@ -1,12 +1,8 @@ -use crate::{LanguageRegistry, Location}; +use crate::Location; use anyhow::Result; -use gpui::{AppContext, Context, Model}; -use std::sync::Arc; -use task::{ - static_source::{tasks_for, TaskDefinitions}, - TaskSource, TaskVariables, VariableName, -}; +use gpui::AppContext; +use task::{static_source::TaskDefinitions, TaskVariables, VariableName}; /// Language Contexts are used by Zed tasks to extract information about source file. pub trait ContextProvider: Send + Sync { @@ -67,45 +63,3 @@ impl ContextProvider for ContextProviderWithTasks { SymbolContextProvider.build_context(location, cx) } } - -/// A source that pulls in the tasks from language registry. -pub struct LanguageSource { - languages: Arc, -} - -impl LanguageSource { - pub fn new( - languages: Arc, - cx: &mut AppContext, - ) -> Model> { - cx.new_model(|_| Box::new(Self { languages }) as Box<_>) - } -} - -impl TaskSource for LanguageSource { - fn as_any(&mut self) -> &mut dyn std::any::Any { - self - } - - fn tasks_for_path( - &mut self, - _: Option<&std::path::Path>, - _: &mut gpui::ModelContext>, - ) -> Vec> { - self.languages - .to_vec() - .into_iter() - .filter_map(|language| { - language - .context_provider()? - .associated_tasks() - .map(|tasks| (tasks, language)) - }) - .flat_map(|(tasks, language)| { - let language_name = language.name(); - let id_base = format!("buffer_source_{language_name}"); - tasks_for(tasks, &id_base) - }) - .collect() - } -} diff --git a/crates/project/src/task_inventory.rs b/crates/project/src/task_inventory.rs index c39743eae2..34a17c611e 100644 --- a/crates/project/src/task_inventory.rs +++ b/crates/project/src/task_inventory.rs @@ -9,14 +9,15 @@ use std::{ use collections::{HashMap, VecDeque}; use gpui::{AppContext, Context, Model, ModelContext, Subscription}; use itertools::Itertools; -use task::{Task, TaskContext, TaskId, TaskSource}; +use language::Language; +use task::{static_source::tasks_for, Task, TaskContext, TaskSource}; use util::{post_inc, NumericPrefixWithSuffix}; use worktree::WorktreeId; /// Inventory tracks available tasks for a given project. pub struct Inventory { sources: Vec, - last_scheduled_tasks: VecDeque<(TaskId, TaskContext)>, + last_scheduled_tasks: VecDeque<(Arc, TaskContext)>, } struct SourceInInventory { @@ -33,17 +34,17 @@ pub enum TaskSourceKind { UserInput, /// ~/.config/zed/task.json - like global files with task definitions, applicable to any path AbsPath(PathBuf), - /// Worktree-specific task definitions, e.g. dynamic tasks from open worktree file, or tasks from the worktree's .zed/task.json + /// Tasks from the worktree's .zed/task.json Worktree { id: WorktreeId, abs_path: PathBuf }, - /// Buffer-specific task definitions, originating in e.g. language extension. - Buffer, + /// Languages-specific tasks coming from extensions. + Language { name: Arc }, } impl TaskSourceKind { fn abs_path(&self) -> Option<&Path> { match self { Self::AbsPath(abs_path) | Self::Worktree { abs_path, .. } => Some(abs_path), - Self::UserInput | Self::Buffer => None, + Self::UserInput | Self::Language { .. } => None, } } @@ -125,21 +126,38 @@ impl Inventory { ) } - /// Pulls its sources to list runanbles for the path given (up to the source to decide what to return for no path). + /// Pulls its sources to list runnables for the editor given, or all runnables for no editor. pub fn list_tasks( &self, - path: Option<&Path>, + language: Option>, worktree: Option, lru: bool, cx: &mut AppContext, ) -> Vec<(TaskSourceKind, Arc)> { + let task_source_kind = language.as_ref().map(|language| TaskSourceKind::Language { + name: language.name(), + }); + let language_tasks = language + .and_then(|language| { + let tasks = language.context_provider()?.associated_tasks()?; + Some((tasks, language)) + }) + .map(|(tasks, language)| { + let language_name = language.name(); + let id_base = format!("buffer_source_{language_name}"); + tasks_for(tasks, &id_base) + }) + .unwrap_or_default() + .into_iter() + .flat_map(|task| Some((task_source_kind.as_ref()?, task))); + let mut lru_score = 0_u32; let tasks_by_usage = if lru { self.last_scheduled_tasks.iter().rev().fold( HashMap::default(), - |mut tasks, (id, context)| { + |mut tasks, (task, context)| { tasks - .entry(id) + .entry(task.id().clone()) .or_insert_with(|| (post_inc(&mut lru_score), Some(context))); tasks }, @@ -158,10 +176,11 @@ impl Inventory { .flat_map(|source| { source .source - .update(cx, |source, cx| source.tasks_for_path(path, cx)) + .update(cx, |source, cx| source.tasks_to_schedule(cx)) .into_iter() .map(|task| (&source.kind, task)) }) + .chain(language_tasks) .map(|task| { let usages = if lru { tasks_by_usage @@ -206,21 +225,13 @@ impl Inventory { } /// Returns the last scheduled task, if any of the sources contains one with the matching id. - pub fn last_scheduled_task(&self, cx: &mut AppContext) -> Option<(Arc, TaskContext)> { - self.last_scheduled_tasks - .back() - .and_then(|(id, task_context)| { - // TODO straighten the `Path` story to understand what has to be passed here: or it will break in the future. - self.list_tasks(None, None, false, cx) - .into_iter() - .find(|(_, task)| task.id() == id) - .map(|(_, task)| (task, task_context.clone())) - }) + pub fn last_scheduled_task(&self) -> Option<(Arc, TaskContext)> { + self.last_scheduled_tasks.back().cloned() } /// Registers task "usage" as being scheduled – to be used for LRU sorting when listing all tasks. - pub fn task_scheduled(&mut self, id: TaskId, task_context: TaskContext) { - self.last_scheduled_tasks.push_back((id, task_context)); + pub fn task_scheduled(&mut self, task: Arc, task_context: TaskContext) { + self.last_scheduled_tasks.push_back((task, task_context)); if self.last_scheduled_tasks.len() > 5_000 { self.last_scheduled_tasks.pop_front(); } @@ -229,7 +240,7 @@ impl Inventory { #[cfg(any(test, feature = "test-support"))] pub mod test_inventory { - use std::{path::Path, sync::Arc}; + use std::sync::Arc; use gpui::{AppContext, Context as _, Model, ModelContext, TestAppContext}; use task::{Task, TaskContext, TaskId, TaskSource}; @@ -288,9 +299,8 @@ pub mod test_inventory { } impl TaskSource for StaticTestSource { - fn tasks_for_path( + fn tasks_to_schedule( &mut self, - _path: Option<&Path>, _cx: &mut ModelContext>, ) -> Vec> { self.tasks @@ -307,14 +317,13 @@ pub mod test_inventory { pub fn list_task_names( inventory: &Model, - path: Option<&Path>, worktree: Option, lru: bool, cx: &mut TestAppContext, ) -> Vec { inventory.update(cx, |inventory, cx| { inventory - .list_tasks(path, worktree, lru, cx) + .list_tasks(None, worktree, lru, cx) .into_iter() .map(|(_, task)| task.name().to_string()) .collect() @@ -332,20 +341,19 @@ pub mod test_inventory { .into_iter() .find(|(_, task)| task.name() == task_name) .unwrap_or_else(|| panic!("Failed to find task with name {task_name}")); - inventory.task_scheduled(task.1.id().clone(), TaskContext::default()); + inventory.task_scheduled(task.1, TaskContext::default()); }); } pub fn list_tasks( inventory: &Model, - path: Option<&Path>, worktree: Option, lru: bool, cx: &mut TestAppContext, ) -> Vec<(TaskSourceKind, String)> { inventory.update(cx, |inventory, cx| { inventory - .list_tasks(path, worktree, lru, cx) + .list_tasks(None, worktree, lru, cx) .into_iter() .map(|(source_kind, task)| (source_kind, task.name().to_string())) .collect() @@ -363,12 +371,12 @@ mod tests { #[gpui::test] fn test_task_list_sorting(cx: &mut TestAppContext) { let inventory = cx.update(Inventory::new); - let initial_tasks = list_task_names(&inventory, None, None, true, cx); + let initial_tasks = list_task_names(&inventory, None, true, cx); assert!( initial_tasks.is_empty(), "No tasks expected for empty inventory, but got {initial_tasks:?}" ); - let initial_tasks = list_task_names(&inventory, None, None, false, cx); + let initial_tasks = list_task_names(&inventory, None, false, cx); assert!( initial_tasks.is_empty(), "No tasks expected for empty inventory, but got {initial_tasks:?}" @@ -405,24 +413,24 @@ mod tests { "3_task".to_string(), ]; assert_eq!( - list_task_names(&inventory, None, None, false, cx), + list_task_names(&inventory, None, false, cx), &expected_initial_state, "Task list without lru sorting, should be sorted alphanumerically" ); assert_eq!( - list_task_names(&inventory, None, None, true, cx), + list_task_names(&inventory, None, true, cx), &expected_initial_state, "Tasks with equal amount of usages should be sorted alphanumerically" ); register_task_used(&inventory, "2_task", cx); assert_eq!( - list_task_names(&inventory, None, None, false, cx), + list_task_names(&inventory, None, false, cx), &expected_initial_state, "Task list without lru sorting, should be sorted alphanumerically" ); assert_eq!( - list_task_names(&inventory, None, None, true, cx), + list_task_names(&inventory, None, true, cx), vec![ "2_task".to_string(), "1_a_task".to_string(), @@ -436,12 +444,12 @@ mod tests { register_task_used(&inventory, "1_task", cx); register_task_used(&inventory, "3_task", cx); assert_eq!( - list_task_names(&inventory, None, None, false, cx), + list_task_names(&inventory, None, false, cx), &expected_initial_state, "Task list without lru sorting, should be sorted alphanumerically" ); assert_eq!( - list_task_names(&inventory, None, None, true, cx), + list_task_names(&inventory, None, true, cx), vec![ "3_task".to_string(), "1_task".to_string(), @@ -468,12 +476,12 @@ mod tests { "11_hello".to_string(), ]; assert_eq!( - list_task_names(&inventory, None, None, false, cx), + list_task_names(&inventory, None, false, cx), &expected_updated_state, "Task list without lru sorting, should be sorted alphanumerically" ); assert_eq!( - list_task_names(&inventory, None, None, true, cx), + list_task_names(&inventory, None, true, cx), vec![ "3_task".to_string(), "1_task".to_string(), @@ -486,12 +494,12 @@ mod tests { register_task_used(&inventory, "11_hello", cx); assert_eq!( - list_task_names(&inventory, None, None, false, cx), + list_task_names(&inventory, None, false, cx), &expected_updated_state, "Task list without lru sorting, should be sorted alphanumerically" ); assert_eq!( - list_task_names(&inventory, None, None, true, cx), + list_task_names(&inventory, None, true, cx), vec![ "11_hello".to_string(), "3_task".to_string(), @@ -633,36 +641,25 @@ mod tests { .cloned() .collect::>(); - for path in [ - None, - Some(path_1), - Some(path_2), - Some(worktree_path_1), - Some(worktree_path_2), - ] { - assert_eq!( - list_tasks(&inventory_with_statics, path, None, false, cx), - all_tasks, - "Path {path:?} choice should not adjust static runnables" - ); - assert_eq!( - list_tasks(&inventory_with_statics, path, Some(worktree_1), false, cx), - worktree_1_tasks - .iter() - .chain(worktree_independent_tasks.iter()) - .cloned() - .collect::>(), - "Path {path:?} choice should not adjust static runnables for worktree_1" - ); - assert_eq!( - list_tasks(&inventory_with_statics, path, Some(worktree_2), false, cx), - worktree_2_tasks - .iter() - .chain(worktree_independent_tasks.iter()) - .cloned() - .collect::>(), - "Path {path:?} choice should not adjust static runnables for worktree_2" - ); - } + assert_eq!( + list_tasks(&inventory_with_statics, None, false, cx), + all_tasks, + ); + assert_eq!( + list_tasks(&inventory_with_statics, Some(worktree_1), false, cx), + worktree_1_tasks + .iter() + .chain(worktree_independent_tasks.iter()) + .cloned() + .collect::>(), + ); + assert_eq!( + list_tasks(&inventory_with_statics, Some(worktree_2), false, cx), + worktree_2_tasks + .iter() + .chain(worktree_independent_tasks.iter()) + .cloned() + .collect::>(), + ); } } diff --git a/crates/task/src/lib.rs b/crates/task/src/lib.rs index 8a45e300fa..2064832b22 100644 --- a/crates/task/src/lib.rs +++ b/crates/task/src/lib.rs @@ -10,7 +10,7 @@ use gpui::ModelContext; use static_source::RevealStrategy; use std::any::Any; use std::borrow::Cow; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use std::sync::Arc; pub use vscode_format::VsCodeTaskFile; @@ -148,10 +148,9 @@ pub trait Task { pub trait TaskSource: Any { /// A way to erase the type of the source, processing and storing them generically. fn as_any(&mut self) -> &mut dyn Any; - /// Collects all tasks available for scheduling, for the path given. - fn tasks_for_path( + /// Collects all tasks available for scheduling. + fn tasks_to_schedule( &mut self, - path: Option<&Path>, cx: &mut ModelContext>, ) -> Vec>; } diff --git a/crates/task/src/oneshot_source.rs b/crates/task/src/oneshot_source.rs index d62eaf886e..cf2c73203d 100644 --- a/crates/task/src/oneshot_source.rs +++ b/crates/task/src/oneshot_source.rs @@ -89,9 +89,8 @@ impl TaskSource for OneshotSource { self } - fn tasks_for_path( + fn tasks_to_schedule( &mut self, - _path: Option<&std::path::Path>, _cx: &mut gpui::ModelContext>, ) -> Vec> { self.tasks.clone() diff --git a/crates/task/src/static_source.rs b/crates/task/src/static_source.rs index 0744d9ab32..cc169db313 100644 --- a/crates/task/src/static_source.rs +++ b/crates/task/src/static_source.rs @@ -1,6 +1,6 @@ //! A source of tasks, based on a static configuration, deserialized from the tasks config file, and related infrastructure for tracking changes to the file. -use std::{borrow::Cow, path::Path, sync::Arc}; +use std::{borrow::Cow, sync::Arc}; use collections::HashMap; use futures::StreamExt; @@ -268,9 +268,8 @@ impl StaticSource { } impl TaskSource for StaticSource { - fn tasks_for_path( + fn tasks_to_schedule( &mut self, - _: Option<&Path>, _: &mut ModelContext>, ) -> Vec> { self.tasks diff --git a/crates/tasks_ui/src/lib.rs b/crates/tasks_ui/src/lib.rs index 8b5e5aef39..2c0bc5e55f 100644 --- a/crates/tasks_ui/src/lib.rs +++ b/crates/tasks_ui/src/lib.rs @@ -1,8 +1,8 @@ -use std::path::PathBuf; +use std::{path::PathBuf, sync::Arc}; use editor::Editor; -use gpui::{AppContext, ViewContext, WindowContext}; -use language::Point; +use gpui::{AppContext, ViewContext, WeakView, WindowContext}; +use language::{Language, Point}; use modal::{Spawn, TasksModal}; use project::{Location, WorktreeId}; use task::{Task, TaskContext, TaskVariables, VariableName}; @@ -19,9 +19,7 @@ pub fn init(cx: &mut AppContext) { .register_action(move |workspace, action: &modal::Rerun, cx| { if let Some((task, old_context)) = workspace.project().update(cx, |project, cx| { - project - .task_inventory() - .update(cx, |inventory, cx| inventory.last_scheduled_task(cx)) + project.task_inventory().read(cx).last_scheduled_task() }) { let task_context = if action.reevaluate_context { @@ -30,7 +28,7 @@ pub fn init(cx: &mut AppContext) { } else { old_context }; - schedule_task(workspace, task.as_ref(), task_context, false, cx) + schedule_task(workspace, &task, task_context, false, cx) }; }); }, @@ -57,19 +55,16 @@ fn spawn_task_with_name(name: String, cx: &mut ViewContext) { cx.spawn(|workspace, mut cx| async move { let did_spawn = workspace .update(&mut cx, |this, cx| { - let active_item = this - .active_item(cx) - .and_then(|item| item.project_path(cx)) - .map(|path| path.worktree_id); + let (worktree, language) = active_item_selection_properties(&workspace, cx); let tasks = this.project().update(cx, |project, cx| { project.task_inventory().update(cx, |inventory, cx| { - inventory.list_tasks(None, active_item, false, cx) + inventory.list_tasks(language, worktree, false, cx) }) }); let (_, target_task) = tasks.into_iter().find(|(_, task)| task.name() == name)?; let cwd = task_cwd(this, cx).log_err().flatten(); let task_context = task_context(this, cwd, cx); - schedule_task(this, target_task.as_ref(), task_context, false, cx); + schedule_task(this, &target_task, task_context, false, cx); Some(()) }) .ok() @@ -86,6 +81,33 @@ fn spawn_task_with_name(name: String, cx: &mut ViewContext) { .detach(); } +fn active_item_selection_properties( + workspace: &WeakView, + cx: &mut WindowContext, +) -> (Option, Option>) { + let active_item = workspace + .update(cx, |workspace, cx| workspace.active_item(cx)) + .ok() + .flatten(); + let worktree_id = active_item + .as_ref() + .and_then(|item| item.project_path(cx)) + .map(|path| path.worktree_id); + let language = active_item + .and_then(|active_item| active_item.act_as::(cx)) + .and_then(|editor| { + editor.update(cx, |editor, cx| { + let selection = editor.selections.newest::(cx); + let (buffer, buffer_position, _) = editor + .buffer() + .read(cx) + .point_to_buffer_offset(selection.start, cx)?; + buffer.read(cx).language_at(buffer_position) + }) + }); + (worktree_id, language) +} + fn task_context( workspace: &Workspace, cwd: Option, @@ -93,8 +115,7 @@ fn task_context( ) -> TaskContext { let current_editor = workspace .active_item(cx) - .and_then(|item| item.act_as::(cx)) - .clone(); + .and_then(|item| item.act_as::(cx)); if let Some(current_editor) = current_editor { (|| { let editor = current_editor.read(cx); @@ -190,7 +211,7 @@ fn task_context( fn schedule_task( workspace: &Workspace, - task: &dyn Task, + task: &Arc, task_cx: TaskContext, omit_history: bool, cx: &mut ViewContext<'_, Workspace>, @@ -200,7 +221,7 @@ fn schedule_task( if !omit_history { workspace.project().update(cx, |project, cx| { project.task_inventory().update(cx, |inventory, _| { - inventory.task_scheduled(task.id().clone(), task_cx); + inventory.task_scheduled(Arc::clone(task), task_cx); }) }); } diff --git a/crates/tasks_ui/src/modal.rs b/crates/tasks_ui/src/modal.rs index e8ecbbafe7..1d0386b6c9 100644 --- a/crates/tasks_ui/src/modal.rs +++ b/crates/tasks_ui/src/modal.rs @@ -1,5 +1,6 @@ -use std::{path::PathBuf, sync::Arc}; +use std::sync::Arc; +use crate::{active_item_selection_properties, schedule_task}; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ impl_actions, rems, AppContext, DismissEvent, EventEmitter, FocusableView, InteractiveElement, @@ -10,7 +11,7 @@ use picker::{ highlighted_match_with_paths::{HighlightedMatchWithPaths, HighlightedText}, Picker, PickerDelegate, }; -use project::{Inventory, ProjectPath, TaskSourceKind}; +use project::{Inventory, TaskSourceKind}; use task::{oneshot_source::OneshotSource, Task, TaskContext}; use ui::{ div, v_flex, ButtonCommon, ButtonSize, Clickable, Color, FluentBuilder as _, IconButton, @@ -20,7 +21,6 @@ use ui::{ use util::{paths::PathExt, ResultExt}; use workspace::{ModalView, Workspace}; -use crate::schedule_task; use serde::Deserialize; /// Spawn a task with name or open tasks modal @@ -116,20 +116,6 @@ impl TasksModalDelegate { Some(()) }); } - fn active_item_path( - workspace: &WeakView, - cx: &mut ViewContext<'_, Picker>, - ) -> Option<(PathBuf, ProjectPath)> { - let workspace = workspace.upgrade()?.read(cx); - let project = workspace.project().read(cx); - let active_item = workspace.active_item(cx)?; - active_item.project_path(cx).and_then(|project_path| { - project - .worktree_for_id(project_path.worktree_id, cx) - .map(|worktree| worktree.read(cx).abs_path().join(&project_path.path)) - .zip(Some(project_path)) - }) - } } pub(crate) struct TasksModal { @@ -212,15 +198,10 @@ impl PickerDelegate for TasksModalDelegate { let Some(candidates) = picker .update(&mut cx, |picker, cx| { let candidates = picker.delegate.candidates.get_or_insert_with(|| { - let (path, worktree) = - match Self::active_item_path(&picker.delegate.workspace, cx) { - Some((abs_path, project_path)) => { - (Some(abs_path), Some(project_path.worktree_id)) - } - None => (None, None), - }; + let (worktree, language) = + active_item_selection_properties(&picker.delegate.workspace, cx); picker.delegate.inventory.update(cx, |inventory, cx| { - inventory.list_tasks(path.as_deref(), worktree, true, cx) + inventory.list_tasks(language, worktree, true, cx) }) }); @@ -283,7 +264,7 @@ impl PickerDelegate for TasksModalDelegate { .update(cx, |workspace, cx| { schedule_task( workspace, - task.as_ref(), + &task, self.task_context.clone(), omit_history_entry, cx, @@ -308,7 +289,7 @@ impl PickerDelegate for TasksModalDelegate { let (source_kind, _) = &candidates.get(hit.candidate_id)?; let details = match source_kind { TaskSourceKind::UserInput => "user input".to_string(), - TaskSourceKind::Buffer => "language extension".to_string(), + TaskSourceKind::Language { name } => format!("{name} language"), TaskSourceKind::Worktree { abs_path, .. } | TaskSourceKind::AbsPath(abs_path) => { abs_path.compact().to_string_lossy().to_string() } @@ -381,7 +362,7 @@ impl PickerDelegate for TasksModalDelegate { .update(cx, |workspace, cx| { schedule_task( workspace, - task.as_ref(), + &task, self.task_context.clone(), omit_history_entry, cx, diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index e8018b362c..95d2c45aee 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -18,7 +18,6 @@ pub use open_listener::*; use anyhow::Context as _; use assets::Assets; use futures::{channel::mpsc, select_biased, StreamExt}; -use language::LanguageSource; use project::TaskSourceKind; use project_panel::ProjectPanel; use quick_action_bar::QuickActionBar; @@ -181,11 +180,6 @@ pub fn initialize_workspace(app_state: Arc, cx: &mut AppContext) { }, cx, ); - inventory.add_source( - TaskSourceKind::Buffer, - |cx| LanguageSource::new(app_state.languages.clone(), cx), - cx, - ); }) }); }