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

@ -800,6 +800,7 @@ impl LocalSettingsKind {
proto::LocalSettingsKind::Settings => Self::Settings,
proto::LocalSettingsKind::Tasks => Self::Tasks,
proto::LocalSettingsKind::Editorconfig => Self::Editorconfig,
proto::LocalSettingsKind::Debug => Self::Debug,
}
}
@ -808,6 +809,7 @@ impl LocalSettingsKind {
Self::Settings => proto::LocalSettingsKind::Settings,
Self::Tasks => proto::LocalSettingsKind::Tasks,
Self::Editorconfig => proto::LocalSettingsKind::Editorconfig,
Self::Debug => proto::LocalSettingsKind::Debug,
}
}
}

View File

@ -32,4 +32,6 @@ pub enum LocalSettingsKind {
Tasks,
#[sea_orm(string_value = "editorconfig")]
Editorconfig,
#[sea_orm(string_value = "debug")]
Debug,
}

View File

@ -680,6 +680,7 @@ async fn test_collaborating_with_code_actions(
editor.toggle_code_actions(
&ToggleCodeActions {
deployed_from_indicator: None,
quick_launch: false,
},
window,
cx,

View File

@ -1824,6 +1824,8 @@ async fn test_active_call_events(
server
.create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
.await;
executor.run_until_parked();
let active_call_a = cx_a.read(ActiveCall::global);
let active_call_b = cx_b.read(ActiveCall::global);

View File

@ -14,10 +14,16 @@ use serde::{Deserialize, Serialize};
use settings::WorktreeId;
use smol::{self, fs::File, lock::Mutex};
use std::{
borrow::Borrow, collections::HashSet, ffi::OsStr, fmt::Debug, net::Ipv4Addr, ops::Deref,
path::PathBuf, sync::Arc,
borrow::Borrow,
collections::HashSet,
ffi::OsStr,
fmt::Debug,
net::Ipv4Addr,
ops::Deref,
path::{Path, PathBuf},
sync::Arc,
};
use task::{DebugTaskDefinition, TcpArgumentsTemplate};
use task::{AttachRequest, DebugRequest, DebugScenario, LaunchRequest, TcpArgumentsTemplate};
use util::ResultExt;
#[derive(Clone, Debug, PartialEq, Eq)]
@ -109,6 +115,116 @@ impl TcpArguments {
}
}
/// Represents a debuggable binary/process (what process is going to be debugged and with what arguments).
///
/// We start off with a [DebugScenario], a user-facing type that additionally defines how a debug target is built; once
/// an optional build step is completed, we turn it's result into a DebugTaskDefinition by running a locator (or using a user-provided task) and resolving task variables.
/// Finally, a [DebugTaskDefinition] has to be turned into a concrete debugger invocation ([DebugAdapterBinary]).
#[derive(Clone, Debug, PartialEq)]
pub struct DebugTaskDefinition {
pub label: SharedString,
pub adapter: SharedString,
pub request: DebugRequest,
/// Additional initialization arguments to be sent on DAP initialization
pub initialize_args: Option<serde_json::Value>,
/// Whether to tell the debug adapter to stop on entry
pub stop_on_entry: Option<bool>,
/// Optional TCP connection information
///
/// If provided, this will be used to connect to the debug adapter instead of
/// spawning a new debug adapter process. This is useful for connecting to a debug adapter
/// that is already running or is started by another process.
pub tcp_connection: Option<TcpArgumentsTemplate>,
}
impl DebugTaskDefinition {
pub fn cwd(&self) -> Option<&Path> {
if let DebugRequest::Launch(config) = &self.request {
config.cwd.as_ref().map(Path::new)
} else {
None
}
}
pub fn to_scenario(&self) -> DebugScenario {
DebugScenario {
label: self.label.clone(),
adapter: self.adapter.clone(),
build: None,
request: Some(self.request.clone()),
stop_on_entry: self.stop_on_entry,
tcp_connection: self.tcp_connection.clone(),
initialize_args: self.initialize_args.clone(),
}
}
pub fn to_proto(&self) -> proto::DebugTaskDefinition {
proto::DebugTaskDefinition {
adapter: self.adapter.to_string(),
request: Some(match &self.request {
DebugRequest::Launch(config) => {
proto::debug_task_definition::Request::DebugLaunchRequest(
proto::DebugLaunchRequest {
program: config.program.clone(),
cwd: config.cwd.as_ref().map(|c| c.to_string_lossy().to_string()),
args: config.args.clone(),
env: config
.env
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect(),
},
)
}
DebugRequest::Attach(attach_request) => {
proto::debug_task_definition::Request::DebugAttachRequest(
proto::DebugAttachRequest {
process_id: attach_request.process_id.unwrap_or_default(),
},
)
}
}),
label: self.label.to_string(),
initialize_args: self.initialize_args.as_ref().map(|v| v.to_string()),
tcp_connection: self.tcp_connection.as_ref().map(|t| t.to_proto()),
stop_on_entry: self.stop_on_entry,
}
}
pub fn from_proto(proto: proto::DebugTaskDefinition) -> Result<Self> {
let request = proto
.request
.ok_or_else(|| anyhow::anyhow!("request is required"))?;
Ok(Self {
label: proto.label.into(),
initialize_args: proto.initialize_args.map(|v| v.into()),
tcp_connection: proto
.tcp_connection
.map(TcpArgumentsTemplate::from_proto)
.transpose()?,
stop_on_entry: proto.stop_on_entry,
adapter: proto.adapter.into(),
request: match request {
proto::debug_task_definition::Request::DebugAttachRequest(config) => {
DebugRequest::Attach(AttachRequest {
process_id: Some(config.process_id),
})
}
proto::debug_task_definition::Request::DebugLaunchRequest(config) => {
DebugRequest::Launch(LaunchRequest {
program: config.program,
cwd: config.cwd.map(|cwd| cwd.into()),
args: config.args,
env: Default::default(),
})
}
},
})
}
}
/// Created from a [DebugTaskDefinition], this struct describes how to spawn the debugger to create a previously-configured debug session.
#[derive(Debug, Clone)]
pub struct DebugAdapterBinary {
pub command: String,

View File

@ -6,7 +6,7 @@ mod registry;
pub mod transport;
pub use dap_types::*;
pub use registry::DapRegistry;
pub use registry::{DapLocator, DapRegistry};
pub use task::DebugRequest;
pub type ScopeId = u64;

View File

@ -1,12 +1,25 @@
use anyhow::Result;
use async_trait::async_trait;
use collections::FxHashMap;
use gpui::{App, Global};
use parking_lot::RwLock;
use task::{DebugRequest, SpawnInTerminal};
use crate::adapters::{DebugAdapter, DebugAdapterName};
use std::{collections::BTreeMap, sync::Arc};
/// Given a user build configuration, locator creates a fill-in debug target ([DebugRequest]) on behalf of the user.
#[async_trait]
pub trait DapLocator: Send + Sync {
/// Determines whether this locator can generate debug target for given task.
fn accepts(&self, build_config: &SpawnInTerminal) -> bool;
async fn run(&self, build_config: SpawnInTerminal) -> Result<DebugRequest>;
}
#[derive(Default)]
struct DapRegistryState {
adapters: BTreeMap<DebugAdapterName, Arc<dyn DebugAdapter>>,
locators: FxHashMap<String, Arc<dyn DapLocator>>,
}
#[derive(Clone, Default)]
@ -35,6 +48,18 @@ impl DapRegistry {
);
}
pub fn add_locator(&self, name: String, locator: Arc<dyn DapLocator>) {
let _previous_value = self.0.write().locators.insert(name, locator);
debug_assert!(
_previous_value.is_none(),
"Attempted to insert a new debug locator when one is already registered"
);
}
pub fn locators(&self) -> FxHashMap<String, Arc<dyn DapLocator>> {
self.0.read().locators.clone()
}
pub fn adapter(&self, name: &str) -> Option<Arc<dyn DebugAdapter>> {
self.0.read().adapters.get(name).cloned()
}

View File

@ -2,9 +2,9 @@ use std::{collections::HashMap, path::PathBuf, sync::OnceLock};
use anyhow::{Result, bail};
use async_trait::async_trait;
use dap::adapters::{InlineValueProvider, latest_github_release};
use dap::adapters::{DebugTaskDefinition, InlineValueProvider, latest_github_release};
use gpui::AsyncApp;
use task::{DebugRequest, DebugTaskDefinition};
use task::DebugRequest;
use crate::*;
@ -25,7 +25,10 @@ impl CodeLldbDebugAdapter {
});
let map = configuration.as_object_mut().unwrap();
// CodeLLDB uses `name` for a terminal label.
map.insert("name".into(), Value::String(config.label.clone()));
map.insert(
"name".into(),
Value::String(String::from(config.label.as_ref())),
);
let request = config.request.to_dap();
match &config.request {
DebugRequest::Attach(attach) => {

View File

@ -2,9 +2,9 @@ use std::{collections::HashMap, ffi::OsStr};
use anyhow::{Result, bail};
use async_trait::async_trait;
use dap::StartDebuggingRequestArguments;
use dap::{StartDebuggingRequestArguments, adapters::DebugTaskDefinition};
use gpui::AsyncApp;
use task::{DebugRequest, DebugTaskDefinition};
use task::DebugRequest;
use crate::*;

View File

@ -1,7 +1,6 @@
use dap::StartDebuggingRequestArguments;
use dap::{StartDebuggingRequestArguments, adapters::DebugTaskDefinition};
use gpui::AsyncApp;
use std::{collections::HashMap, ffi::OsStr, path::PathBuf};
use task::DebugTaskDefinition;
use crate::*;

View File

@ -1,8 +1,8 @@
use adapters::latest_github_release;
use dap::StartDebuggingRequestArguments;
use dap::{StartDebuggingRequestArguments, adapters::DebugTaskDefinition};
use gpui::AsyncApp;
use std::{collections::HashMap, path::PathBuf};
use task::{DebugRequest, DebugTaskDefinition};
use task::DebugRequest;
use crate::*;

View File

@ -1,8 +1,7 @@
use adapters::latest_github_release;
use dap::adapters::TcpArguments;
use dap::adapters::{DebugTaskDefinition, TcpArguments};
use gpui::AsyncApp;
use std::{collections::HashMap, path::PathBuf};
use task::DebugTaskDefinition;
use crate::*;

View File

@ -1,8 +1,10 @@
use crate::*;
use dap::{StartDebuggingRequestArguments, adapters::InlineValueProvider};
use dap::{
DebugRequest, StartDebuggingRequestArguments, adapters::DebugTaskDefinition,
adapters::InlineValueProvider,
};
use gpui::AsyncApp;
use std::{collections::HashMap, ffi::OsStr, path::PathBuf};
use task::DebugTaskDefinition;
#[derive(Default)]
pub(crate) struct PythonDebugAdapter;

View File

@ -1,4 +1,5 @@
use dap::DebugRequest;
use dap::adapters::DebugTaskDefinition;
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{DismissEvent, Entity, EventEmitter, Focusable, Render};
use gpui::{Subscription, WeakEntity};
@ -24,20 +25,20 @@ pub(crate) struct AttachModalDelegate {
selected_index: usize,
matches: Vec<StringMatch>,
placeholder_text: Arc<str>,
pub(crate) definition: DebugTaskDefinition,
workspace: WeakEntity<Workspace>,
pub(crate) debug_config: task::DebugTaskDefinition,
candidates: Arc<[Candidate]>,
}
impl AttachModalDelegate {
fn new(
workspace: Entity<Workspace>,
debug_config: task::DebugTaskDefinition,
definition: DebugTaskDefinition,
candidates: Arc<[Candidate]>,
) -> Self {
Self {
workspace: workspace.downgrade(),
debug_config,
definition,
candidates,
selected_index: 0,
matches: Vec::default(),
@ -53,8 +54,8 @@ pub struct AttachModal {
impl AttachModal {
pub fn new(
definition: DebugTaskDefinition,
workspace: Entity<Workspace>,
debug_config: task::DebugTaskDefinition,
modal: bool,
window: &mut Window,
cx: &mut Context<Self>,
@ -77,12 +78,12 @@ impl AttachModal {
.collect();
processes.sort_by_key(|k| k.name.clone());
let processes = processes.into_iter().collect();
Self::with_processes(workspace, debug_config, processes, modal, window, cx)
Self::with_processes(workspace, definition, processes, modal, window, cx)
}
pub(super) fn with_processes(
workspace: Entity<Workspace>,
debug_config: task::DebugTaskDefinition,
definition: DebugTaskDefinition,
processes: Arc<[Candidate]>,
modal: bool,
window: &mut Window,
@ -90,7 +91,7 @@ impl AttachModal {
) -> Self {
let picker = cx.new(|cx| {
Picker::uniform_list(
AttachModalDelegate::new(workspace, debug_config, processes),
AttachModalDelegate::new(workspace, definition, processes),
window,
cx,
)
@ -217,7 +218,7 @@ impl PickerDelegate for AttachModalDelegate {
return cx.emit(DismissEvent);
};
match &mut self.debug_config.request {
match &mut self.definition.request {
DebugRequest::Attach(config) => {
config.process_id = Some(candidate.pid);
}
@ -227,7 +228,8 @@ impl PickerDelegate for AttachModalDelegate {
}
}
let definition = self.debug_config.clone();
let scenario = self.definition.to_scenario();
let panel = self
.workspace
.update(cx, |workspace, cx| workspace.panel::<DebugPanel>(cx))
@ -235,9 +237,10 @@ impl PickerDelegate for AttachModalDelegate {
.flatten();
if let Some(panel) = panel {
panel.update(cx, |panel, cx| {
panel.start_session(definition, window, cx);
panel.start_session(scenario, Default::default(), None, window, cx);
});
}
cx.emit(DismissEvent);
}

View File

@ -9,11 +9,12 @@ use crate::{new_session_modal::NewSessionModal, session::DebugSession};
use anyhow::{Result, anyhow};
use collections::HashMap;
use command_palette_hooks::CommandPaletteFilter;
use dap::StartDebuggingRequestArguments;
use dap::DebugRequest;
use dap::{
ContinuedEvent, LoadedSourceEvent, ModuleEvent, OutputEvent, StoppedEvent, ThreadEvent,
client::SessionId, debugger_settings::DebuggerSettings,
};
use dap::{StartDebuggingRequestArguments, adapters::DebugTaskDefinition};
use futures::{SinkExt as _, channel::mpsc};
use gpui::{
Action, App, AsyncWindowContext, Context, DismissEvent, Entity, EntityId, EventEmitter,
@ -21,6 +22,7 @@ use gpui::{
actions, anchored, deferred,
};
use language::Buffer;
use project::debugger::session::{Session, SessionStateEvent};
use project::{
Project,
@ -35,9 +37,7 @@ use settings::Settings;
use std::any::TypeId;
use std::path::Path;
use std::sync::Arc;
use task::{
DebugTaskDefinition, DebugTaskTemplate, HideStrategy, RevealStrategy, RevealTarget, TaskId,
};
use task::{DebugScenario, HideStrategy, RevealStrategy, RevealTarget, TaskContext, TaskId};
use terminal_view::TerminalView;
use ui::{ContextMenu, Divider, DropdownMenu, Tooltip, prelude::*};
use workspace::SplitDirection;
@ -87,45 +87,8 @@ impl DebugPanel {
let project = workspace.project().clone();
let dap_store = project.read(cx).dap_store();
let weak = cx.weak_entity();
let modal_subscription =
cx.observe_new::<tasks_ui::TasksModal>(move |_, window, cx| {
let modal_entity = cx.entity();
weak.update(cx, |_: &mut DebugPanel, cx| {
let Some(window) = window else {
log::error!("Debug panel couldn't subscribe to tasks modal because there was no window");
return;
};
cx.subscribe_in(
&modal_entity,
window,
|panel, _, event: &tasks_ui::ShowAttachModal, window, cx| {
panel.workspace.update(cx, |workspace, cx| {
let workspace_handle = cx.entity().clone();
workspace.toggle_modal(window, cx, |window, cx| {
crate::attach_modal::AttachModal::new(
workspace_handle,
event.debug_config.clone(),
true,
window,
cx,
)
});
}).ok();
},
)
.detach();
})
.ok();
});
let _subscriptions = vec![
cx.subscribe_in(&dap_store, window, Self::handle_dap_store_event),
modal_subscription,
];
let _subscriptions =
vec![cx.subscribe_in(&dap_store, window, Self::handle_dap_store_event)];
let debug_panel = Self {
size: px(300.),
@ -259,43 +222,16 @@ impl DebugPanel {
})
}
pub fn start_session(
fn start_from_definition(
&mut self,
definition: DebugTaskDefinition,
window: &mut Window,
cx: &mut Context<Self>,
) {
let task_contexts = self
.workspace
.update(cx, |workspace, cx| {
tasks_ui::task_contexts(workspace, window, cx)
})
.ok();
let dap_store = self.project.read(cx).dap_store().clone();
) -> Task<Result<()>> {
cx.spawn_in(window, async move |this, cx| {
let task_context = if let Some(task) = task_contexts {
task.await
.active_worktree_context
.map_or(task::TaskContext::default(), |context| context.1)
} else {
task::TaskContext::default()
};
let dap_store = this.update(cx, |this, cx| this.project.read(cx).dap_store())?;
let (session, task) = dap_store.update(cx, |dap_store, cx| {
let template = DebugTaskTemplate {
locator: None,
definition: definition.clone(),
};
let session = if let Some(debug_config) = template
.to_zed_format()
.resolve_task("debug_task", &task_context)
.and_then(|resolved_task| resolved_task.resolved_debug_adapter_config())
{
dap_store.new_session(debug_config.definition, None, cx)
} else {
dap_store.new_session(definition.clone(), None, cx)
};
let session = dap_store.new_session(definition, None, cx);
(session.clone(), dap_store.boot_session(session, cx))
})?;
@ -318,6 +254,27 @@ impl DebugPanel {
anyhow::Ok(())
})
}
pub fn start_session(
&mut self,
scenario: DebugScenario,
task_context: TaskContext,
active_buffer: Option<Entity<Buffer>>,
window: &mut Window,
cx: &mut Context<Self>,
) {
cx.spawn_in(window, async move |this, cx| {
let definition = this
.update_in(cx, |this, window, cx| {
this.resolve_scenario(scenario, task_context, active_buffer, window, cx)
})?
.await?;
this.update_in(cx, |this, window, cx| {
this.start_from_definition(definition, window, cx)
})?
.await
})
.detach_and_log_err(cx);
}
@ -343,13 +300,13 @@ impl DebugPanel {
let definition = curr_session.update(cx, |session, _| session.definition());
let task = curr_session.update(cx, |session, cx| session.shutdown(cx));
let definition = definition.clone();
cx.spawn_in(window, async move |this, cx| {
task.await;
this.update_in(cx, |this, window, cx| {
this.start_session(definition, window, cx)
})
this.start_from_definition(definition, window, cx)
})?
.await
})
.detach_and_log_err(cx);
}
@ -503,6 +460,75 @@ impl DebugPanel {
}
}
pub fn resolve_scenario(
&self,
scenario: DebugScenario,
task_context: TaskContext,
buffer: Option<Entity<Buffer>>,
window: &Window,
cx: &mut Context<Self>,
) -> Task<Result<DebugTaskDefinition>> {
let project = self.project.read(cx);
let dap_store = project.dap_store().downgrade();
let task_store = project.task_store().downgrade();
let workspace = self.workspace.clone();
cx.spawn_in(window, async move |_, cx| {
let DebugScenario {
adapter,
label,
build,
request,
initialize_args,
tcp_connection,
stop_on_entry,
} = scenario;
let request = if let Some(mut request) = request {
// Resolve task variables within the request.
if let DebugRequest::Launch(_) = &mut request {}
request
} else if let Some(build) = build {
let Some(task) = task_store.update(cx, |this, cx| {
this.task_inventory().and_then(|inventory| {
inventory
.read(cx)
.task_template_by_label(buffer, &build, cx)
})
})?
else {
anyhow::bail!("Couldn't find task template for {:?}", build)
};
let Some(task) = task.resolve_task("debug-build-task", &task_context) else {
anyhow::bail!("Could not resolve task variables within a debug scenario");
};
let run_build = workspace.update_in(cx, |workspace, window, cx| {
workspace.spawn_in_terminal(task.resolved.clone(), window, cx)
})?;
let exit_status = run_build.await?;
if !exit_status.success() {
anyhow::bail!("Build failed");
}
dap_store
.update(cx, |this, cx| this.run_debug_locator(task.resolved, cx))?
.await?
} else {
return Err(anyhow!("No request or build provided"));
};
Ok(DebugTaskDefinition {
label,
adapter,
request,
initialize_args,
stop_on_entry,
tcp_connection,
})
})
}
fn handle_run_in_terminal_request(
&self,
session_id: SessionId,
@ -1409,10 +1435,17 @@ impl Render for DebugPanel {
struct DebuggerProvider(Entity<DebugPanel>);
impl workspace::DebuggerProvider for DebuggerProvider {
fn start_session(&self, definition: DebugTaskDefinition, window: &mut Window, cx: &mut App) {
fn start_session(
&self,
definition: DebugScenario,
context: TaskContext,
buffer: Option<Entity<Buffer>>,
window: &mut Window,
cx: &mut App,
) {
self.0.update(cx, |_, cx| {
cx.defer_in(window, |this, window, cx| {
this.start_session(definition, window, cx);
this.start_session(definition, context, buffer, window, cx);
})
})
}

View File

@ -4,14 +4,14 @@ use std::{
path::{Path, PathBuf},
};
use dap::{DapRegistry, DebugRequest};
use dap::{DapRegistry, DebugRequest, adapters::DebugTaskDefinition};
use editor::{Editor, EditorElement, EditorStyle};
use gpui::{
App, AppContext, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Render, TextStyle,
WeakEntity,
};
use settings::Settings;
use task::{DebugTaskDefinition, DebugTaskTemplate, LaunchRequest};
use task::{DebugScenario, LaunchRequest, TaskContext};
use theme::ThemeSettings;
use ui::{
ActiveTheme, Button, ButtonCommon, ButtonSize, CheckboxWithLabel, Clickable, Color, Context,
@ -34,7 +34,7 @@ pub(super) struct NewSessionModal {
last_selected_profile_name: Option<SharedString>,
}
fn suggested_label(request: &DebugRequest, debugger: &str) -> String {
fn suggested_label(request: &DebugRequest, debugger: &str) -> SharedString {
match request {
DebugRequest::Launch(config) => {
let last_path_component = Path::new(&config.program)
@ -42,12 +42,13 @@ fn suggested_label(request: &DebugRequest, debugger: &str) -> String {
.map(|name| name.to_string_lossy())
.unwrap_or_else(|| Cow::Borrowed(&config.program));
format!("{} ({debugger})", last_path_component)
format!("{} ({debugger})", last_path_component).into()
}
DebugRequest::Attach(config) => format!(
"pid: {} ({debugger})",
config.process_id.unwrap_or(u32::MAX)
),
)
.into(),
}
}
@ -61,7 +62,7 @@ impl NewSessionModal {
) -> Self {
let debugger = past_debug_definition
.as_ref()
.map(|def| def.adapter.clone().into());
.map(|def| def.adapter.clone());
let stop_on_entry = past_debug_definition
.as_ref()
@ -85,18 +86,20 @@ impl NewSessionModal {
}
}
fn debug_config(&self, cx: &App, debugger: &str) -> DebugTaskDefinition {
fn debug_config(&self, cx: &App, debugger: &str) -> DebugScenario {
let request = self.mode.debug_task(cx);
DebugTaskDefinition {
adapter: debugger.to_owned(),
label: suggested_label(&request, debugger),
request,
let label = suggested_label(&request, debugger);
DebugScenario {
adapter: debugger.to_owned().into(),
label,
request: Some(request),
initialize_args: self.initialize_args.clone(),
tcp_connection: None,
stop_on_entry: match self.stop_on_entry {
ToggleState::Selected => Some(true),
_ => None,
},
build: None,
}
}
@ -109,36 +112,9 @@ impl NewSessionModal {
let config = self.debug_config(cx, debugger);
let debug_panel = self.debug_panel.clone();
let task_contexts = self
.workspace
.update(cx, |workspace, cx| {
tasks_ui::task_contexts(workspace, window, cx)
})
.ok();
cx.spawn_in(window, async move |this, cx| {
let task_context = if let Some(task) = task_contexts {
task.await
.active_worktree_context
.map_or(task::TaskContext::default(), |context| context.1)
} else {
task::TaskContext::default()
};
debug_panel.update_in(cx, |debug_panel, window, cx| {
let template = DebugTaskTemplate {
locator: None,
definition: config.clone(),
};
if let Some(debug_config) = template
.to_zed_format()
.resolve_task("debug_task", &task_context)
.and_then(|resolved_task| resolved_task.resolved_debug_adapter_config())
{
debug_panel.start_session(debug_config.definition, window, cx)
} else {
debug_panel.start_session(config, window, cx)
}
debug_panel.start_session(config, TaskContext::default(), None, window, cx)
})?;
this.update(cx, |_, cx| {
cx.emit(DismissEvent);
@ -156,12 +132,13 @@ impl NewSessionModal {
cx: &mut App,
) {
attach.update(cx, |this, cx| {
if selected_debugger != this.debug_definition.adapter {
this.debug_definition.adapter = selected_debugger.into();
if selected_debugger != this.definition.adapter.as_ref() {
let adapter: SharedString = selected_debugger.to_owned().into();
this.definition.adapter = adapter.clone();
this.attach_picker.update(cx, |this, cx| {
this.picker.update(cx, |this, cx| {
this.delegate.debug_config.adapter = selected_debugger.into();
this.delegate.definition.adapter = adapter;
this.focus(window, cx);
})
});
@ -224,22 +201,22 @@ impl NewSessionModal {
"debug-config-menu",
last_profile.unwrap_or_else(|| SELECT_SCENARIO_LABEL.clone()),
ContextMenu::build(window, cx, move |mut menu, _, cx| {
let setter_for_name = |task: DebugTaskDefinition| {
let setter_for_name = |task: DebugScenario| {
let weak = weak.clone();
move |window: &mut Window, cx: &mut App| {
weak.update(cx, |this, cx| {
this.last_selected_profile_name = Some(SharedString::from(&task.label));
this.debugger = Some(task.adapter.clone().into());
this.debugger = Some(task.adapter.clone());
this.initialize_args = task.initialize_args.clone();
match &task.request {
DebugRequest::Launch(launch_config) => {
Some(DebugRequest::Launch(launch_config)) => {
this.mode = NewSessionMode::launch(
Some(launch_config.clone()),
window,
cx,
);
}
DebugRequest::Attach(_) => {
Some(DebugRequest::Attach(_)) => {
let Some(workspace) = this.workspace.upgrade() else {
return;
};
@ -256,6 +233,7 @@ impl NewSessionModal {
Self::update_attach_picker(&attach, &debugger, window, cx);
}
}
_ => log::warn!("Selected debug scenario without either attach or launch request specified"),
}
cx.notify();
})
@ -263,7 +241,7 @@ impl NewSessionModal {
}
};
let available_adapters: Vec<DebugTaskTemplate> = workspace
let available_tasks: Vec<DebugScenario> = workspace
.update(cx, |this, cx| {
this.project()
.read(cx)
@ -271,19 +249,19 @@ impl NewSessionModal {
.read(cx)
.task_inventory()
.iter()
.flat_map(|task_inventory| task_inventory.read(cx).list_debug_tasks())
.cloned()
.filter_map(|task| task.try_into().ok())
.flat_map(|task_inventory| {
task_inventory.read(cx).list_debug_scenarios(None)
})
.collect()
})
.ok()
.unwrap_or_default();
for debug_definition in available_adapters {
for debug_definition in available_tasks {
menu = menu.entry(
debug_definition.definition.label.clone(),
debug_definition.label.clone(),
None,
setter_for_name(debug_definition.definition),
setter_for_name(debug_definition),
);
}
menu
@ -332,13 +310,14 @@ impl LaunchMode {
program: self.program.read(cx).text(cx),
cwd: path.is_empty().not().then(|| PathBuf::from(path)),
args: Default::default(),
env: Default::default(),
}
}
}
#[derive(Clone)]
struct AttachMode {
debug_definition: DebugTaskDefinition,
definition: DebugTaskDefinition,
attach_picker: Entity<AttachModal>,
}
@ -349,22 +328,22 @@ impl AttachMode {
window: &mut Window,
cx: &mut Context<NewSessionModal>,
) -> Entity<Self> {
let debug_definition = DebugTaskDefinition {
let definition = DebugTaskDefinition {
adapter: debugger.clone().unwrap_or_default(),
label: "Attach New Session Setup".into(),
request: dap::DebugRequest::Attach(task::AttachRequest { process_id: None }),
tcp_connection: None,
adapter: debugger.clone().unwrap_or_default().into(),
initialize_args: None,
tcp_connection: None,
stop_on_entry: Some(false),
};
let attach_picker = cx.new(|cx| {
let modal = AttachModal::new(workspace, debug_definition.clone(), false, window, cx);
let modal = AttachModal::new(definition.clone(), workspace, false, window, cx);
window.focus(&modal.focus_handle(cx));
modal
});
cx.new(|_| Self {
debug_definition,
definition,
attach_picker,
})
}

View File

@ -33,7 +33,7 @@ impl DebugSessionState {
pub struct DebugSession {
remote_id: Option<workspace::ViewId>,
mode: DebugSessionState,
label: OnceLock<String>,
label: OnceLock<SharedString>,
dap_store: WeakEntity<DapStore>,
_debug_panel: WeakEntity<DebugPanel>,
_worktree_store: WeakEntity<WorktreeStore>,
@ -110,9 +110,9 @@ impl DebugSession {
}
}
pub(crate) fn label(&self, cx: &App) -> String {
pub(crate) fn label(&self, cx: &App) -> SharedString {
if let Some(label) = self.label.get() {
return label.to_owned();
return label.clone();
}
let session_id = match &self.mode {
@ -123,7 +123,7 @@ impl DebugSession {
.dap_store
.read_with(cx, |store, _| store.session_by_id(session_id))
else {
return "".to_owned();
return "".into();
};
self.label

View File

@ -1,11 +1,12 @@
use std::sync::Arc;
use anyhow::{Result, anyhow};
use dap::adapters::DebugTaskDefinition;
use dap::{DebugRequest, client::DebugAdapterClient};
use gpui::{Entity, TestAppContext, WindowHandle};
use project::{Project, debugger::session::Session};
use settings::SettingsStore;
use task::DebugTaskDefinition;
use task::TaskContext;
use terminal_view::terminal_panel::TerminalPanel;
use workspace::Workspace;
@ -104,7 +105,13 @@ pub fn start_debug_session_with<T: Fn(&Arc<DebugAdapterClient>) + 'static>(
) -> Result<Entity<Session>> {
let _subscription = project::debugger::test::intercept_debug_sessions(cx, configure);
workspace.update(cx, |workspace, window, cx| {
workspace.start_debug_session(config, window, cx)
workspace.start_debug_session(
config.to_scenario(),
TaskContext::default(),
None,
window,
cx,
)
})?;
cx.run_until_parked();
let session = workspace.read_with(cx, |workspace, cx| {
@ -128,9 +135,9 @@ pub fn start_debug_session<T: Fn(&Arc<DebugAdapterClient>) + 'static>(
workspace,
cx,
DebugTaskDefinition {
adapter: "fake-adapter".to_string(),
adapter: "fake-adapter".into(),
request: DebugRequest::Launch(Default::default()),
label: "test".to_string(),
label: "test".into(),
initialize_args: None,
tcp_connection: None,
stop_on_entry: None,

View File

@ -1,11 +1,11 @@
use crate::{attach_modal::Candidate, tests::start_debug_session_with, *};
use attach_modal::AttachModal;
use dap::FakeAdapter;
use dap::{FakeAdapter, adapters::DebugTaskDefinition};
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
use menu::Confirm;
use project::{FakeFs, Project};
use serde_json::json;
use task::{AttachRequest, DebugTaskDefinition, TcpArgumentsTemplate};
use task::{AttachRequest, TcpArgumentsTemplate};
use tests::{init_test, init_test_workspace};
#[gpui::test]
@ -30,11 +30,11 @@ async fn test_direct_attach_to_process(executor: BackgroundExecutor, cx: &mut Te
&workspace,
cx,
DebugTaskDefinition {
adapter: "fake-adapter".to_string(),
adapter: "fake-adapter".into(),
request: dap::DebugRequest::Attach(AttachRequest {
process_id: Some(10),
}),
label: "label".to_string(),
label: "label".into(),
initialize_args: None,
tcp_connection: None,
stop_on_entry: None,
@ -104,6 +104,7 @@ async fn test_show_attach_modal_and_select_process(
workspace_handle,
DebugTaskDefinition {
adapter: FakeAdapter::ADAPTER_NAME.into(),
request: dap::DebugRequest::Attach(AttachRequest::default()),
label: "attach example".into(),
initialize_args: None,

View File

@ -78,6 +78,10 @@ pub struct ToggleCodeActions {
#[serde(default)]
#[serde(skip)]
pub deployed_from_indicator: Option<DisplayRow>,
// Run first available task if there is only one.
#[serde(default)]
#[serde(skip)]
pub quick_launch: bool,
}
#[derive(PartialEq, Clone, Deserialize, Default, JsonSchema)]

View File

@ -1,4 +1,3 @@
use feature_flags::{DebuggerFeatureFlag, FeatureFlagAppExt as _};
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
AnyElement, BackgroundExecutor, Entity, Focusable, FontWeight, ListSizingBehavior,
@ -13,6 +12,8 @@ use ordered_float::OrderedFloat;
use project::CompletionSource;
use project::lsp_store::CompletionDocumentation;
use project::{CodeAction, Completion, TaskSourceKind};
use task::DebugScenario;
use task::TaskContext;
use std::{
cell::RefCell,
@ -39,6 +40,7 @@ pub const MENU_ASIDE_X_PADDING: Pixels = px(16.);
pub const MENU_ASIDE_MIN_WIDTH: Pixels = px(260.);
pub const MENU_ASIDE_MAX_WIDTH: Pixels = px(500.);
#[allow(clippy::large_enum_variant)]
pub enum CodeContextMenu {
Completions(CompletionsMenu),
CodeActions(CodeActionsMenu),
@ -819,28 +821,25 @@ pub struct AvailableCodeAction {
}
#[derive(Clone)]
pub struct CodeActionContents {
pub(crate) struct CodeActionContents {
tasks: Option<Rc<ResolvedTasks>>,
actions: Option<Rc<[AvailableCodeAction]>>,
debug_scenarios: Vec<DebugScenario>,
pub(crate) context: TaskContext,
}
impl CodeActionContents {
pub fn new(
mut tasks: Option<ResolvedTasks>,
pub(crate) fn new(
tasks: Option<ResolvedTasks>,
actions: Option<Rc<[AvailableCodeAction]>>,
cx: &App,
debug_scenarios: Vec<DebugScenario>,
context: TaskContext,
) -> Self {
if !cx.has_flag::<DebuggerFeatureFlag>() {
if let Some(tasks) = &mut tasks {
tasks
.templates
.retain(|(_, task)| !matches!(task.task_type(), task::TaskType::Debug(_)));
}
}
Self {
tasks: tasks.map(Rc::new),
actions,
debug_scenarios,
context,
}
}
@ -849,21 +848,13 @@ impl CodeActionContents {
}
fn len(&self) -> usize {
match (&self.tasks, &self.actions) {
(Some(tasks), Some(actions)) => actions.len() + tasks.templates.len(),
(Some(tasks), None) => tasks.templates.len(),
(None, Some(actions)) => actions.len(),
(None, None) => 0,
}
let tasks_len = self.tasks.as_ref().map_or(0, |tasks| tasks.templates.len());
let code_actions_len = self.actions.as_ref().map_or(0, |actions| actions.len());
tasks_len + code_actions_len + self.debug_scenarios.len()
}
fn is_empty(&self) -> bool {
match (&self.tasks, &self.actions) {
(Some(tasks), Some(actions)) => actions.is_empty() && tasks.templates.is_empty(),
(Some(tasks), None) => tasks.templates.is_empty(),
(None, Some(actions)) => actions.is_empty(),
(None, None) => true,
}
self.len() == 0
}
fn iter(&self) -> impl Iterator<Item = CodeActionsItem> + '_ {
@ -882,43 +873,38 @@ impl CodeActionContents {
provider: available.provider.clone(),
})
}))
.chain(
self.debug_scenarios
.iter()
.cloned()
.map(CodeActionsItem::DebugScenario),
)
}
pub fn get(&self, index: usize) -> Option<CodeActionsItem> {
match (&self.tasks, &self.actions) {
(Some(tasks), Some(actions)) => {
if index < tasks.templates.len() {
tasks
.templates
.get(index)
.cloned()
.map(|(kind, task)| CodeActionsItem::Task(kind, task))
} else {
actions.get(index - tasks.templates.len()).map(|available| {
CodeActionsItem::CodeAction {
excerpt_id: available.excerpt_id,
action: available.action.clone(),
provider: available.provider.clone(),
}
})
}
pub fn get(&self, mut index: usize) -> Option<CodeActionsItem> {
if let Some(tasks) = &self.tasks {
if let Some((kind, task)) = tasks.templates.get(index) {
return Some(CodeActionsItem::Task(kind.clone(), task.clone()));
} else {
index -= tasks.templates.len();
}
(Some(tasks), None) => tasks
.templates
.get(index)
.cloned()
.map(|(kind, task)| CodeActionsItem::Task(kind, task)),
(None, Some(actions)) => {
actions
.get(index)
.map(|available| CodeActionsItem::CodeAction {
excerpt_id: available.excerpt_id,
action: available.action.clone(),
provider: available.provider.clone(),
})
}
(None, None) => None,
}
if let Some(actions) = &self.actions {
if let Some(available) = actions.get(index) {
return Some(CodeActionsItem::CodeAction {
excerpt_id: available.excerpt_id,
action: available.action.clone(),
provider: available.provider.clone(),
});
} else {
index -= actions.len();
}
}
self.debug_scenarios
.get(index)
.cloned()
.map(CodeActionsItem::DebugScenario)
}
}
@ -931,6 +917,7 @@ pub enum CodeActionsItem {
action: CodeAction,
provider: Rc<dyn CodeActionProvider>,
},
DebugScenario(DebugScenario),
}
impl CodeActionsItem {
@ -947,16 +934,23 @@ impl CodeActionsItem {
};
Some(action)
}
fn as_debug_scenario(&self) -> Option<&DebugScenario> {
let Self::DebugScenario(scenario) = self else {
return None;
};
Some(scenario)
}
pub fn label(&self) -> String {
match self {
Self::CodeAction { action, .. } => action.lsp_action.title().to_owned(),
Self::Task(_, task) => task.resolved_label.clone(),
Self::DebugScenario(scenario) => scenario.label.to_string(),
}
}
}
pub struct CodeActionsMenu {
pub(crate) struct CodeActionsMenu {
pub actions: CodeActionContents,
pub buffer: Entity<Buffer>,
pub selected_item: usize,
@ -1065,19 +1059,7 @@ impl CodeActionsMenu {
.inset(true)
.toggle_state(selected)
.when_some(action.as_code_action(), |this, action| {
this.on_click(cx.listener(move |editor, _, window, cx| {
cx.stop_propagation();
if let Some(task) = editor.confirm_code_action(
&ConfirmCodeAction {
item_ix: Some(item_ix),
},
window,
cx,
) {
task.detach_and_log_err(cx)
}
}))
.child(
this.child(
h_flex()
.overflow_hidden()
.child(
@ -1090,19 +1072,7 @@ impl CodeActionsMenu {
)
})
.when_some(action.as_task(), |this, task| {
this.on_click(cx.listener(move |editor, _, window, cx| {
cx.stop_propagation();
if let Some(task) = editor.confirm_code_action(
&ConfirmCodeAction {
item_ix: Some(item_ix),
},
window,
cx,
) {
task.detach_and_log_err(cx)
}
}))
.child(
this.child(
h_flex()
.overflow_hidden()
.child(task.resolved_label.replace("\n", ""))
@ -1110,7 +1080,29 @@ impl CodeActionsMenu {
this.text_color(colors.text_accent)
}),
)
}),
})
.when_some(action.as_debug_scenario(), |this, scenario| {
this.child(
h_flex()
.overflow_hidden()
.child(scenario.label.clone())
.when(selected, |this| {
this.text_color(colors.text_accent)
}),
)
})
.on_click(cx.listener(move |editor, _, window, cx| {
cx.stop_propagation();
if let Some(task) = editor.confirm_code_action(
&ConfirmCodeAction {
item_ix: Some(item_ix),
},
window,
cx,
) {
task.detach_and_log_err(cx)
}
})),
)
})
.collect()
@ -1128,6 +1120,7 @@ impl CodeActionsMenu {
CodeActionsItem::CodeAction { action, .. } => {
action.lsp_action.title().chars().count()
}
CodeActionsItem::DebugScenario(scenario) => scenario.label.chars().count(),
})
.map(|(ix, _)| ix),
)

View File

@ -5089,6 +5089,7 @@ impl Editor {
window: &mut Window,
cx: &mut Context<Self>,
) {
let quick_launch = action.quick_launch;
let mut context_menu = self.context_menu.borrow_mut();
if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
if code_actions.deployed_from_indicator == action.deployed_from_indicator {
@ -5162,8 +5163,6 @@ impl Editor {
Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
});
let debugger_flag = cx.has_flag::<DebuggerFeatureFlag>();
Some(cx.spawn_in(window, async move |editor, cx| {
let task_context = match task_context {
Some(task_context) => task_context.await,
@ -5171,7 +5170,7 @@ impl Editor {
};
let resolved_tasks =
tasks
.zip(task_context)
.zip(task_context.clone())
.map(|(tasks, task_context)| ResolvedTasks {
templates: tasks.resolve(&task_context).collect(),
position: snapshot.buffer_snapshot.anchor_before(Point::new(
@ -5179,22 +5178,49 @@ impl Editor {
tasks.column,
)),
});
let spawn_straight_away = resolved_tasks.as_ref().map_or(false, |tasks| {
tasks
.templates
.iter()
.filter(|task| {
if matches!(task.1.task_type(), task::TaskType::Debug(_)) {
debugger_flag
} else {
true
}
let spawn_straight_away = quick_launch
&& resolved_tasks
.as_ref()
.map_or(false, |tasks| tasks.templates.len() == 1)
&& code_actions
.as_ref()
.map_or(true, |actions| actions.is_empty());
let debug_scenarios = editor.update(cx, |editor, cx| {
if cx.has_flag::<DebuggerFeatureFlag>() {
maybe!({
let project = editor.project.as_ref()?;
let dap_store = project.read(cx).dap_store();
let mut scenarios = vec![];
let resolved_tasks = resolved_tasks.as_ref()?;
let debug_adapter: SharedString = buffer
.read(cx)
.language()?
.context_provider()?
.debug_adapter()?
.into();
dap_store.update(cx, |this, cx| {
for (_, task) in &resolved_tasks.templates {
if let Some(scenario) = this
.debug_scenario_for_build_task(
task.resolved.clone(),
SharedString::from(
task.original_task().label.clone(),
),
debug_adapter.clone(),
cx,
)
{
scenarios.push(scenario);
}
}
});
Some(scenarios)
})
.count()
== 1
}) && code_actions
.as_ref()
.map_or(true, |actions| actions.is_empty());
.unwrap_or_default()
} else {
vec![]
}
})?;
if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
*editor.context_menu.borrow_mut() =
Some(CodeContextMenu::CodeActions(CodeActionsMenu {
@ -5202,7 +5228,8 @@ impl Editor {
actions: CodeActionContents::new(
resolved_tasks,
code_actions,
cx,
debug_scenarios,
task_context.unwrap_or_default(),
),
selected_item: Default::default(),
scroll_handle: UniformListScrollHandle::default(),
@ -5262,25 +5289,17 @@ impl Editor {
match action {
CodeActionsItem::Task(task_source_kind, resolved_task) => {
match resolved_task.task_type() {
task::TaskType::Script => workspace.update(cx, |workspace, cx| {
workspace.schedule_resolved_task(
task_source_kind,
resolved_task,
false,
window,
cx,
);
workspace.update(cx, |workspace, cx| {
workspace.schedule_resolved_task(
task_source_kind,
resolved_task,
false,
window,
cx,
);
Some(Task::ready(Ok(())))
}),
task::TaskType::Debug(_) => {
workspace.update(cx, |workspace, cx| {
workspace.schedule_debug_task(resolved_task, window, cx);
});
Some(Task::ready(Ok(())))
}
}
Some(Task::ready(Ok(())))
})
}
CodeActionsItem::CodeAction {
excerpt_id,
@ -5302,6 +5321,14 @@ impl Editor {
.await
}))
}
CodeActionsItem::DebugScenario(scenario) => {
let context = actions_menu.actions.context.clone();
workspace.update(cx, |workspace, cx| {
workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
});
Some(Task::ready(Ok(())))
}
}
}
@ -6660,6 +6687,7 @@ impl Editor {
"Toggle Code Actions",
&ToggleCodeActions {
deployed_from_indicator: None,
quick_launch: false,
},
&focus_handle,
window,
@ -6668,11 +6696,13 @@ impl Editor {
}
})
})
.on_click(cx.listener(move |editor, _e, window, cx| {
.on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
let quick_launch = e.down.button == MouseButton::Left;
window.focus(&editor.focus_handle(cx));
editor.toggle_code_actions(
&ToggleCodeActions {
deployed_from_indicator: Some(row),
quick_launch,
},
window,
cx,
@ -7050,7 +7080,7 @@ impl Editor {
let context = task_context.await?;
let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
let resolved = resolved_task.resolved.as_mut()?;
let resolved = &mut resolved_task.resolved;
resolved.reveal = reveal_strategy;
workspace
@ -7140,11 +7170,13 @@ impl Editor {
.icon_size(IconSize::XSmall)
.icon_color(color)
.toggle_state(is_active)
.on_click(cx.listener(move |editor, _e, window, cx| {
.on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
let quick_launch = e.down.button == MouseButton::Left;
window.focus(&editor.focus_handle(cx));
editor.toggle_code_actions(
&ToggleCodeActions {
deployed_from_indicator: Some(row),
quick_launch,
},
window,
cx,

View File

@ -211,6 +211,7 @@ pub fn deploy_context_menu(
"Show Code Actions",
Box::new(ToggleCodeActions {
deployed_from_indicator: None,
quick_launch: false,
}),
)
.separator()

View File

@ -47,4 +47,7 @@ pub trait ContextProvider: Send + Sync {
fn lsp_task_source(&self) -> Option<LanguageServerName> {
None
}
/// Default debug adapter for a given language.
fn debug_adapter(&self) -> Option<String>;
}

View File

@ -630,6 +630,10 @@ impl ContextProvider for GoContextProvider {
},
]))
}
fn debug_adapter(&self) -> Option<String> {
Some("Delve".into())
}
}
fn extract_subtest_name(input: &str) -> Option<String> {

View File

@ -503,6 +503,10 @@ impl ContextProvider for PythonContextProvider {
Some(TaskTemplates(tasks))
}
fn debug_adapter(&self) -> Option<String> {
Some("Debugpy".into())
}
}
fn selected_test_runner(location: Option<&Arc<dyn language::File>>, cx: &App) -> TestRunner {

View File

@ -20,7 +20,7 @@ use std::{
path::{Path, PathBuf},
sync::{Arc, LazyLock},
};
use task::{TaskTemplate, TaskTemplates, TaskType, TaskVariables, VariableName};
use task::{TaskTemplate, TaskTemplates, TaskVariables, VariableName};
use util::merge_json_value_into;
use util::{ResultExt, fs::remove_matching, maybe};
@ -629,7 +629,7 @@ impl ContextProvider for RustContextProvider {
} else {
vec!["run".into()]
};
let debug_task_args = if let Some(package_to_run) = package_to_run {
let build_task_args = if let Some(package_to_run) = package_to_run {
vec!["build".into(), "-p".into(), package_to_run]
} else {
vec!["build".into()]
@ -675,32 +675,6 @@ impl ContextProvider for RustContextProvider {
cwd: Some("$ZED_DIRNAME".to_owned()),
..TaskTemplate::default()
},
TaskTemplate {
label: format!(
"Debug Test '{}' (package: {})",
RUST_TEST_NAME_TASK_VARIABLE.template_value(),
RUST_PACKAGE_TASK_VARIABLE.template_value(),
),
task_type: TaskType::Debug(task::DebugArgs {
adapter: "CodeLLDB".to_owned(),
request: task::DebugArgsRequest::Launch,
locator: Some("cargo".into()),
tcp_connection: None,
initialize_args: None,
stop_on_entry: None,
}),
command: "cargo".into(),
args: vec![
"test".into(),
"-p".into(),
RUST_PACKAGE_TASK_VARIABLE.template_value(),
RUST_TEST_NAME_TASK_VARIABLE.template_value(),
"--no-run".into(),
],
tags: vec!["rust-test".to_owned()],
cwd: Some("$ZED_DIRNAME".to_owned()),
..TaskTemplate::default()
},
TaskTemplate {
label: format!(
"Doc test '{}' (package: {})",
@ -780,31 +754,41 @@ impl ContextProvider for RustContextProvider {
cwd: Some("$ZED_DIRNAME".to_owned()),
..TaskTemplate::default()
},
TaskTemplate {
label: "Clean".into(),
command: "cargo".into(),
args: vec!["clean".into()],
cwd: Some("$ZED_DIRNAME".to_owned()),
..TaskTemplate::default()
},
TaskTemplate {
label: format!(
"Debug {} {} (package: {})",
"Build {} {} (package: {})",
RUST_BIN_KIND_TASK_VARIABLE.template_value(),
RUST_BIN_NAME_TASK_VARIABLE.template_value(),
RUST_PACKAGE_TASK_VARIABLE.template_value(),
),
cwd: Some("$ZED_DIRNAME".to_owned()),
command: "cargo".into(),
task_type: TaskType::Debug(task::DebugArgs {
request: task::DebugArgsRequest::Launch,
adapter: "CodeLLDB".to_owned(),
initialize_args: None,
locator: Some("cargo".into()),
tcp_connection: None,
stop_on_entry: None,
}),
args: debug_task_args,
args: build_task_args,
tags: vec!["rust-main".to_owned()],
..TaskTemplate::default()
},
TaskTemplate {
label: "Clean".into(),
label: format!(
"Build Test '{}' (package: {})",
RUST_TEST_NAME_TASK_VARIABLE.template_value(),
RUST_PACKAGE_TASK_VARIABLE.template_value(),
),
command: "cargo".into(),
args: vec!["clean".into()],
args: vec![
"test".into(),
"-p".into(),
RUST_PACKAGE_TASK_VARIABLE.template_value(),
RUST_TEST_NAME_TASK_VARIABLE.template_value(),
"--no-run".into(),
],
tags: vec!["rust-test".to_owned()],
cwd: Some("$ZED_DIRNAME".to_owned()),
..TaskTemplate::default()
},
@ -832,6 +816,10 @@ impl ContextProvider for RustContextProvider {
fn lsp_task_source(&self) -> Option<LanguageServerName> {
Some(SERVER_NAME)
}
fn debug_adapter(&self) -> Option<String> {
Some("CodeLLDB".to_owned())
}
}
/// Part of the data structure of Cargo metadata

View File

@ -1,6 +1,6 @@
use super::{
breakpoint_store::BreakpointStore,
locators::DapLocator,
locators,
session::{self, Session, SessionStateEvent},
};
use crate::{
@ -13,10 +13,12 @@ use anyhow::{Result, anyhow};
use async_trait::async_trait;
use collections::HashMap;
use dap::{
Capabilities, CompletionItem, CompletionsArguments, DapRegistry, EvaluateArguments,
EvaluateArgumentsContext, EvaluateResponse, RunInTerminalRequestArguments, Source,
StackFrameId, StartDebuggingRequestArguments,
adapters::{DapStatus, DebugAdapterBinary, DebugAdapterName, TcpArguments},
Capabilities, CompletionItem, CompletionsArguments, DapRegistry, DebugRequest,
EvaluateArguments, EvaluateArgumentsContext, EvaluateResponse, RunInTerminalRequestArguments,
Source, StackFrameId, StartDebuggingRequestArguments,
adapters::{
DapStatus, DebugAdapterBinary, DebugAdapterName, DebugTaskDefinition, TcpArguments,
},
client::SessionId,
messages::Message,
requests::{Completions, Evaluate, Request as _, RunInTerminal, StartDebugging},
@ -49,9 +51,9 @@ use std::{
ffi::OsStr,
net::Ipv4Addr,
path::{Path, PathBuf},
sync::Arc,
sync::{Arc, Once},
};
use task::{DebugTaskDefinition, DebugTaskTemplate};
use task::{DebugScenario, SpawnInTerminal};
use util::ResultExt as _;
use worktree::Worktree;
@ -95,7 +97,6 @@ pub struct LocalDapStore {
environment: Entity<ProjectEnvironment>,
language_registry: Arc<LanguageRegistry>,
toolchain_store: Arc<dyn LanguageToolchainStore>,
locators: HashMap<String, Arc<dyn DapLocator>>,
}
pub struct SshDapStore {
@ -118,9 +119,14 @@ pub struct DapStore {
impl EventEmitter<DapStoreEvent> for DapStore {}
impl DapStore {
pub fn init(client: &AnyProtoClient) {
pub fn init(client: &AnyProtoClient, cx: &mut App) {
static ADD_LOCATORS: Once = Once::new();
client.add_entity_request_handler(Self::handle_run_debug_locator);
client.add_entity_request_handler(Self::handle_get_debug_adapter_binary);
ADD_LOCATORS.call_once(|| {
DapRegistry::global(cx)
.add_locator("cargo".into(), Arc::new(locators::cargo::CargoLocator {}))
});
}
#[expect(clippy::too_many_arguments)]
@ -135,11 +141,6 @@ impl DapStore {
breakpoint_store: Entity<BreakpointStore>,
cx: &mut Context<Self>,
) -> Self {
let locators = HashMap::from_iter([(
"cargo".to_string(),
Arc::new(super::locators::cargo::CargoLocator {}) as _,
)]);
let mode = DapStoreMode::Local(LocalDapStore {
fs,
environment,
@ -147,7 +148,6 @@ impl DapStore {
node_runtime,
toolchain_store,
language_registry,
locators,
});
Self::new(mode, breakpoint_store, worktree_store, cx)
@ -273,7 +273,7 @@ impl DapStore {
DapStoreMode::Ssh(ssh) => {
let request = ssh.upstream_client.request(proto::GetDebugAdapterBinary {
project_id: ssh.upstream_project_id,
task: Some(definition.to_proto()),
definition: Some(definition.to_proto()),
});
let ssh_client = ssh.ssh_client.clone();
@ -326,34 +326,100 @@ impl DapStore {
}
}
pub fn debug_scenario_for_build_task(
&self,
mut build: SpawnInTerminal,
unresoved_label: SharedString,
adapter: SharedString,
cx: &mut App,
) -> Option<DebugScenario> {
build.args = build
.args
.into_iter()
.map(|arg| {
if arg.starts_with("$") {
arg.strip_prefix("$")
.and_then(|arg| build.env.get(arg).map(ToOwned::to_owned))
.unwrap_or_else(|| arg)
} else {
arg
}
})
.collect();
DapRegistry::global(cx)
.locators()
.values()
.find(|locator| locator.accepts(&build))
.map(|_| DebugScenario {
adapter,
label: format!("Debug `{}`", build.label).into(),
build: Some(unresoved_label),
request: None,
initialize_args: None,
tcp_connection: None,
stop_on_entry: None,
})
}
pub fn run_debug_locator(
&mut self,
template: DebugTaskTemplate,
mut build_command: SpawnInTerminal,
cx: &mut Context<Self>,
) -> Task<Result<DebugTaskDefinition>> {
let Some(locator_name) = template.locator else {
return Task::ready(Ok(template.definition));
};
) -> Task<Result<DebugRequest>> {
match &self.mode {
DapStoreMode::Local(local) => {
if let Some(locator) = local.locators.get(&locator_name).cloned() {
cx.background_spawn(
async move { locator.run_locator(template.definition).await },
)
DapStoreMode::Local(_) => {
// Pre-resolve args with existing environment.
build_command.args = build_command
.args
.into_iter()
.map(|arg| {
if arg.starts_with("$") {
arg.strip_prefix("$")
.and_then(|arg| build_command.env.get(arg).map(ToOwned::to_owned))
.unwrap_or_else(|| arg)
} else {
arg
}
})
.collect();
let locators = DapRegistry::global(cx)
.locators()
.values()
.filter(|locator| locator.accepts(&build_command))
.cloned()
.collect::<Vec<_>>();
if !locators.is_empty() {
cx.background_spawn(async move {
for locator in locators {
let result = locator
.run(build_command.clone())
.await
.log_with_level(log::Level::Error);
if let Some(result) = result {
return Ok(result);
}
}
Err(anyhow!(
"None of the locators for task `{}` completed successfully",
build_command.label
))
})
} else {
Task::ready(Err(anyhow!("Couldn't find locator {}", locator_name)))
Task::ready(Err(anyhow!(
"Couldn't find any locator for task `{}`. Specify the `attach` or `launch` arguments in your debug scenario definition",
build_command.label
)))
}
}
DapStoreMode::Ssh(ssh) => {
let request = ssh.upstream_client.request(proto::RunDebugLocator {
let request = ssh.upstream_client.request(proto::RunDebugLocators {
project_id: ssh.upstream_project_id,
locator: locator_name,
task: Some(template.definition.to_proto()),
build_command: Some(build_command.to_proto()),
});
cx.background_spawn(async move {
let response = request.await?;
DebugTaskDefinition::from_proto(response)
DebugRequest::from_proto(response)
})
}
DapStoreMode::Collab => {
@ -943,22 +1009,19 @@ impl DapStore {
async fn handle_run_debug_locator(
this: Entity<Self>,
envelope: TypedEnvelope<proto::RunDebugLocator>,
envelope: TypedEnvelope<proto::RunDebugLocators>,
mut cx: AsyncApp,
) -> Result<proto::DebugTaskDefinition> {
let template = DebugTaskTemplate {
locator: Some(envelope.payload.locator),
definition: DebugTaskDefinition::from_proto(
envelope
.payload
.task
.ok_or_else(|| anyhow!("missing definition"))?,
)?,
};
let definition = this
.update(&mut cx, |this, cx| this.run_debug_locator(template, cx))?
) -> Result<proto::DebugRequest> {
let task = envelope
.payload
.build_command
.ok_or_else(|| anyhow!("missing definition"))?;
let build_task = SpawnInTerminal::from_proto(task);
let request = this
.update(&mut cx, |this, cx| this.run_debug_locator(build_task, cx))?
.await?;
Ok(definition.to_proto())
Ok(request.to_proto())
}
async fn handle_get_debug_adapter_binary(
@ -969,7 +1032,7 @@ impl DapStore {
let definition = DebugTaskDefinition::from_proto(
envelope
.payload
.task
.definition
.ok_or_else(|| anyhow!("missing definition"))?,
)?;
let binary = this

View File

@ -1,34 +0,0 @@
use anyhow::{Result, anyhow};
use cargo::CargoLocator;
use collections::HashMap;
use gpui::SharedString;
use locators::DapLocator;
use task::{DebugTaskDefinition, DebugTaskTemplate};
mod cargo;
pub mod locators;
pub(super) struct LocatorStore {
locators: HashMap<SharedString, Box<dyn DapLocator>>,
}
impl LocatorStore {
pub(super) fn new() -> Self {
Self { locators }
}
pub(super) async fn resolve_debug_config(
&self,
template: DebugTaskTemplate,
) -> Result<DebugTaskDefinition> {
let Some(locator_name) = &template.locator else {
return Ok(template.definition);
};
if let Some(locator) = self.locators.get(locator_name as &str) {
locator.run_locator(template.definition).await
} else {
Err(anyhow!("Couldn't find locator {}", locator_name))
}
}
}

View File

@ -1,10 +1 @@
use anyhow::Result;
use async_trait::async_trait;
use task::DebugTaskDefinition;
pub(crate) mod cargo;
#[async_trait]
pub(super) trait DapLocator: Send + Sync {
async fn run_locator(&self, debug_config: DebugTaskDefinition) -> Result<DebugTaskDefinition>;
}

View File

@ -1,12 +1,12 @@
use super::DapLocator;
use anyhow::{Result, anyhow};
use async_trait::async_trait;
use dap::{DapLocator, DebugRequest};
use serde_json::Value;
use smol::{
io::AsyncReadExt,
process::{Command, Stdio},
};
use task::DebugTaskDefinition;
use task::SpawnInTerminal;
pub(crate) struct CargoLocator;
@ -37,26 +37,31 @@ async fn find_best_executable(executables: &[String], test_name: &str) -> Option
}
#[async_trait]
impl DapLocator for CargoLocator {
async fn run_locator(
&self,
mut debug_config: DebugTaskDefinition,
) -> Result<DebugTaskDefinition> {
let Some(launch_config) = (match &mut debug_config.request {
task::DebugRequest::Launch(launch_config) => Some(launch_config),
_ => None,
}) else {
return Err(anyhow!("Couldn't get launch config in locator"));
fn accepts(&self, build_config: &SpawnInTerminal) -> bool {
if build_config.command != "cargo" {
return false;
}
let Some(command) = build_config.args.first().map(|s| s.as_str()) else {
return false;
};
if matches!(command, "check" | "run") {
return false;
}
!matches!(command, "test" | "bench")
|| build_config.args.iter().any(|arg| arg == "--no-run")
}
let Some(cwd) = launch_config.cwd.clone() else {
async fn run(&self, build_config: SpawnInTerminal) -> Result<DebugRequest> {
let Some(cwd) = build_config.cwd.clone() else {
return Err(anyhow!(
"Couldn't get cwd from debug config which is needed for locators"
));
};
let mut child = Command::new("cargo")
.args(&launch_config.args)
.args(&build_config.args)
.arg("--message-format=json")
.envs(build_config.env.iter().map(|(k, v)| (k.clone(), v.clone())))
.current_dir(cwd)
.stdout(Stdio::piped())
.spawn()?;
@ -85,19 +90,16 @@ impl DapLocator for CargoLocator {
return Err(anyhow!("Couldn't get executable in cargo locator"));
};
let is_test = launch_config
.args
.first()
.map_or(false, |arg| arg == "test");
let is_test = build_config.args.first().map_or(false, |arg| arg == "test");
let mut test_name = None;
if is_test {
if let Some(package_index) = launch_config
if let Some(package_index) = build_config
.args
.iter()
.position(|arg| arg == "-p" || arg == "--package")
{
test_name = launch_config
test_name = build_config
.args
.get(package_index + 2)
.filter(|name| !name.starts_with("--"))
@ -116,12 +118,17 @@ impl DapLocator for CargoLocator {
return Err(anyhow!("Couldn't get executable in cargo locator"));
};
launch_config.program = executable;
let args = test_name.into_iter().collect();
launch_config.args.clear();
if let Some(test_name) = test_name {
launch_config.args.push(test_name);
}
Ok(debug_config)
Ok(DebugRequest::Launch(task::LaunchRequest {
program: executable,
cwd: build_config.cwd.clone(),
args,
env: build_config
.env
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect(),
}))
}
}

View File

@ -12,7 +12,7 @@ use super::dap_command::{
use super::dap_store::DapStore;
use anyhow::{Context as _, Result, anyhow};
use collections::{HashMap, HashSet, IndexMap, IndexSet};
use dap::adapters::DebugAdapterBinary;
use dap::adapters::{DebugAdapterBinary, DebugTaskDefinition};
use dap::messages::Response;
use dap::{
Capabilities, ContinueArguments, EvaluateArgumentsContext, Module, Source, StackFrameId,
@ -42,7 +42,6 @@ use std::{
path::Path,
sync::Arc,
};
use task::DebugTaskDefinition;
use text::{PointUtf16, ToPointUtf16};
use util::{ResultExt, merge_json_value_into};
use worktree::Worktree;
@ -125,7 +124,6 @@ enum Mode {
pub struct LocalMode {
client: Arc<DebugAdapterClient>,
binary: DebugAdapterBinary,
root_binary: Option<Arc<DebugAdapterBinary>>,
pub(crate) breakpoint_store: Entity<BreakpointStore>,
tmp_breakpoint: Option<SourceBreakpoint>,
worktree: WeakEntity<Worktree>,
@ -160,12 +158,6 @@ impl LocalMode {
messages_tx.unbounded_send(message).ok();
});
let root_binary = if let Some(parent_session) = parent_session.as_ref() {
Some(parent_session.read_with(&cx, |session, _| session.root_binary().clone())?)
} else {
None
};
let client = Arc::new(
if let Some(client) = parent_session
.and_then(|session| cx.update(|cx| session.read(cx).adapter_client()).ok())
@ -186,7 +178,6 @@ impl LocalMode {
breakpoint_store,
worktree,
tmp_breakpoint: None,
root_binary,
binary,
})
}
@ -834,19 +825,6 @@ impl Session {
&self.capabilities
}
pub(crate) fn root_binary(&self) -> Arc<DebugAdapterBinary> {
match &self.mode {
Mode::Building => {
// todo(debugger): Implement root_binary for building mode
unimplemented!()
}
Mode::Running(running) => running
.root_binary
.clone()
.unwrap_or_else(|| Arc::new(running.binary.clone())),
}
}
pub fn binary(&self) -> &DebugAdapterBinary {
let Mode::Running(local_mode) = &self.mode else {
panic!("Session is not local");
@ -855,10 +833,10 @@ impl Session {
}
pub fn adapter_name(&self) -> SharedString {
self.definition.adapter.clone().into()
self.definition.adapter.clone()
}
pub fn label(&self) -> String {
pub fn label(&self) -> SharedString {
self.definition.label.clone()
}
@ -889,7 +867,7 @@ impl Session {
}
pub(super) fn request_initialize(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
let adapter_id = self.definition.adapter.clone();
let adapter_id = String::from(self.definition.adapter.clone());
let request = Initialize { adapter_id };
match &self.mode {
Mode::Running(local_mode) => {

View File

@ -826,7 +826,7 @@ impl Project {
SettingsObserver::init(&client);
TaskStore::init(Some(&client));
ToolchainStore::init(&client);
DapStore::init(&client);
DapStore::init(&client, cx);
BreakpointStore::init(&client);
}
@ -1159,7 +1159,7 @@ impl Project {
SettingsObserver::init(&ssh_proto);
TaskStore::init(Some(&ssh_proto));
ToolchainStore::init(&ssh_proto);
DapStore::init(&ssh_proto);
DapStore::init(&ssh_proto, cx);
GitStore::init(&ssh_proto);
this

View File

@ -8,7 +8,7 @@ use lsp::LanguageServerName;
use paths::{
EDITORCONFIG_NAME, local_debug_file_relative_path, local_settings_file_relative_path,
local_tasks_file_relative_path, local_vscode_launch_file_relative_path,
local_vscode_tasks_file_relative_path,
local_vscode_tasks_file_relative_path, task_file_name,
};
use rpc::{
AnyProtoClient, TypedEnvelope,
@ -18,7 +18,7 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{
InvalidSettingsError, LocalSettingsKind, Settings, SettingsLocation, SettingsSources,
SettingsStore, TaskKind, parse_json_with_comments, watch_config_file,
SettingsStore, parse_json_with_comments, watch_config_file,
};
use std::{
path::{Path, PathBuf},
@ -377,7 +377,7 @@ pub struct SettingsObserver {
worktree_store: Entity<WorktreeStore>,
project_id: u64,
task_store: Entity<TaskStore>,
_global_task_config_watchers: (Task<()>, Task<()>),
_global_task_config_watcher: Task<()>,
}
/// SettingsObserver observers changes to .zed/{settings, task}.json files in local worktrees
@ -405,19 +405,10 @@ impl SettingsObserver {
mode: SettingsObserverMode::Local(fs.clone()),
downstream_client: None,
project_id: 0,
_global_task_config_watchers: (
Self::subscribe_to_global_task_file_changes(
fs.clone(),
TaskKind::Script,
paths::tasks_file().clone(),
cx,
),
Self::subscribe_to_global_task_file_changes(
fs,
TaskKind::Debug,
paths::debug_tasks_file().clone(),
cx,
),
_global_task_config_watcher: Self::subscribe_to_global_task_file_changes(
fs.clone(),
paths::tasks_file().clone(),
cx,
),
}
}
@ -434,19 +425,10 @@ impl SettingsObserver {
mode: SettingsObserverMode::Remote,
downstream_client: None,
project_id: 0,
_global_task_config_watchers: (
Self::subscribe_to_global_task_file_changes(
fs.clone(),
TaskKind::Script,
paths::tasks_file().clone(),
cx,
),
Self::subscribe_to_global_task_file_changes(
fs.clone(),
TaskKind::Debug,
paths::debug_tasks_file().clone(),
cx,
),
_global_task_config_watcher: Self::subscribe_to_global_task_file_changes(
fs.clone(),
paths::tasks_file().clone(),
cx,
),
}
}
@ -575,7 +557,7 @@ impl SettingsObserver {
)
.unwrap(),
);
(settings_dir, LocalSettingsKind::Tasks(TaskKind::Script))
(settings_dir, LocalSettingsKind::Tasks)
} else if path.ends_with(local_vscode_tasks_file_relative_path()) {
let settings_dir = Arc::<Path>::from(
path.ancestors()
@ -587,7 +569,7 @@ impl SettingsObserver {
)
.unwrap(),
);
(settings_dir, LocalSettingsKind::Tasks(TaskKind::Script))
(settings_dir, LocalSettingsKind::Tasks)
} else if path.ends_with(local_debug_file_relative_path()) {
let settings_dir = Arc::<Path>::from(
path.ancestors()
@ -599,7 +581,7 @@ impl SettingsObserver {
)
.unwrap(),
);
(settings_dir, LocalSettingsKind::Tasks(TaskKind::Debug))
(settings_dir, LocalSettingsKind::Debug)
} else if path.ends_with(local_vscode_launch_file_relative_path()) {
let settings_dir = Arc::<Path>::from(
path.ancestors()
@ -611,7 +593,7 @@ impl SettingsObserver {
)
.unwrap(),
);
(settings_dir, LocalSettingsKind::Tasks(TaskKind::Debug))
(settings_dir, LocalSettingsKind::Debug)
} else if path.ends_with(EDITORCONFIG_NAME) {
let Some(settings_dir) = path.parent().map(Arc::from) else {
continue;
@ -747,7 +729,7 @@ impl SettingsObserver {
}
}
}),
LocalSettingsKind::Tasks(task_kind) => {
LocalSettingsKind::Tasks => {
let result = task_store.update(cx, |task_store, cx| {
task_store.update_user_tasks(
TaskSettingsLocation::Worktree(SettingsLocation {
@ -755,7 +737,6 @@ impl SettingsObserver {
path: directory.as_ref(),
}),
file_content.as_deref(),
task_kind,
cx,
)
});
@ -772,7 +753,38 @@ impl SettingsObserver {
}
Ok(()) => {
cx.emit(SettingsObserverEvent::LocalTasksUpdated(Ok(
task_kind.config_in_dir(&directory)
directory.join(task_file_name())
)));
}
}
}
LocalSettingsKind::Debug => {
let result = task_store.update(cx, |task_store, cx| {
task_store.update_user_debug_scenarios(
TaskSettingsLocation::Worktree(SettingsLocation {
worktree_id,
path: directory.as_ref(),
}),
file_content.as_deref(),
cx,
)
});
match result {
Err(InvalidSettingsError::Debug { path, message }) => {
log::error!(
"Failed to set local debug scenarios in {path:?}: {message:?}"
);
cx.emit(SettingsObserverEvent::LocalTasksUpdated(Err(
InvalidSettingsError::Debug { path, message },
)));
}
Err(e) => {
log::error!("Failed to set local tasks: {e}");
}
Ok(()) => {
cx.emit(SettingsObserverEvent::LocalTasksUpdated(Ok(
directory.join(task_file_name())
)));
}
}
@ -795,7 +807,6 @@ impl SettingsObserver {
fn subscribe_to_global_task_file_changes(
fs: Arc<dyn Fs>,
task_kind: TaskKind,
file_path: PathBuf,
cx: &mut Context<Self>,
) -> Task<()> {
@ -815,7 +826,6 @@ impl SettingsObserver {
.update_user_tasks(
TaskSettingsLocation::Global(&file_path),
Some(&user_tasks_content),
task_kind,
cx,
)
.log_err();
@ -828,7 +838,6 @@ impl SettingsObserver {
task_store.update_user_tasks(
TaskSettingsLocation::Global(&file_path),
Some(&user_tasks_content),
task_kind,
cx,
)
}) else {
@ -856,15 +865,17 @@ impl SettingsObserver {
pub fn local_settings_kind_from_proto(kind: proto::LocalSettingsKind) -> LocalSettingsKind {
match kind {
proto::LocalSettingsKind::Settings => LocalSettingsKind::Settings,
proto::LocalSettingsKind::Tasks => LocalSettingsKind::Tasks(TaskKind::Script),
proto::LocalSettingsKind::Tasks => LocalSettingsKind::Tasks,
proto::LocalSettingsKind::Editorconfig => LocalSettingsKind::Editorconfig,
proto::LocalSettingsKind::Debug => LocalSettingsKind::Debug,
}
}
pub fn local_settings_kind_to_proto(kind: LocalSettingsKind) -> proto::LocalSettingsKind {
match kind {
LocalSettingsKind::Settings => proto::LocalSettingsKind::Settings,
LocalSettingsKind::Tasks(_) => proto::LocalSettingsKind::Tasks,
LocalSettingsKind::Tasks => proto::LocalSettingsKind::Tasks,
LocalSettingsKind::Editorconfig => proto::LocalSettingsKind::Editorconfig,
LocalSettingsKind::Debug => proto::LocalSettingsKind::Debug,
}
}

View File

@ -292,7 +292,7 @@ async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext)
})
.into_iter()
.map(|(source_kind, task)| {
let resolved = task.resolved.unwrap();
let resolved = task.resolved;
(
source_kind,
task.resolved_label,
@ -359,7 +359,6 @@ async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext)
}])
.to_string(),
),
settings::TaskKind::Script,
)
.unwrap();
});
@ -370,7 +369,7 @@ async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext)
.update(|cx| get_all_tasks(&project, &task_contexts, cx))
.into_iter()
.map(|(source_kind, task)| {
let resolved = task.resolved.unwrap();
let resolved = task.resolved;
(
source_kind,
task.resolved_label,
@ -495,7 +494,7 @@ async fn test_fallback_to_single_worktree_tasks(cx: &mut gpui::TestAppContext) {
active_worktree_tasks
.into_iter()
.map(|(source_kind, task)| {
let resolved = task.resolved.unwrap();
let resolved = task.resolved;
(source_kind, resolved.command)
})
.collect::<Vec<_>>(),

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();
});

View File

@ -11,7 +11,7 @@ use language::{
proto::{deserialize_anchor, serialize_anchor},
};
use rpc::{AnyProtoClient, TypedEnvelope, proto};
use settings::{InvalidSettingsError, SettingsLocation, TaskKind};
use settings::{InvalidSettingsError, SettingsLocation};
use task::{TaskContext, TaskVariables, VariableName};
use text::{BufferId, OffsetRangeExt};
use util::ResultExt;
@ -264,7 +264,6 @@ impl TaskStore {
&self,
location: TaskSettingsLocation<'_>,
raw_tasks_json: Option<&str>,
task_type: TaskKind,
cx: &mut Context<Self>,
) -> Result<(), InvalidSettingsError> {
let task_inventory = match self {
@ -276,7 +275,26 @@ impl TaskStore {
.filter(|json| !json.is_empty());
task_inventory.update(cx, |inventory, _| {
inventory.update_file_based_tasks(location, raw_tasks_json, task_type)
inventory.update_file_based_tasks(location, raw_tasks_json)
})
}
pub(super) fn update_user_debug_scenarios(
&self,
location: TaskSettingsLocation<'_>,
raw_tasks_json: Option<&str>,
cx: &mut Context<Self>,
) -> Result<(), InvalidSettingsError> {
let task_inventory = match self {
TaskStore::Functional(state) => &state.task_inventory,
TaskStore::Noop => return Ok(()),
};
let raw_tasks_json = raw_tasks_json
.map(|json| json.trim())
.filter(|json| !json.is_empty());
task_inventory.update(cx, |inventory, _| {
inventory.update_file_based_scenarios(location, raw_tasks_json)
})
}
}

View File

@ -543,6 +543,7 @@ message DebugLaunchRequest {
string program = 1;
optional string cwd = 2;
repeated string args = 3;
map<string, string> env = 4;
}
message DebugAttachRequest {
@ -558,7 +559,7 @@ message DapModuleId {
message GetDebugAdapterBinary {
uint64 project_id = 1;
DebugTaskDefinition task = 2;
DebugTaskDefinition definition = 2;
}
message DebugAdapterBinary {
@ -575,8 +576,32 @@ message DebugAdapterBinary {
}
}
message RunDebugLocator {
message RunDebugLocators {
uint64 project_id = 1;
string locator = 2;
DebugTaskDefinition task = 3;
SpawnInTerminal build_command = 2;
}
message DebugRequest {
oneof request {
DebugLaunchRequest debug_launch_request = 1;
DebugAttachRequest debug_attach_request = 2;
}
}
message DebugScenario {
string label = 1;
string adapter = 2;
reserved 3;
DebugRequest request = 4;
optional TcpHost connection = 5;
optional bool stop_on_entry = 6;
optional string configuration = 7;
}
message SpawnInTerminal {
string label = 1;
string command = 2;
repeated string args = 3;
map<string, string> env = 4;
optional string cwd = 5;
}

View File

@ -148,4 +148,5 @@ enum LocalSettingsKind {
Settings = 0;
Tasks = 1;
Editorconfig = 2;
Debug = 3;
}

View File

@ -377,8 +377,8 @@ message Envelope {
GetDebugAdapterBinary get_debug_adapter_binary = 339;
DebugAdapterBinary debug_adapter_binary = 340;
RunDebugLocator run_debug_locator = 341;
DebugTaskDefinition debug_task_definition = 342; // current max
RunDebugLocators run_debug_locators = 341;
DebugRequest debug_request = 342; // current max
}
reserved 87 to 88;

View File

@ -298,8 +298,8 @@ messages!(
(GitInit, Background),
(GetDebugAdapterBinary, Background),
(DebugAdapterBinary, Background),
(RunDebugLocator, Background),
(DebugTaskDefinition, Background),
(RunDebugLocators, Background),
(DebugRequest, Background),
);
request_messages!(
@ -456,7 +456,7 @@ request_messages!(
(GitInit, Ack),
(ToggleBreakpoint, Ack),
(GetDebugAdapterBinary, DebugAdapterBinary),
(RunDebugLocator, DebugTaskDefinition),
(RunDebugLocators, DebugRequest),
);
entity_messages!(
@ -576,7 +576,7 @@ entity_messages!(
GitInit,
BreakpointsForFile,
ToggleBreakpoint,
RunDebugLocator,
RunDebugLocators,
GetDebugAdapterBinary,
);

View File

@ -245,7 +245,7 @@ impl HeadlessProject {
LspStore::init(&client);
TaskStore::init(Some(&client));
ToolchainStore::init(&client);
DapStore::init(&client);
DapStore::init(&client, cx);
// todo(debugger): Re init breakpoint store when we set it up for collab
// BreakpointStore::init(&client);
GitStore::init(&client);

View File

@ -20,7 +20,7 @@ pub use keymap_file::{
pub use settings_file::*;
pub use settings_store::{
InvalidSettingsError, LocalSettingsKind, Settings, SettingsLocation, SettingsSources,
SettingsStore, TaskKind, parse_json_with_comments,
SettingsStore, parse_json_with_comments,
};
pub use vscode_import::VsCodeSettings;

View File

@ -5,9 +5,7 @@ use fs::Fs;
use futures::{FutureExt, StreamExt, channel::mpsc, future::LocalBoxFuture};
use gpui::{App, AsyncApp, BorrowAppContext, Global, Task, UpdateGlobal};
use paths::{
EDITORCONFIG_NAME, debug_task_file_name, local_settings_file_relative_path, task_file_name,
};
use paths::{EDITORCONFIG_NAME, local_settings_file_relative_path, task_file_name};
use schemars::{JsonSchema, r#gen::SchemaGenerator, schema::RootSchema};
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use serde_json::Value;
@ -217,14 +215,9 @@ impl FromStr for Editorconfig {
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum LocalSettingsKind {
Settings,
Tasks(TaskKind),
Tasks,
Editorconfig,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum TaskKind {
Debug,
Script,
}
impl Global for SettingsStore {}
@ -265,16 +258,6 @@ trait AnySettingValue: 'static + Send + Sync {
struct DeserializedSetting(Box<dyn Any>);
impl TaskKind {
/// Returns a file path of a task configuration file of this kind within the given directory.
pub fn config_in_dir(&self, dir: &Path) -> PathBuf {
dir.join(match self {
Self::Debug => debug_task_file_name(),
Self::Script => task_file_name(),
})
}
}
impl SettingsStore {
pub fn new(cx: &App) -> Self {
let (setting_file_updates_tx, mut setting_file_updates_rx) = mpsc::unbounded();
@ -684,10 +667,17 @@ impl SettingsStore {
.map(|content| content.trim())
.filter(|content| !content.is_empty()),
) {
(LocalSettingsKind::Tasks(task_kind), _) => {
(LocalSettingsKind::Tasks, _) => {
return Err(InvalidSettingsError::Tasks {
message: "Attempted to submit tasks into the settings store".to_string(),
path: task_kind.config_in_dir(&directory_path),
path: directory_path.join(task_file_name()),
});
}
(LocalSettingsKind::Debug, _) => {
return Err(InvalidSettingsError::Debug {
message: "Attempted to submit debugger config into the settings store"
.to_string(),
path: directory_path.join(task_file_name()),
});
}
(LocalSettingsKind::Settings, None) => {
@ -1085,6 +1075,7 @@ pub enum InvalidSettingsError {
DefaultSettings { message: String },
Editorconfig { path: PathBuf, message: String },
Tasks { path: PathBuf, message: String },
Debug { path: PathBuf, message: String },
}
impl std::fmt::Display for InvalidSettingsError {
@ -1095,7 +1086,8 @@ impl std::fmt::Display for InvalidSettingsError {
| InvalidSettingsError::ServerSettings { message }
| InvalidSettingsError::DefaultSettings { message }
| InvalidSettingsError::Tasks { message, .. }
| InvalidSettingsError::Editorconfig { message, .. } => {
| InvalidSettingsError::Editorconfig { message, .. }
| InvalidSettingsError::Debug { message, .. } => {
write!(f, "{message}")
}
}

View File

@ -1,11 +1,11 @@
use anyhow::Result;
use collections::FxHashMap;
use gpui::SharedString;
use schemars::{JsonSchema, r#gen::SchemaSettings};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::{net::Ipv4Addr, path::Path};
use crate::{TaskTemplate, TaskType, task_template::DebugArgs};
/// Represents the host information of the debug adapter
#[derive(Default, Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)]
pub struct TcpArgumentsTemplate {
@ -63,6 +63,8 @@ pub struct LaunchRequest {
/// Arguments to pass to a debuggee
#[serde(default)]
pub args: Vec<String>,
#[serde(default)]
pub env: FxHashMap<String, String>,
}
/// Represents the type that will determine which request to call on the debug adapter
@ -75,6 +77,64 @@ pub enum DebugRequest {
Attach(AttachRequest),
}
impl DebugRequest {
pub fn to_proto(&self) -> proto::DebugRequest {
match self {
DebugRequest::Launch(launch_request) => proto::DebugRequest {
request: Some(proto::debug_request::Request::DebugLaunchRequest(
proto::DebugLaunchRequest {
program: launch_request.program.clone(),
cwd: launch_request
.cwd
.as_ref()
.map(|cwd| cwd.to_string_lossy().into_owned()),
args: launch_request.args.clone(),
env: launch_request
.env
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect(),
},
)),
},
DebugRequest::Attach(attach_request) => proto::DebugRequest {
request: Some(proto::debug_request::Request::DebugAttachRequest(
proto::DebugAttachRequest {
process_id: attach_request
.process_id
.expect("The process ID to be already filled out."),
},
)),
},
}
}
pub fn from_proto(val: proto::DebugRequest) -> Result<DebugRequest> {
let request = val
.request
.ok_or_else(|| anyhow::anyhow!("Missing debug request"))?;
match request {
proto::debug_request::Request::DebugLaunchRequest(proto::DebugLaunchRequest {
program,
cwd,
args,
env,
}) => Ok(DebugRequest::Launch(LaunchRequest {
program,
cwd: cwd.map(From::from),
args,
env: env.into_iter().collect(),
})),
proto::debug_request::Request::DebugAttachRequest(proto::DebugAttachRequest {
process_id,
}) => Ok(DebugRequest::Attach(AttachRequest {
process_id: Some(process_id),
})),
}
}
}
impl From<LaunchRequest> for DebugRequest {
fn from(launch_config: LaunchRequest) -> Self {
DebugRequest::Launch(launch_config)
@ -87,180 +147,46 @@ impl From<AttachRequest> for DebugRequest {
}
}
impl TryFrom<TaskTemplate> for DebugTaskTemplate {
type Error = ();
fn try_from(value: TaskTemplate) -> Result<Self, Self::Error> {
let TaskType::Debug(debug_args) = value.task_type else {
return Err(());
};
let request = match debug_args.request {
crate::DebugArgsRequest::Launch => DebugRequest::Launch(LaunchRequest {
program: value.command,
cwd: value.cwd.map(PathBuf::from),
args: value.args,
}),
crate::DebugArgsRequest::Attach(attach_config) => DebugRequest::Attach(attach_config),
};
Ok(DebugTaskTemplate {
locator: debug_args.locator,
definition: DebugTaskDefinition {
adapter: debug_args.adapter,
request,
label: value.label,
initialize_args: debug_args.initialize_args,
tcp_connection: debug_args.tcp_connection,
stop_on_entry: debug_args.stop_on_entry,
},
})
}
}
impl DebugTaskTemplate {
/// Translate from debug definition to a task template
pub fn to_zed_format(self) -> TaskTemplate {
let (command, cwd, request) = match self.definition.request {
DebugRequest::Launch(launch_config) => (
launch_config.program,
launch_config
.cwd
.map(|cwd| cwd.to_string_lossy().to_string()),
crate::task_template::DebugArgsRequest::Launch,
),
DebugRequest::Attach(attach_config) => (
"".to_owned(),
None,
crate::task_template::DebugArgsRequest::Attach(attach_config),
),
};
let task_type = TaskType::Debug(DebugArgs {
adapter: self.definition.adapter,
request,
initialize_args: self.definition.initialize_args,
locator: self.locator,
tcp_connection: self.definition.tcp_connection,
stop_on_entry: self.definition.stop_on_entry,
});
let label = self.definition.label.clone();
TaskTemplate {
label,
command,
args: vec![],
task_type,
cwd,
..Default::default()
}
}
}
#[derive(Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)]
#[serde(rename_all = "snake_case")]
pub struct DebugTaskTemplate {
pub locator: Option<String>,
#[serde(flatten)]
pub definition: DebugTaskDefinition,
}
/// This struct represent a user created debug task
#[derive(Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)]
#[serde(rename_all = "snake_case")]
pub struct DebugTaskDefinition {
/// The adapter to run
pub adapter: String,
/// The type of request that should be called on the debug adapter
#[serde(flatten)]
pub request: DebugRequest,
pub struct DebugScenario {
pub adapter: SharedString,
/// Name of the debug task
pub label: String,
pub label: SharedString,
/// A task to run prior to spawning the debuggee.
pub build: Option<SharedString>,
#[serde(flatten)]
pub request: Option<DebugRequest>,
/// Additional initialization arguments to be sent on DAP initialization
#[serde(default)]
pub initialize_args: Option<serde_json::Value>,
/// Optional TCP connection information
///
/// If provided, this will be used to connect to the debug adapter instead of
/// spawning a new process. This is useful for connecting to a debug adapter
/// that is already running or is started by another process.
#[serde(default)]
pub tcp_connection: Option<TcpArgumentsTemplate>,
/// Whether to tell the debug adapter to stop on entry
#[serde(default)]
pub stop_on_entry: Option<bool>,
}
impl DebugTaskDefinition {
impl DebugScenario {
pub fn cwd(&self) -> Option<&Path> {
if let DebugRequest::Launch(config) = &self.request {
config.cwd.as_deref()
if let Some(DebugRequest::Launch(config)) = &self.request {
config.cwd.as_ref().map(Path::new)
} else {
None
}
}
pub fn to_proto(&self) -> proto::DebugTaskDefinition {
proto::DebugTaskDefinition {
adapter: self.adapter.clone(),
request: Some(match &self.request {
DebugRequest::Launch(config) => {
proto::debug_task_definition::Request::DebugLaunchRequest(
proto::DebugLaunchRequest {
program: config.program.clone(),
cwd: config.cwd.as_ref().map(|c| c.to_string_lossy().to_string()),
args: config.args.clone(),
},
)
}
DebugRequest::Attach(attach_request) => {
proto::debug_task_definition::Request::DebugAttachRequest(
proto::DebugAttachRequest {
process_id: attach_request.process_id.unwrap_or_default(),
},
)
}
}),
label: self.label.clone(),
initialize_args: self.initialize_args.as_ref().map(|v| v.to_string()),
tcp_connection: self.tcp_connection.as_ref().map(|t| t.to_proto()),
stop_on_entry: self.stop_on_entry,
}
}
pub fn from_proto(proto: proto::DebugTaskDefinition) -> Result<Self> {
let request = proto
.request
.ok_or_else(|| anyhow::anyhow!("request is required"))?;
Ok(Self {
label: proto.label,
initialize_args: proto.initialize_args.map(|v| v.into()),
tcp_connection: proto
.tcp_connection
.map(TcpArgumentsTemplate::from_proto)
.transpose()?,
stop_on_entry: proto.stop_on_entry,
adapter: proto.adapter.clone(),
request: match request {
proto::debug_task_definition::Request::DebugAttachRequest(config) => {
DebugRequest::Attach(AttachRequest {
process_id: Some(config.process_id),
})
}
proto::debug_task_definition::Request::DebugLaunchRequest(config) => {
DebugRequest::Launch(LaunchRequest {
program: config.program,
cwd: config.cwd.map(|cwd| cwd.into()),
args: config.args,
})
}
},
})
}
}
/// A group of Debug Tasks defined in a JSON file.
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(transparent)]
pub struct DebugTaskFile(pub Vec<DebugTaskTemplate>);
pub struct DebugTaskFile(pub Vec<DebugScenario>);
impl DebugTaskFile {
/// Generates JSON schema of Tasks JSON template format.

View File

@ -16,12 +16,10 @@ use std::path::PathBuf;
use std::str::FromStr;
pub use debug_format::{
AttachRequest, DebugRequest, DebugTaskDefinition, DebugTaskFile, DebugTaskTemplate,
LaunchRequest, TcpArgumentsTemplate,
AttachRequest, DebugRequest, DebugScenario, DebugTaskFile, LaunchRequest, TcpArgumentsTemplate,
};
pub use task_template::{
DebugArgs, DebugArgsRequest, HideStrategy, RevealStrategy, TaskModal, TaskTemplate,
TaskTemplates, TaskType,
DebugArgsRequest, HideStrategy, RevealStrategy, TaskModal, TaskTemplate, TaskTemplates,
};
pub use vscode_debug_format::VsCodeDebugTaskFile;
pub use vscode_format::VsCodeTaskFile;
@ -29,11 +27,11 @@ pub use zed_actions::RevealTarget;
/// Task identifier, unique within the application.
/// Based on it, task reruns and terminal tabs are managed.
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Deserialize)]
#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Deserialize)]
pub struct TaskId(pub String);
/// Contains all information needed by Zed to spawn a new terminal tab for the given task.
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub struct SpawnInTerminal {
/// Id of the task to use when determining task tab affinity.
pub id: TaskId,
@ -72,6 +70,36 @@ pub struct SpawnInTerminal {
pub show_rerun: bool,
}
impl SpawnInTerminal {
pub fn to_proto(&self) -> proto::SpawnInTerminal {
proto::SpawnInTerminal {
label: self.label.clone(),
command: self.command.clone(),
args: self.args.clone(),
env: self
.env
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect(),
cwd: self
.cwd
.clone()
.map(|cwd| cwd.to_string_lossy().into_owned()),
}
}
pub fn from_proto(proto: proto::SpawnInTerminal) -> Self {
Self {
label: proto.label.clone(),
command: proto.command.clone(),
args: proto.args.clone(),
env: proto.env.into_iter().collect(),
cwd: proto.cwd.map(PathBuf::from).clone(),
..Default::default()
}
}
}
/// A final form of the [`TaskTemplate`], that got resolved with a particular [`TaskContext`] and now is ready to spawn the actual task.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ResolvedTask {
@ -89,7 +117,7 @@ pub struct ResolvedTask {
substituted_variables: HashSet<VariableName>,
/// Further actions that need to take place after the resolved task is spawned,
/// with all task variables resolved.
pub resolved: Option<SpawnInTerminal>,
pub resolved: SpawnInTerminal,
}
impl ResolvedTask {
@ -98,63 +126,6 @@ impl ResolvedTask {
&self.original_task
}
/// Get the task type that determines what this task is used for
/// And where is it shown in the UI
pub fn task_type(&self) -> TaskType {
self.original_task.task_type.clone()
}
/// Get the configuration for the debug adapter that should be used for this task.
pub fn resolved_debug_adapter_config(&self) -> Option<DebugTaskTemplate> {
match self.original_task.task_type.clone() {
TaskType::Debug(debug_args) if self.resolved.is_some() => {
let resolved = self
.resolved
.as_ref()
.expect("We just checked if this was some");
let args = resolved
.args
.iter()
.cloned()
.map(|arg| {
if arg.starts_with("$") {
arg.strip_prefix("$")
.and_then(|arg| resolved.env.get(arg).map(ToOwned::to_owned))
.unwrap_or_else(|| arg)
} else {
arg
}
})
.collect();
Some(DebugTaskTemplate {
locator: debug_args.locator.clone(),
definition: DebugTaskDefinition {
label: resolved.label.clone(),
adapter: debug_args.adapter.clone(),
request: match debug_args.request {
crate::task_template::DebugArgsRequest::Launch => {
DebugRequest::Launch(LaunchRequest {
program: resolved.command.clone(),
cwd: resolved.cwd.clone(),
args,
})
}
crate::task_template::DebugArgsRequest::Attach(attach_config) => {
DebugRequest::Attach(attach_config)
}
},
initialize_args: debug_args.initialize_args,
tcp_connection: debug_args.tcp_connection,
stop_on_entry: debug_args.stop_on_entry,
},
})
}
_ => None,
}
}
/// Variables that were substituted during the task template resolution.
pub fn substituted_variables(&self) -> &HashSet<VariableName> {
&self.substituted_variables
@ -162,10 +133,7 @@ impl ResolvedTask {
/// A human-readable label to display in the UI.
pub fn display_label(&self) -> &str {
self.resolved
.as_ref()
.map(|resolved| resolved.label.as_str())
.unwrap_or_else(|| self.resolved_label.as_str())
self.resolved.label.as_str()
}
}

View File

@ -7,7 +7,6 @@ use std::path::PathBuf;
use util::serde::default_true;
use util::{ResultExt, truncate_and_remove_front};
use crate::debug_format::TcpArgumentsTemplate;
use crate::{
AttachRequest, ResolvedTask, RevealTarget, Shell, SpawnInTerminal, TaskContext, TaskId,
VariableName, ZED_VARIABLE_NAME_PREFIX,
@ -59,9 +58,6 @@ pub struct TaskTemplate {
/// * `on_success` — hide the terminal tab on task success only, otherwise behaves similar to `always`.
#[serde(default)]
pub hide: HideStrategy,
/// If this task should start a debugger or not
#[serde(default, skip)]
pub task_type: TaskType,
/// Represents the tags which this template attaches to.
/// Adding this removes this task from other UI and gives you ability to run it by tag.
#[serde(default, deserialize_with = "non_empty_string_vec")]
@ -87,34 +83,6 @@ pub enum DebugArgsRequest {
Attach(AttachRequest),
}
#[derive(Deserialize, Eq, PartialEq, Clone, Debug)]
/// This represents the arguments for the debug task.
pub struct DebugArgs {
/// The launch type
pub request: DebugArgsRequest,
/// Adapter choice
pub adapter: String,
/// TCP connection to make with debug adapter
pub tcp_connection: Option<TcpArgumentsTemplate>,
/// Args to send to debug adapter
pub initialize_args: Option<serde_json::value::Value>,
/// the locator to use
pub locator: Option<String>,
/// Whether to tell the debug adapter to stop on entry
pub stop_on_entry: Option<bool>,
}
/// Represents the type of task that is being ran
#[derive(Default, Eq, PartialEq, Clone, Debug)]
#[allow(clippy::large_enum_variant)]
pub enum TaskType {
/// Act like a typically task that runs commands
#[default]
Script,
/// This task starts the debugger for a language
Debug(DebugArgs),
}
#[derive(Clone, Debug, PartialEq, Eq)]
/// The type of task modal to spawn
pub enum TaskModal {
@ -174,9 +142,7 @@ impl TaskTemplate {
/// Every [`ResolvedTask`] gets a [`TaskId`], based on the `id_base` (to avoid collision with various task sources),
/// and hashes of its template and [`TaskContext`], see [`ResolvedTask`] fields' documentation for more details.
pub fn resolve_task(&self, id_base: &str, cx: &TaskContext) -> Option<ResolvedTask> {
if self.label.trim().is_empty()
|| (self.command.trim().is_empty() && matches!(self.task_type, TaskType::Script))
{
if self.label.trim().is_empty() || self.command.trim().is_empty() {
return None;
}
@ -285,7 +251,7 @@ impl TaskTemplate {
substituted_variables,
original_task: self.clone(),
resolved_label: full_label.clone(),
resolved: Some(SpawnInTerminal {
resolved: SpawnInTerminal {
id,
cwd,
full_label,
@ -310,7 +276,7 @@ impl TaskTemplate {
show_summary: self.show_summary,
show_command: self.show_command,
show_rerun: true,
}),
},
})
}
}
@ -474,12 +440,7 @@ mod tests {
.resolve_task(TEST_ID_BASE, task_cx)
.unwrap_or_else(|| panic!("failed to resolve task {task_without_cwd:?}"));
assert_substituted_variables(&resolved_task, Vec::new());
resolved_task
.resolved
.clone()
.unwrap_or_else(|| {
panic!("failed to get resolve data for resolved task. Template: {task_without_cwd:?} Resolved: {resolved_task:?}")
})
resolved_task.resolved
};
let cx = TaskContext {
@ -626,10 +587,7 @@ mod tests {
all_variables.iter().map(|(name, _)| name.clone()).collect(),
);
let spawn_in_terminal = resolved_task
.resolved
.as_ref()
.expect("should have resolved a spawn in terminal task");
let spawn_in_terminal = &resolved_task.resolved;
assert_eq!(
spawn_in_terminal.label,
format!(
@ -713,7 +671,7 @@ mod tests {
.resolve_task(TEST_ID_BASE, &TaskContext::default())
.unwrap();
assert_substituted_variables(&resolved_task, Vec::new());
let resolved = resolved_task.resolved.unwrap();
let resolved = resolved_task.resolved;
assert_eq!(resolved.label, task.label);
assert_eq!(resolved.command, task.command);
assert_eq!(resolved.args, task.args);
@ -882,8 +840,7 @@ mod tests {
let resolved = template
.resolve_task(TEST_ID_BASE, &context)
.unwrap()
.resolved
.unwrap();
.resolved;
assert_eq!(resolved.env["TASK_ENV_VAR1"], "TASK_ENV_VAR1_VALUE");
assert_eq!(resolved.env["TASK_ENV_VAR2"], "env_var_2 1234 5678");

View File

@ -2,12 +2,13 @@ use std::path::PathBuf;
use anyhow::anyhow;
use collections::HashMap;
use gpui::SharedString;
use serde::Deserialize;
use util::ResultExt as _;
use crate::{
AttachRequest, DebugRequest, DebugTaskDefinition, DebugTaskFile, DebugTaskTemplate,
EnvVariableReplacer, LaunchRequest, TcpArgumentsTemplate, VariableName,
AttachRequest, DebugRequest, DebugScenario, DebugTaskFile, EnvVariableReplacer, LaunchRequest,
TcpArgumentsTemplate, VariableName,
};
#[derive(Clone, Debug, Deserialize, PartialEq)]
@ -43,11 +44,12 @@ struct VsCodeDebugTaskDefinition {
}
impl VsCodeDebugTaskDefinition {
fn try_to_zed(self, replacer: &EnvVariableReplacer) -> anyhow::Result<DebugTaskTemplate> {
let label = replacer.replace(&self.name);
fn try_to_zed(self, replacer: &EnvVariableReplacer) -> anyhow::Result<DebugScenario> {
let label = replacer.replace(&self.name).into();
// TODO based on grep.app results it seems that vscode supports whitespace-splitting this field (ugh)
let definition = DebugTaskDefinition {
let definition = DebugScenario {
label,
build: None,
request: match self.request {
Request::Launch => {
let cwd = self.cwd.map(|cwd| PathBuf::from(replacer.replace(&cwd)));
@ -60,11 +62,22 @@ impl VsCodeDebugTaskDefinition {
.into_iter()
.map(|arg| replacer.replace(&arg))
.collect();
DebugRequest::Launch(LaunchRequest { program, cwd, args })
let env = self
.env
.into_iter()
.filter_map(|(k, v)| v.map(|v| (k, v)))
.collect();
DebugRequest::Launch(LaunchRequest {
program,
cwd,
args,
env,
})
.into()
}
Request::Attach => DebugRequest::Attach(AttachRequest { process_id: None }),
Request::Attach => DebugRequest::Attach(AttachRequest { process_id: None }).into(),
},
adapter: task_type_to_adapter_name(self.r#type),
adapter: task_type_to_adapter_name(&self.r#type),
// TODO host?
tcp_connection: self.port.map(|port| TcpArgumentsTemplate {
port: Some(port),
@ -75,11 +88,7 @@ impl VsCodeDebugTaskDefinition {
// TODO
initialize_args: None,
};
let template = DebugTaskTemplate {
locator: None,
definition,
};
Ok(template)
Ok(definition)
}
}
@ -110,24 +119,26 @@ impl TryFrom<VsCodeDebugTaskFile> for DebugTaskFile {
}
}
// TODO figure out how to make JsDebugAdapter::ADAPTER_NAME et al available here
fn task_type_to_adapter_name(task_type: String) -> String {
match task_type.as_str() {
"node" => "JavaScript".to_owned(),
"go" => "Delve".to_owned(),
"php" => "PHP".to_owned(),
"cppdbg" | "lldb" => "CodeLLDB".to_owned(),
"debugpy" => "Debugpy".to_owned(),
// todo(debugger) figure out how to make JsDebugAdapter::ADAPTER_NAME et al available here
fn task_type_to_adapter_name(task_type: &str) -> SharedString {
match task_type {
"node" => "JavaScript",
"go" => "Delve",
"php" => "PHP",
"cppdbg" | "lldb" => "CodeLLDB",
"debugpy" => "Debugpy",
_ => task_type,
}
.to_owned()
.into()
}
#[cfg(test)]
mod tests {
use crate::{
DebugRequest, DebugTaskDefinition, DebugTaskFile, DebugTaskTemplate, LaunchRequest,
TcpArgumentsTemplate,
};
use collections::FxHashMap;
use crate::{DebugRequest, DebugScenario, DebugTaskFile, LaunchRequest, TcpArgumentsTemplate};
use super::VsCodeDebugTaskFile;
@ -159,24 +170,23 @@ mod tests {
let zed = DebugTaskFile::try_from(parsed).expect("converting to Zed debug templates");
pretty_assertions::assert_eq!(
zed,
DebugTaskFile(vec![DebugTaskTemplate {
locator: None,
definition: DebugTaskDefinition {
label: "Debug my JS app".into(),
adapter: "JavaScript".into(),
stop_on_entry: Some(true),
initialize_args: None,
tcp_connection: Some(TcpArgumentsTemplate {
port: Some(17),
host: None,
timeout: None,
}),
request: DebugRequest::Launch(LaunchRequest {
program: "${ZED_WORKTREE_ROOT}/xyz.js".into(),
args: vec!["--foo".into(), "${ZED_WORKTREE_ROOT}/thing".into()],
cwd: Some("${ZED_WORKTREE_ROOT}/${FOO}/sub".into()),
}),
}
DebugTaskFile(vec![DebugScenario {
label: "Debug my JS app".into(),
adapter: "JavaScript".into(),
stop_on_entry: Some(true),
initialize_args: None,
tcp_connection: Some(TcpArgumentsTemplate {
port: Some(17),
host: None,
timeout: None,
}),
request: Some(DebugRequest::Launch(LaunchRequest {
program: "${ZED_WORKTREE_ROOT}/xyz.js".into(),
args: vec!["--foo".into(), "${ZED_WORKTREE_ROOT}/thing".into()],
cwd: Some("${ZED_WORKTREE_ROOT}/${FOO}/sub".into()),
env: FxHashMap::from_iter([("X".into(), "Y".into())])
})),
build: None
}])
);
}

View File

@ -10,10 +10,7 @@ use gpui::{
use itertools::Itertools;
use picker::{Picker, PickerDelegate, highlighted_match_with_paths::HighlightedMatch};
use project::{TaskSourceKind, task_store::TaskStore};
use task::{
DebugRequest, DebugTaskDefinition, ResolvedTask, RevealTarget, TaskContext, TaskModal,
TaskTemplate, TaskType,
};
use task::{DebugScenario, ResolvedTask, RevealTarget, TaskContext, TaskModal, TaskTemplate};
use ui::{
ActiveTheme, Button, ButtonCommon, ButtonSize, Clickable, Color, FluentBuilder as _, Icon,
IconButton, IconButtonShape, IconName, IconSize, IntoElement, KeyBinding, Label, LabelSize,
@ -187,7 +184,7 @@ impl Render for TasksModal {
}
pub struct ShowAttachModal {
pub debug_config: DebugTaskDefinition,
pub debug_config: DebugScenario,
}
impl EventEmitter<DismissEvent> for TasksModal {}
@ -354,48 +351,20 @@ impl PickerDelegate for TasksModalDelegate {
reveal_target: Some(reveal_target),
}) = &self.task_overrides
{
if let Some(resolved_task) = &mut task.resolved {
resolved_task.reveal_target = *reveal_target;
}
task.resolved.reveal_target = *reveal_target;
}
match task.task_type() {
TaskType::Debug(_) => {
let Some(config) = task.resolved_debug_adapter_config() else {
return;
};
let config = config.definition;
match &config.request {
DebugRequest::Attach(attach_config) if attach_config.process_id.is_none() => {
cx.emit(ShowAttachModal {
debug_config: config.clone(),
});
return;
}
_ => {
self.workspace
.update(cx, |workspace, cx| {
workspace.schedule_debug_task(task, window, cx);
})
.ok();
}
}
}
TaskType::Script => {
self.workspace
.update(cx, |workspace, cx| {
workspace.schedule_resolved_task(
task_source_kind,
task,
omit_history_entry,
window,
cx,
);
})
.ok();
}
};
self.workspace
.update(cx, |workspace, cx| {
workspace.schedule_resolved_task(
task_source_kind,
task,
omit_history_entry,
window,
cx,
);
})
.ok();
cx.emit(DismissEvent);
}
@ -422,16 +391,14 @@ impl PickerDelegate for TasksModalDelegate {
} else {
String::new()
};
if let Some(resolved) = resolved_task.resolved.as_ref() {
if resolved.command_label != display_label
&& resolved.command_label != resolved_task.resolved_label
{
if !tooltip_label_text.trim().is_empty() {
tooltip_label_text.push('\n');
}
tooltip_label_text.push_str(&resolved.command_label);
if resolved_task.resolved.command_label != resolved_task.resolved_label {
if !tooltip_label_text.trim().is_empty() {
tooltip_label_text.push('\n');
}
tooltip_label_text.push_str(&resolved_task.resolved.command_label);
}
if template.tags.len() > 0 {
tooltip_label_text.push('\n');
tooltip_label_text.push_str(
@ -553,7 +520,7 @@ impl PickerDelegate for TasksModalDelegate {
let task_index = self.matches.get(self.selected_index())?.candidate_id;
let tasks = self.candidates.as_ref()?;
let (_, task) = tasks.get(task_index)?;
Some(task.resolved.as_ref()?.command_label.clone())
Some(task.resolved.command_label.clone())
}
fn confirm_input(
@ -570,26 +537,17 @@ impl PickerDelegate for TasksModalDelegate {
reveal_target: Some(reveal_target),
}) = self.task_overrides
{
if let Some(resolved_task) = &mut task.resolved {
resolved_task.reveal_target = reveal_target;
}
task.resolved.reveal_target = reveal_target;
}
self.workspace
.update(cx, |workspace, cx| {
match task.task_type() {
TaskType::Script => workspace.schedule_resolved_task(
task_source_kind,
task,
omit_history_entry,
window,
cx,
),
// todo(debugger): Should create a schedule_resolved_debug_task function
// This would allow users to access to debug history and other issues
TaskType::Debug(_) => {
workspace.schedule_debug_task(task, window, cx);
}
};
workspace.schedule_resolved_task(
task_source_kind,
task,
omit_history_entry,
window,
cx,
)
})
.ok();
cx.emit(DismissEvent);
@ -716,10 +674,7 @@ fn string_match_candidates<'a>(
candidates
.into_iter()
.enumerate()
.filter(|(_, (_, candidate))| match candidate.task_type() {
TaskType::Script => task_modal_type == TaskModal::ScriptModal,
TaskType::Debug(_) => task_modal_type == TaskModal::DebugModal,
})
.filter(|(_, (_, _))| task_modal_type == TaskModal::ScriptModal)
.map(|(index, (_, candidate))| StringMatchCandidate::new(index, candidate.display_label()))
.collect()
}

View File

@ -65,13 +65,13 @@ pub fn init(cx: &mut App) {
})
.detach()
} else {
if let Some(resolved) = last_scheduled_task.resolved.as_mut() {
if let Some(allow_concurrent_runs) = action.allow_concurrent_runs {
resolved.allow_concurrent_runs = allow_concurrent_runs;
}
if let Some(use_new_terminal) = action.use_new_terminal {
resolved.use_new_terminal = use_new_terminal;
}
let resolved = &mut last_scheduled_task.resolved;
if let Some(allow_concurrent_runs) = action.allow_concurrent_runs {
resolved.allow_concurrent_runs = allow_concurrent_runs;
}
if let Some(use_new_terminal) = action.use_new_terminal {
resolved.use_new_terminal = use_new_terminal;
}
workspace.schedule_resolved_task(

View File

@ -1,10 +1,11 @@
use std::process::ExitStatus;
use anyhow::{Result, anyhow};
use gpui::{Context, Task};
use gpui::{Context, Entity, Task};
use language::Buffer;
use project::TaskSourceKind;
use remote::ConnectionState;
use task::{DebugTaskDefinition, ResolvedTask, SpawnInTerminal, TaskContext, TaskTemplate};
use task::{DebugScenario, ResolvedTask, SpawnInTerminal, TaskContext, TaskTemplate};
use ui::Window;
use crate::Workspace;
@ -48,84 +49,41 @@ impl Workspace {
pub fn schedule_resolved_task(
self: &mut Workspace,
task_source_kind: TaskSourceKind,
mut resolved_task: ResolvedTask,
resolved_task: ResolvedTask,
omit_history: bool,
window: &mut Window,
cx: &mut Context<Workspace>,
) {
if let Some(spawn_in_terminal) = resolved_task.resolved.take() {
if !omit_history {
resolved_task.resolved = Some(spawn_in_terminal.clone());
self.project().update(cx, |project, cx| {
if let Some(task_inventory) =
project.task_store().read(cx).task_inventory().cloned()
{
task_inventory.update(cx, |inventory, _| {
inventory.task_scheduled(task_source_kind, resolved_task);
})
}
});
}
if let Some(terminal_provider) = self.terminal_provider.as_ref() {
terminal_provider
.spawn(spawn_in_terminal, window, cx)
.detach_and_log_err(cx);
}
}
}
pub fn schedule_debug_task(
&mut self,
task: ResolvedTask,
window: &mut Window,
cx: &mut Context<Workspace>,
) {
let Some(debug_config) = task.resolved_debug_adapter_config() else {
log::error!("Debug task has no debug adapter config");
return;
};
let project = self.project().clone();
cx.spawn_in(window, async move |workspace, cx| {
let config = if debug_config.locator.is_some() {
let task = workspace.update_in(cx, |workspace, window, cx| {
workspace.spawn_in_terminal(task.resolved.unwrap(), window, cx)
})?;
let exit_code = task.await?;
if !exit_code.success() {
return anyhow::Ok(());
let spawn_in_terminal = resolved_task.resolved.clone();
if !omit_history {
self.project().update(cx, |project, cx| {
if let Some(task_inventory) =
project.task_store().read(cx).task_inventory().cloned()
{
task_inventory.update(cx, |inventory, _| {
inventory.task_scheduled(task_source_kind, resolved_task);
})
}
let ret = project
.update(cx, |project, cx| {
project.dap_store().update(cx, |dap_store, cx| {
dap_store.run_debug_locator(debug_config, cx)
})
})?
.await?;
ret
} else {
debug_config.definition
};
});
}
workspace.update_in(cx, |workspace, window, cx| {
workspace.start_debug_session(config, window, cx);
})?;
anyhow::Ok(())
})
.detach_and_log_err(cx);
if let Some(terminal_provider) = self.terminal_provider.as_ref() {
terminal_provider
.spawn(spawn_in_terminal, window, cx)
.detach_and_log_err(cx);
}
}
pub fn start_debug_session(
&mut self,
definition: DebugTaskDefinition,
scenario: DebugScenario,
task_context: TaskContext,
active_buffer: Option<Entity<Buffer>>,
window: &mut Window,
cx: &mut Context<Self>,
) {
if let Some(provider) = self.debugger_provider.as_mut() {
provider.start_session(definition, window, cx)
provider.start_session(scenario, task_context, active_buffer, window, cx)
}
}

View File

@ -49,7 +49,7 @@ pub use item::{
ProjectItem, SerializableItem, SerializableItemHandle, WeakItemHandle,
};
use itertools::Itertools;
use language::{LanguageRegistry, Rope};
use language::{Buffer, LanguageRegistry, Rope};
pub use modal_layer::*;
use node_runtime::NodeRuntime;
use notifications::{
@ -96,7 +96,7 @@ use std::{
sync::{Arc, LazyLock, Weak, atomic::AtomicUsize},
time::Duration,
};
use task::{DebugTaskDefinition, SpawnInTerminal};
use task::{DebugScenario, SpawnInTerminal, TaskContext};
use theme::{ActiveTheme, SystemAppearance, ThemeSettings};
pub use toolbar::{Toolbar, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
pub use ui;
@ -140,7 +140,15 @@ pub trait TerminalProvider {
}
pub trait DebuggerProvider {
fn start_session(&self, definition: DebugTaskDefinition, window: &mut Window, cx: &mut App);
// `active_buffer` is used to resolve build task's name against language-specific tasks.
fn start_session(
&self,
definition: DebugScenario,
task_context: TaskContext,
active_buffer: Option<Entity<Buffer>>,
window: &mut Window,
cx: &mut App,
);
}
actions!(

View File

@ -4273,7 +4273,7 @@ mod tests {
project::debugger::breakpoint_store::BreakpointStore::init(
&app_state.client.clone().into(),
);
project::debugger::dap_store::DapStore::init(&app_state.client.clone().into());
project::debugger::dap_store::DapStore::init(&app_state.client.clone().into(), cx);
debugger_ui::init(cx);
initialize_workspace(app_state.clone(), prompt_builder, cx);
search::init(cx);