Edit debug tasks (#32908)
Release Notes: - Added the ability to edit LSP provided debug tasks --------- Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
parent
d549993c73
commit
a9107dfaeb
8 changed files with 500 additions and 400 deletions
|
@ -5,9 +5,7 @@
|
||||||
"build": {
|
"build": {
|
||||||
"label": "Build Zed",
|
"label": "Build Zed",
|
||||||
"command": "cargo",
|
"command": "cargo",
|
||||||
"args": [
|
"args": ["build"]
|
||||||
"build"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -16,9 +14,7 @@
|
||||||
"build": {
|
"build": {
|
||||||
"label": "Build Zed",
|
"label": "Build Zed",
|
||||||
"command": "cargo",
|
"command": "cargo",
|
||||||
"args": [
|
"args": ["build"]
|
||||||
"build"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
]
|
]
|
||||||
|
|
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -4324,6 +4324,7 @@ dependencies = [
|
||||||
"futures 0.3.31",
|
"futures 0.3.31",
|
||||||
"fuzzy",
|
"fuzzy",
|
||||||
"gpui",
|
"gpui",
|
||||||
|
"indoc",
|
||||||
"itertools 0.14.0",
|
"itertools 0.14.0",
|
||||||
"language",
|
"language",
|
||||||
"log",
|
"log",
|
||||||
|
@ -4344,6 +4345,7 @@ dependencies = [
|
||||||
"tasks_ui",
|
"tasks_ui",
|
||||||
"telemetry",
|
"telemetry",
|
||||||
"terminal_view",
|
"terminal_view",
|
||||||
|
"text",
|
||||||
"theme",
|
"theme",
|
||||||
"tree-sitter",
|
"tree-sitter",
|
||||||
"tree-sitter-go",
|
"tree-sitter-go",
|
||||||
|
|
|
@ -40,6 +40,7 @@ file_icons.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
fuzzy.workspace = true
|
fuzzy.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
|
indoc.workspace = true
|
||||||
itertools.workspace = true
|
itertools.workspace = true
|
||||||
language.workspace = true
|
language.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
|
@ -60,6 +61,7 @@ task.workspace = true
|
||||||
tasks_ui.workspace = true
|
tasks_ui.workspace = true
|
||||||
telemetry.workspace = true
|
telemetry.workspace = true
|
||||||
terminal_view.workspace = true
|
terminal_view.workspace = true
|
||||||
|
text.workspace = true
|
||||||
theme.workspace = true
|
theme.workspace = true
|
||||||
tree-sitter.workspace = true
|
tree-sitter.workspace = true
|
||||||
tree-sitter-json.workspace = true
|
tree-sitter-json.workspace = true
|
||||||
|
|
|
@ -206,7 +206,7 @@ impl PickerDelegate for AttachModalDelegate {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn confirm(&mut self, _: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
fn confirm(&mut self, secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||||
let candidate = self
|
let candidate = self
|
||||||
.matches
|
.matches
|
||||||
.get(self.selected_index())
|
.get(self.selected_index())
|
||||||
|
@ -229,30 +229,44 @@ impl PickerDelegate for AttachModalDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let workspace = self.workspace.clone();
|
||||||
|
let Some(panel) = workspace
|
||||||
|
.update(cx, |workspace, cx| workspace.panel::<DebugPanel>(cx))
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if secondary {
|
||||||
|
// let Some(id) = worktree_id else { return };
|
||||||
|
// cx.spawn_in(window, async move |_, cx| {
|
||||||
|
// panel
|
||||||
|
// .update_in(cx, |debug_panel, window, cx| {
|
||||||
|
// debug_panel.save_scenario(&debug_scenario, id, window, cx)
|
||||||
|
// })?
|
||||||
|
// .await?;
|
||||||
|
// anyhow::Ok(())
|
||||||
|
// })
|
||||||
|
// .detach_and_log_err(cx);
|
||||||
|
}
|
||||||
let Some(adapter) = cx.read_global::<DapRegistry, _>(|registry, _| {
|
let Some(adapter) = cx.read_global::<DapRegistry, _>(|registry, _| {
|
||||||
registry.adapter(&self.definition.adapter)
|
registry.adapter(&self.definition.adapter)
|
||||||
}) else {
|
}) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let workspace = self.workspace.clone();
|
|
||||||
let definition = self.definition.clone();
|
let definition = self.definition.clone();
|
||||||
cx.spawn_in(window, async move |this, cx| {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
let Ok(scenario) = adapter.config_from_zed_format(definition).await else {
|
let Ok(scenario) = adapter.config_from_zed_format(definition).await else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let panel = workspace
|
panel
|
||||||
.update(cx, |workspace, cx| workspace.panel::<DebugPanel>(cx))
|
.update_in(cx, |panel, window, cx| {
|
||||||
.ok()
|
panel.start_session(scenario, Default::default(), None, None, window, cx);
|
||||||
.flatten();
|
})
|
||||||
if let Some(panel) = panel {
|
.ok();
|
||||||
panel
|
|
||||||
.update_in(cx, |panel, window, cx| {
|
|
||||||
panel.start_session(scenario, Default::default(), None, None, window, cx);
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
this.update(cx, |_, cx| {
|
this.update(cx, |_, cx| {
|
||||||
cx.emit(DismissEvent);
|
cx.emit(DismissEvent);
|
||||||
})
|
})
|
||||||
|
|
|
@ -16,16 +16,18 @@ use dap::{
|
||||||
client::SessionId, debugger_settings::DebuggerSettings,
|
client::SessionId, debugger_settings::DebuggerSettings,
|
||||||
};
|
};
|
||||||
use dap::{DapRegistry, StartDebuggingRequestArguments};
|
use dap::{DapRegistry, StartDebuggingRequestArguments};
|
||||||
|
use editor::Editor;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
Action, App, AsyncWindowContext, ClipboardItem, Context, DismissEvent, Entity, EntityId,
|
Action, App, AsyncWindowContext, ClipboardItem, Context, DismissEvent, Entity, EntityId,
|
||||||
EventEmitter, FocusHandle, Focusable, MouseButton, MouseDownEvent, Point, Subscription, Task,
|
EventEmitter, FocusHandle, Focusable, MouseButton, MouseDownEvent, Point, Subscription, Task,
|
||||||
WeakEntity, anchored, deferred,
|
WeakEntity, anchored, deferred,
|
||||||
};
|
};
|
||||||
|
use text::ToPoint as _;
|
||||||
|
|
||||||
use itertools::Itertools as _;
|
use itertools::Itertools as _;
|
||||||
use language::Buffer;
|
use language::Buffer;
|
||||||
use project::debugger::session::{Session, SessionStateEvent};
|
use project::debugger::session::{Session, SessionStateEvent};
|
||||||
use project::{DebugScenarioContext, Fs, ProjectPath, WorktreeId};
|
use project::{DebugScenarioContext, Fs, ProjectPath, TaskSourceKind, 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;
|
||||||
|
@ -35,8 +37,9 @@ use tree_sitter::{Query, StreamingIterator as _};
|
||||||
use ui::{ContextMenu, Divider, PopoverMenuHandle, Tooltip, prelude::*};
|
use ui::{ContextMenu, Divider, PopoverMenuHandle, Tooltip, prelude::*};
|
||||||
use util::{ResultExt, maybe};
|
use util::{ResultExt, maybe};
|
||||||
use workspace::SplitDirection;
|
use workspace::SplitDirection;
|
||||||
|
use workspace::item::SaveOptions;
|
||||||
use workspace::{
|
use workspace::{
|
||||||
Pane, Workspace,
|
Item, Pane, Workspace,
|
||||||
dock::{DockPosition, Panel, PanelEvent},
|
dock::{DockPosition, Panel, PanelEvent},
|
||||||
};
|
};
|
||||||
use zed_actions::ToggleFocus;
|
use zed_actions::ToggleFocus;
|
||||||
|
@ -988,13 +991,90 @@ impl DebugPanel {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn save_scenario(
|
pub(crate) fn go_to_scenario_definition(
|
||||||
&self,
|
&self,
|
||||||
scenario: &DebugScenario,
|
kind: TaskSourceKind,
|
||||||
|
scenario: DebugScenario,
|
||||||
worktree_id: WorktreeId,
|
worktree_id: WorktreeId,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut Context<Self>,
|
||||||
) -> Task<Result<ProjectPath>> {
|
) -> Task<Result<()>> {
|
||||||
|
let Some(workspace) = self.workspace.upgrade() else {
|
||||||
|
return Task::ready(Ok(()));
|
||||||
|
};
|
||||||
|
let project_path = match kind {
|
||||||
|
TaskSourceKind::AbsPath { abs_path, .. } => {
|
||||||
|
let Some(project_path) = workspace
|
||||||
|
.read(cx)
|
||||||
|
.project()
|
||||||
|
.read(cx)
|
||||||
|
.project_path_for_absolute_path(&abs_path, cx)
|
||||||
|
else {
|
||||||
|
return Task::ready(Err(anyhow!("no abs path")));
|
||||||
|
};
|
||||||
|
|
||||||
|
project_path
|
||||||
|
}
|
||||||
|
TaskSourceKind::Worktree {
|
||||||
|
id,
|
||||||
|
directory_in_worktree: dir,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let relative_path = if dir.ends_with(".vscode") {
|
||||||
|
dir.join("launch.json")
|
||||||
|
} else {
|
||||||
|
dir.join("debug.json")
|
||||||
|
};
|
||||||
|
ProjectPath {
|
||||||
|
worktree_id: id,
|
||||||
|
path: Arc::from(relative_path),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => return self.save_scenario(scenario, worktree_id, window, cx),
|
||||||
|
};
|
||||||
|
|
||||||
|
let editor = workspace.update(cx, |workspace, cx| {
|
||||||
|
workspace.open_path(project_path, None, true, window, cx)
|
||||||
|
});
|
||||||
|
cx.spawn_in(window, async move |_, cx| {
|
||||||
|
let editor = editor.await?;
|
||||||
|
let editor = cx
|
||||||
|
.update(|_, cx| editor.act_as::<Editor>(cx))?
|
||||||
|
.context("expected editor")?;
|
||||||
|
|
||||||
|
// unfortunately debug tasks don't have an easy way to globally
|
||||||
|
// identify them. to jump to the one that you just created or an
|
||||||
|
// old one that you're choosing to edit we use a heuristic of searching for a line with `label: <your label>` from the end rather than the start so we bias towards more renctly
|
||||||
|
editor.update_in(cx, |editor, window, cx| {
|
||||||
|
let row = editor.text(cx).lines().enumerate().find_map(|(row, text)| {
|
||||||
|
if text.contains(scenario.label.as_ref()) && text.contains("\"label\": ") {
|
||||||
|
Some(row)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if let Some(row) = row {
|
||||||
|
editor.go_to_singleton_buffer_point(
|
||||||
|
text::Point::new(row as u32, 4),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn save_scenario(
|
||||||
|
&self,
|
||||||
|
scenario: DebugScenario,
|
||||||
|
worktree_id: WorktreeId,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> Task<Result<()>> {
|
||||||
|
let this = cx.weak_entity();
|
||||||
|
let project = self.project.clone();
|
||||||
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 {
|
||||||
|
@ -1027,47 +1107,7 @@ impl DebugPanel {
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
let project_path = workspace.update(cx, |workspace, cx| {
|
||||||
let mut content = fs.load(path).await?;
|
|
||||||
let new_scenario = serde_json_lenient::to_string_pretty(&serialized_scenario)?
|
|
||||||
.lines()
|
|
||||||
.map(|l| format!(" {l}"))
|
|
||||||
.join("\n");
|
|
||||||
|
|
||||||
static ARRAY_QUERY: LazyLock<Query> = LazyLock::new(|| {
|
|
||||||
Query::new(
|
|
||||||
&tree_sitter_json::LANGUAGE.into(),
|
|
||||||
"(document (array (object) @object))", // TODO: use "." anchor to only match last object
|
|
||||||
)
|
|
||||||
.expect("Failed to create ARRAY_QUERY")
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut parser = tree_sitter::Parser::new();
|
|
||||||
parser
|
|
||||||
.set_language(&tree_sitter_json::LANGUAGE.into())
|
|
||||||
.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
|
workspace
|
||||||
.project()
|
.project()
|
||||||
.read(cx)
|
.read(cx)
|
||||||
|
@ -1075,12 +1115,113 @@ impl DebugPanel {
|
||||||
.context(
|
.context(
|
||||||
"Couldn't get project path for .zed/debug.json in active worktree",
|
"Couldn't get project path for .zed/debug.json in active worktree",
|
||||||
)
|
)
|
||||||
})?
|
})??;
|
||||||
|
|
||||||
|
let editor = this
|
||||||
|
.update_in(cx, |this, window, cx| {
|
||||||
|
this.workspace.update(cx, |workspace, cx| {
|
||||||
|
workspace.open_path(project_path, None, true, window, cx)
|
||||||
|
})
|
||||||
|
})??
|
||||||
|
.await?;
|
||||||
|
let editor = cx
|
||||||
|
.update(|_, cx| editor.act_as::<Editor>(cx))?
|
||||||
|
.context("expected editor")?;
|
||||||
|
|
||||||
|
let new_scenario = serde_json_lenient::to_string_pretty(&serialized_scenario)?
|
||||||
|
.lines()
|
||||||
|
.map(|l| format!(" {l}"))
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
|
editor
|
||||||
|
.update_in(cx, |editor, window, cx| {
|
||||||
|
Self::insert_task_into_editor(editor, new_scenario, project, window, cx)
|
||||||
|
})??
|
||||||
|
.await
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|err| Task::ready(Err(err)))
|
.unwrap_or_else(|err| Task::ready(Err(err)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn insert_task_into_editor(
|
||||||
|
editor: &mut Editor,
|
||||||
|
new_scenario: String,
|
||||||
|
project: Entity<Project>,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Editor>,
|
||||||
|
) -> Result<Task<Result<()>>> {
|
||||||
|
static LAST_ITEM_QUERY: LazyLock<Query> = LazyLock::new(|| {
|
||||||
|
Query::new(
|
||||||
|
&tree_sitter_json::LANGUAGE.into(),
|
||||||
|
"(document (array (object) @object))", // TODO: use "." anchor to only match last object
|
||||||
|
)
|
||||||
|
.expect("Failed to create LAST_ITEM_QUERY")
|
||||||
|
});
|
||||||
|
static EMPTY_ARRAY_QUERY: LazyLock<Query> = LazyLock::new(|| {
|
||||||
|
Query::new(
|
||||||
|
&tree_sitter_json::LANGUAGE.into(),
|
||||||
|
"(document (array) @array)",
|
||||||
|
)
|
||||||
|
.expect("Failed to create EMPTY_ARRAY_QUERY")
|
||||||
|
});
|
||||||
|
|
||||||
|
let content = editor.text(cx);
|
||||||
|
let mut parser = tree_sitter::Parser::new();
|
||||||
|
parser.set_language(&tree_sitter_json::LANGUAGE.into())?;
|
||||||
|
let mut cursor = tree_sitter::QueryCursor::new();
|
||||||
|
let syntax_tree = parser
|
||||||
|
.parse(&content, None)
|
||||||
|
.context("could not parse debug.json")?;
|
||||||
|
let mut matches = cursor.matches(
|
||||||
|
&LAST_ITEM_QUERY,
|
||||||
|
syntax_tree.root_node(),
|
||||||
|
content.as_bytes(),
|
||||||
|
);
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut edits = Vec::new();
|
||||||
|
let mut cursor_position = 0;
|
||||||
|
|
||||||
|
if let Some(pos) = last_offset {
|
||||||
|
edits.push((pos..pos, format!(",\n{new_scenario}")));
|
||||||
|
cursor_position = pos + ",\n ".len();
|
||||||
|
} else {
|
||||||
|
let mut matches = cursor.matches(
|
||||||
|
&EMPTY_ARRAY_QUERY,
|
||||||
|
syntax_tree.root_node(),
|
||||||
|
content.as_bytes(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(mat) = matches.next() {
|
||||||
|
if let Some(pos) = mat.captures.first().map(|m| m.node.byte_range().end - 1) {
|
||||||
|
edits.push((pos..pos, format!("\n{new_scenario}\n")));
|
||||||
|
cursor_position = pos + "\n ".len();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
edits.push((0..0, format!("[\n{}\n]", new_scenario)));
|
||||||
|
cursor_position = "[\n ".len();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
editor.transact(window, cx, |editor, window, cx| {
|
||||||
|
editor.edit(edits, cx);
|
||||||
|
let snapshot = editor
|
||||||
|
.buffer()
|
||||||
|
.read(cx)
|
||||||
|
.as_singleton()
|
||||||
|
.unwrap()
|
||||||
|
.read(cx)
|
||||||
|
.snapshot();
|
||||||
|
let point = cursor_position.to_point(&snapshot);
|
||||||
|
editor.go_to_singleton_buffer_point(point, window, cx);
|
||||||
|
});
|
||||||
|
Ok(editor.save(SaveOptions::default(), project, window, cx))
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
use anyhow::bail;
|
use anyhow::{Context as _, bail};
|
||||||
use collections::{FxHashMap, HashMap};
|
use collections::{FxHashMap, HashMap};
|
||||||
use language::LanguageRegistry;
|
use language::LanguageRegistry;
|
||||||
use paths::local_debug_file_relative_path;
|
|
||||||
use std::{
|
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};
|
||||||
|
@ -18,35 +16,27 @@ use editor::{Editor, EditorElement, EditorStyle};
|
||||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
Action, App, AppContext, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,
|
Action, App, AppContext, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,
|
||||||
HighlightStyle, InteractiveText, KeyContext, PromptButton, PromptLevel, Render, StyledText,
|
KeyContext, Render, Subscription, Task, TextStyle, WeakEntity,
|
||||||
Subscription, Task, TextStyle, UnderlineStyle, WeakEntity,
|
|
||||||
};
|
};
|
||||||
use itertools::Itertools as _;
|
use itertools::Itertools as _;
|
||||||
use picker::{Picker, PickerDelegate, highlighted_match_with_paths::HighlightedMatch};
|
use picker::{Picker, PickerDelegate, highlighted_match_with_paths::HighlightedMatch};
|
||||||
use project::{
|
use project::{DebugScenarioContext, TaskContexts, TaskSourceKind, task_store::TaskStore};
|
||||||
DebugScenarioContext, ProjectPath, TaskContexts, TaskSourceKind, task_store::TaskStore,
|
use settings::Settings;
|
||||||
};
|
|
||||||
use settings::{Settings, initial_local_debug_tasks_content};
|
|
||||||
use task::{DebugScenario, RevealTarget, ZedDebugConfig};
|
use task::{DebugScenario, RevealTarget, ZedDebugConfig};
|
||||||
use theme::ThemeSettings;
|
use theme::ThemeSettings;
|
||||||
use ui::{
|
use ui::{
|
||||||
ActiveTheme, CheckboxWithLabel, Clickable, Context, ContextMenu, Disableable, DropdownMenu,
|
ActiveTheme, Button, ButtonCommon, ButtonSize, CheckboxWithLabel, Clickable, Color, Context,
|
||||||
FluentBuilder, IconWithIndicator, Indicator, IntoElement, KeyBinding, ListItem,
|
ContextMenu, Disableable, DropdownMenu, FluentBuilder, Icon, IconName, IconSize,
|
||||||
ListItemSpacing, ParentElement, StyledExt, ToggleButton, ToggleState, Toggleable, Tooltip,
|
IconWithIndicator, Indicator, InteractiveElement, IntoElement, KeyBinding, Label,
|
||||||
Window, div, prelude::*, px, relative, rems,
|
LabelCommon as _, LabelSize, ListItem, ListItemSpacing, ParentElement, RenderOnce,
|
||||||
|
SharedString, Styled, StyledExt, ToggleButton, ToggleState, Toggleable, Tooltip, Window, div,
|
||||||
|
h_flex, relative, rems, v_flex,
|
||||||
};
|
};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::{ModalView, Workspace, pane};
|
use workspace::{ModalView, Workspace, notifications::DetachAndPromptErr, pane};
|
||||||
|
|
||||||
use crate::{attach_modal::AttachModal, debugger_panel::DebugPanel};
|
use crate::{attach_modal::AttachModal, debugger_panel::DebugPanel};
|
||||||
|
|
||||||
#[allow(unused)]
|
|
||||||
enum SaveScenarioState {
|
|
||||||
Saving,
|
|
||||||
Saved((ProjectPath, SharedString)),
|
|
||||||
Failed(SharedString),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) struct NewProcessModal {
|
pub(super) struct NewProcessModal {
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
debug_panel: WeakEntity<DebugPanel>,
|
debug_panel: WeakEntity<DebugPanel>,
|
||||||
|
@ -56,7 +46,6 @@ 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>,
|
|
||||||
_subscriptions: [Subscription; 3],
|
_subscriptions: [Subscription; 3],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,7 +257,6 @@ impl NewProcessModal {
|
||||||
mode,
|
mode,
|
||||||
debug_panel: debug_panel.downgrade(),
|
debug_panel: debug_panel.downgrade(),
|
||||||
workspace: workspace_handle,
|
workspace: workspace_handle,
|
||||||
save_scenario_state: None,
|
|
||||||
_subscriptions,
|
_subscriptions,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -420,63 +408,29 @@ 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>) {
|
pub fn save_debug_scenario(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
let task_contents = self.task_contexts(cx);
|
let task_contexts = self.task_contexts(cx);
|
||||||
let Some(adapter) = self.debugger.as_ref() else {
|
let Some(adapter) = self.debugger.as_ref() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let scenario = self.debug_scenario(&adapter, cx);
|
let scenario = self.debug_scenario(&adapter, cx);
|
||||||
|
|
||||||
self.save_scenario_state = Some(SaveScenarioState::Saving);
|
|
||||||
|
|
||||||
cx.spawn_in(window, async move |this, cx| {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
let Some((scenario, worktree_id)) = scenario
|
let scenario = scenario.await.context("no scenario to save")?;
|
||||||
.await
|
let worktree_id = task_contexts
|
||||||
.zip(task_contents.and_then(|tcx| tcx.worktree()))
|
.context("no task contexts")?
|
||||||
else {
|
.worktree()
|
||||||
this.update(cx, |this, _| {
|
.context("no active worktree")?;
|
||||||
this.save_scenario_state = Some(SaveScenarioState::Failed(
|
this.update_in(cx, |this, window, cx| {
|
||||||
"Couldn't get scenario or task contents".into(),
|
this.debug_panel.update(cx, |panel, cx| {
|
||||||
))
|
panel.save_scenario(scenario, worktree_id, window, cx)
|
||||||
})
|
})
|
||||||
.ok();
|
})??
|
||||||
return;
|
.await?;
|
||||||
};
|
this.update_in(cx, |_, _, cx| {
|
||||||
|
cx.emit(DismissEvent);
|
||||||
let Some(save_scenario) = this
|
|
||||||
.update_in(cx, |this, window, cx| {
|
|
||||||
this.debug_panel
|
|
||||||
.update(cx, |panel, cx| {
|
|
||||||
panel.save_scenario(&scenario, worktree_id, window, cx)
|
|
||||||
})
|
|
||||||
.ok()
|
|
||||||
})
|
|
||||||
.ok()
|
|
||||||
.flatten()
|
|
||||||
else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let res = save_scenario.await;
|
|
||||||
|
|
||||||
this.update(cx, |this, _| match res {
|
|
||||||
Ok(saved_file) => {
|
|
||||||
this.save_scenario_state = Some(SaveScenarioState::Saved((
|
|
||||||
saved_file,
|
|
||||||
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();
|
.detach_and_prompt_err("Failed to edit debug.json", window, cx, |_, _, _| None);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn adapter_drop_down_menu(
|
fn adapter_drop_down_menu(
|
||||||
|
@ -544,70 +498,6 @@ impl NewProcessModal {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open_debug_json(&self, window: &mut Window, cx: &mut Context<NewProcessModal>) {
|
|
||||||
let this = cx.entity();
|
|
||||||
window
|
|
||||||
.spawn(cx, async move |cx| {
|
|
||||||
let worktree_id = this.update(cx, |this, cx| {
|
|
||||||
let tcx = this.task_contexts(cx);
|
|
||||||
tcx?.worktree()
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let Some(worktree_id) = worktree_id else {
|
|
||||||
let _ = cx.prompt(
|
|
||||||
PromptLevel::Critical,
|
|
||||||
"Cannot open debug.json",
|
|
||||||
Some("You must have at least one project open"),
|
|
||||||
&[PromptButton::ok("Ok")],
|
|
||||||
);
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
|
|
||||||
let editor = this
|
|
||||||
.update_in(cx, |this, window, cx| {
|
|
||||||
this.workspace.update(cx, |workspace, cx| {
|
|
||||||
workspace.open_path(
|
|
||||||
ProjectPath {
|
|
||||||
worktree_id,
|
|
||||||
path: local_debug_file_relative_path().into(),
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
true,
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})??
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
cx.update(|_window, cx| {
|
|
||||||
if let Some(editor) = editor.act_as::<Editor>(cx) {
|
|
||||||
editor.update(cx, |editor, cx| {
|
|
||||||
editor.buffer().update(cx, |buffer, cx| {
|
|
||||||
if let Some(singleton) = buffer.as_singleton() {
|
|
||||||
singleton.update(cx, |buffer, cx| {
|
|
||||||
if buffer.is_empty() {
|
|
||||||
buffer.edit(
|
|
||||||
[(0..0, initial_local_debug_tasks_content())],
|
|
||||||
None,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
this.update(cx, |_, cx| cx.emit(DismissEvent)).ok();
|
|
||||||
|
|
||||||
anyhow::Ok(())
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static SELECT_DEBUGGER_LABEL: SharedString = SharedString::new_static("Select Debugger");
|
static SELECT_DEBUGGER_LABEL: SharedString = SharedString::new_static("Select Debugger");
|
||||||
|
@ -812,39 +702,21 @@ impl Render for NewProcessModal {
|
||||||
NewProcessMode::Launch => el.child(
|
NewProcessMode::Launch => el.child(
|
||||||
container
|
container
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex().child(
|
||||||
.text_ui_sm(cx)
|
Button::new("edit-custom-debug", "Edit in debug.json")
|
||||||
.text_color(Color::Muted.color(cx))
|
.on_click(cx.listener(|this, _, window, cx| {
|
||||||
.child(
|
this.save_debug_scenario(window, cx);
|
||||||
InteractiveText::new(
|
}))
|
||||||
"open-debug-json",
|
.disabled(
|
||||||
StyledText::new(
|
self.debugger.is_none()
|
||||||
"Open .zed/debug.json for advanced configuration.",
|
|| self
|
||||||
)
|
.configure_mode
|
||||||
.with_highlights([(
|
.read(cx)
|
||||||
5..20,
|
.program
|
||||||
HighlightStyle {
|
.read(cx)
|
||||||
underline: Some(UnderlineStyle {
|
.is_empty(cx),
|
||||||
thickness: px(1.0),
|
|
||||||
color: None,
|
|
||||||
wavy: false,
|
|
||||||
}),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
)]),
|
|
||||||
)
|
|
||||||
.on_click(
|
|
||||||
vec![5..20],
|
|
||||||
{
|
|
||||||
let this = cx.entity();
|
|
||||||
move |_, window, cx| {
|
|
||||||
this.update(cx, |this, cx| {
|
|
||||||
this.open_debug_json(window, cx);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
Button::new("debugger-spawn", "Start")
|
Button::new("debugger-spawn", "Start")
|
||||||
|
@ -862,29 +734,48 @@ impl Render for NewProcessModal {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
NewProcessMode::Attach => el.child(
|
NewProcessMode::Attach => el.child({
|
||||||
|
let disabled = self.debugger.is_none()
|
||||||
|
|| self
|
||||||
|
.attach_mode
|
||||||
|
.read(cx)
|
||||||
|
.attach_picker
|
||||||
|
.read(cx)
|
||||||
|
.picker
|
||||||
|
.read(cx)
|
||||||
|
.delegate
|
||||||
|
.match_count()
|
||||||
|
== 0;
|
||||||
|
let secondary_action = menu::SecondaryConfirm.boxed_clone();
|
||||||
container
|
container
|
||||||
.child(div().child(self.adapter_drop_down_menu(window, cx)))
|
.child(div().children(
|
||||||
|
KeyBinding::for_action(&*secondary_action, window, cx).map(
|
||||||
|
|keybind| {
|
||||||
|
Button::new("edit-attach-task", "Edit in debug.json")
|
||||||
|
.label_size(LabelSize::Small)
|
||||||
|
.key_binding(keybind)
|
||||||
|
.on_click(move |_, window, cx| {
|
||||||
|
window.dispatch_action(
|
||||||
|
secondary_action.boxed_clone(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.disabled(disabled)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
))
|
||||||
.child(
|
.child(
|
||||||
Button::new("debugger-spawn", "Start")
|
h_flex()
|
||||||
.on_click(cx.listener(|this, _, window, cx| {
|
.child(div().child(self.adapter_drop_down_menu(window, cx)))
|
||||||
this.start_new_session(window, cx)
|
.child(
|
||||||
}))
|
Button::new("debugger-spawn", "Start")
|
||||||
.disabled(
|
.on_click(cx.listener(|this, _, window, cx| {
|
||||||
self.debugger.is_none()
|
this.start_new_session(window, cx)
|
||||||
|| self
|
}))
|
||||||
.attach_mode
|
.disabled(disabled),
|
||||||
.read(cx)
|
|
||||||
.attach_picker
|
|
||||||
.read(cx)
|
|
||||||
.picker
|
|
||||||
.read(cx)
|
|
||||||
.delegate
|
|
||||||
.match_count()
|
|
||||||
== 0,
|
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
),
|
}),
|
||||||
NewProcessMode::Debug => el,
|
NewProcessMode::Debug => el,
|
||||||
NewProcessMode::Task => el,
|
NewProcessMode::Task => el,
|
||||||
}
|
}
|
||||||
|
@ -1048,25 +939,6 @@ impl ConfigureMode {
|
||||||
)
|
)
|
||||||
.checkbox_position(ui::IconPosition::End),
|
.checkbox_position(ui::IconPosition::End),
|
||||||
)
|
)
|
||||||
.child(
|
|
||||||
CheckboxWithLabel::new(
|
|
||||||
"debugger-save-to-debug-json",
|
|
||||||
Label::new("Save to debug.json")
|
|
||||||
.size(LabelSize::Small)
|
|
||||||
.color(Color::Muted),
|
|
||||||
self.save_to_debug_json,
|
|
||||||
{
|
|
||||||
let this = cx.weak_entity();
|
|
||||||
move |state, _, cx| {
|
|
||||||
this.update(cx, |this, _| {
|
|
||||||
this.save_to_debug_json = *state;
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.checkbox_position(ui::IconPosition::End),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1329,12 +1201,7 @@ impl PickerDelegate for DebugDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn confirm_input(
|
fn confirm_input(&mut self, _: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||||
&mut self,
|
|
||||||
_secondary: bool,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut Context<Picker<Self>>,
|
|
||||||
) {
|
|
||||||
let text = self.prompt.clone();
|
let text = self.prompt.clone();
|
||||||
let (task_context, worktree_id) = self
|
let (task_context, worktree_id) = self
|
||||||
.task_contexts
|
.task_contexts
|
||||||
|
@ -1364,7 +1231,7 @@ impl PickerDelegate for DebugDelegate {
|
||||||
|
|
||||||
let args = args.collect::<Vec<_>>();
|
let args = args.collect::<Vec<_>>();
|
||||||
let task = task::TaskTemplate {
|
let task = task::TaskTemplate {
|
||||||
label: "one-off".to_owned(),
|
label: "one-off".to_owned(), // TODO: rename using command as label
|
||||||
env,
|
env,
|
||||||
command: program,
|
command: program,
|
||||||
args,
|
args,
|
||||||
|
@ -1405,7 +1272,11 @@ impl PickerDelegate for DebugDelegate {
|
||||||
.background_spawn(async move {
|
.background_spawn(async move {
|
||||||
for locator in locators {
|
for locator in locators {
|
||||||
if let Some(scenario) =
|
if let Some(scenario) =
|
||||||
locator.1.create_scenario(&task, "one-off", &adapter).await
|
// TODO: use a more informative label than "one-off"
|
||||||
|
locator
|
||||||
|
.1
|
||||||
|
.create_scenario(&task, &task.label, &adapter)
|
||||||
|
.await
|
||||||
{
|
{
|
||||||
return Some(scenario);
|
return Some(scenario);
|
||||||
}
|
}
|
||||||
|
@ -1439,13 +1310,18 @@ impl PickerDelegate for DebugDelegate {
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn confirm(&mut self, _: bool, window: &mut Window, cx: &mut Context<picker::Picker<Self>>) {
|
fn confirm(
|
||||||
|
&mut self,
|
||||||
|
secondary: bool,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<picker::Picker<Self>>,
|
||||||
|
) {
|
||||||
let debug_scenario = self
|
let debug_scenario = self
|
||||||
.matches
|
.matches
|
||||||
.get(self.selected_index())
|
.get(self.selected_index())
|
||||||
.and_then(|match_candidate| self.candidates.get(match_candidate.candidate_id).cloned());
|
.and_then(|match_candidate| self.candidates.get(match_candidate.candidate_id).cloned());
|
||||||
|
|
||||||
let Some((_, debug_scenario, context)) = debug_scenario else {
|
let Some((kind, debug_scenario, context)) = debug_scenario else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1463,24 +1339,38 @@ impl PickerDelegate for DebugDelegate {
|
||||||
});
|
});
|
||||||
let DebugScenarioContext {
|
let DebugScenarioContext {
|
||||||
task_context,
|
task_context,
|
||||||
active_buffer,
|
active_buffer: _,
|
||||||
worktree_id,
|
worktree_id,
|
||||||
} = context;
|
} = context;
|
||||||
let active_buffer = active_buffer.and_then(|buffer| buffer.upgrade());
|
|
||||||
|
|
||||||
send_telemetry(&debug_scenario, TelemetrySpawnLocation::ScenarioList, cx);
|
if secondary {
|
||||||
self.debug_panel
|
let Some(kind) = kind else { return };
|
||||||
.update(cx, |panel, cx| {
|
let Some(id) = worktree_id else { return };
|
||||||
panel.start_session(
|
let debug_panel = self.debug_panel.clone();
|
||||||
debug_scenario,
|
cx.spawn_in(window, async move |_, cx| {
|
||||||
task_context,
|
debug_panel
|
||||||
active_buffer,
|
.update_in(cx, |debug_panel, window, cx| {
|
||||||
worktree_id,
|
debug_panel.go_to_scenario_definition(kind, debug_scenario, id, window, cx)
|
||||||
window,
|
})?
|
||||||
cx,
|
.await?;
|
||||||
);
|
anyhow::Ok(())
|
||||||
})
|
})
|
||||||
.ok();
|
.detach();
|
||||||
|
} else {
|
||||||
|
send_telemetry(&debug_scenario, TelemetrySpawnLocation::ScenarioList, cx);
|
||||||
|
self.debug_panel
|
||||||
|
.update(cx, |panel, cx| {
|
||||||
|
panel.start_session(
|
||||||
|
debug_scenario,
|
||||||
|
task_context,
|
||||||
|
None,
|
||||||
|
worktree_id,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
|
||||||
cx.emit(DismissEvent);
|
cx.emit(DismissEvent);
|
||||||
}
|
}
|
||||||
|
@ -1498,19 +1388,23 @@ impl PickerDelegate for DebugDelegate {
|
||||||
let footer = h_flex()
|
let footer = h_flex()
|
||||||
.w_full()
|
.w_full()
|
||||||
.p_1p5()
|
.p_1p5()
|
||||||
.justify_end()
|
.justify_between()
|
||||||
.border_t_1()
|
.border_t_1()
|
||||||
.border_color(cx.theme().colors().border_variant)
|
.border_color(cx.theme().colors().border_variant)
|
||||||
// .child(
|
.children({
|
||||||
// // TODO: add button to open selected task in debug.json
|
let action = menu::SecondaryConfirm.boxed_clone();
|
||||||
// h_flex().into_any_element(),
|
KeyBinding::for_action(&*action, window, cx).map(|keybind| {
|
||||||
// )
|
Button::new("edit-debug-task", "Edit in debug.json")
|
||||||
|
.label_size(LabelSize::Small)
|
||||||
|
.key_binding(keybind)
|
||||||
|
.on_click(move |_, window, cx| {
|
||||||
|
window.dispatch_action(action.boxed_clone(), cx)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
.map(|this| {
|
.map(|this| {
|
||||||
if (current_modifiers.alt || self.matches.is_empty()) && !self.prompt.is_empty() {
|
if (current_modifiers.alt || self.matches.is_empty()) && !self.prompt.is_empty() {
|
||||||
let action = picker::ConfirmInput {
|
let action = picker::ConfirmInput { secondary: false }.boxed_clone();
|
||||||
secondary: current_modifiers.secondary(),
|
|
||||||
}
|
|
||||||
.boxed_clone();
|
|
||||||
this.children(KeyBinding::for_action(&*action, window, cx).map(|keybind| {
|
this.children(KeyBinding::for_action(&*action, window, cx).map(|keybind| {
|
||||||
Button::new("launch-custom", "Launch Custom")
|
Button::new("launch-custom", "Launch Custom")
|
||||||
.key_binding(keybind)
|
.key_binding(keybind)
|
||||||
|
@ -1607,3 +1501,35 @@ pub(crate) fn resolve_path(path: &mut String) {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
impl NewProcessModal {
|
||||||
|
pub(crate) fn set_configure(
|
||||||
|
&mut self,
|
||||||
|
program: impl AsRef<str>,
|
||||||
|
cwd: impl AsRef<str>,
|
||||||
|
stop_on_entry: bool,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
self.mode = NewProcessMode::Launch;
|
||||||
|
self.debugger = Some(dap::adapters::DebugAdapterName("fake-adapter".into()));
|
||||||
|
|
||||||
|
self.configure_mode.update(cx, |configure, cx| {
|
||||||
|
configure.program.update(cx, |editor, cx| {
|
||||||
|
editor.clear(window, cx);
|
||||||
|
editor.set_text(program.as_ref(), window, cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
configure.cwd.update(cx, |editor, cx| {
|
||||||
|
editor.clear(window, cx);
|
||||||
|
editor.set_text(cwd.as_ref(), window, cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
configure.stop_on_entry = match stop_on_entry {
|
||||||
|
true => ToggleState::Selected,
|
||||||
|
_ => ToggleState::Unselected,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
use dap::DapRegistry;
|
use dap::DapRegistry;
|
||||||
|
use editor::Editor;
|
||||||
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
|
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
|
||||||
use project::{FakeFs, Project};
|
use project::{FakeFs, Fs as _, Project};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use task::{DebugRequest, DebugScenario, LaunchRequest, TaskContext, VariableName, ZedDebugConfig};
|
use task::{DebugRequest, DebugScenario, LaunchRequest, TaskContext, VariableName, ZedDebugConfig};
|
||||||
|
use text::Point;
|
||||||
use util::path;
|
use util::path;
|
||||||
|
|
||||||
// use crate::new_process_modal::NewProcessMode;
|
use crate::NewProcessMode;
|
||||||
use crate::tests::{init_test, init_test_workspace};
|
use crate::tests::{init_test, init_test_workspace};
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
|
@ -159,111 +161,127 @@ async fn test_debug_session_substitutes_variables_and_relativizes_paths(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[gpui::test]
|
#[gpui::test]
|
||||||
// async fn test_save_debug_scenario_to_file(executor: BackgroundExecutor, cx: &mut TestAppContext) {
|
async fn test_save_debug_scenario_to_file(executor: BackgroundExecutor, cx: &mut TestAppContext) {
|
||||||
// init_test(cx);
|
init_test(cx);
|
||||||
|
|
||||||
// let fs = FakeFs::new(executor.clone());
|
let fs = FakeFs::new(executor.clone());
|
||||||
// fs.insert_tree(
|
fs.insert_tree(
|
||||||
// path!("/project"),
|
path!("/project"),
|
||||||
// json!({
|
json!({
|
||||||
// "main.rs": "fn main() {}"
|
"main.rs": "fn main() {}"
|
||||||
// }),
|
}),
|
||||||
// )
|
)
|
||||||
// .await;
|
.await;
|
||||||
|
|
||||||
// let project = Project::test(fs.clone(), [path!("/project").as_ref()], cx).await;
|
let project = Project::test(fs.clone(), [path!("/project").as_ref()], cx).await;
|
||||||
// let workspace = init_test_workspace(&project, cx).await;
|
let workspace = init_test_workspace(&project, cx).await;
|
||||||
// let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||||
|
|
||||||
// workspace
|
workspace
|
||||||
// .update(cx, |workspace, window, cx| {
|
.update(cx, |workspace, window, cx| {
|
||||||
// crate::new_process_modal::NewProcessModal::show(
|
crate::new_process_modal::NewProcessModal::show(
|
||||||
// workspace,
|
workspace,
|
||||||
// window,
|
window,
|
||||||
// NewProcessMode::Debug,
|
NewProcessMode::Debug,
|
||||||
// None,
|
None,
|
||||||
// cx,
|
cx,
|
||||||
// );
|
);
|
||||||
// })
|
})
|
||||||
// .unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
|
|
||||||
// let modal = workspace
|
let modal = workspace
|
||||||
// .update(cx, |workspace, _, cx| {
|
.update(cx, |workspace, _, cx| {
|
||||||
// workspace.active_modal::<crate::new_process_modal::NewProcessModal>(cx)
|
workspace.active_modal::<crate::new_process_modal::NewProcessModal>(cx)
|
||||||
// })
|
})
|
||||||
// .unwrap()
|
.unwrap()
|
||||||
// .expect("Modal should be active");
|
.expect("Modal should be active");
|
||||||
|
|
||||||
// modal.update_in(cx, |modal, window, cx| {
|
modal.update_in(cx, |modal, window, cx| {
|
||||||
// modal.set_configure("/project/main", "/project", false, window, cx);
|
modal.set_configure("/project/main", "/project", false, window, cx);
|
||||||
// modal.save_scenario(window, cx);
|
modal.save_debug_scenario(window, cx);
|
||||||
// });
|
});
|
||||||
|
|
||||||
// cx.executor().run_until_parked();
|
cx.executor().run_until_parked();
|
||||||
|
|
||||||
// let debug_json_content = fs
|
let editor = workspace
|
||||||
// .load(path!("/project/.zed/debug.json").as_ref())
|
.update(cx, |workspace, _window, cx| {
|
||||||
// .await
|
workspace.active_item_as::<Editor>(cx).unwrap()
|
||||||
// .expect("debug.json should exist");
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// let expected_content = vec![
|
let debug_json_content = fs
|
||||||
// "[",
|
.load(path!("/project/.zed/debug.json").as_ref())
|
||||||
// " {",
|
.await
|
||||||
// r#" "adapter": "fake-adapter","#,
|
.expect("debug.json should exist")
|
||||||
// r#" "label": "main (fake-adapter)","#,
|
.lines()
|
||||||
// r#" "request": "launch","#,
|
.filter(|line| !line.starts_with("//"))
|
||||||
// r#" "program": "/project/main","#,
|
.collect::<Vec<_>>()
|
||||||
// r#" "cwd": "/project","#,
|
.join("\n");
|
||||||
// r#" "args": [],"#,
|
|
||||||
// r#" "env": {}"#,
|
|
||||||
// " }",
|
|
||||||
// "]",
|
|
||||||
// ];
|
|
||||||
|
|
||||||
// let actual_lines: Vec<&str> = debug_json_content.lines().collect();
|
let expected_content = indoc::indoc! {r#"
|
||||||
// pretty_assertions::assert_eq!(expected_content, actual_lines);
|
[
|
||||||
|
{
|
||||||
|
"adapter": "fake-adapter",
|
||||||
|
"label": "main (fake-adapter)",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "/project/main",
|
||||||
|
"cwd": "/project",
|
||||||
|
"args": [],
|
||||||
|
"env": {}
|
||||||
|
}
|
||||||
|
]"#};
|
||||||
|
|
||||||
// modal.update_in(cx, |modal, window, cx| {
|
pretty_assertions::assert_eq!(expected_content, debug_json_content);
|
||||||
// modal.set_configure("/project/other", "/project", true, window, cx);
|
|
||||||
// modal.save_scenario(window, cx);
|
|
||||||
// });
|
|
||||||
|
|
||||||
// cx.executor().run_until_parked();
|
editor.update(cx, |editor, cx| {
|
||||||
|
assert_eq!(
|
||||||
|
editor.selections.newest::<Point>(cx).head(),
|
||||||
|
Point::new(5, 2)
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
// let debug_json_content = fs
|
modal.update_in(cx, |modal, window, cx| {
|
||||||
// .load(path!("/project/.zed/debug.json").as_ref())
|
modal.set_configure("/project/other", "/project", true, window, cx);
|
||||||
// .await
|
modal.save_debug_scenario(window, cx);
|
||||||
// .expect("debug.json should exist after second save");
|
});
|
||||||
|
|
||||||
// let expected_content = vec![
|
cx.executor().run_until_parked();
|
||||||
// "[",
|
|
||||||
// " {",
|
|
||||||
// r#" "adapter": "fake-adapter","#,
|
|
||||||
// r#" "label": "main (fake-adapter)","#,
|
|
||||||
// r#" "request": "launch","#,
|
|
||||||
// r#" "program": "/project/main","#,
|
|
||||||
// r#" "cwd": "/project","#,
|
|
||||||
// r#" "args": [],"#,
|
|
||||||
// r#" "env": {}"#,
|
|
||||||
// " },",
|
|
||||||
// " {",
|
|
||||||
// r#" "adapter": "fake-adapter","#,
|
|
||||||
// r#" "label": "other (fake-adapter)","#,
|
|
||||||
// r#" "request": "launch","#,
|
|
||||||
// r#" "program": "/project/other","#,
|
|
||||||
// r#" "cwd": "/project","#,
|
|
||||||
// r#" "args": [],"#,
|
|
||||||
// r#" "env": {}"#,
|
|
||||||
// " }",
|
|
||||||
// "]",
|
|
||||||
// ];
|
|
||||||
|
|
||||||
// let actual_lines: Vec<&str> = debug_json_content.lines().collect();
|
let expected_content = indoc::indoc! {r#"
|
||||||
// pretty_assertions::assert_eq!(expected_content, actual_lines);
|
[
|
||||||
// }
|
{
|
||||||
|
"adapter": "fake-adapter",
|
||||||
|
"label": "main (fake-adapter)",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "/project/main",
|
||||||
|
"cwd": "/project",
|
||||||
|
"args": [],
|
||||||
|
"env": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"adapter": "fake-adapter",
|
||||||
|
"label": "other (fake-adapter)",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "/project/other",
|
||||||
|
"cwd": "/project",
|
||||||
|
"args": [],
|
||||||
|
"env": {}
|
||||||
|
}
|
||||||
|
]"#};
|
||||||
|
|
||||||
|
let debug_json_content = fs
|
||||||
|
.load(path!("/project/.zed/debug.json").as_ref())
|
||||||
|
.await
|
||||||
|
.expect("debug.json should exist")
|
||||||
|
.lines()
|
||||||
|
.filter(|line| !line.starts_with("//"))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n");
|
||||||
|
pretty_assertions::assert_eq!(expected_content, debug_json_content);
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_dap_adapter_config_conversion_and_validation(cx: &mut TestAppContext) {
|
async fn test_dap_adapter_config_conversion_and_validation(cx: &mut TestAppContext) {
|
||||||
|
|
|
@ -2829,6 +2829,7 @@ impl EditorElement {
|
||||||
) -> Vec<AnyElement> {
|
) -> Vec<AnyElement> {
|
||||||
self.editor.update(cx, |editor, cx| {
|
self.editor.update(cx, |editor, cx| {
|
||||||
let active_task_indicator_row =
|
let active_task_indicator_row =
|
||||||
|
// TODO: add edit button on the right side of each row in the context menu
|
||||||
if let Some(crate::CodeContextMenu::CodeActions(CodeActionsMenu {
|
if let Some(crate::CodeContextMenu::CodeActions(CodeActionsMenu {
|
||||||
deployed_from,
|
deployed_from,
|
||||||
actions,
|
actions,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue