debugger: Improve Go support (#31559)
Supersedes https://github.com/zed-industries/zed/pull/31345 This PR does not have any terminal/console related stuff so that it can be solved separately. Introduces inline hints in debugger: <img width="1141" alt="image" src="https://github.com/user-attachments/assets/b0575f1e-ddf8-41fe-8958-2da6d4974912" /> Adds locators for go, so that you can your app in debug mode: <img width="706" alt="image" src="https://github.com/user-attachments/assets/df29bba5-8264-4bea-976f-686c32a5605b" /> As well is allows you to specify an existing compiled binary: <img width="604" alt="image" src="https://github.com/user-attachments/assets/548f2ab5-88c1-41fb-af84-115a19e685ea" /> Release Notes: - Added inline value hints for Go debugging, displaying variable values directly in the editor during debug sessions - Added Go debug locator support, enabling debugging of Go applications through task templates - Improved Go debug adapter to support both source debugging (mode: "debug") and binary execution (mode: "exec") based on program path cc @osiewicz, @Anthony-Eid
This commit is contained in:
parent
c0a5ace8b8
commit
94a5fe265d
8 changed files with 651 additions and 9 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -4031,6 +4031,8 @@ dependencies = [
|
||||||
"smol",
|
"smol",
|
||||||
"task",
|
"task",
|
||||||
"telemetry",
|
"telemetry",
|
||||||
|
"tree-sitter",
|
||||||
|
"tree-sitter-go",
|
||||||
"util",
|
"util",
|
||||||
"workspace-hack",
|
"workspace-hack",
|
||||||
"zlog",
|
"zlog",
|
||||||
|
|
|
@ -56,5 +56,7 @@ async-pipe.workspace = true
|
||||||
gpui = { workspace = true, features = ["test-support"] }
|
gpui = { workspace = true, features = ["test-support"] }
|
||||||
settings = { workspace = true, features = ["test-support"] }
|
settings = { workspace = true, features = ["test-support"] }
|
||||||
task = { workspace = true, features = ["test-support"] }
|
task = { workspace = true, features = ["test-support"] }
|
||||||
|
tree-sitter.workspace = true
|
||||||
|
tree-sitter-go.workspace = true
|
||||||
util = { workspace = true, features = ["test-support"] }
|
util = { workspace = true, features = ["test-support"] }
|
||||||
zlog.workspace = true
|
zlog.workspace = true
|
||||||
|
|
|
@ -275,3 +275,386 @@ impl InlineValueProvider for PythonInlineValueProvider {
|
||||||
variables
|
variables
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct GoInlineValueProvider;
|
||||||
|
|
||||||
|
impl InlineValueProvider for GoInlineValueProvider {
|
||||||
|
fn provide(
|
||||||
|
&self,
|
||||||
|
mut node: language::Node,
|
||||||
|
source: &str,
|
||||||
|
max_row: usize,
|
||||||
|
) -> Vec<InlineValueLocation> {
|
||||||
|
let mut variables = Vec::new();
|
||||||
|
let mut variable_names = HashSet::new();
|
||||||
|
let mut scope = VariableScope::Local;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let mut variable_names_in_scope = HashMap::new();
|
||||||
|
for child in node.named_children(&mut node.walk()) {
|
||||||
|
if child.start_position().row >= max_row {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if scope == VariableScope::Local {
|
||||||
|
match child.kind() {
|
||||||
|
"var_declaration" => {
|
||||||
|
for var_spec in child.named_children(&mut child.walk()) {
|
||||||
|
if var_spec.kind() == "var_spec" {
|
||||||
|
if let Some(name_node) = var_spec.child_by_field_name("name") {
|
||||||
|
let variable_name =
|
||||||
|
source[name_node.byte_range()].to_string();
|
||||||
|
|
||||||
|
if variable_names.contains(&variable_name) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(index) =
|
||||||
|
variable_names_in_scope.get(&variable_name)
|
||||||
|
{
|
||||||
|
variables.remove(*index);
|
||||||
|
}
|
||||||
|
|
||||||
|
variable_names_in_scope
|
||||||
|
.insert(variable_name.clone(), variables.len());
|
||||||
|
variables.push(InlineValueLocation {
|
||||||
|
variable_name,
|
||||||
|
scope: VariableScope::Local,
|
||||||
|
lookup: VariableLookupKind::Variable,
|
||||||
|
row: name_node.end_position().row,
|
||||||
|
column: name_node.end_position().column,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"short_var_declaration" => {
|
||||||
|
if let Some(left_side) = child.child_by_field_name("left") {
|
||||||
|
for identifier in left_side.named_children(&mut left_side.walk()) {
|
||||||
|
if identifier.kind() == "identifier" {
|
||||||
|
let variable_name =
|
||||||
|
source[identifier.byte_range()].to_string();
|
||||||
|
|
||||||
|
if variable_names.contains(&variable_name) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(index) =
|
||||||
|
variable_names_in_scope.get(&variable_name)
|
||||||
|
{
|
||||||
|
variables.remove(*index);
|
||||||
|
}
|
||||||
|
|
||||||
|
variable_names_in_scope
|
||||||
|
.insert(variable_name.clone(), variables.len());
|
||||||
|
variables.push(InlineValueLocation {
|
||||||
|
variable_name,
|
||||||
|
scope: VariableScope::Local,
|
||||||
|
lookup: VariableLookupKind::Variable,
|
||||||
|
row: identifier.end_position().row,
|
||||||
|
column: identifier.end_position().column,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"assignment_statement" => {
|
||||||
|
if let Some(left_side) = child.child_by_field_name("left") {
|
||||||
|
for identifier in left_side.named_children(&mut left_side.walk()) {
|
||||||
|
if identifier.kind() == "identifier" {
|
||||||
|
let variable_name =
|
||||||
|
source[identifier.byte_range()].to_string();
|
||||||
|
|
||||||
|
if variable_names.contains(&variable_name) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(index) =
|
||||||
|
variable_names_in_scope.get(&variable_name)
|
||||||
|
{
|
||||||
|
variables.remove(*index);
|
||||||
|
}
|
||||||
|
|
||||||
|
variable_names_in_scope
|
||||||
|
.insert(variable_name.clone(), variables.len());
|
||||||
|
variables.push(InlineValueLocation {
|
||||||
|
variable_name,
|
||||||
|
scope: VariableScope::Local,
|
||||||
|
lookup: VariableLookupKind::Variable,
|
||||||
|
row: identifier.end_position().row,
|
||||||
|
column: identifier.end_position().column,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"function_declaration" | "method_declaration" => {
|
||||||
|
if let Some(params) = child.child_by_field_name("parameters") {
|
||||||
|
for param in params.named_children(&mut params.walk()) {
|
||||||
|
if param.kind() == "parameter_declaration" {
|
||||||
|
if let Some(name_node) = param.child_by_field_name("name") {
|
||||||
|
let variable_name =
|
||||||
|
source[name_node.byte_range()].to_string();
|
||||||
|
|
||||||
|
if variable_names.contains(&variable_name) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(index) =
|
||||||
|
variable_names_in_scope.get(&variable_name)
|
||||||
|
{
|
||||||
|
variables.remove(*index);
|
||||||
|
}
|
||||||
|
|
||||||
|
variable_names_in_scope
|
||||||
|
.insert(variable_name.clone(), variables.len());
|
||||||
|
variables.push(InlineValueLocation {
|
||||||
|
variable_name,
|
||||||
|
scope: VariableScope::Local,
|
||||||
|
lookup: VariableLookupKind::Variable,
|
||||||
|
row: name_node.end_position().row,
|
||||||
|
column: name_node.end_position().column,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"for_statement" => {
|
||||||
|
if let Some(clause) = child.named_child(0) {
|
||||||
|
if clause.kind() == "for_clause" {
|
||||||
|
if let Some(init) = clause.named_child(0) {
|
||||||
|
if init.kind() == "short_var_declaration" {
|
||||||
|
if let Some(left_side) =
|
||||||
|
init.child_by_field_name("left")
|
||||||
|
{
|
||||||
|
if left_side.kind() == "expression_list" {
|
||||||
|
for identifier in left_side
|
||||||
|
.named_children(&mut left_side.walk())
|
||||||
|
{
|
||||||
|
if identifier.kind() == "identifier" {
|
||||||
|
let variable_name = source
|
||||||
|
[identifier.byte_range()]
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
if variable_names
|
||||||
|
.contains(&variable_name)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(index) =
|
||||||
|
variable_names_in_scope
|
||||||
|
.get(&variable_name)
|
||||||
|
{
|
||||||
|
variables.remove(*index);
|
||||||
|
}
|
||||||
|
|
||||||
|
variable_names_in_scope.insert(
|
||||||
|
variable_name.clone(),
|
||||||
|
variables.len(),
|
||||||
|
);
|
||||||
|
variables.push(InlineValueLocation {
|
||||||
|
variable_name,
|
||||||
|
scope: VariableScope::Local,
|
||||||
|
lookup:
|
||||||
|
VariableLookupKind::Variable,
|
||||||
|
row: identifier.end_position().row,
|
||||||
|
column: identifier
|
||||||
|
.end_position()
|
||||||
|
.column,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if clause.kind() == "range_clause" {
|
||||||
|
if let Some(left) = clause.child_by_field_name("left") {
|
||||||
|
if left.kind() == "expression_list" {
|
||||||
|
for identifier in left.named_children(&mut left.walk())
|
||||||
|
{
|
||||||
|
if identifier.kind() == "identifier" {
|
||||||
|
let variable_name =
|
||||||
|
source[identifier.byte_range()].to_string();
|
||||||
|
|
||||||
|
if variable_name == "_" {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if variable_names.contains(&variable_name) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(index) =
|
||||||
|
variable_names_in_scope.get(&variable_name)
|
||||||
|
{
|
||||||
|
variables.remove(*index);
|
||||||
|
}
|
||||||
|
variable_names_in_scope.insert(
|
||||||
|
variable_name.clone(),
|
||||||
|
variables.len(),
|
||||||
|
);
|
||||||
|
variables.push(InlineValueLocation {
|
||||||
|
variable_name,
|
||||||
|
scope: VariableScope::Local,
|
||||||
|
lookup: VariableLookupKind::Variable,
|
||||||
|
row: identifier.end_position().row,
|
||||||
|
column: identifier.end_position().column,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
} else if child.kind() == "var_declaration" {
|
||||||
|
for var_spec in child.named_children(&mut child.walk()) {
|
||||||
|
if var_spec.kind() == "var_spec" {
|
||||||
|
if let Some(name_node) = var_spec.child_by_field_name("name") {
|
||||||
|
let variable_name = source[name_node.byte_range()].to_string();
|
||||||
|
variables.push(InlineValueLocation {
|
||||||
|
variable_name,
|
||||||
|
scope: VariableScope::Global,
|
||||||
|
lookup: VariableLookupKind::Expression,
|
||||||
|
row: name_node.end_position().row,
|
||||||
|
column: name_node.end_position().column,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variable_names.extend(variable_names_in_scope.keys().cloned());
|
||||||
|
|
||||||
|
if matches!(node.kind(), "function_declaration" | "method_declaration") {
|
||||||
|
scope = VariableScope::Global;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(parent) = node.parent() {
|
||||||
|
node = parent;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variables
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use tree_sitter::Parser;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_go_inline_value_provider() {
|
||||||
|
let provider = GoInlineValueProvider;
|
||||||
|
let source = r#"
|
||||||
|
package main
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
items := []int{1, 2, 3, 4, 5}
|
||||||
|
for i, v := range items {
|
||||||
|
println(i, v)
|
||||||
|
}
|
||||||
|
for j := 0; j < 10; j++ {
|
||||||
|
println(j)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let mut parser = Parser::new();
|
||||||
|
if parser
|
||||||
|
.set_language(&tree_sitter_go::LANGUAGE.into())
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let Some(tree) = parser.parse(source, None) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let root_node = tree.root_node();
|
||||||
|
|
||||||
|
let mut main_body = None;
|
||||||
|
for child in root_node.named_children(&mut root_node.walk()) {
|
||||||
|
if child.kind() == "function_declaration" {
|
||||||
|
if let Some(name) = child.child_by_field_name("name") {
|
||||||
|
if &source[name.byte_range()] == "main" {
|
||||||
|
if let Some(body) = child.child_by_field_name("body") {
|
||||||
|
main_body = Some(body);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(main_body) = main_body else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let variables = provider.provide(main_body, source, 100);
|
||||||
|
assert!(variables.len() >= 2);
|
||||||
|
|
||||||
|
let variable_names: Vec<&str> =
|
||||||
|
variables.iter().map(|v| v.variable_name.as_str()).collect();
|
||||||
|
assert!(variable_names.contains(&"items"));
|
||||||
|
assert!(variable_names.contains(&"j"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_go_inline_value_provider_counter_pattern() {
|
||||||
|
let provider = GoInlineValueProvider;
|
||||||
|
let source = r#"
|
||||||
|
package main
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
N := 10
|
||||||
|
for i := range N {
|
||||||
|
println(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let mut parser = Parser::new();
|
||||||
|
if parser
|
||||||
|
.set_language(&tree_sitter_go::LANGUAGE.into())
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let Some(tree) = parser.parse(source, None) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let root_node = tree.root_node();
|
||||||
|
|
||||||
|
let mut main_body = None;
|
||||||
|
for child in root_node.named_children(&mut root_node.walk()) {
|
||||||
|
if child.kind() == "function_declaration" {
|
||||||
|
if let Some(name) = child.child_by_field_name("name") {
|
||||||
|
if &source[name.byte_range()] == "main" {
|
||||||
|
if let Some(body) = child.child_by_field_name("body") {
|
||||||
|
main_body = Some(body);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(main_body) = main_body else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let variables = provider.provide(main_body, source, 100);
|
||||||
|
|
||||||
|
let variable_names: Vec<&str> =
|
||||||
|
variables.iter().map(|v| v.variable_name.as_str()).collect();
|
||||||
|
assert!(variable_names.contains(&"N"));
|
||||||
|
assert!(variable_names.contains(&"i"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ use dap::{
|
||||||
GithubRepo,
|
GithubRepo,
|
||||||
},
|
},
|
||||||
configure_tcp_connection,
|
configure_tcp_connection,
|
||||||
inline_value::{PythonInlineValueProvider, RustInlineValueProvider},
|
inline_value::{GoInlineValueProvider, PythonInlineValueProvider, RustInlineValueProvider},
|
||||||
};
|
};
|
||||||
use gdb::GdbDebugAdapter;
|
use gdb::GdbDebugAdapter;
|
||||||
use go::GoDebugAdapter;
|
use go::GoDebugAdapter;
|
||||||
|
@ -48,5 +48,6 @@ pub fn init(cx: &mut App) {
|
||||||
registry.add_inline_value_provider("Rust".to_string(), Arc::from(RustInlineValueProvider));
|
registry.add_inline_value_provider("Rust".to_string(), Arc::from(RustInlineValueProvider));
|
||||||
registry
|
registry
|
||||||
.add_inline_value_provider("Python".to_string(), Arc::from(PythonInlineValueProvider));
|
.add_inline_value_provider("Python".to_string(), Arc::from(PythonInlineValueProvider));
|
||||||
|
registry.add_inline_value_provider("Go".to_string(), Arc::from(GoInlineValueProvider));
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -312,14 +312,22 @@ impl DebugAdapter for GoDebugAdapter {
|
||||||
"processId": attach_config.process_id,
|
"processId": attach_config.process_id,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
dap::DebugRequest::Launch(launch_config) => json!({
|
dap::DebugRequest::Launch(launch_config) => {
|
||||||
|
let mode = if launch_config.program != "." {
|
||||||
|
"exec"
|
||||||
|
} else {
|
||||||
|
"debug"
|
||||||
|
};
|
||||||
|
|
||||||
|
json!({
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"mode": "debug",
|
"mode": mode,
|
||||||
"program": launch_config.program,
|
"program": launch_config.program,
|
||||||
"cwd": launch_config.cwd,
|
"cwd": launch_config.cwd,
|
||||||
"args": launch_config.args,
|
"args": launch_config.args,
|
||||||
"env": launch_config.env_json()
|
"env": launch_config.env_json()
|
||||||
}),
|
})
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let map = args.as_object_mut().unwrap();
|
let map = args.as_object_mut().unwrap();
|
||||||
|
|
|
@ -104,6 +104,7 @@ impl DapStore {
|
||||||
let registry = DapRegistry::global(cx);
|
let registry = DapRegistry::global(cx);
|
||||||
registry.add_locator(Arc::new(locators::cargo::CargoLocator {}));
|
registry.add_locator(Arc::new(locators::cargo::CargoLocator {}));
|
||||||
registry.add_locator(Arc::new(locators::python::PythonLocator));
|
registry.add_locator(Arc::new(locators::python::PythonLocator));
|
||||||
|
registry.add_locator(Arc::new(locators::go::GoLocator {}));
|
||||||
});
|
});
|
||||||
client.add_entity_request_handler(Self::handle_run_debug_locator);
|
client.add_entity_request_handler(Self::handle_run_debug_locator);
|
||||||
client.add_entity_request_handler(Self::handle_get_debug_adapter_binary);
|
client.add_entity_request_handler(Self::handle_get_debug_adapter_binary);
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
pub(crate) mod cargo;
|
pub(crate) mod cargo;
|
||||||
|
pub(crate) mod go;
|
||||||
pub(crate) mod python;
|
pub(crate) mod python;
|
||||||
|
|
244
crates/project/src/debugger/locators/go.rs
Normal file
244
crates/project/src/debugger/locators/go.rs
Normal file
|
@ -0,0 +1,244 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use collections::FxHashMap;
|
||||||
|
use dap::{DapLocator, DebugRequest, adapters::DebugAdapterName};
|
||||||
|
use gpui::SharedString;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use task::{
|
||||||
|
BuildTaskDefinition, DebugScenario, RevealStrategy, RevealTarget, Shell, SpawnInTerminal,
|
||||||
|
TaskTemplate,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(crate) struct GoLocator;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl DapLocator for GoLocator {
|
||||||
|
fn name(&self) -> SharedString {
|
||||||
|
SharedString::new_static("go-debug-locator")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_scenario(
|
||||||
|
&self,
|
||||||
|
build_config: &TaskTemplate,
|
||||||
|
resolved_label: &str,
|
||||||
|
adapter: DebugAdapterName,
|
||||||
|
) -> Option<DebugScenario> {
|
||||||
|
let go_action = build_config.args.first()?;
|
||||||
|
|
||||||
|
match go_action.as_str() {
|
||||||
|
"run" => {
|
||||||
|
let program = build_config
|
||||||
|
.args
|
||||||
|
.get(1)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_else(|| ".".to_string());
|
||||||
|
|
||||||
|
let build_task = TaskTemplate {
|
||||||
|
label: "go build debug".into(),
|
||||||
|
command: "go".into(),
|
||||||
|
args: vec![
|
||||||
|
"build".into(),
|
||||||
|
"-gcflags \"all=-N -l\"".into(),
|
||||||
|
program.clone(),
|
||||||
|
],
|
||||||
|
env: build_config.env.clone(),
|
||||||
|
cwd: build_config.cwd.clone(),
|
||||||
|
use_new_terminal: false,
|
||||||
|
allow_concurrent_runs: false,
|
||||||
|
reveal: RevealStrategy::Always,
|
||||||
|
reveal_target: RevealTarget::Dock,
|
||||||
|
hide: task::HideStrategy::Never,
|
||||||
|
shell: Shell::System,
|
||||||
|
tags: vec![],
|
||||||
|
show_summary: true,
|
||||||
|
show_command: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(DebugScenario {
|
||||||
|
label: resolved_label.to_string().into(),
|
||||||
|
adapter: adapter.0,
|
||||||
|
build: Some(BuildTaskDefinition::Template {
|
||||||
|
task_template: build_task,
|
||||||
|
locator_name: Some(self.name()),
|
||||||
|
}),
|
||||||
|
config: serde_json::Value::Null,
|
||||||
|
tcp_connection: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(&self, build_config: SpawnInTerminal) -> Result<DebugRequest> {
|
||||||
|
if build_config.args.is_empty() {
|
||||||
|
return Err(anyhow::anyhow!("Invalid Go command"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let go_action = &build_config.args[0];
|
||||||
|
let cwd = build_config
|
||||||
|
.cwd
|
||||||
|
.as_ref()
|
||||||
|
.map(|p| p.to_string_lossy().to_string())
|
||||||
|
.unwrap_or_else(|| ".".to_string());
|
||||||
|
|
||||||
|
let mut env = FxHashMap::default();
|
||||||
|
for (key, value) in &build_config.env {
|
||||||
|
env.insert(key.clone(), value.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
match go_action.as_str() {
|
||||||
|
"build" => {
|
||||||
|
let package = build_config
|
||||||
|
.args
|
||||||
|
.get(2)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_else(|| ".".to_string());
|
||||||
|
|
||||||
|
Ok(DebugRequest::Launch(task::LaunchRequest {
|
||||||
|
program: package,
|
||||||
|
cwd: Some(PathBuf::from(&cwd)),
|
||||||
|
args: vec![],
|
||||||
|
env,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
_ => Err(anyhow::anyhow!("Unsupported Go command: {}", go_action)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use task::{HideStrategy, RevealStrategy, RevealTarget, Shell, TaskTemplate};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_scenario_for_go_run() {
|
||||||
|
let locator = GoLocator;
|
||||||
|
let task = TaskTemplate {
|
||||||
|
label: "go run main.go".into(),
|
||||||
|
command: "go".into(),
|
||||||
|
args: vec!["run".into(), "main.go".into()],
|
||||||
|
env: Default::default(),
|
||||||
|
cwd: Some("${ZED_WORKTREE_ROOT}".into()),
|
||||||
|
use_new_terminal: false,
|
||||||
|
allow_concurrent_runs: false,
|
||||||
|
reveal: RevealStrategy::Always,
|
||||||
|
reveal_target: RevealTarget::Dock,
|
||||||
|
hide: HideStrategy::Never,
|
||||||
|
shell: Shell::System,
|
||||||
|
tags: vec![],
|
||||||
|
show_summary: true,
|
||||||
|
show_command: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
let scenario =
|
||||||
|
locator.create_scenario(&task, "test label", DebugAdapterName("Delve".into()));
|
||||||
|
|
||||||
|
assert!(scenario.is_some());
|
||||||
|
let scenario = scenario.unwrap();
|
||||||
|
assert_eq!(scenario.adapter, "Delve");
|
||||||
|
assert_eq!(scenario.label, "test label");
|
||||||
|
assert!(scenario.build.is_some());
|
||||||
|
|
||||||
|
if let Some(BuildTaskDefinition::Template { task_template, .. }) = &scenario.build {
|
||||||
|
assert_eq!(task_template.command, "go");
|
||||||
|
assert!(task_template.args.contains(&"build".into()));
|
||||||
|
assert!(
|
||||||
|
task_template
|
||||||
|
.args
|
||||||
|
.contains(&"-gcflags \"all=-N -l\"".into())
|
||||||
|
);
|
||||||
|
assert!(task_template.args.contains(&"main.go".into()));
|
||||||
|
} else {
|
||||||
|
panic!("Expected BuildTaskDefinition::Template");
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
scenario.config.is_null(),
|
||||||
|
"Initial config should be null to ensure it's invalid"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_scenario_for_go_build() {
|
||||||
|
let locator = GoLocator;
|
||||||
|
let task = TaskTemplate {
|
||||||
|
label: "go build".into(),
|
||||||
|
command: "go".into(),
|
||||||
|
args: vec!["build".into(), ".".into()],
|
||||||
|
env: Default::default(),
|
||||||
|
cwd: Some("${ZED_WORKTREE_ROOT}".into()),
|
||||||
|
use_new_terminal: false,
|
||||||
|
allow_concurrent_runs: false,
|
||||||
|
reveal: RevealStrategy::Always,
|
||||||
|
reveal_target: RevealTarget::Dock,
|
||||||
|
hide: HideStrategy::Never,
|
||||||
|
shell: Shell::System,
|
||||||
|
tags: vec![],
|
||||||
|
show_summary: true,
|
||||||
|
show_command: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
let scenario =
|
||||||
|
locator.create_scenario(&task, "test label", DebugAdapterName("Delve".into()));
|
||||||
|
|
||||||
|
assert!(scenario.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_skip_non_go_commands_with_non_delve_adapter() {
|
||||||
|
let locator = GoLocator;
|
||||||
|
let task = TaskTemplate {
|
||||||
|
label: "cargo build".into(),
|
||||||
|
command: "cargo".into(),
|
||||||
|
args: vec!["build".into()],
|
||||||
|
env: Default::default(),
|
||||||
|
cwd: Some("${ZED_WORKTREE_ROOT}".into()),
|
||||||
|
use_new_terminal: false,
|
||||||
|
allow_concurrent_runs: false,
|
||||||
|
reveal: RevealStrategy::Always,
|
||||||
|
reveal_target: RevealTarget::Dock,
|
||||||
|
hide: HideStrategy::Never,
|
||||||
|
shell: Shell::System,
|
||||||
|
tags: vec![],
|
||||||
|
show_summary: true,
|
||||||
|
show_command: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
let scenario = locator.create_scenario(
|
||||||
|
&task,
|
||||||
|
"test label",
|
||||||
|
DebugAdapterName("SomeOtherAdapter".into()),
|
||||||
|
);
|
||||||
|
assert!(scenario.is_none());
|
||||||
|
|
||||||
|
let scenario =
|
||||||
|
locator.create_scenario(&task, "test label", DebugAdapterName("Delve".into()));
|
||||||
|
assert!(scenario.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_skip_unsupported_go_commands() {
|
||||||
|
let locator = GoLocator;
|
||||||
|
let task = TaskTemplate {
|
||||||
|
label: "go clean".into(),
|
||||||
|
command: "go".into(),
|
||||||
|
args: vec!["clean".into()],
|
||||||
|
env: Default::default(),
|
||||||
|
cwd: Some("${ZED_WORKTREE_ROOT}".into()),
|
||||||
|
use_new_terminal: false,
|
||||||
|
allow_concurrent_runs: false,
|
||||||
|
reveal: RevealStrategy::Always,
|
||||||
|
reveal_target: RevealTarget::Dock,
|
||||||
|
hide: HideStrategy::Never,
|
||||||
|
shell: Shell::System,
|
||||||
|
tags: vec![],
|
||||||
|
show_summary: true,
|
||||||
|
show_command: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
let scenario =
|
||||||
|
locator.create_scenario(&task, "test label", DebugAdapterName("Delve".into()));
|
||||||
|
assert!(scenario.is_none());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue