Detailed changes
@@ -1,93 +1,73 @@
use crate::Editor;
-use gpui::{App, AppContext as _, Task as AsyncTask, Window};
+use gpui::{App, Task, Window};
use project::Location;
use task::{TaskContext, TaskVariables, VariableName};
use text::{ToOffset, ToPoint};
-use workspace::Workspace;
-fn task_context_with_editor(
- editor: &mut Editor,
- window: &mut Window,
- cx: &mut App,
-) -> AsyncTask<Option<TaskContext>> {
- let Some(project) = editor.project.clone() else {
- return AsyncTask::ready(None);
- };
- let (selection, buffer, editor_snapshot) = {
- let selection = editor.selections.newest_adjusted(cx);
- let Some((buffer, _)) = editor
- .buffer()
- .read(cx)
- .point_to_buffer_offset(selection.start, cx)
- else {
- return AsyncTask::ready(None);
+impl Editor {
+ 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 snapshot = editor.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 editor
- .tasks
- .range((buffer_id, 0)..(buffer_id, starting_point.row + 1))
- {
- if !tasks
- .context_range
- .contains(&crate::BufferOffset(starting_offset))
+ let (selection, buffer, editor_snapshot) = {
+ let selection = self.selections.newest_adjusted(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
+ .tasks
+ .range((buffer_id, 0)..(buffer_id, starting_point.row + 1))
{
- continue;
- }
- for (capture_name, value) in tasks.extra_variables.iter() {
- variables.insert(
- VariableName::Custom(capture_name.to_owned().into()),
- value.clone(),
- );
+ 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
- };
+ variables
+ };
- project.update(cx, |project, cx| {
- project.task_store().update(cx, |task_store, cx| {
- task_store.task_context_for_location(captured_variables, location, cx)
+ 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 task_context(
- workspace: &Workspace,
- window: &mut Window,
- cx: &mut App,
-) -> AsyncTask<TaskContext> {
- let Some(editor) = workspace
- .active_item(cx)
- .and_then(|item| item.act_as::<Editor>(cx))
- else {
- return AsyncTask::ready(TaskContext::default());
- };
- editor.update(cx, |editor, cx| {
- let context_task = task_context_with_editor(editor, window, cx);
- cx.background_spawn(async move { context_task.await.unwrap_or_default() })
- })
+ }
}
@@ -107,7 +107,7 @@ pub use language::Location;
#[cfg(any(test, feature = "test-support"))]
pub use prettier::FORMAT_SUFFIX as TEST_PRETTIER_FORMAT_SUFFIX;
pub use task_inventory::{
- BasicContextProvider, ContextProviderWithTasks, Inventory, TaskSourceKind,
+ BasicContextProvider, ContextProviderWithTasks, Inventory, TaskContexts, TaskSourceKind,
};
pub use worktree::{
Entry, EntryKind, File, LocalWorktree, PathChange, ProjectEntryId, UpdatedEntriesSet,
@@ -1,4 +1,4 @@
-use crate::{Event, *};
+use crate::{task_inventory::TaskContexts, Event, *};
use buffer_diff::{assert_hunks, DiffHunkSecondaryStatus, DiffHunkStatus};
use fs::FakeFs;
use futures::{future, StreamExt};
@@ -233,7 +233,7 @@ async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext)
let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
- let task_context = TaskContext::default();
+ let task_contexts = TaskContexts::default();
cx.executor().run_until_parked();
let worktree_id = cx.update(|cx| {
@@ -265,7 +265,7 @@ async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext)
assert_eq!(settings_a.tab_size.get(), 8);
assert_eq!(settings_b.tab_size.get(), 2);
- get_all_tasks(&project, Some(worktree_id), &task_context, cx)
+ get_all_tasks(&project, Some(worktree_id), &task_contexts, cx)
})
.into_iter()
.map(|(source_kind, task)| {
@@ -305,7 +305,7 @@ async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext)
);
let (_, resolved_task) = cx
- .update(|cx| get_all_tasks(&project, Some(worktree_id), &task_context, cx))
+ .update(|cx| get_all_tasks(&project, Some(worktree_id), &task_contexts, cx))
.into_iter()
.find(|(source_kind, _)| source_kind == &topmost_local_task_source_kind)
.expect("should have one global task");
@@ -343,7 +343,7 @@ async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext)
cx.run_until_parked();
let all_tasks = cx
- .update(|cx| get_all_tasks(&project, Some(worktree_id), &task_context, cx))
+ .update(|cx| get_all_tasks(&project, Some(worktree_id), &task_contexts, cx))
.into_iter()
.map(|(source_kind, task)| {
let resolved = task.resolved.unwrap();
@@ -398,6 +398,96 @@ async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext)
);
}
+#[gpui::test]
+async fn test_fallback_to_single_worktree_tasks(cx: &mut gpui::TestAppContext) {
+ init_test(cx);
+ TaskStore::init(None);
+
+ let fs = FakeFs::new(cx.executor());
+ fs.insert_tree(
+ path!("/dir"),
+ json!({
+ ".zed": {
+ "tasks.json": r#"[{
+ "label": "test worktree root",
+ "command": "echo $ZED_WORKTREE_ROOT"
+ }]"#,
+ },
+ "a": {
+ "a.rs": "fn a() {\n A\n}"
+ },
+ }),
+ )
+ .await;
+
+ let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
+ let _worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
+
+ cx.executor().run_until_parked();
+ let worktree_id = cx.update(|cx| {
+ project.update(cx, |project, cx| {
+ project.worktrees(cx).next().unwrap().read(cx).id()
+ })
+ });
+
+ let active_non_worktree_item_tasks = cx.update(|cx| {
+ get_all_tasks(
+ &project,
+ Some(worktree_id),
+ &TaskContexts {
+ active_item_context: Some((Some(worktree_id), TaskContext::default())),
+ active_worktree_context: None,
+ other_worktree_contexts: Vec::new(),
+ },
+ cx,
+ )
+ });
+ assert!(
+ active_non_worktree_item_tasks.is_empty(),
+ "A task can not be resolved with context with no ZED_WORKTREE_ROOT data"
+ );
+
+ let active_worktree_tasks = cx.update(|cx| {
+ get_all_tasks(
+ &project,
+ Some(worktree_id),
+ &TaskContexts {
+ active_item_context: Some((Some(worktree_id), TaskContext::default())),
+ active_worktree_context: Some((worktree_id, {
+ let mut worktree_context = TaskContext::default();
+ worktree_context
+ .task_variables
+ .insert(task::VariableName::WorktreeRoot, "/dir".to_string());
+ worktree_context
+ })),
+ other_worktree_contexts: Vec::new(),
+ },
+ cx,
+ )
+ });
+ assert_eq!(
+ active_worktree_tasks
+ .into_iter()
+ .map(|(source_kind, task)| {
+ let resolved = task.resolved.unwrap();
+ (source_kind, resolved.command)
+ })
+ .collect::<Vec<_>>(),
+ vec![(
+ TaskSourceKind::Worktree {
+ id: worktree_id,
+ directory_in_worktree: PathBuf::from(separator!(".zed")),
+ id_base: if cfg!(windows) {
+ "local worktree tasks from directory \".zed\"".into()
+ } else {
+ "local worktree tasks from directory \".zed\"".into()
+ },
+ },
+ "echo /dir".to_string(),
+ )]
+ );
+}
+
#[gpui::test]
async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
init_test(cx);
@@ -6050,7 +6140,7 @@ fn tsx_lang() -> Arc<Language> {
fn get_all_tasks(
project: &Entity<Project>,
worktree_id: Option<WorktreeId>,
- task_context: &TaskContext,
+ task_contexts: &TaskContexts,
cx: &mut App,
) -> Vec<(TaskSourceKind, ResolvedTask)> {
let (mut old, new) = project.update(cx, |project, cx| {
@@ -6060,7 +6150,7 @@ fn get_all_tasks(
.task_inventory()
.unwrap()
.read(cx)
- .used_and_current_resolved_tasks(worktree_id, None, task_context, cx)
+ .used_and_current_resolved_tasks(worktree_id, None, task_contexts, cx)
});
old.extend(new);
old
@@ -56,6 +56,32 @@ pub enum TaskSourceKind {
Language { name: SharedString },
}
+/// A collection of task contexts, derived from the current state of the workspace.
+/// Only contains worktrees that are visible and with their root being a directory.
+#[derive(Debug, Default)]
+pub struct TaskContexts {
+ /// A context, related to the currently opened item.
+ /// Item can be opened from an invisible worktree, or any other, not necessarily active worktree.
+ pub active_item_context: Option<(Option<WorktreeId>, TaskContext)>,
+ /// A worktree that corresponds to the active item, or the only worktree in the workspace.
+ pub active_worktree_context: Option<(WorktreeId, TaskContext)>,
+ /// If there are multiple worktrees in the workspace, all non-active ones are included here.
+ pub other_worktree_contexts: Vec<(WorktreeId, TaskContext)>,
+}
+
+impl TaskContexts {
+ pub fn active_context(&self) -> Option<&TaskContext> {
+ self.active_item_context
+ .as_ref()
+ .map(|(_, context)| context)
+ .or_else(|| {
+ self.active_worktree_context
+ .as_ref()
+ .map(|(_, context)| context)
+ })
+ }
+}
+
impl TaskSourceKind {
pub fn to_id_base(&self) -> String {
match self {
@@ -106,7 +132,7 @@ impl Inventory {
.collect()
}
- /// Pulls its task sources relevant to the worktree and the language given and resolves them with the [`TaskContext`] given.
+ /// Pulls its task sources relevant to the worktree and the language given and resolves them with the [`TaskContexts`] given.
/// Joins the new resolutions with the resolved tasks that were used (spawned) before,
/// orders them so that the most recently used come first, all equally used ones are ordered so that the most specific tasks come first.
/// Deduplicates the tasks by their labels and context and splits the ordered list into two: used tasks and the rest, newly resolved tasks.
@@ -114,7 +140,7 @@ impl Inventory {
&self,
worktree: Option<WorktreeId>,
location: Option<Location>,
- task_context: &TaskContext,
+ task_contexts: &TaskContexts,
cx: &App,
) -> (
Vec<(TaskSourceKind, ResolvedTask)>,
@@ -179,30 +205,55 @@ impl Inventory {
.worktree_templates_from_settings(worktree)
.chain(language_tasks);
- let new_resolved_tasks = worktree_tasks
- .filter_map(|(kind, task)| {
- let id_base = kind.to_id_base();
- Some((
- kind,
- task.resolve_task(&id_base, task_context)?,
- not_used_score,
- ))
- })
- .filter(|(_, resolved_task, _)| {
- match task_labels_to_ids.entry(resolved_task.resolved_label.clone()) {
- hash_map::Entry::Occupied(mut o) => {
- // Allow new tasks with the same label, if their context is different
- o.get_mut().insert(resolved_task.id.clone())
- }
- hash_map::Entry::Vacant(v) => {
- v.insert(HashSet::from_iter(Some(resolved_task.id.clone())));
- true
+ let new_resolved_tasks =
+ worktree_tasks
+ .flat_map(|(kind, task)| {
+ let id_base = kind.to_id_base();
+ None.or_else(|| {
+ let (_, item_context) = task_contexts.active_item_context.as_ref().filter(
+ |(worktree_id, _)| worktree.is_none() || worktree == *worktree_id,
+ )?;
+ task.resolve_task(&id_base, item_context)
+ })
+ .or_else(|| {
+ let (_, worktree_context) = task_contexts
+ .active_worktree_context
+ .as_ref()
+ .filter(|(worktree_id, _)| {
+ worktree.is_none() || worktree == Some(*worktree_id)
+ })?;
+ task.resolve_task(&id_base, worktree_context)
+ })
+ .or_else(|| {
+ if let TaskSourceKind::Worktree { id, .. } = &kind {
+ let worktree_context = task_contexts
+ .other_worktree_contexts
+ .iter()
+ .find(|(worktree_id, _)| worktree_id == id)
+ .map(|(_, context)| context)?;
+ task.resolve_task(&id_base, worktree_context)
+ } else {
+ None
+ }
+ })
+ .or_else(|| task.resolve_task(&id_base, &TaskContext::default()))
+ .map(move |resolved_task| (kind.clone(), resolved_task, not_used_score))
+ })
+ .filter(|(_, resolved_task, _)| {
+ match task_labels_to_ids.entry(resolved_task.resolved_label.clone()) {
+ hash_map::Entry::Occupied(mut o) => {
+ // Allow new tasks with the same label, if their context is different
+ o.get_mut().insert(resolved_task.id.clone())
+ }
+ hash_map::Entry::Vacant(v) => {
+ v.insert(HashSet::from_iter(Some(resolved_task.id.clone())));
+ true
+ }
}
- }
- })
- .sorted_unstable_by(task_lru_comparator)
- .map(|(kind, task, _)| (kind, task))
- .collect::<Vec<_>>();
+ })
+ .sorted_unstable_by(task_lru_comparator)
+ .map(|(kind, task, _)| (kind, task))
+ .collect::<Vec<_>>();
(previously_spawned_tasks, new_resolved_tasks)
}
@@ -497,9 +548,9 @@ impl ContextProvider for BasicContextProvider {
self.worktree_store
.read(cx)
.worktree_for_id(worktree_id, cx)
- .map(|worktree| worktree.read(cx).root_dir())
+ .and_then(|worktree| worktree.read(cx).root_dir())
});
- if let Some(Some(worktree_path)) = worktree_root_dir {
+ if let Some(worktree_path) = worktree_root_dir {
task_variables.insert(
VariableName::WorktreeRoot,
worktree_path.to_sanitized_string(),
@@ -864,7 +915,7 @@ mod tests {
cx: &mut TestAppContext,
) -> Vec<String> {
let (used, current) = inventory.update(cx, |inventory, cx| {
- inventory.used_and_current_resolved_tasks(worktree, None, &TaskContext::default(), cx)
+ inventory.used_and_current_resolved_tasks(worktree, None, &TaskContexts::default(), cx)
});
used.into_iter()
.chain(current)
@@ -893,7 +944,7 @@ mod tests {
cx: &mut TestAppContext,
) -> Vec<(TaskSourceKind, String)> {
let (used, current) = inventory.update(cx, |inventory, cx| {
- inventory.used_and_current_resolved_tasks(worktree, None, &TaskContext::default(), cx)
+ inventory.used_and_current_resolved_tasks(worktree, None, &TaskContexts::default(), cx)
});
let mut all = used;
all.extend(current);
@@ -1,6 +1,6 @@
use std::sync::Arc;
-use crate::active_item_selection_properties;
+use crate::{active_item_selection_properties, TaskContexts};
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
rems, Action, AnyElement, App, AppContext as _, Context, DismissEvent, Entity, EventEmitter,
@@ -30,7 +30,7 @@ pub(crate) struct TasksModalDelegate {
selected_index: usize,
workspace: WeakEntity<Workspace>,
prompt: String,
- task_context: TaskContext,
+ task_contexts: TaskContexts,
placeholder_text: Arc<str>,
}
@@ -44,7 +44,7 @@ pub(crate) struct TaskOverrides {
impl TasksModalDelegate {
fn new(
task_store: Entity<TaskStore>,
- task_context: TaskContext,
+ task_contexts: TaskContexts,
task_overrides: Option<TaskOverrides>,
workspace: WeakEntity<Workspace>,
) -> Self {
@@ -65,7 +65,7 @@ impl TasksModalDelegate {
divider_index: None,
selected_index: 0,
prompt: String::default(),
- task_context,
+ task_contexts,
task_overrides,
placeholder_text,
}
@@ -76,6 +76,11 @@ impl TasksModalDelegate {
return None;
}
+ let default_context = TaskContext::default();
+ let active_context = self
+ .task_contexts
+ .active_context()
+ .unwrap_or(&default_context);
let source_kind = TaskSourceKind::UserInput;
let id_base = source_kind.to_id_base();
let mut new_oneshot = TaskTemplate {
@@ -91,7 +96,7 @@ impl TasksModalDelegate {
}
Some((
source_kind,
- new_oneshot.resolve_task(&id_base, &self.task_context)?,
+ new_oneshot.resolve_task(&id_base, active_context)?,
))
}
@@ -122,7 +127,7 @@ pub(crate) struct TasksModal {
impl TasksModal {
pub(crate) fn new(
task_store: Entity<TaskStore>,
- task_context: TaskContext,
+ task_contexts: TaskContexts,
task_overrides: Option<TaskOverrides>,
workspace: WeakEntity<Workspace>,
window: &mut Window,
@@ -130,7 +135,7 @@ impl TasksModal {
) -> Self {
let picker = cx.new(|cx| {
Picker::uniform_list(
- TasksModalDelegate::new(task_store, task_context, task_overrides, workspace),
+ TasksModalDelegate::new(task_store, task_contexts, task_overrides, workspace),
window,
cx,
)
@@ -225,7 +230,7 @@ impl PickerDelegate for TasksModalDelegate {
task_inventory.read(cx).used_and_current_resolved_tasks(
worktree,
location,
- &picker.delegate.task_context,
+ &picker.delegate.task_contexts,
cx,
);
picker.delegate.last_used_candidate_index = if used.is_empty() {
@@ -1,9 +1,12 @@
+use std::collections::HashMap;
+use std::path::Path;
+
use ::settings::Settings;
-use editor::{tasks::task_context, Editor};
-use gpui::{App, Context, Task as AsyncTask, Window};
+use editor::Editor;
+use gpui::{App, AppContext as _, Context, Entity, Task, Window};
use modal::{TaskOverrides, TasksModal};
-use project::{Location, WorktreeId};
-use task::{RevealTarget, TaskId};
+use project::{Location, TaskContexts, Worktree, WorktreeId};
+use task::{RevealTarget, TaskContext, TaskId, TaskVariables, VariableName};
use workspace::tasks::schedule_task;
use workspace::{tasks::schedule_resolved_task, Workspace};
@@ -43,19 +46,23 @@ pub fn init(cx: &mut App) {
if let Some(use_new_terminal) = action.use_new_terminal {
original_task.use_new_terminal = use_new_terminal;
}
- let context_task = task_context(workspace, window, cx);
+ let task_contexts = task_contexts(workspace, window, cx);
cx.spawn_in(window, |workspace, mut cx| async move {
- let task_context = context_task.await;
+ let task_contexts = task_contexts.await;
workspace
- .update(&mut cx, |workspace, cx| {
- schedule_task(
- workspace,
- task_source_kind,
- &original_task,
- &task_context,
- false,
- cx,
- )
+ .update_in(&mut cx, |workspace, window, cx| {
+ if let Some(task_context) = task_contexts.active_context() {
+ schedule_task(
+ workspace,
+ task_source_kind,
+ &original_task,
+ task_context,
+ false,
+ cx,
+ )
+ } else {
+ toggle_modal(workspace, None, window, cx).detach();
+ }
})
.ok()
})
@@ -114,22 +121,22 @@ fn toggle_modal(
reveal_target: Option<RevealTarget>,
window: &mut Window,
cx: &mut Context<Workspace>,
-) -> AsyncTask<()> {
+) -> Task<()> {
let task_store = workspace.project().read(cx).task_store().clone();
let workspace_handle = workspace.weak_handle();
let can_open_modal = workspace.project().update(cx, |project, cx| {
project.is_local() || project.ssh_connection_string(cx).is_some() || project.is_via_ssh()
});
if can_open_modal {
- let context_task = task_context(workspace, window, cx);
+ let task_contexts = task_contexts(workspace, window, cx);
cx.spawn_in(window, |workspace, mut cx| async move {
- let task_context = context_task.await;
+ let task_contexts = task_contexts.await;
workspace
.update_in(&mut cx, |workspace, window, cx| {
workspace.toggle_modal(window, cx, |window, cx| {
TasksModal::new(
task_store.clone(),
- task_context,
+ task_contexts,
reveal_target.map(|target| TaskOverrides {
reveal_target: Some(target),
}),
@@ -142,7 +149,7 @@ fn toggle_modal(
.ok();
})
} else {
- AsyncTask::ready(())
+ Task::ready(())
}
}
@@ -151,12 +158,12 @@ fn spawn_task_with_name(
overrides: Option<TaskOverrides>,
window: &mut Window,
cx: &mut Context<Workspace>,
-) -> AsyncTask<anyhow::Result<()>> {
+) -> Task<anyhow::Result<()>> {
cx.spawn_in(window, |workspace, mut cx| async move {
- let context_task = workspace.update_in(&mut cx, |workspace, window, cx| {
- task_context(workspace, window, cx)
+ let task_contexts = workspace.update_in(&mut cx, |workspace, window, cx| {
+ task_contexts(workspace, window, cx)
})?;
- let task_context = context_task.await;
+ let task_contexts = task_contexts.await;
let tasks = workspace.update(&mut cx, |workspace, cx| {
let Some(task_inventory) = workspace
.project()
@@ -192,11 +199,13 @@ fn spawn_task_with_name(
target_task.reveal_target = target_override;
}
}
+ let default_context = TaskContext::default();
+ let active_context = task_contexts.active_context().unwrap_or(&default_context);
schedule_task(
workspace,
task_source_kind,
&target_task,
- &task_context,
+ active_context,
false,
cx,
);
@@ -230,7 +239,14 @@ fn active_item_selection_properties(
let worktree_id = active_item
.as_ref()
.and_then(|item| item.project_path(cx))
- .map(|path| path.worktree_id);
+ .map(|path| path.worktree_id)
+ .filter(|worktree_id| {
+ workspace
+ .project()
+ .read(cx)
+ .worktree_for_id(*worktree_id, cx)
+ .map_or(false, |worktree| is_visible_directory(&worktree, cx))
+ });
let location = active_item
.and_then(|active_item| active_item.act_as::<Editor>(cx))
.and_then(|editor| {
@@ -251,6 +267,84 @@ fn active_item_selection_properties(
(worktree_id, location)
}
+fn task_contexts(workspace: &Workspace, window: &mut Window, cx: &mut App) -> Task<TaskContexts> {
+ let active_item = workspace.active_item(cx);
+ let active_worktree = active_item
+ .as_ref()
+ .and_then(|item| item.project_path(cx))
+ .map(|project_path| project_path.worktree_id)
+ .filter(|worktree_id| {
+ workspace
+ .project()
+ .read(cx)
+ .worktree_for_id(*worktree_id, cx)
+ .map_or(false, |worktree| is_visible_directory(&worktree, cx))
+ });
+
+ let editor_context_task =
+ active_item
+ .and_then(|item| item.act_as::<Editor>(cx))
+ .map(|active_editor| {
+ active_editor.update(cx, |editor, cx| editor.task_context(window, cx))
+ });
+
+ let mut worktree_abs_paths = workspace
+ .worktrees(cx)
+ .filter(|worktree| is_visible_directory(worktree, cx))
+ .map(|worktree| {
+ let worktree = worktree.read(cx);
+ (worktree.id(), worktree.abs_path())
+ })
+ .collect::<HashMap<_, _>>();
+
+ cx.background_spawn(async move {
+ let mut task_contexts = TaskContexts::default();
+
+ if let Some(editor_context_task) = editor_context_task {
+ if let Some(editor_context) = editor_context_task.await {
+ task_contexts.active_item_context = Some((active_worktree, editor_context));
+ }
+ }
+
+ if let Some(active_worktree) = active_worktree {
+ if let Some(active_worktree_abs_path) = worktree_abs_paths.remove(&active_worktree) {
+ task_contexts.active_worktree_context =
+ Some((active_worktree, worktree_context(&active_worktree_abs_path)));
+ }
+ } else if worktree_abs_paths.len() == 1 {
+ task_contexts.active_worktree_context = worktree_abs_paths
+ .drain()
+ .next()
+ .map(|(id, abs_path)| (id, worktree_context(&abs_path)));
+ }
+
+ task_contexts.other_worktree_contexts.extend(
+ worktree_abs_paths
+ .into_iter()
+ .map(|(id, abs_path)| (id, worktree_context(&abs_path))),
+ );
+ task_contexts
+ })
+}
+
+fn is_visible_directory(worktree: &Entity<Worktree>, cx: &App) -> bool {
+ let worktree = worktree.read(cx);
+ worktree.is_visible() && worktree.root_entry().map_or(false, |entry| entry.is_dir())
+}
+
+fn worktree_context(worktree_abs_path: &Path) -> TaskContext {
+ let mut task_variables = TaskVariables::default();
+ task_variables.insert(
+ VariableName::WorktreeRoot,
+ worktree_abs_path.to_string_lossy().to_string(),
+ );
+ TaskContext {
+ cwd: Some(worktree_abs_path.to_path_buf()),
+ task_variables,
+ project_env: HashMap::default(),
+ }
+}
+
#[cfg(test)]
mod tests {
use std::{collections::HashMap, sync::Arc};
@@ -265,7 +359,7 @@ mod tests {
use util::{path, separator};
use workspace::{AppState, Workspace};
- use crate::task_context;
+ use crate::task_contexts;
#[gpui::test]
async fn test_default_language_context(cx: &mut TestAppContext) {
@@ -325,8 +419,8 @@ mod tests {
"function" @context
name: (_) @name
parameters: (formal_parameters
- "(" @context
- ")" @context)) @item"#,
+ "(" @context
+ ")" @context)) @item"#,
)
.unwrap()
.with_context_provider(Some(Arc::new(BasicContextProvider::new(
@@ -373,13 +467,15 @@ mod tests {
workspace.active_item(cx).unwrap().item_id(),
editor2.entity_id()
);
- task_context(workspace, window, cx)
+ task_contexts(workspace, window, cx)
})
.await;
assert_eq!(
- first_context,
- TaskContext {
+ first_context
+ .active_context()
+ .expect("Should have an active context"),
+ &TaskContext {
cwd: Some(path!("/dir").into()),
task_variables: TaskVariables::from_iter([
(VariableName::File, path!("/dir/rust/b.rs").into()),
@@ -405,10 +501,12 @@ mod tests {
assert_eq!(
workspace
.update_in(cx, |workspace, window, cx| {
- task_context(workspace, window, cx)
+ task_contexts(workspace, window, cx)
})
- .await,
- TaskContext {
+ .await
+ .active_context()
+ .expect("Should have an active context"),
+ &TaskContext {
cwd: Some(path!("/dir").into()),
task_variables: TaskVariables::from_iter([
(VariableName::File, path!("/dir/rust/b.rs").into()),
@@ -431,10 +529,12 @@ mod tests {
.update_in(cx, |workspace, window, cx| {
// Now, let's switch the active item to .ts file.
workspace.activate_item(&editor1, true, true, window, cx);
- task_context(workspace, window, cx)
+ task_contexts(workspace, window, cx)
})
- .await,
- TaskContext {
+ .await
+ .active_context()
+ .expect("Should have an active context"),
+ &TaskContext {
cwd: Some(path!("/dir").into()),
task_variables: TaskVariables::from_iter([
(VariableName::File, path!("/dir/a.ts").into()),