debugger/tasks: Remove TaskType enum (#29208)

Closes #ISSUE

Release Notes:

- N/A

---------

Co-authored-by: Cole Miller <m@cole-miller.net>
Co-authored-by: Anthony Eid <hello@anthonyeid.me>
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
Co-authored-by: Anthony <anthony@zed.dev>
Co-authored-by: Conrad <conrad@zed.dev>
This commit is contained in:
Piotr Osiewicz 2025-04-26 01:44:56 +02:00 committed by GitHub
parent 053fafa90e
commit 67615b968b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
53 changed files with 1272 additions and 1114 deletions

View file

@ -13,14 +13,15 @@ use collections::{HashMap, HashSet, VecDeque};
use gpui::{App, AppContext as _, Entity, SharedString, Task};
use itertools::Itertools;
use language::{
ContextProvider, File, Language, LanguageToolchainStore, Location,
Buffer, ContextProvider, File, Language, LanguageToolchainStore, Location,
language_settings::language_settings,
};
use lsp::{LanguageServerId, LanguageServerName};
use settings::{InvalidSettingsError, TaskKind, parse_json_with_comments};
use paths::{debug_task_file_name, task_file_name};
use settings::{InvalidSettingsError, parse_json_with_comments};
use task::{
DebugTaskTemplate, ResolvedTask, TaskContext, TaskId, TaskTemplate, TaskTemplates,
TaskVariables, VariableName,
DebugScenario, ResolvedTask, TaskContext, TaskId, TaskTemplate, TaskTemplates, TaskVariables,
VariableName,
};
use text::{BufferId, Point, ToPoint};
use util::{NumericPrefixWithSuffix, ResultExt as _, paths::PathExt as _, post_inc};
@ -32,13 +33,84 @@ use crate::{task_store::TaskSettingsLocation, worktree_store::WorktreeStore};
#[derive(Debug, Default)]
pub struct Inventory {
last_scheduled_tasks: VecDeque<(TaskSourceKind, ResolvedTask)>,
templates_from_settings: ParsedTemplates,
templates_from_settings: InventoryFor<TaskTemplate>,
scenarios_from_settings: InventoryFor<DebugScenario>,
}
#[derive(Debug, Default)]
struct ParsedTemplates {
global: HashMap<PathBuf, Vec<TaskTemplate>>,
worktree: HashMap<WorktreeId, HashMap<(Arc<Path>, TaskKind), Vec<TaskTemplate>>>,
// Helper trait for better error messages in [InventoryFor]
trait InventoryContents: Clone {
const GLOBAL_SOURCE_FILE: &'static str;
const LABEL: &'static str;
}
impl InventoryContents for TaskTemplate {
const GLOBAL_SOURCE_FILE: &'static str = "tasks.json";
const LABEL: &'static str = "tasks";
}
impl InventoryContents for DebugScenario {
const GLOBAL_SOURCE_FILE: &'static str = "debug.json";
const LABEL: &'static str = "debug scenarios";
}
#[derive(Debug)]
struct InventoryFor<T> {
global: HashMap<PathBuf, Vec<T>>,
worktree: HashMap<WorktreeId, HashMap<Arc<Path>, Vec<T>>>,
}
impl<T: InventoryContents> InventoryFor<T> {
fn worktree_scenarios(
&self,
worktree: Option<WorktreeId>,
) -> impl '_ + Iterator<Item = (TaskSourceKind, T)> {
worktree.into_iter().flat_map(|worktree| {
self.worktree
.get(&worktree)
.into_iter()
.flatten()
.flat_map(|(directory, templates)| {
templates.iter().map(move |template| (directory, template))
})
.map(move |(directory, template)| {
(
TaskSourceKind::Worktree {
id: worktree,
directory_in_worktree: directory.to_path_buf(),
id_base: Cow::Owned(format!(
"local worktree {} from directory {directory:?}",
T::LABEL
)),
},
template.clone(),
)
})
})
}
fn global_scenarios(&self) -> impl '_ + Iterator<Item = (TaskSourceKind, T)> {
self.global.iter().flat_map(|(file_path, templates)| {
templates.into_iter().map(|template| {
(
TaskSourceKind::AbsPath {
id_base: Cow::Owned(format!("global {}", T::GLOBAL_SOURCE_FILE)),
abs_path: file_path.clone(),
},
template.clone(),
)
})
})
}
}
impl<T> Default for InventoryFor<T> {
fn default() -> Self {
Self {
global: HashMap::default(),
worktree: HashMap::default(),
}
}
}
/// Kind of a source the tasks are fetched from, used to display more source information in the UI.
@ -134,22 +206,40 @@ impl Inventory {
cx.new(|_| Self::default())
}
pub fn list_debug_tasks(&self) -> Vec<&TaskTemplate> {
self.templates_from_settings
.worktree
.values()
.flat_map(|tasks| {
tasks.iter().filter_map(|(kind, tasks)| {
if matches!(kind.1, TaskKind::Debug) {
Some(tasks)
} else {
None
}
})
})
.flatten()
pub fn list_debug_scenarios(&self, worktree: Option<WorktreeId>) -> Vec<DebugScenario> {
let global_scenarios = self.global_debug_scenarios_from_settings();
let worktree_scenarios = self.worktree_scenarios_from_settings(worktree);
worktree_scenarios
.chain(global_scenarios)
.map(|(_, scenario)| scenario)
.collect()
}
pub fn task_template_by_label(
&self,
buffer: Option<Entity<Buffer>>,
label: &str,
cx: &App,
) -> Option<TaskTemplate> {
let (worktree_id, file, language) = buffer
.map(|buffer| {
let buffer = buffer.read(cx);
let file = buffer.file().cloned();
(
file.as_ref().map(|file| file.worktree_id(cx)),
file,
buffer.language().cloned(),
)
})
.unwrap_or((None, None, None));
self.list_tasks(file, language, worktree_id, cx)
.iter()
.find(|(_, template)| template.label == label)
.map(|val| val.1.clone())
}
/// Pulls its task sources relevant to the worktree and the language given,
/// returns all task templates with their source kinds, worktree tasks first, language tasks second
/// and global tasks last. No specific order inside source kinds groups.
@ -160,10 +250,11 @@ impl Inventory {
worktree: Option<WorktreeId>,
cx: &App,
) -> Vec<(TaskSourceKind, TaskTemplate)> {
let global_tasks = self.global_templates_from_settings();
let worktree_tasks = self.worktree_templates_from_settings(worktree);
let task_source_kind = language.as_ref().map(|language| TaskSourceKind::Language {
name: language.name().into(),
});
let global_tasks = self.global_templates_from_settings();
let language_tasks = language
.filter(|language| {
language_settings(Some(language.name()), file.as_ref(), cx)
@ -173,11 +264,11 @@ impl Inventory {
.and_then(|language| language.context_provider()?.associated_tasks(file, cx))
.into_iter()
.flat_map(|tasks| tasks.0.into_iter())
.flat_map(|task| Some((task_source_kind.clone()?, task)))
.chain(global_tasks);
.flat_map(|task| Some((task_source_kind.clone()?, task)));
self.worktree_templates_from_settings(worktree)
worktree_tasks
.chain(language_tasks)
.chain(global_tasks)
.collect()
}
@ -358,51 +449,27 @@ impl Inventory {
fn global_templates_from_settings(
&self,
) -> impl '_ + Iterator<Item = (TaskSourceKind, TaskTemplate)> {
self.templates_from_settings
.global
.iter()
.flat_map(|(file_path, templates)| {
templates.into_iter().map(|template| {
(
TaskSourceKind::AbsPath {
id_base: match template.task_type {
task::TaskType::Script => Cow::Borrowed("global tasks.json"),
task::TaskType::Debug(_) => Cow::Borrowed("global debug.json"),
},
abs_path: file_path.clone(),
},
template.clone(),
)
})
})
self.templates_from_settings.global_scenarios()
}
fn global_debug_scenarios_from_settings(
&self,
) -> impl '_ + Iterator<Item = (TaskSourceKind, DebugScenario)> {
self.scenarios_from_settings.global_scenarios()
}
fn worktree_scenarios_from_settings(
&self,
worktree: Option<WorktreeId>,
) -> impl '_ + Iterator<Item = (TaskSourceKind, DebugScenario)> {
self.scenarios_from_settings.worktree_scenarios(worktree)
}
fn worktree_templates_from_settings(
&self,
worktree: Option<WorktreeId>,
) -> impl '_ + Iterator<Item = (TaskSourceKind, TaskTemplate)> {
worktree.into_iter().flat_map(|worktree| {
self.templates_from_settings
.worktree
.get(&worktree)
.into_iter()
.flatten()
.flat_map(|(directory, templates)| {
templates.iter().map(move |template| (directory, template))
})
.map(move |((directory, _task_kind), template)| {
(
TaskSourceKind::Worktree {
id: worktree,
directory_in_worktree: directory.to_path_buf(),
id_base: Cow::Owned(format!(
"local worktree tasks from directory {directory:?}"
)),
},
template.clone(),
)
})
})
self.templates_from_settings.worktree_scenarios(worktree)
}
/// Updates in-memory task metadata from the JSON string given.
@ -413,7 +480,6 @@ impl Inventory {
&mut self,
location: TaskSettingsLocation<'_>,
raw_tasks_json: Option<&str>,
task_kind: TaskKind,
) -> Result<(), InvalidSettingsError> {
let raw_tasks = match parse_json_with_comments::<Vec<serde_json::Value>>(
raw_tasks_json.unwrap_or("[]"),
@ -424,21 +490,16 @@ impl Inventory {
path: match location {
TaskSettingsLocation::Global(path) => path.to_owned(),
TaskSettingsLocation::Worktree(settings_location) => {
task_kind.config_in_dir(settings_location.path)
settings_location.path.join(task_file_name())
}
},
message: format!("Failed to parse tasks file content as a JSON array: {e}"),
});
}
};
let new_templates = raw_tasks
.into_iter()
.filter_map(|raw_template| match &task_kind {
TaskKind::Script => serde_json::from_value::<TaskTemplate>(raw_template).log_err(),
TaskKind::Debug => serde_json::from_value::<DebugTaskTemplate>(raw_template)
.log_err()
.map(|content| content.to_zed_format()),
});
let new_templates = raw_tasks.into_iter().filter_map(|raw_template| {
serde_json::from_value::<TaskTemplate>(raw_template).log_err()
});
let parsed_templates = &mut self.templates_from_settings;
match location {
@ -454,14 +515,72 @@ impl Inventory {
if let Some(worktree_tasks) =
parsed_templates.worktree.get_mut(&location.worktree_id)
{
worktree_tasks.remove(&(Arc::from(location.path), task_kind));
worktree_tasks.remove(location.path);
}
} else {
parsed_templates
.worktree
.entry(location.worktree_id)
.or_default()
.insert((Arc::from(location.path), task_kind), new_templates);
.insert(Arc::from(location.path), new_templates);
}
}
}
Ok(())
}
/// Updates in-memory task metadata from the JSON string given.
/// Will fail if the JSON is not a valid array of objects, but will continue if any object will not parse into a [`TaskTemplate`].
///
/// Global tasks are updated for no worktree provided, otherwise the worktree metadata for a given path will be updated.
pub(crate) fn update_file_based_scenarios(
&mut self,
location: TaskSettingsLocation<'_>,
raw_tasks_json: Option<&str>,
) -> Result<(), InvalidSettingsError> {
let raw_tasks = match parse_json_with_comments::<Vec<serde_json::Value>>(
raw_tasks_json.unwrap_or("[]"),
) {
Ok(tasks) => tasks,
Err(e) => {
return Err(InvalidSettingsError::Debug {
path: match location {
TaskSettingsLocation::Global(path) => path.to_owned(),
TaskSettingsLocation::Worktree(settings_location) => {
settings_location.path.join(debug_task_file_name())
}
},
message: format!("Failed to parse tasks file content as a JSON array: {e}"),
});
}
};
let new_templates = raw_tasks.into_iter().filter_map(|raw_template| {
serde_json::from_value::<DebugScenario>(raw_template).log_err()
});
let parsed_scenarios = &mut self.scenarios_from_settings;
match location {
TaskSettingsLocation::Global(path) => {
parsed_scenarios
.global
.entry(path.to_owned())
.insert_entry(new_templates.collect());
}
TaskSettingsLocation::Worktree(location) => {
let new_templates = new_templates.collect::<Vec<_>>();
if new_templates.is_empty() {
if let Some(worktree_tasks) =
parsed_scenarios.worktree.get_mut(&location.worktree_id)
{
worktree_tasks.remove(location.path);
}
} else {
parsed_scenarios
.worktree
.entry(location.worktree_id)
.or_default()
.insert(Arc::from(location.path), new_templates);
}
}
}
@ -677,6 +796,10 @@ impl ContextProvider for BasicContextProvider {
Task::ready(Ok(task_variables))
}
fn debug_adapter(&self) -> Option<String> {
None
}
}
/// A ContextProvider that doesn't provide any task variables on it's own, though it has some associated tasks.
@ -700,6 +823,10 @@ impl ContextProvider for ContextProviderWithTasks {
) -> Option<TaskTemplates> {
Some(self.templates.clone())
}
fn debug_adapter(&self) -> Option<String> {
None
}
}
#[cfg(test)]
@ -744,7 +871,6 @@ mod tests {
Some(&mock_tasks_from_names(
expected_initial_state.iter().map(|name| name.as_str()),
)),
settings::TaskKind::Script,
)
.unwrap();
});
@ -800,7 +926,6 @@ mod tests {
.into_iter()
.chain(expected_initial_state.iter().map(|name| name.as_str())),
)),
settings::TaskKind::Script,
)
.unwrap();
});
@ -925,7 +1050,6 @@ mod tests {
.iter()
.map(|(_, name)| name.as_str()),
)),
settings::TaskKind::Script,
)
.unwrap();
inventory
@ -937,7 +1061,6 @@ mod tests {
Some(&mock_tasks_from_names(
worktree_1_tasks.iter().map(|(_, name)| name.as_str()),
)),
settings::TaskKind::Script,
)
.unwrap();
inventory
@ -949,7 +1072,6 @@ mod tests {
Some(&mock_tasks_from_names(
worktree_2_tasks.iter().map(|(_, name)| name.as_str()),
)),
settings::TaskKind::Script,
)
.unwrap();
});