Detailed changes
@@ -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,
}
}
}
@@ -32,4 +32,6 @@ pub enum LocalSettingsKind {
Tasks,
#[sea_orm(string_value = "editorconfig")]
Editorconfig,
+ #[sea_orm(string_value = "debug")]
+ Debug,
}
@@ -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,
@@ -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);
@@ -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,
@@ -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;
@@ -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()
}
@@ -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) => {
@@ -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::*;
@@ -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::*;
@@ -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::*;
@@ -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::*;
@@ -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;
@@ -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);
}
@@ -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);
})
})
}
@@ -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,
})
}
@@ -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
@@ -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,
@@ -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,
@@ -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)]
@@ -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(),
- })
+ }
+ 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();
}
- (None, None) => None,
}
+
+ 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),
)
@@ -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,
@@ -211,6 +211,7 @@ pub fn deploy_context_menu(
"Show Code Actions",
Box::new(ToggleCodeActions {
deployed_from_indicator: None,
+ quick_launch: false,
}),
)
.separator()
@@ -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>;
}
@@ -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> {
@@ -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 {
@@ -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
@@ -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
@@ -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))
- }
- }
-}
@@ -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>;
-}
@@ -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(),
+ }))
}
}
@@ -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) => {
@@ -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
@@ -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,
}
}
@@ -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<_>>(),
@@ -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();
});
@@ -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)
})
}
}
@@ -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;
}
@@ -148,4 +148,5 @@ enum LocalSettingsKind {
Settings = 0;
Tasks = 1;
Editorconfig = 2;
+ Debug = 3;
}
@@ -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;
@@ -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,
);
@@ -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);
@@ -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;
@@ -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}")
}
}
@@ -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.
@@ -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()
}
}
@@ -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");
@@ -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
}])
);
}
@@ -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()
}
@@ -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(
@@ -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)
}
}
@@ -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!(
@@ -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);