debugger/tasks: Remove TaskType enum (#29208)
Closes #ISSUE Release Notes: - N/A --------- Co-authored-by: Cole Miller <m@cole-miller.net> Co-authored-by: Anthony Eid <hello@anthonyeid.me> Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com> Co-authored-by: Anthony <anthony@zed.dev> Co-authored-by: Conrad <conrad@zed.dev>
This commit is contained in:
parent
053fafa90e
commit
67615b968b
@ -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(),
|
||||
})
|
||||
}
|
||||
(None, None) => None,
|
||||
}
|
||||
if let Some(actions) = &self.actions {
|
||||
if let Some(available) = actions.get(index) {
|
||||
return Some(CodeActionsItem::CodeAction {
|
||||
excerpt_id: available.excerpt_id,
|
||||
action: available.action.clone(),
|
||||
provider: available.provider.clone(),
|
||||
});
|
||||
} else {
|
||||
index -= actions.len();
|
||||
}
|
||||
}
|
||||
|
||||
self.debug_scenarios
|
||||
.get(index)
|
||||
.cloned()
|
||||
.map(CodeActionsItem::DebugScenario)
|
||||
}
|
||||
}
|
||||
|
||||
@ -931,6 +917,7 @@ pub enum CodeActionsItem {
|
||||
action: CodeAction,
|
||||
provider: Rc<dyn CodeActionProvider>,
|
||||
},
|
||||
DebugScenario(DebugScenario),
|
||||
}
|
||||
|
||||
impl CodeActionsItem {
|
||||
@ -947,16 +934,23 @@ impl CodeActionsItem {
|
||||
};
|
||||
Some(action)
|
||||
}
|
||||
fn as_debug_scenario(&self) -> Option<&DebugScenario> {
|
||||
let Self::DebugScenario(scenario) = self else {
|
||||
return None;
|
||||
};
|
||||
Some(scenario)
|
||||
}
|
||||
|
||||
pub fn label(&self) -> String {
|
||||
match self {
|
||||
Self::CodeAction { action, .. } => action.lsp_action.title().to_owned(),
|
||||
Self::Task(_, task) => task.resolved_label.clone(),
|
||||
Self::DebugScenario(scenario) => scenario.label.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CodeActionsMenu {
|
||||
pub(crate) struct CodeActionsMenu {
|
||||
pub actions: CodeActionContents,
|
||||
pub buffer: Entity<Buffer>,
|
||||
pub selected_item: usize,
|
||||
@ -1065,19 +1059,7 @@ impl CodeActionsMenu {
|
||||
.inset(true)
|
||||
.toggle_state(selected)
|
||||
.when_some(action.as_code_action(), |this, action| {
|
||||
this.on_click(cx.listener(move |editor, _, window, cx| {
|
||||
cx.stop_propagation();
|
||||
if let Some(task) = editor.confirm_code_action(
|
||||
&ConfirmCodeAction {
|
||||
item_ix: Some(item_ix),
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
) {
|
||||
task.detach_and_log_err(cx)
|
||||
}
|
||||
}))
|
||||
.child(
|
||||
this.child(
|
||||
h_flex()
|
||||
.overflow_hidden()
|
||||
.child(
|
||||
@ -1090,19 +1072,7 @@ impl CodeActionsMenu {
|
||||
)
|
||||
})
|
||||
.when_some(action.as_task(), |this, task| {
|
||||
this.on_click(cx.listener(move |editor, _, window, cx| {
|
||||
cx.stop_propagation();
|
||||
if let Some(task) = editor.confirm_code_action(
|
||||
&ConfirmCodeAction {
|
||||
item_ix: Some(item_ix),
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
) {
|
||||
task.detach_and_log_err(cx)
|
||||
}
|
||||
}))
|
||||
.child(
|
||||
this.child(
|
||||
h_flex()
|
||||
.overflow_hidden()
|
||||
.child(task.resolved_label.replace("\n", ""))
|
||||
@ -1110,7 +1080,29 @@ impl CodeActionsMenu {
|
||||
this.text_color(colors.text_accent)
|
||||
}),
|
||||
)
|
||||
}),
|
||||
})
|
||||
.when_some(action.as_debug_scenario(), |this, scenario| {
|
||||
this.child(
|
||||
h_flex()
|
||||
.overflow_hidden()
|
||||
.child(scenario.label.clone())
|
||||
.when(selected, |this| {
|
||||
this.text_color(colors.text_accent)
|
||||
}),
|
||||
)
|
||||
})
|
||||
.on_click(cx.listener(move |editor, _, window, cx| {
|
||||
cx.stop_propagation();
|
||||
if let Some(task) = editor.confirm_code_action(
|
||||
&ConfirmCodeAction {
|
||||
item_ix: Some(item_ix),
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
) {
|
||||
task.detach_and_log_err(cx)
|
||||
}
|
||||
})),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
@ -1128,6 +1120,7 @@ impl CodeActionsMenu {
|
||||
CodeActionsItem::CodeAction { action, .. } => {
|
||||
action.lsp_action.title().chars().count()
|
||||
}
|
||||
CodeActionsItem::DebugScenario(scenario) => scenario.label.chars().count(),
|
||||
})
|
||||
.map(|(ix, _)| ix),
|
||||
)
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user