debugger: Add comment-preserving debug.json editing (#32896)
Release Notes: - Re-added "Save to `debug.json`" for custom debug tasks --------- Co-authored-by: Cole Miller <cole@zed.dev>
This commit is contained in:
parent
2f1d25d7f3
commit
e47c48fd3b
5 changed files with 186 additions and 134 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -4293,6 +4293,7 @@ dependencies = [
|
||||||
"rpc",
|
"rpc",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"serde_json_lenient",
|
||||||
"settings",
|
"settings",
|
||||||
"shlex",
|
"shlex",
|
||||||
"sysinfo",
|
"sysinfo",
|
||||||
|
@ -4300,6 +4301,8 @@ dependencies = [
|
||||||
"tasks_ui",
|
"tasks_ui",
|
||||||
"terminal_view",
|
"terminal_view",
|
||||||
"theme",
|
"theme",
|
||||||
|
"tree-sitter",
|
||||||
|
"tree-sitter-json",
|
||||||
"ui",
|
"ui",
|
||||||
"unindent",
|
"unindent",
|
||||||
"util",
|
"util",
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use ::fs::Fs;
|
|
||||||
use anyhow::{Context as _, Result, anyhow};
|
use anyhow::{Context as _, Result, anyhow};
|
||||||
use async_compression::futures::bufread::GzipDecoder;
|
use async_compression::futures::bufread::GzipDecoder;
|
||||||
use async_tar::Archive;
|
use async_tar::Archive;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
pub use dap_types::{StartDebuggingRequestArguments, StartDebuggingRequestArgumentsRequest};
|
pub use dap_types::{StartDebuggingRequestArguments, StartDebuggingRequestArgumentsRequest};
|
||||||
|
use fs::Fs;
|
||||||
use futures::io::BufReader;
|
use futures::io::BufReader;
|
||||||
use gpui::{AsyncApp, SharedString};
|
use gpui::{AsyncApp, SharedString};
|
||||||
pub use http_client::{HttpClient, github::latest_github_release};
|
pub use http_client::{HttpClient, github::latest_github_release};
|
||||||
|
|
|
@ -50,7 +50,7 @@ project.workspace = true
|
||||||
rpc.workspace = true
|
rpc.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
# serde_json_lenient.workspace = true
|
serde_json_lenient.workspace = true
|
||||||
settings.workspace = true
|
settings.workspace = true
|
||||||
shlex.workspace = true
|
shlex.workspace = true
|
||||||
sysinfo.workspace = true
|
sysinfo.workspace = true
|
||||||
|
@ -58,6 +58,8 @@ task.workspace = true
|
||||||
tasks_ui.workspace = true
|
tasks_ui.workspace = true
|
||||||
terminal_view.workspace = true
|
terminal_view.workspace = true
|
||||||
theme.workspace = true
|
theme.workspace = true
|
||||||
|
tree-sitter.workspace = true
|
||||||
|
tree-sitter-json.workspace = true
|
||||||
ui.workspace = true
|
ui.workspace = true
|
||||||
util.workspace = true
|
util.workspace = true
|
||||||
workspace.workspace = true
|
workspace.workspace = true
|
||||||
|
|
|
@ -7,7 +7,7 @@ use crate::{
|
||||||
NewProcessModal, NewProcessMode, Pause, Restart, StepInto, StepOut, StepOver, Stop,
|
NewProcessModal, NewProcessMode, Pause, Restart, StepInto, StepOut, StepOver, Stop,
|
||||||
ToggleExpandItem, ToggleSessionPicker, ToggleThreadPicker, persistence, spawn_task_or_modal,
|
ToggleExpandItem, ToggleSessionPicker, ToggleThreadPicker, persistence, spawn_task_or_modal,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::{Context as _, Result, anyhow};
|
||||||
use dap::adapters::DebugAdapterName;
|
use dap::adapters::DebugAdapterName;
|
||||||
use dap::debugger_settings::DebugPanelDockPosition;
|
use dap::debugger_settings::DebugPanelDockPosition;
|
||||||
use dap::{
|
use dap::{
|
||||||
|
@ -21,14 +21,16 @@ use gpui::{
|
||||||
WeakEntity, actions, anchored, deferred,
|
WeakEntity, actions, anchored, deferred,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use itertools::Itertools as _;
|
||||||
use language::Buffer;
|
use language::Buffer;
|
||||||
use project::debugger::session::{Session, SessionStateEvent};
|
use project::debugger::session::{Session, SessionStateEvent};
|
||||||
use project::{Fs, WorktreeId};
|
use project::{Fs, ProjectPath, WorktreeId};
|
||||||
use project::{Project, debugger::session::ThreadStatus};
|
use project::{Project, debugger::session::ThreadStatus};
|
||||||
use rpc::proto::{self};
|
use rpc::proto::{self};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use std::sync::Arc;
|
use std::sync::{Arc, LazyLock};
|
||||||
use task::{DebugScenario, TaskContext};
|
use task::{DebugScenario, TaskContext};
|
||||||
|
use tree_sitter::{Query, StreamingIterator as _};
|
||||||
use ui::{ContextMenu, Divider, PopoverMenuHandle, Tooltip, prelude::*};
|
use ui::{ContextMenu, Divider, PopoverMenuHandle, Tooltip, prelude::*};
|
||||||
use util::maybe;
|
use util::maybe;
|
||||||
use workspace::SplitDirection;
|
use workspace::SplitDirection;
|
||||||
|
@ -957,69 +959,98 @@ impl DebugPanel {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: restore once we have proper comment preserving file edits
|
pub(crate) fn save_scenario(
|
||||||
// pub(crate) fn save_scenario(
|
&self,
|
||||||
// &self,
|
scenario: &DebugScenario,
|
||||||
// scenario: &DebugScenario,
|
worktree_id: WorktreeId,
|
||||||
// worktree_id: WorktreeId,
|
window: &mut Window,
|
||||||
// window: &mut Window,
|
cx: &mut App,
|
||||||
// cx: &mut App,
|
) -> Task<Result<ProjectPath>> {
|
||||||
// ) -> Task<Result<ProjectPath>> {
|
self.workspace
|
||||||
// self.workspace
|
.update(cx, |workspace, cx| {
|
||||||
// .update(cx, |workspace, cx| {
|
let Some(mut path) = workspace.absolute_path_of_worktree(worktree_id, cx) else {
|
||||||
// let Some(mut path) = workspace.absolute_path_of_worktree(worktree_id, cx) else {
|
return Task::ready(Err(anyhow!("Couldn't get worktree path")));
|
||||||
// return Task::ready(Err(anyhow!("Couldn't get worktree path")));
|
};
|
||||||
// };
|
|
||||||
|
|
||||||
// let serialized_scenario = serde_json::to_value(scenario);
|
let serialized_scenario = serde_json::to_value(scenario);
|
||||||
|
|
||||||
// cx.spawn_in(window, async move |workspace, cx| {
|
cx.spawn_in(window, async move |workspace, cx| {
|
||||||
// let serialized_scenario = serialized_scenario?;
|
let serialized_scenario = serialized_scenario?;
|
||||||
// let fs =
|
let fs =
|
||||||
// workspace.read_with(cx, |workspace, _| workspace.app_state().fs.clone())?;
|
workspace.read_with(cx, |workspace, _| workspace.app_state().fs.clone())?;
|
||||||
|
|
||||||
// path.push(paths::local_settings_folder_relative_path());
|
path.push(paths::local_settings_folder_relative_path());
|
||||||
// if !fs.is_dir(path.as_path()).await {
|
if !fs.is_dir(path.as_path()).await {
|
||||||
// fs.create_dir(path.as_path()).await?;
|
fs.create_dir(path.as_path()).await?;
|
||||||
// }
|
}
|
||||||
// path.pop();
|
path.pop();
|
||||||
|
|
||||||
// path.push(paths::local_debug_file_relative_path());
|
path.push(paths::local_debug_file_relative_path());
|
||||||
// let path = path.as_path();
|
let path = path.as_path();
|
||||||
|
|
||||||
// if !fs.is_file(path).await {
|
if !fs.is_file(path).await {
|
||||||
// fs.create_file(path, Default::default()).await?;
|
fs.create_file(path, Default::default()).await?;
|
||||||
// fs.write(
|
fs.write(
|
||||||
// path,
|
path,
|
||||||
// initial_local_debug_tasks_content().to_string().as_bytes(),
|
settings::initial_local_debug_tasks_content()
|
||||||
// )
|
.to_string()
|
||||||
// .await?;
|
.as_bytes(),
|
||||||
// }
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
// let content = fs.load(path).await?;
|
let mut content = fs.load(path).await?;
|
||||||
// let mut values =
|
let new_scenario = serde_json_lenient::to_string_pretty(&serialized_scenario)?
|
||||||
// serde_json_lenient::from_str::<Vec<serde_json::Value>>(&content)?;
|
.lines()
|
||||||
// values.push(serialized_scenario);
|
.map(|l| format!(" {l}"))
|
||||||
// fs.save(
|
.join("\n");
|
||||||
// path,
|
|
||||||
// &serde_json_lenient::to_string_pretty(&values).map(Into::into)?,
|
|
||||||
// Default::default(),
|
|
||||||
// )
|
|
||||||
// .await?;
|
|
||||||
|
|
||||||
// workspace.update(cx, |workspace, cx| {
|
static ARRAY_QUERY: LazyLock<Query> = LazyLock::new(|| {
|
||||||
// workspace
|
Query::new(
|
||||||
// .project()
|
&tree_sitter_json::LANGUAGE.into(),
|
||||||
// .read(cx)
|
"(document (array (object) @object))", // TODO: use "." anchor to only match last object
|
||||||
// .project_path_for_absolute_path(&path, cx)
|
)
|
||||||
// .context(
|
.expect("Failed to create ARRAY_QUERY")
|
||||||
// "Couldn't get project path for .zed/debug.json in active worktree",
|
});
|
||||||
// )
|
|
||||||
// })?
|
let mut parser = tree_sitter::Parser::new();
|
||||||
// })
|
parser
|
||||||
// })
|
.set_language(&tree_sitter_json::LANGUAGE.into())
|
||||||
// .unwrap_or_else(|err| Task::ready(Err(err)))
|
.unwrap();
|
||||||
// }
|
let mut cursor = tree_sitter::QueryCursor::new();
|
||||||
|
let syntax_tree = parser.parse(&content, None).unwrap();
|
||||||
|
let mut matches =
|
||||||
|
cursor.matches(&ARRAY_QUERY, syntax_tree.root_node(), content.as_bytes());
|
||||||
|
|
||||||
|
// we don't have `.last()` since it's a lending iterator, so loop over
|
||||||
|
// the whole thing to find the last one
|
||||||
|
let mut last_offset = None;
|
||||||
|
while let Some(mat) = matches.next() {
|
||||||
|
if let Some(pos) = mat.captures.first().map(|m| m.node.byte_range().end) {
|
||||||
|
last_offset = Some(pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(pos) = last_offset {
|
||||||
|
content.insert_str(pos, &new_scenario);
|
||||||
|
content.insert_str(pos, ",\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.write(path, content.as_bytes()).await?;
|
||||||
|
|
||||||
|
workspace.update(cx, |workspace, cx| {
|
||||||
|
workspace
|
||||||
|
.project()
|
||||||
|
.read(cx)
|
||||||
|
.project_path_for_absolute_path(&path, cx)
|
||||||
|
.context(
|
||||||
|
"Couldn't get project path for .zed/debug.json in active worktree",
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|err| Task::ready(Err(err)))
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn toggle_thread_picker(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
pub(crate) fn toggle_thread_picker(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
self.thread_picker_menu_handle.toggle(window, cx);
|
self.thread_picker_menu_handle.toggle(window, cx);
|
||||||
|
|
|
@ -6,6 +6,7 @@ use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
|
time::Duration,
|
||||||
usize,
|
usize,
|
||||||
};
|
};
|
||||||
use tasks_ui::{TaskOverrides, TasksModal};
|
use tasks_ui::{TaskOverrides, TasksModal};
|
||||||
|
@ -39,11 +40,12 @@ use workspace::{ModalView, Workspace, pane};
|
||||||
|
|
||||||
use crate::{attach_modal::AttachModal, debugger_panel::DebugPanel};
|
use crate::{attach_modal::AttachModal, debugger_panel::DebugPanel};
|
||||||
|
|
||||||
// enum SaveScenarioState {
|
#[allow(unused)]
|
||||||
// Saving,
|
enum SaveScenarioState {
|
||||||
// Saved((ProjectPath, SharedString)),
|
Saving,
|
||||||
// Failed(SharedString),
|
Saved((ProjectPath, SharedString)),
|
||||||
// }
|
Failed(SharedString),
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) struct NewProcessModal {
|
pub(super) struct NewProcessModal {
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
|
@ -54,7 +56,7 @@ pub(super) struct NewProcessModal {
|
||||||
configure_mode: Entity<ConfigureMode>,
|
configure_mode: Entity<ConfigureMode>,
|
||||||
task_mode: TaskMode,
|
task_mode: TaskMode,
|
||||||
debugger: Option<DebugAdapterName>,
|
debugger: Option<DebugAdapterName>,
|
||||||
// save_scenario_state: Option<SaveScenarioState>,
|
save_scenario_state: Option<SaveScenarioState>,
|
||||||
_subscriptions: [Subscription; 3],
|
_subscriptions: [Subscription; 3],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -265,7 +267,7 @@ impl NewProcessModal {
|
||||||
mode,
|
mode,
|
||||||
debug_panel: debug_panel.downgrade(),
|
debug_panel: debug_panel.downgrade(),
|
||||||
workspace: workspace_handle,
|
workspace: workspace_handle,
|
||||||
// save_scenario_state: None,
|
save_scenario_state: None,
|
||||||
_subscriptions,
|
_subscriptions,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -352,12 +354,11 @@ impl NewProcessModal {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Restore once we have proper, comment preserving edits
|
if let NewProcessMode::Launch = &self.mode {
|
||||||
// if let NewProcessMode::Launch = &self.mode {
|
if self.configure_mode.read(cx).save_to_debug_json.selected() {
|
||||||
// if self.launch_mode.read(cx).save_to_debug_json.selected() {
|
self.save_debug_scenario(window, cx);
|
||||||
// self.save_debug_scenario(window, cx);
|
}
|
||||||
// }
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
let Some(debugger) = self.debugger.clone() else {
|
let Some(debugger) = self.debugger.clone() else {
|
||||||
return;
|
return;
|
||||||
|
@ -418,47 +419,64 @@ impl NewProcessModal {
|
||||||
self.debug_picker.read(cx).delegate.task_contexts.clone()
|
self.debug_picker.read(cx).delegate.task_contexts.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
// fn save_debug_scenario(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
fn save_debug_scenario(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
// let Some((save_scenario, scenario_label)) = self
|
let task_contents = self.task_contexts(cx);
|
||||||
// .debugger
|
let Some(adapter) = self.debugger.as_ref() else {
|
||||||
// .as_ref()
|
return;
|
||||||
// .and_then(|debugger| self.debug_scenario(&debugger, cx))
|
};
|
||||||
// .zip(self.task_contexts(cx).and_then(|tcx| tcx.worktree()))
|
let scenario = self.debug_scenario(&adapter, cx);
|
||||||
// .and_then(|(scenario, worktree_id)| {
|
|
||||||
// self.debug_panel
|
|
||||||
// .update(cx, |panel, cx| {
|
|
||||||
// panel.save_scenario(&scenario, worktree_id, window, cx)
|
|
||||||
// })
|
|
||||||
// .ok()
|
|
||||||
// .zip(Some(scenario.label.clone()))
|
|
||||||
// })
|
|
||||||
// else {
|
|
||||||
// return;
|
|
||||||
// };
|
|
||||||
|
|
||||||
// self.save_scenario_state = Some(SaveScenarioState::Saving);
|
self.save_scenario_state = Some(SaveScenarioState::Saving);
|
||||||
|
|
||||||
// cx.spawn(async move |this, cx| {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
// let res = save_scenario.await;
|
let Some((scenario, worktree_id)) = scenario
|
||||||
|
.await
|
||||||
|
.zip(task_contents.and_then(|tcx| tcx.worktree()))
|
||||||
|
else {
|
||||||
|
this.update(cx, |this, _| {
|
||||||
|
this.save_scenario_state = Some(SaveScenarioState::Failed(
|
||||||
|
"Couldn't get scenario or task contents".into(),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
// this.update(cx, |this, _| match res {
|
let Some(save_scenario) = this
|
||||||
// Ok(saved_file) => {
|
.update_in(cx, |this, window, cx| {
|
||||||
// this.save_scenario_state =
|
this.debug_panel
|
||||||
// Some(SaveScenarioState::Saved((saved_file, scenario_label)))
|
.update(cx, |panel, cx| {
|
||||||
// }
|
panel.save_scenario(&scenario, worktree_id, window, cx)
|
||||||
// Err(error) => {
|
})
|
||||||
// this.save_scenario_state =
|
.ok()
|
||||||
// Some(SaveScenarioState::Failed(error.to_string().into()))
|
})
|
||||||
// }
|
.ok()
|
||||||
// })
|
.flatten()
|
||||||
// .ok();
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let res = save_scenario.await;
|
||||||
|
|
||||||
// cx.background_executor().timer(Duration::from_secs(3)).await;
|
this.update(cx, |this, _| match res {
|
||||||
// this.update(cx, |this, _| this.save_scenario_state.take())
|
Ok(saved_file) => {
|
||||||
// .ok();
|
this.save_scenario_state = Some(SaveScenarioState::Saved((
|
||||||
// })
|
saved_file,
|
||||||
// .detach();
|
scenario.label.clone(),
|
||||||
// }
|
)))
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
this.save_scenario_state =
|
||||||
|
Some(SaveScenarioState::Failed(error.to_string().into()))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
|
||||||
|
cx.background_executor().timer(Duration::from_secs(3)).await;
|
||||||
|
this.update(cx, |this, _| this.save_scenario_state.take())
|
||||||
|
.ok();
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
|
||||||
fn adapter_drop_down_menu(
|
fn adapter_drop_down_menu(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -903,7 +921,7 @@ pub(super) struct ConfigureMode {
|
||||||
program: Entity<Editor>,
|
program: Entity<Editor>,
|
||||||
cwd: Entity<Editor>,
|
cwd: Entity<Editor>,
|
||||||
stop_on_entry: ToggleState,
|
stop_on_entry: ToggleState,
|
||||||
// save_to_debug_json: ToggleState,
|
save_to_debug_json: ToggleState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConfigureMode {
|
impl ConfigureMode {
|
||||||
|
@ -922,7 +940,7 @@ impl ConfigureMode {
|
||||||
program,
|
program,
|
||||||
cwd,
|
cwd,
|
||||||
stop_on_entry: ToggleState::Unselected,
|
stop_on_entry: ToggleState::Unselected,
|
||||||
// save_to_debug_json: ToggleState::Unselected,
|
save_to_debug_json: ToggleState::Unselected,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1028,27 +1046,25 @@ impl ConfigureMode {
|
||||||
)
|
)
|
||||||
.checkbox_position(ui::IconPosition::End),
|
.checkbox_position(ui::IconPosition::End),
|
||||||
)
|
)
|
||||||
// TODO: restore once we have proper, comment preserving
|
.child(
|
||||||
// file edits.
|
CheckboxWithLabel::new(
|
||||||
// .child(
|
"debugger-save-to-debug-json",
|
||||||
// CheckboxWithLabel::new(
|
Label::new("Save to debug.json")
|
||||||
// "debugger-save-to-debug-json",
|
.size(ui::LabelSize::Small)
|
||||||
// Label::new("Save to debug.json")
|
.color(Color::Muted),
|
||||||
// .size(ui::LabelSize::Small)
|
self.save_to_debug_json,
|
||||||
// .color(Color::Muted),
|
{
|
||||||
// self.save_to_debug_json,
|
let this = cx.weak_entity();
|
||||||
// {
|
move |state, _, cx| {
|
||||||
// let this = cx.weak_entity();
|
this.update(cx, |this, _| {
|
||||||
// move |state, _, cx| {
|
this.save_to_debug_json = *state;
|
||||||
// this.update(cx, |this, _| {
|
})
|
||||||
// this.save_to_debug_json = *state;
|
.ok();
|
||||||
// })
|
}
|
||||||
// .ok();
|
},
|
||||||
// }
|
)
|
||||||
// },
|
.checkbox_position(ui::IconPosition::End),
|
||||||
// )
|
)
|
||||||
// .checkbox_position(ui::IconPosition::End),
|
|
||||||
// )
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue