Compare commits
5 Commits
debugger-e
...
thomas/add
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c080d3d60d | ||
|
|
dfbd132d9f | ||
|
|
2e8ee9b64f | ||
|
|
c15382c4d8 | ||
|
|
70c51b513b |
3
Cargo.lock
generated
3
Cargo.lock
generated
@@ -3042,7 +3042,6 @@ dependencies = [
|
||||
"strum 0.27.1",
|
||||
"subtle",
|
||||
"supermaven_api",
|
||||
"task",
|
||||
"telemetry_events",
|
||||
"text",
|
||||
"theme",
|
||||
@@ -4190,7 +4189,6 @@ dependencies = [
|
||||
"command_palette_hooks",
|
||||
"dap",
|
||||
"db",
|
||||
"debugger_tools",
|
||||
"editor",
|
||||
"env_logger 0.11.8",
|
||||
"feature_flags",
|
||||
@@ -4200,7 +4198,6 @@ dependencies = [
|
||||
"language",
|
||||
"log",
|
||||
"menu",
|
||||
"parking_lot",
|
||||
"picker",
|
||||
"pretty_assertions",
|
||||
"project",
|
||||
|
||||
@@ -1028,10 +1028,10 @@
|
||||
// Using `ctrl-shift-space` in Zed requires disabling the macOS global shortcut.
|
||||
// System Preferences->Keyboard->Keyboard Shortcuts->Input Sources->Select the previous input source (uncheck)
|
||||
"ctrl-shift-space": "terminal::ToggleViMode",
|
||||
"ctrl-k up": "pane::SplitUp",
|
||||
"ctrl-k down": "pane::SplitDown",
|
||||
"ctrl-k left": "pane::SplitLeft",
|
||||
"ctrl-k right": "pane::SplitRight"
|
||||
"ctrl-alt-up": "pane::SplitUp",
|
||||
"ctrl-alt-down": "pane::SplitDown",
|
||||
"ctrl-alt-left": "pane::SplitLeft",
|
||||
"ctrl-alt-right": "pane::SplitRight"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1489,7 +1489,12 @@
|
||||
"use_multiline_find": false,
|
||||
"use_smartcase_find": false,
|
||||
"highlight_on_yank_duration": 200,
|
||||
"custom_digraphs": {}
|
||||
"custom_digraphs": {},
|
||||
// Cursor shape for the each mode.
|
||||
// Specify the mode as the key and the shape as the value.
|
||||
// The mode can be one of the following: "normal", "replace", "insert", "visual".
|
||||
// The shape can be one of the following: "block", "bar", "underline", "hollow".
|
||||
"cursor_shape": {}
|
||||
},
|
||||
// The server to connect to. If the environment variable
|
||||
// ZED_SERVER_URL is set, it will override this setting.
|
||||
|
||||
@@ -3282,12 +3282,10 @@ pub(crate) fn open_context(
|
||||
}
|
||||
}
|
||||
AssistantContext::Directory(directory_context) => {
|
||||
let project_path = directory_context.project_path(cx);
|
||||
let entry_id = directory_context.entry_id;
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
workspace.project().update(cx, |project, cx| {
|
||||
if let Some(entry) = project.entry_for_path(&project_path, cx) {
|
||||
cx.emit(project::Event::RevealInProjectPanel(entry.id));
|
||||
}
|
||||
workspace.project().update(cx, |_project, cx| {
|
||||
cx.emit(project::Event::RevealInProjectPanel(entry_id));
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::{ops::Range, path::Path, sync::Arc};
|
||||
use gpui::{App, Entity, SharedString};
|
||||
use language::{Buffer, File};
|
||||
use language_model::LanguageModelRequestMessage;
|
||||
use project::{ProjectPath, Worktree};
|
||||
use project::{ProjectEntryId, ProjectPath, Worktree};
|
||||
use prompt_store::UserPromptId;
|
||||
use rope::Point;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -83,17 +83,25 @@ pub struct FileContext {
|
||||
pub struct DirectoryContext {
|
||||
pub id: ContextId,
|
||||
pub worktree: Entity<Worktree>,
|
||||
pub path: Arc<Path>,
|
||||
pub entry_id: ProjectEntryId,
|
||||
pub last_path: Arc<Path>,
|
||||
/// Buffers of the files within the directory.
|
||||
pub context_buffers: Vec<ContextBuffer>,
|
||||
}
|
||||
|
||||
impl DirectoryContext {
|
||||
pub fn project_path(&self, cx: &App) -> ProjectPath {
|
||||
ProjectPath {
|
||||
worktree_id: self.worktree.read(cx).id(),
|
||||
path: self.path.clone(),
|
||||
}
|
||||
pub fn entry<'a>(&self, cx: &'a App) -> Option<&'a project::Entry> {
|
||||
self.worktree.read(cx).entry_for_id(self.entry_id)
|
||||
}
|
||||
|
||||
pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
|
||||
let worktree = self.worktree.read(cx);
|
||||
worktree
|
||||
.entry_for_id(self.entry_id)
|
||||
.map(|entry| ProjectPath {
|
||||
worktree_id: worktree.id(),
|
||||
path: entry.path.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +139,7 @@ impl ThreadContext {
|
||||
#[derive(Clone)]
|
||||
pub struct ContextBuffer {
|
||||
pub id: BufferId,
|
||||
// TODO: Entity<Buffer> holds onto the thread even if the thread is deleted. Should probably be
|
||||
// TODO: Entity<Buffer> holds onto the buffer even if the buffer is deleted. Should probably be
|
||||
// a WeakEntity and handle removal from the UI when it has dropped.
|
||||
pub buffer: Entity<Buffer>,
|
||||
pub file: Arc<dyn File>,
|
||||
|
||||
@@ -8,7 +8,7 @@ use futures::future::join_all;
|
||||
use futures::{self, Future, FutureExt, future};
|
||||
use gpui::{App, AppContext as _, Context, Entity, SharedString, Task, WeakEntity};
|
||||
use language::{Buffer, File};
|
||||
use project::{Project, ProjectItem, ProjectPath, Worktree};
|
||||
use project::{Project, ProjectEntryId, ProjectItem, ProjectPath, Worktree};
|
||||
use prompt_store::UserPromptId;
|
||||
use rope::{Point, Rope};
|
||||
use text::{Anchor, BufferId, OffsetRangeExt};
|
||||
@@ -162,6 +162,14 @@ impl ContextStore {
|
||||
return Task::ready(Err(anyhow!("failed to read project")));
|
||||
};
|
||||
|
||||
let Some(entry_id) = project
|
||||
.read(cx)
|
||||
.entry_for_path(&project_path, cx)
|
||||
.map(|entry| entry.id)
|
||||
else {
|
||||
return Task::ready(Err(anyhow!("no entry found for directory context")));
|
||||
};
|
||||
|
||||
let already_included = match self.includes_directory(&project_path) {
|
||||
Some(FileInclusion::Direct(context_id)) => {
|
||||
if remove_if_exists {
|
||||
@@ -231,7 +239,7 @@ impl ContextStore {
|
||||
}
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
this.insert_directory(worktree, project_path, context_buffers, cx);
|
||||
this.insert_directory(worktree, entry_id, project_path, context_buffers, cx);
|
||||
})?;
|
||||
|
||||
anyhow::Ok(())
|
||||
@@ -241,19 +249,21 @@ impl ContextStore {
|
||||
fn insert_directory(
|
||||
&mut self,
|
||||
worktree: Entity<Worktree>,
|
||||
entry_id: ProjectEntryId,
|
||||
project_path: ProjectPath,
|
||||
context_buffers: Vec<ContextBuffer>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let id = self.next_context_id.post_inc();
|
||||
let path = project_path.path.clone();
|
||||
let last_path = project_path.path.clone();
|
||||
self.directories.insert(project_path, id);
|
||||
|
||||
self.context
|
||||
.push(AssistantContext::Directory(DirectoryContext {
|
||||
id,
|
||||
worktree,
|
||||
path,
|
||||
entry_id,
|
||||
last_path,
|
||||
context_buffers,
|
||||
}));
|
||||
cx.notify();
|
||||
@@ -875,6 +885,7 @@ pub fn refresh_context_store_text(
|
||||
let task = maybe!({
|
||||
match context {
|
||||
AssistantContext::File(file_context) => {
|
||||
// TODO: Should refresh if the path has changed, as it's in the text.
|
||||
if changed_buffers.is_empty()
|
||||
|| changed_buffers.contains(&file_context.context_buffer.buffer)
|
||||
{
|
||||
@@ -883,8 +894,9 @@ pub fn refresh_context_store_text(
|
||||
}
|
||||
}
|
||||
AssistantContext::Directory(directory_context) => {
|
||||
let directory_path = directory_context.project_path(cx);
|
||||
let should_refresh = changed_buffers.is_empty()
|
||||
let directory_path = directory_context.project_path(cx)?;
|
||||
let should_refresh = directory_path.path != directory_context.last_path
|
||||
|| changed_buffers.is_empty()
|
||||
|| changed_buffers.iter().any(|buffer| {
|
||||
let Some(buffer_path) = buffer.read(cx).project_path(cx) else {
|
||||
return false;
|
||||
@@ -894,10 +906,16 @@ pub fn refresh_context_store_text(
|
||||
|
||||
if should_refresh {
|
||||
let context_store = context_store.clone();
|
||||
return refresh_directory_text(context_store, directory_context, cx);
|
||||
return refresh_directory_text(
|
||||
context_store,
|
||||
directory_context,
|
||||
directory_path,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
AssistantContext::Symbol(symbol_context) => {
|
||||
// TODO: Should refresh if the path has changed, as it's in the text.
|
||||
if changed_buffers.is_empty()
|
||||
|| changed_buffers.contains(&symbol_context.context_symbol.buffer)
|
||||
{
|
||||
@@ -906,6 +924,7 @@ pub fn refresh_context_store_text(
|
||||
}
|
||||
}
|
||||
AssistantContext::Excerpt(excerpt_context) => {
|
||||
// TODO: Should refresh if the path has changed, as it's in the text.
|
||||
if changed_buffers.is_empty()
|
||||
|| changed_buffers.contains(&excerpt_context.context_buffer.buffer)
|
||||
{
|
||||
@@ -965,6 +984,7 @@ fn refresh_file_text(
|
||||
fn refresh_directory_text(
|
||||
context_store: Entity<ContextStore>,
|
||||
directory_context: &DirectoryContext,
|
||||
directory_path: ProjectPath,
|
||||
cx: &App,
|
||||
) -> Option<Task<()>> {
|
||||
let mut stale = false;
|
||||
@@ -989,7 +1009,8 @@ fn refresh_directory_text(
|
||||
|
||||
let id = directory_context.id;
|
||||
let worktree = directory_context.worktree.clone();
|
||||
let path = directory_context.path.clone();
|
||||
let entry_id = directory_context.entry_id;
|
||||
let last_path = directory_path.path;
|
||||
Some(cx.spawn(async move |cx| {
|
||||
let context_buffers = context_buffers.await;
|
||||
context_store
|
||||
@@ -997,7 +1018,8 @@ fn refresh_directory_text(
|
||||
let new_directory_context = DirectoryContext {
|
||||
id,
|
||||
worktree,
|
||||
path,
|
||||
entry_id,
|
||||
last_path,
|
||||
context_buffers,
|
||||
};
|
||||
context_store.replace_context(AssistantContext::Directory(new_directory_context));
|
||||
|
||||
@@ -264,10 +264,14 @@ impl AddedContext {
|
||||
}
|
||||
|
||||
AssistantContext::Directory(directory_context) => {
|
||||
let full_path = directory_context
|
||||
.worktree
|
||||
.read(cx)
|
||||
.full_path(&directory_context.path);
|
||||
let worktree = directory_context.worktree.read(cx);
|
||||
// If the directory no longer exists, use its last known path.
|
||||
let full_path = worktree
|
||||
.entry_for_id(directory_context.entry_id)
|
||||
.map_or_else(
|
||||
|| directory_context.last_path.clone(),
|
||||
|entry| worktree.full_path(&entry.path).into(),
|
||||
);
|
||||
let full_path_string: SharedString =
|
||||
full_path.to_string_lossy().into_owned().into();
|
||||
let name = full_path
|
||||
|
||||
@@ -128,7 +128,6 @@ serde_json.workspace = true
|
||||
session = { workspace = true, features = ["test-support"] }
|
||||
settings = { workspace = true, features = ["test-support"] }
|
||||
sqlx = { version = "0.8", features = ["sqlite"] }
|
||||
task.workspace = true
|
||||
theme.workspace = true
|
||||
unindent.workspace = true
|
||||
util.workspace = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use call::ActiveCall;
|
||||
use dap::DebugRequestType;
|
||||
use dap::requests::{Initialize, Launch, StackTrace};
|
||||
use dap::{SourceBreakpoint, requests::SetBreakpoints};
|
||||
use dap::DebugRequestType;
|
||||
use dap::{requests::SetBreakpoints, SourceBreakpoint};
|
||||
use debugger_ui::debugger_panel::DebugPanel;
|
||||
use debugger_ui::session::DebugSession;
|
||||
use editor::Editor;
|
||||
@@ -13,7 +13,7 @@ use std::{
|
||||
path::Path,
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
};
|
||||
use workspace::{Workspace, dock::Panel};
|
||||
use workspace::{dock::Panel, Workspace};
|
||||
|
||||
use super::{TestClient, TestServer};
|
||||
|
||||
|
||||
@@ -2,13 +2,11 @@ use crate::tests::TestServer;
|
||||
use call::ActiveCall;
|
||||
use collections::{HashMap, HashSet};
|
||||
|
||||
use debugger_ui::debugger_panel::DebugPanel;
|
||||
use extension::ExtensionHostProxy;
|
||||
use fs::{FakeFs, Fs as _, RemoveOptions};
|
||||
use futures::StreamExt as _;
|
||||
use gpui::{
|
||||
AppContext as _, BackgroundExecutor, SemanticVersion, TestAppContext, UpdateGlobal as _,
|
||||
VisualContext,
|
||||
};
|
||||
use http_client::BlockedHttpClient;
|
||||
use language::{
|
||||
@@ -26,7 +24,6 @@ use project::{
|
||||
};
|
||||
use remote::SshRemoteClient;
|
||||
use remote_server::{HeadlessAppState, HeadlessProject};
|
||||
use rpc::proto;
|
||||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
use std::{path::Path, sync::Arc};
|
||||
@@ -579,108 +576,3 @@ async fn test_ssh_collaboration_formatting_with_prettier(
|
||||
"Prettier formatting was not applied to client buffer after host's request"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_remote_server_debugger(cx_a: &mut TestAppContext, server_cx: &mut TestAppContext) {
|
||||
cx_a.update(|cx| {
|
||||
release_channel::init(SemanticVersion::default(), cx);
|
||||
command_palette_hooks::init(cx);
|
||||
if std::env::var("RUST_LOG").is_ok() {
|
||||
env_logger::try_init().ok();
|
||||
}
|
||||
});
|
||||
server_cx.update(|cx| {
|
||||
release_channel::init(SemanticVersion::default(), cx);
|
||||
});
|
||||
let (opts, server_ssh) = SshRemoteClient::fake_server(cx_a, server_cx);
|
||||
let remote_fs = FakeFs::new(server_cx.executor());
|
||||
remote_fs
|
||||
.insert_tree(
|
||||
path!("/code"),
|
||||
json!({
|
||||
"lib.rs": "fn one() -> usize { 1 }"
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
// User A connects to the remote project via SSH.
|
||||
server_cx.update(HeadlessProject::init);
|
||||
let remote_http_client = Arc::new(BlockedHttpClient);
|
||||
let node = NodeRuntime::unavailable();
|
||||
let languages = Arc::new(LanguageRegistry::new(server_cx.executor()));
|
||||
let _headless_project = server_cx.new(|cx| {
|
||||
client::init_settings(cx);
|
||||
HeadlessProject::new(
|
||||
HeadlessAppState {
|
||||
session: server_ssh,
|
||||
fs: remote_fs.clone(),
|
||||
http_client: remote_http_client,
|
||||
node_runtime: node,
|
||||
languages,
|
||||
extension_host_proxy: Arc::new(ExtensionHostProxy::new()),
|
||||
},
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let client_ssh = SshRemoteClient::fake_client(opts, cx_a).await;
|
||||
let mut server = TestServer::start(server_cx.executor()).await;
|
||||
let client_a = server.create_client(cx_a, "user_a").await;
|
||||
cx_a.update(|cx| {
|
||||
debugger_ui::init(cx);
|
||||
command_palette_hooks::init(cx);
|
||||
});
|
||||
let (project_a, _) = client_a
|
||||
.build_ssh_project(path!("/code"), client_ssh.clone(), cx_a)
|
||||
.await;
|
||||
|
||||
let (workspace, cx_a) = client_a.build_workspace(&project_a, cx_a);
|
||||
|
||||
let debugger_panel = workspace
|
||||
.update_in(cx_a, |_workspace, window, cx| {
|
||||
cx.spawn_in(window, DebugPanel::load)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
workspace.update_in(cx_a, |workspace, window, cx| {
|
||||
workspace.add_panel(debugger_panel, window, cx);
|
||||
});
|
||||
|
||||
cx_a.run_until_parked();
|
||||
let debug_panel = workspace
|
||||
.update(cx_a, |workspace, cx| workspace.panel::<DebugPanel>(cx))
|
||||
.unwrap();
|
||||
|
||||
let workspace_window = cx_a
|
||||
.window_handle()
|
||||
.downcast::<workspace::Workspace>()
|
||||
.unwrap();
|
||||
|
||||
let session = debugger_ui::tests::start_debug_session(&workspace_window, cx_a, |_| {}).unwrap();
|
||||
cx_a.run_until_parked();
|
||||
debug_panel.update(cx_a, |debug_panel, cx| {
|
||||
assert_eq!(
|
||||
debug_panel.active_session().unwrap().read(cx).session(cx),
|
||||
session
|
||||
)
|
||||
});
|
||||
|
||||
session.update(cx_a, |session, _| {
|
||||
assert_eq!(session.binary().command, "ssh");
|
||||
});
|
||||
|
||||
let shutdown_session = workspace.update(cx_a, |workspace, cx| {
|
||||
workspace.project().update(cx, |project, cx| {
|
||||
project.dap_store().update(cx, |dap_store, cx| {
|
||||
dap_store.shutdown_session(session.read(cx).session_id(), cx)
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
client_ssh.update(cx_a, |a, _| {
|
||||
a.shutdown_processes(Some(proto::ShutdownRemoteServer {}))
|
||||
});
|
||||
|
||||
shutdown_session.await.unwrap();
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
use task::TcpArgumentsTemplate;
|
||||
use util::{ResultExt as _, TryFutureExt};
|
||||
use util::ResultExt as _;
|
||||
|
||||
use crate::{adapters::DebugAdapterBinary, debugger_settings::DebuggerSettings};
|
||||
|
||||
@@ -126,7 +126,6 @@ pub(crate) struct TransportDelegate {
|
||||
pending_requests: Requests,
|
||||
transport: Transport,
|
||||
server_tx: Arc<Mutex<Option<Sender<Message>>>>,
|
||||
_tasks: Vec<gpui::Task<Option<()>>>,
|
||||
}
|
||||
|
||||
impl TransportDelegate {
|
||||
@@ -141,7 +140,6 @@ impl TransportDelegate {
|
||||
log_handlers: Default::default(),
|
||||
current_requests: Default::default(),
|
||||
pending_requests: Default::default(),
|
||||
_tasks: Default::default(),
|
||||
};
|
||||
let messages = this.start_handlers(transport_pipes, cx).await?;
|
||||
Ok((messages, this))
|
||||
@@ -168,43 +166,35 @@ impl TransportDelegate {
|
||||
|
||||
cx.update(|cx| {
|
||||
if let Some(stdout) = params.stdout.take() {
|
||||
self._tasks.push(
|
||||
cx.background_executor()
|
||||
.spawn(Self::handle_adapter_log(stdout, log_handler.clone()).log_err()),
|
||||
);
|
||||
cx.background_executor()
|
||||
.spawn(Self::handle_adapter_log(stdout, log_handler.clone()))
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
self._tasks.push(
|
||||
cx.background_executor().spawn(
|
||||
Self::handle_output(
|
||||
params.output,
|
||||
client_tx,
|
||||
self.pending_requests.clone(),
|
||||
log_handler.clone(),
|
||||
)
|
||||
.log_err(),
|
||||
),
|
||||
);
|
||||
cx.background_executor()
|
||||
.spawn(Self::handle_output(
|
||||
params.output,
|
||||
client_tx,
|
||||
self.pending_requests.clone(),
|
||||
log_handler.clone(),
|
||||
))
|
||||
.detach_and_log_err(cx);
|
||||
|
||||
if let Some(stderr) = params.stderr.take() {
|
||||
self._tasks.push(
|
||||
cx.background_executor()
|
||||
.spawn(Self::handle_error(stderr, self.log_handlers.clone()).log_err()),
|
||||
);
|
||||
cx.background_executor()
|
||||
.spawn(Self::handle_error(stderr, self.log_handlers.clone()))
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
self._tasks.push(
|
||||
cx.background_executor().spawn(
|
||||
Self::handle_input(
|
||||
params.input,
|
||||
client_rx,
|
||||
self.current_requests.clone(),
|
||||
self.pending_requests.clone(),
|
||||
log_handler.clone(),
|
||||
)
|
||||
.log_err(),
|
||||
),
|
||||
);
|
||||
cx.background_executor()
|
||||
.spawn(Self::handle_input(
|
||||
params.input,
|
||||
client_rx,
|
||||
self.current_requests.clone(),
|
||||
self.pending_requests.clone(),
|
||||
log_handler.clone(),
|
||||
))
|
||||
.detach_and_log_err(cx);
|
||||
})?;
|
||||
|
||||
{
|
||||
@@ -377,7 +367,6 @@ impl TransportDelegate {
|
||||
where
|
||||
Stderr: AsyncRead + Unpin + Send + 'static,
|
||||
{
|
||||
log::debug!("Handle error started");
|
||||
let mut buffer = String::new();
|
||||
|
||||
let mut reader = BufReader::new(stderr);
|
||||
|
||||
@@ -12,9 +12,6 @@ workspace = true
|
||||
path = "src/debugger_tools.rs"
|
||||
doctest = false
|
||||
|
||||
[features]
|
||||
test-support = []
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
dap.workspace = true
|
||||
|
||||
@@ -41,7 +41,7 @@ struct DapLogView {
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
pub struct LogStore {
|
||||
struct LogStore {
|
||||
projects: HashMap<WeakEntity<Project>, ProjectState>,
|
||||
debug_clients: HashMap<SessionId, DebugAdapterState>,
|
||||
rpc_tx: UnboundedSender<(SessionId, IoKind, String)>,
|
||||
@@ -101,7 +101,7 @@ impl DebugAdapterState {
|
||||
}
|
||||
|
||||
impl LogStore {
|
||||
pub fn new(cx: &Context<Self>) -> Self {
|
||||
fn new(cx: &Context<Self>) -> Self {
|
||||
let (rpc_tx, mut rpc_rx) = unbounded::<(SessionId, IoKind, String)>();
|
||||
cx.spawn(async move |this, cx| {
|
||||
while let Some((client_id, io_kind, message)) = rpc_rx.next().await {
|
||||
@@ -845,29 +845,3 @@ impl EventEmitter<Event> for LogStore {}
|
||||
impl EventEmitter<Event> for DapLogView {}
|
||||
impl EventEmitter<EditorEvent> for DapLogView {}
|
||||
impl EventEmitter<SearchEvent> for DapLogView {}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
impl LogStore {
|
||||
pub fn contained_session_ids(&self) -> Vec<SessionId> {
|
||||
self.debug_clients.keys().cloned().collect()
|
||||
}
|
||||
|
||||
pub fn rpc_messages_for_session_id(&self, session_id: SessionId) -> Vec<String> {
|
||||
self.debug_clients
|
||||
.get(&session_id)
|
||||
.expect("This session should exist if a test is calling")
|
||||
.rpc_messages
|
||||
.messages
|
||||
.clone()
|
||||
.into()
|
||||
}
|
||||
|
||||
pub fn log_messages_for_session_id(&self, session_id: SessionId) -> Vec<String> {
|
||||
self.debug_clients
|
||||
.get(&session_id)
|
||||
.expect("This session should exist if a test is calling")
|
||||
.log_messages
|
||||
.clone()
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,6 @@ test-support = [
|
||||
"project/test-support",
|
||||
"util/test-support",
|
||||
"workspace/test-support",
|
||||
"env_logger",
|
||||
"unindent",
|
||||
"debugger_tools"
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
@@ -40,7 +37,6 @@ gpui.workspace = true
|
||||
language.workspace = true
|
||||
log.workspace = true
|
||||
menu.workspace = true
|
||||
parking_lot.workspace = true
|
||||
picker.workspace = true
|
||||
pretty_assertions.workspace = true
|
||||
project.workspace = true
|
||||
@@ -57,13 +53,9 @@ ui.workspace = true
|
||||
util.workspace = true
|
||||
workspace.workspace = true
|
||||
workspace-hack.workspace = true
|
||||
env_logger = { workspace = true, optional = true }
|
||||
debugger_tools = { workspace = true, optional = true }
|
||||
unindent = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
dap = { workspace = true, features = ["test-support"] }
|
||||
debugger_tools = { workspace = true, features = ["test-support"] }
|
||||
editor = { workspace = true, features = ["test-support"] }
|
||||
env_logger.workspace = true
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use dap::DebugRequest;
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use gpui::Subscription;
|
||||
use gpui::{DismissEvent, Entity, EventEmitter, Focusable, Render};
|
||||
use gpui::{Subscription, WeakEntity};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
|
||||
use std::sync::Arc;
|
||||
@@ -9,9 +9,7 @@ use sysinfo::System;
|
||||
use ui::{Context, Tooltip, prelude::*};
|
||||
use ui::{ListItem, ListItemSpacing};
|
||||
use util::debug_panic;
|
||||
use workspace::{ModalView, Workspace};
|
||||
|
||||
use crate::debugger_panel::DebugPanel;
|
||||
use workspace::ModalView;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(super) struct Candidate {
|
||||
@@ -24,19 +22,19 @@ pub(crate) struct AttachModalDelegate {
|
||||
selected_index: usize,
|
||||
matches: Vec<StringMatch>,
|
||||
placeholder_text: Arc<str>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
project: Entity<project::Project>,
|
||||
pub(crate) debug_config: task::DebugTaskDefinition,
|
||||
candidates: Arc<[Candidate]>,
|
||||
}
|
||||
|
||||
impl AttachModalDelegate {
|
||||
fn new(
|
||||
workspace: Entity<Workspace>,
|
||||
project: Entity<project::Project>,
|
||||
debug_config: task::DebugTaskDefinition,
|
||||
candidates: Arc<[Candidate]>,
|
||||
) -> Self {
|
||||
Self {
|
||||
workspace: workspace.downgrade(),
|
||||
project,
|
||||
debug_config,
|
||||
candidates,
|
||||
selected_index: 0,
|
||||
@@ -53,7 +51,7 @@ pub struct AttachModal {
|
||||
|
||||
impl AttachModal {
|
||||
pub fn new(
|
||||
workspace: Entity<Workspace>,
|
||||
project: Entity<project::Project>,
|
||||
debug_config: task::DebugTaskDefinition,
|
||||
modal: bool,
|
||||
window: &mut Window,
|
||||
@@ -77,11 +75,11 @@ 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(project, debug_config, processes, modal, window, cx)
|
||||
}
|
||||
|
||||
pub(super) fn with_processes(
|
||||
workspace: Entity<Workspace>,
|
||||
project: Entity<project::Project>,
|
||||
debug_config: task::DebugTaskDefinition,
|
||||
processes: Arc<[Candidate]>,
|
||||
modal: bool,
|
||||
@@ -90,7 +88,7 @@ impl AttachModal {
|
||||
) -> Self {
|
||||
let picker = cx.new(|cx| {
|
||||
Picker::uniform_list(
|
||||
AttachModalDelegate::new(workspace, debug_config, processes),
|
||||
AttachModalDelegate::new(project, debug_config, processes),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
@@ -204,7 +202,7 @@ impl PickerDelegate for AttachModalDelegate {
|
||||
})
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||
fn confirm(&mut self, _: bool, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||
let candidate = self
|
||||
.matches
|
||||
.get(self.selected_index())
|
||||
@@ -227,17 +225,14 @@ impl PickerDelegate for AttachModalDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
let definition = self.debug_config.clone();
|
||||
let panel = self
|
||||
.workspace
|
||||
.update(cx, |workspace, cx| workspace.panel::<DebugPanel>(cx))
|
||||
.ok()
|
||||
.flatten();
|
||||
if let Some(panel) = panel {
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.start_session(definition, window, cx);
|
||||
});
|
||||
}
|
||||
let config = self.debug_config.clone();
|
||||
self.project
|
||||
.update(cx, |project, cx| {
|
||||
let ret = project.start_debug_session(config, cx);
|
||||
ret
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
|
||||
cx.emit(DismissEvent);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ 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::{
|
||||
ContinuedEvent, LoadedSourceEvent, ModuleEvent, OutputEvent, StoppedEvent, ThreadEvent,
|
||||
client::SessionId, debugger_settings::DebuggerSettings,
|
||||
@@ -18,7 +17,6 @@ use gpui::{
|
||||
actions, anchored, deferred,
|
||||
};
|
||||
|
||||
use project::debugger::session::{Session, SessionStateEvent};
|
||||
use project::{
|
||||
Project,
|
||||
debugger::{
|
||||
@@ -32,9 +30,10 @@ use settings::Settings;
|
||||
use std::any::TypeId;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use task::{DebugTaskDefinition, DebugTaskTemplate};
|
||||
use task::DebugTaskDefinition;
|
||||
use terminal_view::terminal_panel::TerminalPanel;
|
||||
use ui::{ContextMenu, Divider, DropdownMenu, Tooltip, prelude::*};
|
||||
use util::debug_panic;
|
||||
use workspace::{
|
||||
Workspace,
|
||||
dock::{DockPosition, Panel, PanelEvent},
|
||||
@@ -64,7 +63,7 @@ pub struct DebugPanel {
|
||||
active_session: Option<Entity<DebugSession>>,
|
||||
/// This represents the last debug definition that was created in the new session modal
|
||||
pub(crate) past_debug_definition: Option<DebugTaskDefinition>,
|
||||
project: Entity<Project>,
|
||||
project: WeakEntity<Project>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
focus_handle: FocusHandle,
|
||||
context_menu: Option<(Entity<ContextMenu>, Point<Pixels>, Subscription)>,
|
||||
@@ -98,10 +97,10 @@ impl DebugPanel {
|
||||
window,
|
||||
|panel, _, event: &tasks_ui::ShowAttachModal, window, cx| {
|
||||
panel.workspace.update(cx, |workspace, cx| {
|
||||
let workspace_handle = cx.entity().clone();
|
||||
let project = workspace.project().clone();
|
||||
workspace.toggle_modal(window, cx, |window, cx| {
|
||||
crate::attach_modal::AttachModal::new(
|
||||
workspace_handle,
|
||||
project,
|
||||
event.debug_config.clone(),
|
||||
true,
|
||||
window,
|
||||
@@ -128,7 +127,7 @@ impl DebugPanel {
|
||||
_subscriptions,
|
||||
past_debug_definition: None,
|
||||
focus_handle: cx.focus_handle(),
|
||||
project,
|
||||
project: project.downgrade(),
|
||||
workspace: workspace.weak_handle(),
|
||||
context_menu: None,
|
||||
};
|
||||
@@ -220,7 +219,7 @@ impl DebugPanel {
|
||||
|
||||
pub fn load(
|
||||
workspace: WeakEntity<Workspace>,
|
||||
cx: &mut AsyncWindowContext,
|
||||
cx: AsyncWindowContext,
|
||||
) -> Task<Result<Entity<Self>>> {
|
||||
cx.spawn(async move |cx| {
|
||||
workspace.update_in(cx, |workspace, window, cx| {
|
||||
@@ -246,226 +245,114 @@ impl DebugPanel {
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
workspace.set_debugger_provider(DebuggerProvider(debug_panel.clone()));
|
||||
|
||||
debug_panel
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn start_session(
|
||||
&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();
|
||||
|
||||
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 (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)
|
||||
};
|
||||
|
||||
(session.clone(), dap_store.boot_session(session, cx))
|
||||
})?;
|
||||
|
||||
match task.await {
|
||||
Err(e) => {
|
||||
this.update(cx, |this, cx| {
|
||||
this.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.show_error(&e, cx);
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.ok();
|
||||
|
||||
session
|
||||
.update(cx, |session, cx| session.shutdown(cx))?
|
||||
.await;
|
||||
}
|
||||
Ok(_) => Self::register_session(this, session, cx).await?,
|
||||
}
|
||||
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
async fn register_session(
|
||||
this: WeakEntity<Self>,
|
||||
session: Entity<Session>,
|
||||
cx: &mut AsyncWindowContext,
|
||||
) -> Result<()> {
|
||||
let adapter_name = session.update(cx, |session, _| session.adapter_name())?;
|
||||
this.update_in(cx, |_, window, cx| {
|
||||
cx.subscribe_in(
|
||||
&session,
|
||||
window,
|
||||
move |_, session, event: &SessionStateEvent, window, cx| match event {
|
||||
SessionStateEvent::Restart => {
|
||||
let mut curr_session = session.clone();
|
||||
while let Some(parent_session) = curr_session
|
||||
.read_with(cx, |session, _| session.parent_session().cloned())
|
||||
{
|
||||
curr_session = parent_session;
|
||||
}
|
||||
|
||||
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)
|
||||
})
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
})
|
||||
.ok();
|
||||
|
||||
let serialized_layout = persistence::get_serialized_pane_layout(adapter_name).await;
|
||||
|
||||
let workspace = this.update_in(cx, |this, window, cx| {
|
||||
this.sessions.retain(|session| {
|
||||
session
|
||||
.read(cx)
|
||||
.mode()
|
||||
.as_running()
|
||||
.map_or(false, |running_state| {
|
||||
!running_state.read(cx).session().read(cx).is_terminated()
|
||||
})
|
||||
});
|
||||
|
||||
let session_item = DebugSession::running(
|
||||
this.project.clone(),
|
||||
this.workspace.clone(),
|
||||
session,
|
||||
cx.weak_entity(),
|
||||
serialized_layout,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
|
||||
if let Some(running) = session_item.read(cx).mode().as_running().cloned() {
|
||||
// We might want to make this an event subscription and only notify when a new thread is selected
|
||||
// This is used to filter the command menu correctly
|
||||
cx.observe(&running, |_, _, cx| cx.notify()).detach();
|
||||
}
|
||||
|
||||
this.sessions.push(session_item.clone());
|
||||
this.activate_session(session_item, window, cx);
|
||||
this.workspace.clone()
|
||||
})?;
|
||||
|
||||
workspace.update_in(cx, |workspace, window, cx| {
|
||||
workspace.focus_panel::<Self>(window, cx);
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn start_child_session(
|
||||
&mut self,
|
||||
request: &StartDebuggingRequestArguments,
|
||||
parent_session: Entity<Session>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let Some(worktree) = parent_session.read(cx).worktree() else {
|
||||
log::error!("Attempted to start a child session from non local debug session");
|
||||
return;
|
||||
};
|
||||
|
||||
let dap_store_handle = self.project.read(cx).dap_store().clone();
|
||||
let breakpoint_store = self.project.read(cx).breakpoint_store();
|
||||
let definition = parent_session.read(cx).definition().clone();
|
||||
let mut binary = parent_session.read(cx).binary().clone();
|
||||
binary.request_args = request.clone();
|
||||
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
let (session, task) = dap_store_handle.update(cx, |dap_store, cx| {
|
||||
let session =
|
||||
dap_store.new_session(definition.clone(), Some(parent_session.clone()), cx);
|
||||
|
||||
let task = session.update(cx, |session, cx| {
|
||||
session.boot(
|
||||
binary,
|
||||
worktree,
|
||||
breakpoint_store,
|
||||
dap_store_handle.downgrade(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
(session, task)
|
||||
})?;
|
||||
|
||||
match task.await {
|
||||
Err(e) => {
|
||||
this.update(cx, |this, cx| {
|
||||
this.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.show_error(&e, cx);
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.ok();
|
||||
|
||||
session
|
||||
.update(cx, |session, cx| session.shutdown(cx))?
|
||||
.await;
|
||||
}
|
||||
Ok(_) => Self::register_session(this, session, cx).await?,
|
||||
}
|
||||
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
pub fn active_session(&self) -> Option<Entity<DebugSession>> {
|
||||
self.active_session.clone()
|
||||
}
|
||||
|
||||
pub fn debug_panel_items_by_client(
|
||||
&self,
|
||||
client_id: &SessionId,
|
||||
cx: &Context<Self>,
|
||||
) -> Vec<Entity<DebugSession>> {
|
||||
self.sessions
|
||||
.iter()
|
||||
.filter(|item| item.read(cx).session_id(cx) == *client_id)
|
||||
.map(|item| item.clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn debug_panel_item_by_client(
|
||||
&self,
|
||||
client_id: SessionId,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<Entity<DebugSession>> {
|
||||
self.sessions
|
||||
.iter()
|
||||
.find(|item| {
|
||||
let item = item.read(cx);
|
||||
|
||||
item.session_id(cx) == client_id
|
||||
})
|
||||
.cloned()
|
||||
}
|
||||
|
||||
fn handle_dap_store_event(
|
||||
&mut self,
|
||||
_dap_store: &Entity<DapStore>,
|
||||
dap_store: &Entity<DapStore>,
|
||||
event: &dap_store::DapStoreEvent,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
match event {
|
||||
dap_store::DapStoreEvent::DebugSessionInitialized(session_id) => {
|
||||
let Some(session) = dap_store.read(cx).session_by_id(session_id) else {
|
||||
return log::error!(
|
||||
"Couldn't get session with id: {session_id:?} from DebugClientStarted event"
|
||||
);
|
||||
};
|
||||
|
||||
let adapter_name = session.read(cx).adapter_name();
|
||||
|
||||
let session_id = *session_id;
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
let serialized_layout =
|
||||
persistence::get_serialized_pane_layout(adapter_name).await;
|
||||
|
||||
this.update_in(cx, |this, window, cx| {
|
||||
let Some(project) = this.project.upgrade() else {
|
||||
return log::error!(
|
||||
"Debug Panel out lived it's weak reference to Project"
|
||||
);
|
||||
};
|
||||
|
||||
if this
|
||||
.sessions
|
||||
.iter()
|
||||
.any(|item| item.read(cx).session_id(cx) == session_id)
|
||||
{
|
||||
// We already have an item for this session.
|
||||
debug_panic!("We should never reuse session ids");
|
||||
return;
|
||||
}
|
||||
|
||||
this.sessions.retain(|session| {
|
||||
session
|
||||
.read(cx)
|
||||
.mode()
|
||||
.as_running()
|
||||
.map_or(false, |running_state| {
|
||||
!running_state.read(cx).session().read(cx).is_terminated()
|
||||
})
|
||||
});
|
||||
|
||||
let session_item = DebugSession::running(
|
||||
project,
|
||||
this.workspace.clone(),
|
||||
session,
|
||||
cx.weak_entity(),
|
||||
serialized_layout,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
|
||||
if let Some(running) = session_item.read(cx).mode().as_running().cloned() {
|
||||
// We might want to make this an event subscription and only notify when a new thread is selected
|
||||
// This is used to filter the command menu correctly
|
||||
cx.observe(&running, |_, _, cx| cx.notify()).detach();
|
||||
}
|
||||
|
||||
this.sessions.push(session_item.clone());
|
||||
this.activate_session(session_item, window, cx);
|
||||
})
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
dap_store::DapStoreEvent::RunInTerminal {
|
||||
title,
|
||||
cwd,
|
||||
@@ -487,12 +374,6 @@ impl DebugPanel {
|
||||
)
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
dap_store::DapStoreEvent::SpawnChildSession {
|
||||
request,
|
||||
parent_session,
|
||||
} => {
|
||||
self.start_child_session(request, parent_session.clone(), window, cx);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -527,7 +408,7 @@ impl DebugPanel {
|
||||
cwd,
|
||||
title,
|
||||
},
|
||||
task::RevealStrategy::Never,
|
||||
task::RevealStrategy::Always,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
@@ -587,6 +468,8 @@ impl DebugPanel {
|
||||
let session = this.dap_store().read(cx).session_by_id(session_id);
|
||||
session.map(|session| !session.read(cx).is_terminated())
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or_default();
|
||||
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
@@ -1010,6 +893,7 @@ impl DebugPanel {
|
||||
|
||||
impl EventEmitter<PanelEvent> for DebugPanel {}
|
||||
impl EventEmitter<DebugPanelEvent> for DebugPanel {}
|
||||
impl EventEmitter<project::Event> for DebugPanel {}
|
||||
|
||||
impl Focusable for DebugPanel {
|
||||
fn focus_handle(&self, _: &App) -> FocusHandle {
|
||||
@@ -1155,15 +1039,3 @@ impl Render for DebugPanel {
|
||||
.into_any()
|
||||
}
|
||||
}
|
||||
|
||||
struct DebuggerProvider(Entity<DebugPanel>);
|
||||
|
||||
impl workspace::DebuggerProvider for DebuggerProvider {
|
||||
fn start_session(&self, definition: DebugTaskDefinition, window: &mut Window, cx: &mut App) {
|
||||
self.0.update(cx, |_, cx| {
|
||||
cx.defer_in(window, |this, window, cx| {
|
||||
this.start_session(definition, window, cx);
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ mod new_session_modal;
|
||||
mod persistence;
|
||||
pub(crate) mod session;
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[cfg(test)]
|
||||
pub mod tests;
|
||||
|
||||
actions!(
|
||||
|
||||
@@ -4,12 +4,14 @@ use std::{
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use anyhow::{Result, anyhow};
|
||||
use dap::{DapRegistry, DebugRequest};
|
||||
use editor::{Editor, EditorElement, EditorStyle};
|
||||
use gpui::{
|
||||
App, AppContext, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Render, TextStyle,
|
||||
WeakEntity,
|
||||
};
|
||||
use project::Project;
|
||||
use settings::Settings;
|
||||
use task::{DebugTaskDefinition, DebugTaskTemplate, LaunchRequest};
|
||||
use theme::ThemeSettings;
|
||||
@@ -19,6 +21,7 @@ use ui::{
|
||||
LabelCommon as _, ParentElement, RenderOnce, SharedString, Styled, StyledExt, ToggleButton,
|
||||
ToggleState, Toggleable, Window, div, h_flex, relative, rems, v_flex,
|
||||
};
|
||||
use util::ResultExt;
|
||||
use workspace::{ModalView, Workspace};
|
||||
|
||||
use crate::{attach_modal::AttachModal, debugger_panel::DebugPanel};
|
||||
@@ -85,11 +88,11 @@ impl NewSessionModal {
|
||||
}
|
||||
}
|
||||
|
||||
fn debug_config(&self, cx: &App, debugger: &str) -> DebugTaskDefinition {
|
||||
fn debug_config(&self, cx: &App) -> Option<DebugTaskDefinition> {
|
||||
let request = self.mode.debug_task(cx);
|
||||
DebugTaskDefinition {
|
||||
adapter: debugger.to_owned(),
|
||||
label: suggested_label(&request, debugger),
|
||||
Some(DebugTaskDefinition {
|
||||
adapter: self.debugger.clone()?.to_string(),
|
||||
label: suggested_label(&request, self.debugger.as_deref()?),
|
||||
request,
|
||||
initialize_args: self.initialize_args.clone(),
|
||||
tcp_connection: None,
|
||||
@@ -97,26 +100,26 @@ impl NewSessionModal {
|
||||
ToggleState::Selected => Some(true),
|
||||
_ => None,
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn start_new_session(&self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let Some(debugger) = self.debugger.as_ref() else {
|
||||
// todo: show in UI.
|
||||
log::error!("No debugger selected");
|
||||
return;
|
||||
};
|
||||
let config = self.debug_config(cx, debugger);
|
||||
let debug_panel = self.debug_panel.clone();
|
||||
fn start_new_session(&self, window: &mut Window, cx: &mut Context<Self>) -> Result<()> {
|
||||
let workspace = self.workspace.clone();
|
||||
let config = self
|
||||
.debug_config(cx)
|
||||
.ok_or_else(|| anyhow!("Failed to create a debug config"))?;
|
||||
|
||||
let task_contexts = self
|
||||
.workspace
|
||||
let _ = self.debug_panel.update(cx, |panel, _| {
|
||||
panel.past_debug_definition = Some(config.clone());
|
||||
});
|
||||
|
||||
let task_contexts = workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
tasks_ui::task_contexts(workspace, window, cx)
|
||||
})
|
||||
.ok();
|
||||
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
cx.spawn(async move |this, cx| {
|
||||
let task_context = if let Some(task) = task_contexts {
|
||||
task.await
|
||||
.active_worktree_context
|
||||
@@ -124,8 +127,9 @@ impl NewSessionModal {
|
||||
} else {
|
||||
task::TaskContext::default()
|
||||
};
|
||||
let project = workspace.update(cx, |workspace, _| workspace.project().clone())?;
|
||||
|
||||
debug_panel.update_in(cx, |debug_panel, window, cx| {
|
||||
let task = project.update(cx, |this, cx| {
|
||||
let template = DebugTaskTemplate {
|
||||
locator: None,
|
||||
definition: config.clone(),
|
||||
@@ -135,18 +139,23 @@ impl NewSessionModal {
|
||||
.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)
|
||||
this.start_debug_session(debug_config.definition, cx)
|
||||
} else {
|
||||
debug_panel.start_session(config, window, cx)
|
||||
this.start_debug_session(config, cx)
|
||||
}
|
||||
})?;
|
||||
this.update(cx, |_, cx| {
|
||||
cx.emit(DismissEvent);
|
||||
})
|
||||
.ok();
|
||||
let spawn_result = task.await;
|
||||
if spawn_result.is_ok() {
|
||||
this.update(cx, |_, cx| {
|
||||
cx.emit(DismissEvent);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
spawn_result?;
|
||||
anyhow::Result::<_, anyhow::Error>::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_attach_picker(
|
||||
@@ -240,12 +249,15 @@ impl NewSessionModal {
|
||||
);
|
||||
}
|
||||
DebugRequest::Attach(_) => {
|
||||
let Some(workspace) = this.workspace.upgrade() else {
|
||||
let Ok(project) = this
|
||||
.workspace
|
||||
.read_with(cx, |this, _| this.project().clone())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
this.mode = NewSessionMode::attach(
|
||||
this.debugger.clone(),
|
||||
workspace,
|
||||
project,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
@@ -345,7 +357,7 @@ struct AttachMode {
|
||||
impl AttachMode {
|
||||
fn new(
|
||||
debugger: Option<SharedString>,
|
||||
workspace: Entity<Workspace>,
|
||||
project: Entity<Project>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<NewSessionModal>,
|
||||
) -> Entity<Self> {
|
||||
@@ -358,7 +370,7 @@ impl AttachMode {
|
||||
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(project, debug_definition.clone(), false, window, cx);
|
||||
window.focus(&modal.focus_handle(cx));
|
||||
|
||||
modal
|
||||
@@ -458,11 +470,11 @@ impl RenderOnce for NewSessionMode {
|
||||
impl NewSessionMode {
|
||||
fn attach(
|
||||
debugger: Option<SharedString>,
|
||||
workspace: Entity<Workspace>,
|
||||
project: Entity<Project>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<NewSessionModal>,
|
||||
) -> Self {
|
||||
Self::Attach(AttachMode::new(debugger, workspace, window, cx))
|
||||
Self::Attach(AttachMode::new(debugger, project, window, cx))
|
||||
}
|
||||
fn launch(
|
||||
past_launch_config: Option<LaunchRequest>,
|
||||
@@ -557,12 +569,15 @@ impl Render for NewSessionModal {
|
||||
.toggle_state(matches!(self.mode, NewSessionMode::Attach(_)))
|
||||
.style(ui::ButtonStyle::Subtle)
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
let Some(workspace) = this.workspace.upgrade() else {
|
||||
let Ok(project) = this
|
||||
.workspace
|
||||
.read_with(cx, |this, _| this.project().clone())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
this.mode = NewSessionMode::attach(
|
||||
this.debugger.clone(),
|
||||
workspace,
|
||||
project,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
@@ -616,7 +631,7 @@ impl Render for NewSessionModal {
|
||||
.child(
|
||||
Button::new("debugger-spawn", "Start")
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
this.start_new_session(window, cx);
|
||||
this.start_new_session(window, cx).log_err();
|
||||
}))
|
||||
.disabled(self.debugger.is_none()),
|
||||
),
|
||||
|
||||
@@ -88,12 +88,6 @@ impl DebugSession {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session(&self, cx: &App) -> Entity<Session> {
|
||||
match &self.mode {
|
||||
DebugSessionState::Running(entity) => entity.read(cx).session().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn shutdown(&mut self, cx: &mut Context<Self>) {
|
||||
match &self.mode {
|
||||
DebugSessionState::Running(state) => state.update(cx, |state, cx| state.shutdown(cx)),
|
||||
@@ -121,7 +115,13 @@ impl DebugSession {
|
||||
};
|
||||
|
||||
self.label
|
||||
.get_or_init(|| session.read(cx).label())
|
||||
.get_or_init(|| {
|
||||
session
|
||||
.read(cx)
|
||||
.as_local()
|
||||
.expect("Remote Debug Sessions are not implemented yet")
|
||||
.label()
|
||||
})
|
||||
.to_owned()
|
||||
}
|
||||
|
||||
|
||||
@@ -418,19 +418,6 @@ impl RunningState {
|
||||
let threads = this.session.update(cx, |this, cx| this.threads(cx));
|
||||
this.select_current_thread(&threads, cx);
|
||||
}
|
||||
SessionEvent::CapabilitiesLoaded => {
|
||||
let capabilities = this.capabilities(cx);
|
||||
if !capabilities.supports_modules_request.unwrap_or(false) {
|
||||
this.remove_pane_item(DebuggerPaneItem::Modules, window, cx);
|
||||
}
|
||||
if !capabilities
|
||||
.supports_loaded_sources_request
|
||||
.unwrap_or(false)
|
||||
{
|
||||
this.remove_pane_item(DebuggerPaneItem::LoadedSources, window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
cx.notify()
|
||||
@@ -460,14 +447,35 @@ impl RunningState {
|
||||
workspace::PaneGroup::with_root(root)
|
||||
} else {
|
||||
pane_close_subscriptions.clear();
|
||||
let module_list = if session
|
||||
.read(cx)
|
||||
.capabilities()
|
||||
.supports_modules_request
|
||||
.unwrap_or(false)
|
||||
{
|
||||
Some(&module_list)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let loaded_source_list = if session
|
||||
.read(cx)
|
||||
.capabilities()
|
||||
.supports_loaded_sources_request
|
||||
.unwrap_or(false)
|
||||
{
|
||||
Some(&loaded_source_list)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let root = Self::default_pane_layout(
|
||||
project,
|
||||
&workspace,
|
||||
&stack_frame_list,
|
||||
&variable_list,
|
||||
&module_list,
|
||||
&loaded_source_list,
|
||||
module_list,
|
||||
loaded_source_list,
|
||||
&console,
|
||||
&breakpoint_list,
|
||||
&mut pane_close_subscriptions,
|
||||
@@ -504,6 +512,11 @@ impl RunningState {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
debug_assert!(
|
||||
item_kind.is_supported(self.session.read(cx).capabilities()),
|
||||
"We should only allow removing supported item kinds"
|
||||
);
|
||||
|
||||
if let Some((pane, item_id)) = self.panes.panes().iter().find_map(|pane| {
|
||||
Some(pane).zip(
|
||||
pane.read(cx)
|
||||
@@ -933,8 +946,8 @@ impl RunningState {
|
||||
workspace: &WeakEntity<Workspace>,
|
||||
stack_frame_list: &Entity<StackFrameList>,
|
||||
variable_list: &Entity<VariableList>,
|
||||
module_list: &Entity<ModuleList>,
|
||||
loaded_source_list: &Entity<LoadedSourceList>,
|
||||
module_list: Option<&Entity<ModuleList>>,
|
||||
loaded_source_list: Option<&Entity<LoadedSourceList>>,
|
||||
console: &Entity<Console>,
|
||||
breakpoints: &Entity<BreakpointList>,
|
||||
subscriptions: &mut HashMap<EntityId, Subscription>,
|
||||
@@ -990,36 +1003,41 @@ impl RunningState {
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
this.add_item(
|
||||
Box::new(SubView::new(
|
||||
module_list.focus_handle(cx),
|
||||
module_list.clone().into(),
|
||||
DebuggerPaneItem::Modules,
|
||||
if let Some(module_list) = module_list {
|
||||
this.add_item(
|
||||
Box::new(SubView::new(
|
||||
module_list.focus_handle(cx),
|
||||
module_list.clone().into(),
|
||||
DebuggerPaneItem::Modules,
|
||||
None,
|
||||
cx,
|
||||
)),
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
)),
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
);
|
||||
this.activate_item(0, false, false, window, cx);
|
||||
}
|
||||
|
||||
this.add_item(
|
||||
Box::new(SubView::new(
|
||||
loaded_source_list.focus_handle(cx),
|
||||
loaded_source_list.clone().into(),
|
||||
DebuggerPaneItem::LoadedSources,
|
||||
if let Some(loaded_source_list) = loaded_source_list {
|
||||
this.add_item(
|
||||
Box::new(SubView::new(
|
||||
loaded_source_list.focus_handle(cx),
|
||||
loaded_source_list.clone().into(),
|
||||
DebuggerPaneItem::LoadedSources,
|
||||
None,
|
||||
cx,
|
||||
)),
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
)),
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
this.activate_item(0, false, false, window, cx);
|
||||
);
|
||||
this.activate_item(1, false, false, window, cx);
|
||||
}
|
||||
});
|
||||
|
||||
let rightmost_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
|
||||
|
||||
@@ -1,29 +1,16 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{Result, anyhow};
|
||||
use dap::{DebugRequest, client::DebugAdapterClient};
|
||||
use gpui::{Entity, TestAppContext, WindowHandle};
|
||||
use project::{Project, debugger::session::Session};
|
||||
use project::Project;
|
||||
use settings::SettingsStore;
|
||||
use task::DebugTaskDefinition;
|
||||
use terminal_view::terminal_panel::TerminalPanel;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::{debugger_panel::DebugPanel, session::DebugSession};
|
||||
|
||||
#[cfg(test)]
|
||||
mod attach_modal;
|
||||
#[cfg(test)]
|
||||
mod console;
|
||||
#[cfg(test)]
|
||||
mod dap_logger;
|
||||
#[cfg(test)]
|
||||
mod debugger_panel;
|
||||
#[cfg(test)]
|
||||
mod module_list;
|
||||
#[cfg(test)]
|
||||
mod stack_frame_list;
|
||||
#[cfg(test)]
|
||||
mod variable_list;
|
||||
|
||||
pub fn init_test(cx: &mut gpui::TestAppContext) {
|
||||
@@ -55,7 +42,7 @@ pub async fn init_test_workspace(
|
||||
let debugger_panel = workspace_handle
|
||||
.update(cx, |_, window, cx| {
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
DebugPanel::load(this, cx).await
|
||||
DebugPanel::load(this, cx.clone()).await
|
||||
})
|
||||
})
|
||||
.unwrap()
|
||||
@@ -95,46 +82,3 @@ pub fn active_debug_session_panel(
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn start_debug_session_with<T: Fn(&Arc<DebugAdapterClient>) + 'static>(
|
||||
workspace: &WindowHandle<Workspace>,
|
||||
cx: &mut gpui::TestAppContext,
|
||||
config: DebugTaskDefinition,
|
||||
configure: T,
|
||||
) -> 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)
|
||||
})?;
|
||||
cx.run_until_parked();
|
||||
let session = workspace.read_with(cx, |workspace, cx| {
|
||||
workspace
|
||||
.panel::<DebugPanel>(cx)
|
||||
.and_then(|panel| panel.read(cx).active_session())
|
||||
.and_then(|session| session.read(cx).mode().as_running().cloned())
|
||||
.map(|running| running.read(cx).session().clone())
|
||||
.ok_or_else(|| anyhow!("Failed to get active session"))
|
||||
})??;
|
||||
|
||||
Ok(session)
|
||||
}
|
||||
|
||||
pub fn start_debug_session<T: Fn(&Arc<DebugAdapterClient>) + 'static>(
|
||||
workspace: &WindowHandle<Workspace>,
|
||||
cx: &mut gpui::TestAppContext,
|
||||
configure: T,
|
||||
) -> Result<Entity<Session>> {
|
||||
start_debug_session_with(
|
||||
workspace,
|
||||
cx,
|
||||
DebugTaskDefinition {
|
||||
adapter: "fake-adapter".to_string(),
|
||||
request: DebugRequest::Launch(Default::default()),
|
||||
label: "test".to_string(),
|
||||
initialize_args: None,
|
||||
tcp_connection: None,
|
||||
stop_on_entry: None,
|
||||
},
|
||||
configure,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{attach_modal::Candidate, tests::start_debug_session_with, *};
|
||||
use crate::{attach_modal::Candidate, *};
|
||||
use attach_modal::AttachModal;
|
||||
use dap::{FakeAdapter, client::SessionId};
|
||||
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
|
||||
@@ -26,8 +26,8 @@ async fn test_direct_attach_to_process(executor: BackgroundExecutor, cx: &mut Te
|
||||
let workspace = init_test_workspace(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
let session = start_debug_session_with(
|
||||
&workspace,
|
||||
let session = debugger::test::start_debug_session_with(
|
||||
&project,
|
||||
cx,
|
||||
DebugTaskDefinition {
|
||||
adapter: "fake-adapter".to_string(),
|
||||
@@ -47,6 +47,7 @@ async fn test_direct_attach_to_process(executor: BackgroundExecutor, cx: &mut Te
|
||||
});
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
cx.run_until_parked();
|
||||
@@ -98,10 +99,9 @@ async fn test_show_attach_modal_and_select_process(
|
||||
});
|
||||
let attach_modal = workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
let workspace_handle = cx.entity();
|
||||
workspace.toggle_modal(window, cx, |window, cx| {
|
||||
AttachModal::with_processes(
|
||||
workspace_handle,
|
||||
project.clone(),
|
||||
DebugTaskDefinition {
|
||||
adapter: FakeAdapter::ADAPTER_NAME.into(),
|
||||
request: dap::DebugRequest::Attach(AttachRequest::default()),
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
use crate::{
|
||||
tests::{active_debug_session_panel, start_debug_session},
|
||||
*,
|
||||
};
|
||||
use crate::{tests::active_debug_session_panel, *};
|
||||
use dap::requests::StackTrace;
|
||||
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
|
||||
use project::{FakeFs, Project};
|
||||
@@ -31,7 +28,9 @@ async fn test_handle_output_event(executor: BackgroundExecutor, cx: &mut TestApp
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||
let session = debugger::test::start_debug_session(&project, cx, |_| {})
|
||||
.await
|
||||
.unwrap();
|
||||
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||
|
||||
client.on_request::<StackTrace, _>(move |_, _| {
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
use crate::tests::{init_test, init_test_workspace, start_debug_session};
|
||||
use dap::requests::{StackTrace, Threads};
|
||||
use debugger_tools::LogStore;
|
||||
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
|
||||
use project::Project;
|
||||
use serde_json::json;
|
||||
use std::cell::OnceCell;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_dap_logger_captures_all_session_rpc_messages(
|
||||
executor: BackgroundExecutor,
|
||||
cx: &mut TestAppContext,
|
||||
) {
|
||||
let log_store_cell = std::rc::Rc::new(OnceCell::new());
|
||||
|
||||
cx.update(|cx| {
|
||||
let log_store_cell = log_store_cell.clone();
|
||||
cx.observe_new::<LogStore>(move |_, _, cx| {
|
||||
log_store_cell.set(cx.entity()).unwrap();
|
||||
})
|
||||
.detach();
|
||||
debugger_tools::init(cx);
|
||||
});
|
||||
init_test(cx);
|
||||
|
||||
let log_store = log_store_cell.get().unwrap().clone();
|
||||
|
||||
// Create a filesystem with a simple project
|
||||
let fs = project::FakeFs::new(executor.clone());
|
||||
fs.insert_tree(
|
||||
"/project",
|
||||
json!({
|
||||
"main.rs": "fn main() {\n println!(\"Hello, world!\");\n}"
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(
|
||||
log_store.read_with(cx, |log_store, _| log_store
|
||||
.contained_session_ids()
|
||||
.is_empty()),
|
||||
"log_store shouldn't contain any session IDs before any sessions were created"
|
||||
);
|
||||
|
||||
let project = Project::test(fs, ["/project".as_ref()], cx).await;
|
||||
|
||||
let workspace = init_test_workspace(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
// Start a debug session
|
||||
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||
let session_id = session.read_with(cx, |session, _| session.session_id());
|
||||
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||
|
||||
assert_eq!(
|
||||
log_store.read_with(cx, |log_store, _| log_store.contained_session_ids().len()),
|
||||
1,
|
||||
);
|
||||
|
||||
assert!(
|
||||
log_store.read_with(cx, |log_store, _| log_store
|
||||
.contained_session_ids()
|
||||
.contains(&session_id)),
|
||||
"log_store should contain the session IDs of the started session"
|
||||
);
|
||||
|
||||
assert!(
|
||||
!log_store.read_with(cx, |log_store, _| log_store
|
||||
.rpc_messages_for_session_id(session_id)
|
||||
.is_empty()),
|
||||
"We should have the initialization sequence in the log store"
|
||||
);
|
||||
|
||||
// Set up basic responses for common requests
|
||||
client.on_request::<Threads, _>(move |_, _| {
|
||||
Ok(dap::ThreadsResponse {
|
||||
threads: vec![dap::Thread {
|
||||
id: 1,
|
||||
name: "Thread 1".into(),
|
||||
}],
|
||||
})
|
||||
});
|
||||
|
||||
client.on_request::<StackTrace, _>(move |_, _| {
|
||||
Ok(dap::StackTraceResponse {
|
||||
stack_frames: Vec::default(),
|
||||
total_frames: None,
|
||||
})
|
||||
});
|
||||
|
||||
// Run until all pending tasks are executed
|
||||
cx.run_until_parked();
|
||||
|
||||
// Simulate a stopped event to generate more DAP messages
|
||||
client
|
||||
.fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
|
||||
reason: dap::StoppedEventReason::Pause,
|
||||
description: None,
|
||||
thread_id: Some(1),
|
||||
preserve_focus_hint: None,
|
||||
text: None,
|
||||
all_threads_stopped: None,
|
||||
hit_breakpoint_ids: None,
|
||||
}))
|
||||
.await;
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
// Shutdown the debug session
|
||||
let shutdown_session = project.update(cx, |project, cx| {
|
||||
project.dap_store().update(cx, |dap_store, cx| {
|
||||
dap_store.shutdown_session(session.read(cx).session_id(), cx)
|
||||
})
|
||||
});
|
||||
|
||||
shutdown_session.await.unwrap();
|
||||
cx.run_until_parked();
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{tests::start_debug_session, *};
|
||||
use crate::*;
|
||||
use dap::{
|
||||
ErrorResponse, Message, RunInTerminalRequestArguments, SourceBreakpoint,
|
||||
StartDebuggingRequestArguments, StartDebuggingRequestArgumentsRequest,
|
||||
@@ -48,7 +48,9 @@ async fn test_basic_show_debug_panel(executor: BackgroundExecutor, cx: &mut Test
|
||||
let workspace = init_test_workspace(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||
let session = debugger::test::start_debug_session(&project, cx, |_| {})
|
||||
.await
|
||||
.unwrap();
|
||||
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||
|
||||
client.on_request::<Threads, _>(move |_, _| {
|
||||
@@ -185,7 +187,9 @@ async fn test_we_can_only_have_one_panel_per_debug_session(
|
||||
let workspace = init_test_workspace(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||
let session = debugger::test::start_debug_session(&project, cx, |_| {})
|
||||
.await
|
||||
.unwrap();
|
||||
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||
|
||||
client.on_request::<Threads, _>(move |_, _| {
|
||||
@@ -350,7 +354,9 @@ async fn test_handle_successful_run_in_terminal_reverse_request(
|
||||
let workspace = init_test_workspace(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||
let session = debugger::test::start_debug_session(&project, cx, |_| {})
|
||||
.await
|
||||
.unwrap();
|
||||
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||
|
||||
client
|
||||
@@ -413,86 +419,6 @@ async fn test_handle_successful_run_in_terminal_reverse_request(
|
||||
shutdown_session.await.unwrap();
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_handle_start_debugging_request(
|
||||
executor: BackgroundExecutor,
|
||||
cx: &mut TestAppContext,
|
||||
) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(executor.clone());
|
||||
|
||||
fs.insert_tree(
|
||||
"/project",
|
||||
json!({
|
||||
"main.rs": "First line\nSecond line\nThird line\nFourth line",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs, ["/project".as_ref()], cx).await;
|
||||
let workspace = init_test_workspace(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||
|
||||
let fake_config = json!({"one": "two"});
|
||||
let launched_with = Arc::new(parking_lot::Mutex::new(None));
|
||||
|
||||
let _subscription = project::debugger::test::intercept_debug_sessions(cx, {
|
||||
let launched_with = launched_with.clone();
|
||||
move |client| {
|
||||
let launched_with = launched_with.clone();
|
||||
client.on_request::<dap::requests::Launch, _>(move |_, args| {
|
||||
launched_with.lock().replace(args.raw);
|
||||
Ok(())
|
||||
});
|
||||
client.on_request::<dap::requests::Attach, _>(move |_, _| {
|
||||
assert!(false, "should not get attach request");
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
client
|
||||
.fake_reverse_request::<StartDebugging>(StartDebuggingRequestArguments {
|
||||
request: StartDebuggingRequestArgumentsRequest::Launch,
|
||||
configuration: fake_config.clone(),
|
||||
})
|
||||
.await;
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
workspace
|
||||
.update(cx, |workspace, _window, cx| {
|
||||
let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
|
||||
let active_session = debug_panel
|
||||
.read(cx)
|
||||
.active_session()
|
||||
.unwrap()
|
||||
.read(cx)
|
||||
.session(cx);
|
||||
let parent_session = active_session.read(cx).parent_session().unwrap();
|
||||
|
||||
assert_eq!(
|
||||
active_session.read(cx).definition(),
|
||||
parent_session.read(cx).definition()
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(&fake_config, launched_with.lock().as_ref().unwrap());
|
||||
|
||||
let shutdown_session = project.update(cx, |project, cx| {
|
||||
project.dap_store().update(cx, |dap_store, cx| {
|
||||
dap_store.shutdown_session(session.read(cx).session_id(), cx)
|
||||
})
|
||||
});
|
||||
|
||||
shutdown_session.await.unwrap();
|
||||
}
|
||||
|
||||
// // covers that we always send a response back, if something when wrong,
|
||||
// // while spawning the terminal
|
||||
#[gpui::test]
|
||||
@@ -518,7 +444,9 @@ async fn test_handle_error_run_in_terminal_reverse_request(
|
||||
let workspace = init_test_workspace(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||
let session = debugger::test::start_debug_session(&project, cx, |_| {})
|
||||
.await
|
||||
.unwrap();
|
||||
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||
|
||||
client
|
||||
@@ -594,7 +522,9 @@ async fn test_handle_start_debugging_reverse_request(
|
||||
let workspace = init_test_workspace(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||
let session = debugger::test::start_debug_session(&project, cx, |_| {})
|
||||
.await
|
||||
.unwrap();
|
||||
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||
|
||||
client.on_request::<dap::requests::Threads, _>(move |_, _| {
|
||||
@@ -699,7 +629,9 @@ async fn test_shutdown_children_when_parent_session_shutdown(
|
||||
let workspace = init_test_workspace(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
let parent_session = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||
let parent_session = debugger::test::start_debug_session(&project, cx, |_| {})
|
||||
.await
|
||||
.unwrap();
|
||||
let client = parent_session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||
|
||||
client.on_request::<dap::requests::Threads, _>(move |_, _| {
|
||||
@@ -805,7 +737,9 @@ async fn test_shutdown_parent_session_if_all_children_are_shutdown(
|
||||
let workspace = init_test_workspace(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
let parent_session = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||
let parent_session = debugger::test::start_debug_session(&project, cx, |_| {})
|
||||
.await
|
||||
.unwrap();
|
||||
let client = parent_session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||
|
||||
client.on_response::<StartDebugging, _>(move |_| {}).await;
|
||||
@@ -924,7 +858,7 @@ async fn test_debug_panel_item_thread_status_reset_on_failure(
|
||||
let workspace = init_test_workspace(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
let session = start_debug_session(&workspace, cx, |client| {
|
||||
let session = debugger::test::start_debug_session(&project, cx, |client| {
|
||||
client.on_request::<dap::requests::Initialize, _>(move |_, _| {
|
||||
Ok(dap::Capabilities {
|
||||
supports_step_back: Some(true),
|
||||
@@ -932,6 +866,7 @@ async fn test_debug_panel_item_thread_status_reset_on_failure(
|
||||
})
|
||||
});
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||
@@ -1138,7 +1073,9 @@ async fn test_send_breakpoints_when_editor_has_been_saved(
|
||||
.update(cx, |_, _, cx| worktree.read(cx).id())
|
||||
.unwrap();
|
||||
|
||||
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||
let session = debugger::test::start_debug_session(&project, cx, |_| {})
|
||||
.await
|
||||
.unwrap();
|
||||
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||
|
||||
let buffer = project
|
||||
@@ -1353,7 +1290,9 @@ async fn test_unsetting_breakpoints_on_clear_breakpoint_action(
|
||||
editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
|
||||
});
|
||||
|
||||
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||
let session = debugger::test::start_debug_session(&project, cx, |_| {})
|
||||
.await
|
||||
.unwrap();
|
||||
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||
|
||||
let called_set_breakpoints = Arc::new(AtomicBool::new(false));
|
||||
@@ -1419,7 +1358,7 @@ async fn test_debug_session_is_shutdown_when_attach_and_launch_request_fails(
|
||||
let workspace = init_test_workspace(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
start_debug_session(&workspace, cx, |client| {
|
||||
let task = project::debugger::test::start_debug_session(&project, cx, |client| {
|
||||
client.on_request::<dap::requests::Initialize, _>(|_, _| {
|
||||
Err(ErrorResponse {
|
||||
error: Some(Message {
|
||||
@@ -1433,8 +1372,12 @@ async fn test_debug_session_is_shutdown_when_attach_and_launch_request_fails(
|
||||
}),
|
||||
})
|
||||
});
|
||||
})
|
||||
.ok();
|
||||
});
|
||||
|
||||
assert!(
|
||||
task.await.is_err(),
|
||||
"Session should failed to start if launch request fails"
|
||||
);
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
use crate::{
|
||||
debugger_panel::DebugPanel,
|
||||
tests::{active_debug_session_panel, init_test, init_test_workspace, start_debug_session},
|
||||
tests::{active_debug_session_panel, init_test, init_test_workspace},
|
||||
};
|
||||
use dap::{
|
||||
StoppedEvent,
|
||||
requests::{Initialize, Modules},
|
||||
};
|
||||
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
|
||||
use project::{FakeFs, Project};
|
||||
use project::{
|
||||
FakeFs, Project,
|
||||
debugger::{self},
|
||||
};
|
||||
use std::sync::{
|
||||
Arc,
|
||||
atomic::{AtomicBool, AtomicI32, Ordering},
|
||||
@@ -28,7 +31,7 @@ async fn test_module_list(executor: BackgroundExecutor, cx: &mut TestAppContext)
|
||||
.unwrap();
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
let session = start_debug_session(&workspace, cx, |client| {
|
||||
let session = debugger::test::start_debug_session(&project, cx, |client| {
|
||||
client.on_request::<Initialize, _>(move |_, _| {
|
||||
Ok(dap::Capabilities {
|
||||
supports_modules_request: Some(true),
|
||||
@@ -36,6 +39,7 @@ async fn test_module_list(executor: BackgroundExecutor, cx: &mut TestAppContext)
|
||||
})
|
||||
});
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
debugger_panel::DebugPanel,
|
||||
session::running::stack_frame_list::StackFrameEntry,
|
||||
tests::{active_debug_session_panel, init_test, init_test_workspace, start_debug_session},
|
||||
tests::{active_debug_session_panel, init_test, init_test_workspace},
|
||||
};
|
||||
use dap::{
|
||||
StackFrame,
|
||||
@@ -9,7 +9,7 @@ use dap::{
|
||||
};
|
||||
use editor::{Editor, ToPoint as _};
|
||||
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
|
||||
use project::{FakeFs, Project};
|
||||
use project::{FakeFs, Project, debugger};
|
||||
use serde_json::json;
|
||||
use std::sync::Arc;
|
||||
use unindent::Unindent as _;
|
||||
@@ -50,7 +50,9 @@ async fn test_fetch_initial_stack_frames_and_go_to_stack_frame(
|
||||
let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
|
||||
let workspace = init_test_workspace(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||
let session = debugger::test::start_debug_session(&project, cx, |_| {})
|
||||
.await
|
||||
.unwrap();
|
||||
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||
client.on_request::<Scopes, _>(move |_, _| Ok(dap::ScopesResponse { scopes: vec![] }));
|
||||
|
||||
@@ -227,7 +229,9 @@ async fn test_select_stack_frame(executor: BackgroundExecutor, cx: &mut TestAppC
|
||||
});
|
||||
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||
let session = debugger::test::start_debug_session(&project, cx, |_| {})
|
||||
.await
|
||||
.unwrap();
|
||||
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||
|
||||
client.on_request::<Threads, _>(move |_, _| {
|
||||
@@ -491,7 +495,9 @@ async fn test_collapsed_entries(executor: BackgroundExecutor, cx: &mut TestAppCo
|
||||
let workspace = init_test_workspace(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||
let session = debugger::test::start_debug_session(&project, cx, |_| {})
|
||||
.await
|
||||
.unwrap();
|
||||
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||
|
||||
client.on_request::<Threads, _>(move |_, _| {
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::sync::{
|
||||
use crate::{
|
||||
DebugPanel,
|
||||
session::running::variable_list::{CollapseSelectedEntry, ExpandSelectedEntry},
|
||||
tests::{active_debug_session_panel, init_test, init_test_workspace, start_debug_session},
|
||||
tests::{active_debug_session_panel, init_test, init_test_workspace},
|
||||
};
|
||||
use collections::HashMap;
|
||||
use dap::{
|
||||
@@ -15,7 +15,7 @@ use dap::{
|
||||
};
|
||||
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
|
||||
use menu::{SelectFirst, SelectNext, SelectPrevious};
|
||||
use project::{FakeFs, Project};
|
||||
use project::{FakeFs, Project, debugger};
|
||||
use serde_json::json;
|
||||
use unindent::Unindent as _;
|
||||
use util::path;
|
||||
@@ -54,7 +54,9 @@ async fn test_basic_fetch_initial_scope_and_variables(
|
||||
})
|
||||
.unwrap();
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||
let session = debugger::test::start_debug_session(&project, cx, |_| {})
|
||||
.await
|
||||
.unwrap();
|
||||
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||
|
||||
client.on_request::<dap::requests::Threads, _>(move |_, _| {
|
||||
@@ -264,7 +266,9 @@ async fn test_fetch_variables_for_multiple_scopes(
|
||||
.unwrap();
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||
let session = debugger::test::start_debug_session(&project, cx, |_| {})
|
||||
.await
|
||||
.unwrap();
|
||||
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||
|
||||
client.on_request::<dap::requests::Threads, _>(move |_, _| {
|
||||
@@ -524,7 +528,9 @@ async fn test_keyboard_navigation(executor: BackgroundExecutor, cx: &mut TestApp
|
||||
})
|
||||
.unwrap();
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||
let session = debugger::test::start_debug_session(&project, cx, |_| {})
|
||||
.await
|
||||
.unwrap();
|
||||
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||
|
||||
client.on_request::<dap::requests::Threads, _>(move |_, _| {
|
||||
@@ -1307,7 +1313,9 @@ async fn test_variable_list_only_sends_requests_when_rendering(
|
||||
let workspace = init_test_workspace(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||
let session = debugger::test::start_debug_session(&project, cx, |_| {})
|
||||
.await
|
||||
.unwrap();
|
||||
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||
|
||||
client.on_request::<dap::requests::Threads, _>(move |_, _| {
|
||||
@@ -1552,7 +1560,9 @@ async fn test_it_fetches_scopes_variables_when_you_select_a_stack_frame(
|
||||
.unwrap();
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||
let session = debugger::test::start_debug_session(&project, cx, |_| {})
|
||||
.await
|
||||
.unwrap();
|
||||
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||
|
||||
client.on_request::<dap::requests::Threads, _>(move |_, _| {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# Pull Request: https://github.com/zed-industries/zed/pull/27934
|
||||
url = "https://github.com/zed-industries/zed.git"
|
||||
revision = "889bc13b7dd61c67d894cee8a6cdd87f56c6c45b"
|
||||
language_extension = "rs"
|
||||
@@ -0,0 +1,3 @@
|
||||
1. The changes must add internal state to track whether the user is providing feedback comments and to store the comment editor instance. This includes adding new fields to the ActiveThread struct and initializing them appropriately when the thread is created.
|
||||
2. When a user selects negative feedback, the application should show a UI for submitting additional comments. This includes rendering a short prompt, a multi-line text editor, and submit/cancel buttons. The editor should only be created when first needed, and the UI should be dismissed and cleaned up after submission or cancellation.
|
||||
3. On submit, the system must report the negative feedback as before, and if the comment field is not empty, it must also log the comment as a separate telemetry event. Positive feedback handling should remain unchanged and bypass the comment UI entirely.
|
||||
@@ -0,0 +1 @@
|
||||
Add support for optional user comments when the thumbs down is given on a thread. Comments should be submitted along with the reaction and logged if provided. Make sure the UI highlights the ui icon when the user clicks it.
|
||||
@@ -0,0 +1,3 @@
|
||||
1. The first tool call should perform a regex search for terms related to "negative feedback." Since no specific file path or code snippet was provided, regex is necessary to locate relevant content. Once the matching files are found, their contents should be read.
|
||||
2. Only the `zed/crates/agent/src/active_thread.rs` file needs to be edited. All logic related to reactions and negative comments should be contained within this file.
|
||||
3. A comment box should appear only when the negative reaction is clicked. The positive reaction behavior should remain unchanged.
|
||||
@@ -40,8 +40,8 @@ struct Args {
|
||||
/// Model to use (default: "claude-3-7-sonnet-latest")
|
||||
#[arg(long, default_value = "claude-3-7-sonnet-latest")]
|
||||
model: String,
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
languages: Option<Vec<String>>,
|
||||
#[arg(long, value_delimiter = ',', default_value = "rs,ts")]
|
||||
languages: Vec<String>,
|
||||
/// How many times to run each example. Note that this is currently not very efficient as N
|
||||
/// worktrees will be created for the examples.
|
||||
#[arg(long, default_value = "1")]
|
||||
@@ -59,7 +59,6 @@ fn main() {
|
||||
|
||||
let args = Args::parse();
|
||||
let all_available_examples = list_all_examples().unwrap();
|
||||
let languages = args.languages.unwrap_or_else(|| vec!["rs".to_string()]);
|
||||
|
||||
let example_paths = all_available_examples
|
||||
.iter()
|
||||
@@ -151,7 +150,7 @@ fn main() {
|
||||
.base
|
||||
.language_extension
|
||||
.as_ref()
|
||||
.map_or(false, |lang| languages.contains(lang))
|
||||
.map_or(false, |lang| args.languages.contains(lang))
|
||||
{
|
||||
skipped.push(example.name);
|
||||
continue;
|
||||
|
||||
@@ -4,35 +4,33 @@ use super::{
|
||||
session::{self, Session, SessionStateEvent},
|
||||
};
|
||||
use crate::{
|
||||
ProjectEnvironment,
|
||||
project_settings::ProjectSettings,
|
||||
terminals::{SshCommand, wrap_for_ssh},
|
||||
worktree_store::WorktreeStore,
|
||||
ProjectEnvironment, debugger, project_settings::ProjectSettings, worktree_store::WorktreeStore,
|
||||
};
|
||||
use anyhow::{Result, anyhow};
|
||||
use async_trait::async_trait;
|
||||
use collections::HashMap;
|
||||
use dap::{
|
||||
Capabilities, CompletionItem, CompletionsArguments, DapRegistry, EvaluateArguments,
|
||||
EvaluateArgumentsContext, EvaluateResponse, RunInTerminalRequestArguments, Source,
|
||||
StartDebuggingRequestArguments,
|
||||
adapters::{DapStatus, DebugAdapterBinary, DebugAdapterName, TcpArguments},
|
||||
Capabilities, CompletionItem, CompletionsArguments, DapRegistry, ErrorResponse,
|
||||
EvaluateArguments, EvaluateArgumentsContext, EvaluateResponse, RunInTerminalRequestArguments,
|
||||
Source, StartDebuggingRequestArguments,
|
||||
adapters::{DapStatus, DebugAdapterBinary, DebugAdapterName},
|
||||
client::SessionId,
|
||||
messages::Message,
|
||||
requests::{Completions, Evaluate, Request as _, RunInTerminal, StartDebugging},
|
||||
};
|
||||
use fs::Fs;
|
||||
use futures::{
|
||||
channel::mpsc,
|
||||
channel::{mpsc, oneshot},
|
||||
future::{Shared, join_all},
|
||||
};
|
||||
use gpui::{App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Task};
|
||||
use gpui::{
|
||||
App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Task, WeakEntity,
|
||||
};
|
||||
use http_client::HttpClient;
|
||||
use language::{BinaryStatus, LanguageRegistry, LanguageToolchainStore};
|
||||
use lsp::LanguageServerName;
|
||||
use node_runtime::NodeRuntime;
|
||||
|
||||
use remote::SshRemoteClient;
|
||||
use rpc::{
|
||||
AnyProtoClient, TypedEnvelope,
|
||||
proto::{self},
|
||||
@@ -44,7 +42,6 @@ use std::{
|
||||
borrow::Borrow,
|
||||
collections::{BTreeMap, HashSet},
|
||||
ffi::OsStr,
|
||||
net::Ipv4Addr,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
@@ -69,10 +66,6 @@ pub enum DapStoreEvent {
|
||||
envs: HashMap<String, String>,
|
||||
sender: mpsc::Sender<Result<u32>>,
|
||||
},
|
||||
SpawnChildSession {
|
||||
request: StartDebuggingRequestArguments,
|
||||
parent_session: Entity<Session>,
|
||||
},
|
||||
Notification(String),
|
||||
RemoteHasInitialized,
|
||||
}
|
||||
@@ -90,12 +83,12 @@ pub struct LocalDapStore {
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
environment: Entity<ProjectEnvironment>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
worktree_store: Entity<WorktreeStore>,
|
||||
toolchain_store: Arc<dyn LanguageToolchainStore>,
|
||||
locators: HashMap<String, Arc<dyn DapLocator>>,
|
||||
}
|
||||
|
||||
pub struct SshDapStore {
|
||||
ssh_client: Entity<SshRemoteClient>,
|
||||
upstream_client: AnyProtoClient,
|
||||
upstream_project_id: u64,
|
||||
}
|
||||
@@ -104,7 +97,6 @@ pub struct DapStore {
|
||||
mode: DapStoreMode,
|
||||
downstream_client: Option<(AnyProtoClient, u64)>,
|
||||
breakpoint_store: Entity<BreakpointStore>,
|
||||
worktree_store: Entity<WorktreeStore>,
|
||||
sessions: BTreeMap<SessionId, Entity<Session>>,
|
||||
next_session_id: u32,
|
||||
start_debugging_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>,
|
||||
@@ -144,43 +136,40 @@ impl DapStore {
|
||||
http_client,
|
||||
node_runtime,
|
||||
toolchain_store,
|
||||
worktree_store,
|
||||
language_registry,
|
||||
locators,
|
||||
});
|
||||
|
||||
Self::new(mode, breakpoint_store, worktree_store, cx)
|
||||
Self::new(mode, breakpoint_store, cx)
|
||||
}
|
||||
|
||||
pub fn new_ssh(
|
||||
project_id: u64,
|
||||
ssh_client: Entity<SshRemoteClient>,
|
||||
upstream_client: AnyProtoClient,
|
||||
breakpoint_store: Entity<BreakpointStore>,
|
||||
worktree_store: Entity<WorktreeStore>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let mode = DapStoreMode::Ssh(SshDapStore {
|
||||
upstream_client: ssh_client.read(cx).proto_client(),
|
||||
ssh_client,
|
||||
upstream_client,
|
||||
upstream_project_id: project_id,
|
||||
});
|
||||
|
||||
Self::new(mode, breakpoint_store, worktree_store, cx)
|
||||
Self::new(mode, breakpoint_store, cx)
|
||||
}
|
||||
|
||||
pub fn new_collab(
|
||||
_project_id: u64,
|
||||
_upstream_client: AnyProtoClient,
|
||||
breakpoint_store: Entity<BreakpointStore>,
|
||||
worktree_store: Entity<WorktreeStore>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
Self::new(DapStoreMode::Collab, breakpoint_store, worktree_store, cx)
|
||||
Self::new(DapStoreMode::Collab, breakpoint_store, cx)
|
||||
}
|
||||
|
||||
fn new(
|
||||
mode: DapStoreMode,
|
||||
breakpoint_store: Entity<BreakpointStore>,
|
||||
worktree_store: Entity<WorktreeStore>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let (start_debugging_tx, mut message_rx) =
|
||||
@@ -213,7 +202,6 @@ impl DapStore {
|
||||
next_session_id: 0,
|
||||
downstream_client: None,
|
||||
breakpoint_store,
|
||||
worktree_store,
|
||||
sessions: Default::default(),
|
||||
}
|
||||
}
|
||||
@@ -224,8 +212,8 @@ impl DapStore {
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<DebugAdapterBinary>> {
|
||||
match &self.mode {
|
||||
DapStoreMode::Local(_) => {
|
||||
let Some(worktree) = self.worktree_store.read(cx).visible_worktrees(cx).next()
|
||||
DapStoreMode::Local(local) => {
|
||||
let Some(worktree) = local.worktree_store.read(cx).visible_worktrees(cx).next()
|
||||
else {
|
||||
return Task::ready(Err(anyhow!("Failed to find a worktree")));
|
||||
};
|
||||
@@ -273,49 +261,10 @@ impl DapStore {
|
||||
project_id: ssh.upstream_project_id,
|
||||
task: Some(definition.to_proto()),
|
||||
});
|
||||
let ssh_client = ssh.ssh_client.clone();
|
||||
|
||||
cx.spawn(async move |_, cx| {
|
||||
cx.background_spawn(async move {
|
||||
let response = request.await?;
|
||||
let binary = DebugAdapterBinary::from_proto(response)?;
|
||||
let mut ssh_command = ssh_client.update(cx, |ssh, _| {
|
||||
anyhow::Ok(SshCommand {
|
||||
arguments: ssh
|
||||
.ssh_args()
|
||||
.ok_or_else(|| anyhow!("SSH arguments not found"))?,
|
||||
})
|
||||
})??;
|
||||
|
||||
let mut connection = None;
|
||||
if let Some(c) = binary.connection {
|
||||
let local_bind_addr = Ipv4Addr::new(127, 0, 0, 1);
|
||||
let port =
|
||||
dap::transport::TcpTransport::unused_port(local_bind_addr).await?;
|
||||
|
||||
ssh_command.add_port_forwarding(port, c.host.to_string(), c.port);
|
||||
connection = Some(TcpArguments {
|
||||
port: c.port,
|
||||
host: local_bind_addr,
|
||||
timeout: c.timeout,
|
||||
})
|
||||
}
|
||||
|
||||
let (program, args) = wrap_for_ssh(
|
||||
&ssh_command,
|
||||
Some((&binary.command, &binary.arguments)),
|
||||
binary.cwd.as_deref(),
|
||||
binary.envs,
|
||||
None,
|
||||
);
|
||||
|
||||
Ok(DebugAdapterBinary {
|
||||
command: program,
|
||||
arguments: args,
|
||||
envs: HashMap::default(),
|
||||
cwd: None,
|
||||
connection,
|
||||
request_args: binary.request_args,
|
||||
})
|
||||
DebugAdapterBinary::from_proto(response)
|
||||
})
|
||||
}
|
||||
DapStoreMode::Collab => {
|
||||
@@ -367,79 +316,27 @@ impl DapStore {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_session(
|
||||
pub fn add_remote_client(
|
||||
&mut self,
|
||||
template: DebugTaskDefinition,
|
||||
parent_session: Option<Entity<Session>>,
|
||||
session_id: SessionId,
|
||||
ignore: Option<bool>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Entity<Session> {
|
||||
let session_id = SessionId(util::post_inc(&mut self.next_session_id));
|
||||
|
||||
if let Some(session) = &parent_session {
|
||||
session.update(cx, |session, _| {
|
||||
session.add_child_session_id(session_id);
|
||||
});
|
||||
) {
|
||||
if let DapStoreMode::Ssh(remote) = &self.mode {
|
||||
self.sessions.insert(
|
||||
session_id,
|
||||
cx.new(|_| {
|
||||
debugger::session::Session::remote(
|
||||
session_id,
|
||||
remote.upstream_client.clone(),
|
||||
remote.upstream_project_id,
|
||||
ignore.unwrap_or(false),
|
||||
)
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
debug_assert!(false);
|
||||
}
|
||||
|
||||
let start_debugging_tx = self.start_debugging_tx.clone();
|
||||
|
||||
let session = Session::new(
|
||||
self.breakpoint_store.clone(),
|
||||
session_id,
|
||||
parent_session,
|
||||
template.clone(),
|
||||
start_debugging_tx,
|
||||
cx,
|
||||
);
|
||||
|
||||
self.sessions.insert(session_id, session.clone());
|
||||
cx.notify();
|
||||
|
||||
cx.subscribe(&session, {
|
||||
move |this: &mut DapStore, _, event: &SessionStateEvent, cx| match event {
|
||||
SessionStateEvent::Shutdown => {
|
||||
this.shutdown_session(session_id, cx).detach_and_log_err(cx);
|
||||
}
|
||||
SessionStateEvent::Restart => {}
|
||||
SessionStateEvent::Running => {
|
||||
cx.emit(DapStoreEvent::DebugClientStarted(session_id));
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
session
|
||||
}
|
||||
|
||||
pub fn boot_session(
|
||||
&self,
|
||||
session: Entity<Session>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let Some(worktree) = self.worktree_store.read(cx).visible_worktrees(cx).next() else {
|
||||
return Task::ready(Err(anyhow!("Failed to find a worktree")));
|
||||
};
|
||||
|
||||
let dap_store = cx.weak_entity();
|
||||
let breakpoint_store = self.breakpoint_store.clone();
|
||||
let definition = session.read(cx).definition();
|
||||
|
||||
cx.spawn({
|
||||
let session = session.clone();
|
||||
async move |this, cx| {
|
||||
let binary = this
|
||||
.update(cx, |this, cx| {
|
||||
this.get_debug_adapter_binary(definition.clone(), cx)
|
||||
})?
|
||||
.await?;
|
||||
|
||||
session
|
||||
.update(cx, |session, cx| {
|
||||
session.boot(binary, worktree, breakpoint_store, dap_store, cx)
|
||||
})?
|
||||
.await
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn session_by_id(
|
||||
@@ -470,10 +367,6 @@ impl DapStore {
|
||||
&self.breakpoint_store
|
||||
}
|
||||
|
||||
pub fn worktree_store(&self) -> &Entity<WorktreeStore> {
|
||||
&self.worktree_store
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
async fn handle_ignore_breakpoint_state(
|
||||
this: Entity<Self>,
|
||||
@@ -514,6 +407,52 @@ impl DapStore {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_session(
|
||||
&mut self,
|
||||
binary: DebugAdapterBinary,
|
||||
config: DebugTaskDefinition,
|
||||
worktree: WeakEntity<Worktree>,
|
||||
parent_session: Option<Entity<Session>>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> (SessionId, Task<Result<Entity<Session>>>) {
|
||||
let session_id = SessionId(util::post_inc(&mut self.next_session_id));
|
||||
|
||||
if let Some(session) = &parent_session {
|
||||
session.update(cx, |session, _| {
|
||||
session.add_child_session_id(session_id);
|
||||
});
|
||||
}
|
||||
|
||||
let (initialized_tx, initialized_rx) = oneshot::channel();
|
||||
|
||||
let start_debugging_tx = self.start_debugging_tx.clone();
|
||||
|
||||
let task = cx.spawn(async move |this, cx| {
|
||||
let start_client_task = this.update(cx, |this, cx| {
|
||||
Session::local(
|
||||
this.breakpoint_store.clone(),
|
||||
worktree.clone(),
|
||||
session_id,
|
||||
parent_session,
|
||||
binary,
|
||||
config,
|
||||
start_debugging_tx.clone(),
|
||||
initialized_tx,
|
||||
cx,
|
||||
)
|
||||
})?;
|
||||
|
||||
let ret = this
|
||||
.update(cx, |_, cx| {
|
||||
create_new_session(session_id, initialized_rx, start_client_task, worktree, cx)
|
||||
})?
|
||||
.await;
|
||||
ret
|
||||
});
|
||||
|
||||
(session_id, task)
|
||||
}
|
||||
|
||||
fn handle_start_debugging_request(
|
||||
&mut self,
|
||||
session_id: SessionId,
|
||||
@@ -523,35 +462,56 @@ impl DapStore {
|
||||
let Some(parent_session) = self.session_by_id(session_id) else {
|
||||
return Task::ready(Err(anyhow!("Session not found")));
|
||||
};
|
||||
|
||||
let Some(worktree) = parent_session
|
||||
.read(cx)
|
||||
.as_local()
|
||||
.map(|local| local.worktree().clone())
|
||||
else {
|
||||
return Task::ready(Err(anyhow!(
|
||||
"Cannot handle start debugging request from remote end"
|
||||
)));
|
||||
};
|
||||
|
||||
let args = serde_json::from_value::<StartDebuggingRequestArguments>(
|
||||
request.arguments.unwrap_or_default(),
|
||||
)
|
||||
.expect("To parse StartDebuggingRequestArguments");
|
||||
let mut binary = parent_session.read(cx).binary().clone();
|
||||
let config = parent_session.read(cx).configuration().unwrap().clone();
|
||||
binary.request_args = args;
|
||||
|
||||
let new_session_task = self
|
||||
.new_session(binary, config, worktree, Some(parent_session.clone()), cx)
|
||||
.1;
|
||||
|
||||
let request_seq = request.seq;
|
||||
|
||||
let launch_request: Option<Result<StartDebuggingRequestArguments, _>> = request
|
||||
.arguments
|
||||
.as_ref()
|
||||
.map(|value| serde_json::from_value(value.clone()));
|
||||
|
||||
let mut success = true;
|
||||
if let Some(Ok(request)) = launch_request {
|
||||
cx.emit(DapStoreEvent::SpawnChildSession {
|
||||
request,
|
||||
parent_session: parent_session.clone(),
|
||||
});
|
||||
} else {
|
||||
log::error!(
|
||||
"Failed to parse launch request arguments: {:?}",
|
||||
request.arguments
|
||||
);
|
||||
success = false;
|
||||
}
|
||||
|
||||
cx.spawn(async move |_, cx| {
|
||||
let (success, body) = match new_session_task.await {
|
||||
Ok(_) => (true, None),
|
||||
Err(error) => (
|
||||
false,
|
||||
Some(serde_json::to_value(ErrorResponse {
|
||||
error: Some(dap::Message {
|
||||
id: request_seq,
|
||||
format: error.to_string(),
|
||||
variables: None,
|
||||
send_telemetry: None,
|
||||
show_user: None,
|
||||
url: None,
|
||||
url_label: None,
|
||||
}),
|
||||
})?),
|
||||
),
|
||||
};
|
||||
|
||||
parent_session
|
||||
.update(cx, |session, cx| {
|
||||
session.respond_to_client(
|
||||
request_seq,
|
||||
success,
|
||||
StartDebugging::COMMAND.to_string(),
|
||||
None,
|
||||
body,
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
@@ -792,7 +752,7 @@ impl DapStore {
|
||||
|
||||
let shutdown_parent_task = if let Some(parent_session) = session
|
||||
.read(cx)
|
||||
.parent_id(cx)
|
||||
.parent_id()
|
||||
.and_then(|session_id| self.session_by_id(session_id))
|
||||
{
|
||||
let shutdown_id = parent_session.update(cx, |parent_session, _| {
|
||||
@@ -882,6 +842,121 @@ impl DapStore {
|
||||
}
|
||||
}
|
||||
|
||||
fn create_new_session(
|
||||
session_id: SessionId,
|
||||
initialized_rx: oneshot::Receiver<()>,
|
||||
start_client_task: Task<Result<Entity<Session>, anyhow::Error>>,
|
||||
worktree: WeakEntity<Worktree>,
|
||||
cx: &mut Context<DapStore>,
|
||||
) -> Task<Result<Entity<Session>>> {
|
||||
let task = cx.spawn(async move |this, cx| {
|
||||
let session = match start_client_task.await {
|
||||
Ok(session) => session,
|
||||
Err(error) => {
|
||||
this.update(cx, |_, cx| {
|
||||
cx.emit(DapStoreEvent::Notification(error.to_string()));
|
||||
})
|
||||
.log_err();
|
||||
|
||||
return Err(error);
|
||||
}
|
||||
};
|
||||
|
||||
// we have to insert the session early, so we can handle reverse requests
|
||||
// that need the session to be available
|
||||
this.update(cx, |store, cx| {
|
||||
store.sessions.insert(session_id, session.clone());
|
||||
cx.emit(DapStoreEvent::DebugClientStarted(session_id));
|
||||
cx.notify();
|
||||
})?;
|
||||
let seq_result = async || {
|
||||
session
|
||||
.update(cx, |session, cx| session.request_initialize(cx))?
|
||||
.await?;
|
||||
|
||||
session
|
||||
.update(cx, |session, cx| {
|
||||
session.initialize_sequence(initialized_rx, this.clone(), cx)
|
||||
})?
|
||||
.await
|
||||
};
|
||||
match seq_result().await {
|
||||
Ok(_) => {}
|
||||
Err(error) => {
|
||||
this.update(cx, |this, cx| {
|
||||
cx.emit(DapStoreEvent::Notification(error.to_string()));
|
||||
this.shutdown_session(session_id, cx)
|
||||
})?
|
||||
.await
|
||||
.log_err();
|
||||
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
|
||||
this.update(cx, |_, cx| {
|
||||
cx.subscribe(
|
||||
&session,
|
||||
move |this: &mut DapStore, session, event: &SessionStateEvent, cx| match event {
|
||||
SessionStateEvent::Shutdown => {
|
||||
this.shutdown_session(session_id, cx).detach_and_log_err(cx);
|
||||
}
|
||||
SessionStateEvent::Restart => {
|
||||
let mut curr_session = session;
|
||||
while let Some(parent_id) = curr_session.read(cx).parent_id() {
|
||||
if let Some(parent_session) = this.sessions.get(&parent_id).cloned() {
|
||||
curr_session = parent_session;
|
||||
} else {
|
||||
log::error!("Failed to get parent session from parent session id");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let Some((config, binary)) = curr_session.read_with(cx, |session, _| {
|
||||
session
|
||||
.configuration()
|
||||
.map(|config| (config, session.root_binary().clone()))
|
||||
}) else {
|
||||
log::error!("Failed to get debug config from session");
|
||||
return;
|
||||
};
|
||||
|
||||
let session_id = curr_session.read(cx).session_id();
|
||||
|
||||
let task = curr_session.update(cx, |session, cx| session.shutdown(cx));
|
||||
|
||||
let worktree = worktree.clone();
|
||||
cx.spawn(async move |this, cx| {
|
||||
task.await;
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
this.sessions.remove(&session_id);
|
||||
this.new_session(
|
||||
binary.as_ref().clone(),
|
||||
config,
|
||||
worktree,
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.1
|
||||
.await?;
|
||||
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
cx.emit(DapStoreEvent::DebugSessionInitialized(session_id));
|
||||
})?;
|
||||
|
||||
Ok(session)
|
||||
});
|
||||
task
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DapAdapterDelegate {
|
||||
fs: Arc<dyn Fs>,
|
||||
|
||||
@@ -28,6 +28,7 @@ use gpui::{
|
||||
Task, WeakEntity,
|
||||
};
|
||||
|
||||
use rpc::AnyProtoClient;
|
||||
use serde_json::{Value, json};
|
||||
use smol::stream::StreamExt;
|
||||
use std::any::TypeId;
|
||||
@@ -114,14 +115,54 @@ impl From<dap::Thread> for Thread {
|
||||
}
|
||||
}
|
||||
|
||||
type UpstreamProjectId = u64;
|
||||
|
||||
struct RemoteConnection {
|
||||
_client: AnyProtoClient,
|
||||
_upstream_project_id: UpstreamProjectId,
|
||||
_adapter_name: SharedString,
|
||||
}
|
||||
|
||||
impl RemoteConnection {
|
||||
fn send_proto_client_request<R: DapCommand>(
|
||||
&self,
|
||||
_request: R,
|
||||
_session_id: SessionId,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<R::Response>> {
|
||||
// let message = request.to_proto(session_id, self.upstream_project_id);
|
||||
// let upstream_client = self.client.clone();
|
||||
cx.background_executor().spawn(async move {
|
||||
// debugger(todo): Properly send messages when we wrap dap_commands in envelopes again
|
||||
// let response = upstream_client.request(message).await?;
|
||||
// request.response_from_proto(response)
|
||||
Err(anyhow!("Sending dap commands over RPC isn't supported yet"))
|
||||
})
|
||||
}
|
||||
|
||||
fn request<R: DapCommand>(
|
||||
&self,
|
||||
request: R,
|
||||
session_id: SessionId,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<R::Response>>
|
||||
where
|
||||
<R::DapRequest as dap::requests::Request>::Response: 'static,
|
||||
<R::DapRequest as dap::requests::Request>::Arguments: 'static + Send,
|
||||
{
|
||||
return self.send_proto_client_request::<R>(request, session_id, cx);
|
||||
}
|
||||
}
|
||||
|
||||
enum Mode {
|
||||
Building,
|
||||
Running(LocalMode),
|
||||
Local(LocalMode),
|
||||
Remote(RemoteConnection),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct LocalMode {
|
||||
client: Arc<DebugAdapterClient>,
|
||||
definition: DebugTaskDefinition,
|
||||
binary: DebugAdapterBinary,
|
||||
root_binary: Option<Arc<DebugAdapterBinary>>,
|
||||
pub(crate) breakpoint_store: Entity<BreakpointStore>,
|
||||
@@ -145,47 +186,56 @@ fn client_source(abs_path: &Path) -> dap::Source {
|
||||
}
|
||||
|
||||
impl LocalMode {
|
||||
async fn new(
|
||||
fn new(
|
||||
session_id: SessionId,
|
||||
parent_session: Option<Entity<Session>>,
|
||||
worktree: WeakEntity<Worktree>,
|
||||
breakpoint_store: Entity<BreakpointStore>,
|
||||
config: DebugTaskDefinition,
|
||||
binary: DebugAdapterBinary,
|
||||
messages_tx: futures::channel::mpsc::UnboundedSender<Message>,
|
||||
cx: AsyncApp,
|
||||
) -> Result<Self> {
|
||||
let message_handler = Box::new(move |message| {
|
||||
messages_tx.unbounded_send(message).ok();
|
||||
});
|
||||
) -> Task<Result<Self>> {
|
||||
cx.spawn(async move |cx| {
|
||||
let message_handler = Box::new(move |message| {
|
||||
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())
|
||||
.flatten()
|
||||
{
|
||||
client
|
||||
.reconnect(session_id, binary.clone(), message_handler, cx.clone())
|
||||
.await?
|
||||
let root_binary = if let Some(parent_session) = parent_session.as_ref() {
|
||||
Some(parent_session.read_with(cx, |session, _| session.root_binary().clone())?)
|
||||
} else {
|
||||
DebugAdapterClient::start(session_id, binary.clone(), message_handler, cx.clone())
|
||||
None
|
||||
};
|
||||
|
||||
let client = Arc::new(
|
||||
if let Some(client) = parent_session
|
||||
.and_then(|session| cx.update(|cx| session.read(cx).adapter_client()).ok())
|
||||
.flatten()
|
||||
{
|
||||
client
|
||||
.reconnect(session_id, binary.clone(), message_handler, cx.clone())
|
||||
.await?
|
||||
} else {
|
||||
DebugAdapterClient::start(
|
||||
session_id,
|
||||
binary.clone(),
|
||||
message_handler,
|
||||
cx.clone(),
|
||||
)
|
||||
.await
|
||||
.with_context(|| "Failed to start communication with debug adapter")?
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
client,
|
||||
breakpoint_store,
|
||||
worktree,
|
||||
tmp_breakpoint: None,
|
||||
root_binary,
|
||||
binary,
|
||||
Ok(Self {
|
||||
client,
|
||||
breakpoint_store,
|
||||
worktree,
|
||||
tmp_breakpoint: None,
|
||||
definition: config,
|
||||
root_binary,
|
||||
binary,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -321,10 +371,19 @@ impl LocalMode {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn label(&self) -> String {
|
||||
self.definition.label.clone()
|
||||
}
|
||||
|
||||
fn request_initialization(&self, cx: &App) -> Task<Result<Capabilities>> {
|
||||
let adapter_id = self.definition.adapter.clone();
|
||||
|
||||
self.request(Initialize { adapter_id }, cx.background_executor().clone())
|
||||
}
|
||||
|
||||
fn initialize_sequence(
|
||||
&self,
|
||||
capabilities: &Capabilities,
|
||||
definition: &DebugTaskDefinition,
|
||||
initialized_rx: oneshot::Receiver<()>,
|
||||
dap_store: WeakEntity<DapStore>,
|
||||
cx: &App,
|
||||
@@ -332,7 +391,7 @@ impl LocalMode {
|
||||
let mut raw = self.binary.request_args.clone();
|
||||
|
||||
merge_json_value_into(
|
||||
definition.initialize_args.clone().unwrap_or(json!({})),
|
||||
self.definition.initialize_args.clone().unwrap_or(json!({})),
|
||||
&mut raw.configuration,
|
||||
);
|
||||
|
||||
@@ -367,9 +426,9 @@ impl LocalMode {
|
||||
let supports_exception_filters = capabilities
|
||||
.supports_exception_filter_options
|
||||
.unwrap_or_default();
|
||||
let this = self.clone();
|
||||
let worktree = self.worktree().clone();
|
||||
let configuration_sequence = cx.spawn({
|
||||
let this = self.clone();
|
||||
let worktree = self.worktree().clone();
|
||||
async move |cx| {
|
||||
initialized_rx.await?;
|
||||
let errors_by_path = cx
|
||||
@@ -452,10 +511,16 @@ impl LocalMode {
|
||||
})
|
||||
}
|
||||
}
|
||||
impl From<RemoteConnection> for Mode {
|
||||
fn from(value: RemoteConnection) -> Self {
|
||||
Self::Remote(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mode {
|
||||
fn request_dap<R: DapCommand>(
|
||||
&self,
|
||||
session_id: SessionId,
|
||||
request: R,
|
||||
cx: &mut Context<Session>,
|
||||
) -> Task<Result<R::Response>>
|
||||
@@ -464,13 +529,10 @@ impl Mode {
|
||||
<R::DapRequest as dap::requests::Request>::Arguments: 'static + Send,
|
||||
{
|
||||
match self {
|
||||
Mode::Running(debug_adapter_client) => {
|
||||
Mode::Local(debug_adapter_client) => {
|
||||
debug_adapter_client.request(request, cx.background_executor().clone())
|
||||
}
|
||||
Mode::Building => Task::ready(Err(anyhow!(
|
||||
"no adapter running to send request: {:?}",
|
||||
request
|
||||
))),
|
||||
Mode::Remote(remote_connection) => remote_connection.request(request, session_id, cx),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -547,11 +609,10 @@ pub struct OutputToken(pub usize);
|
||||
/// Represents a current state of a single debug adapter and provides ways to mutate it.
|
||||
pub struct Session {
|
||||
mode: Mode,
|
||||
definition: DebugTaskDefinition,
|
||||
pub(super) capabilities: Capabilities,
|
||||
id: SessionId,
|
||||
child_session_ids: HashSet<SessionId>,
|
||||
parent_session: Option<Entity<Session>>,
|
||||
parent_id: Option<SessionId>,
|
||||
ignore_breakpoints: bool,
|
||||
modules: Vec<dap::Module>,
|
||||
loaded_sources: Vec<dap::Source>,
|
||||
@@ -565,8 +626,7 @@ pub struct Session {
|
||||
is_session_terminated: bool,
|
||||
requests: HashMap<TypeId, HashMap<RequestSlot, Shared<Task<Option<()>>>>>,
|
||||
exception_breakpoints: BTreeMap<String, (ExceptionBreakpointsFilter, IsEnabled)>,
|
||||
start_debugging_requests_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>,
|
||||
background_tasks: Vec<Task<()>>,
|
||||
_background_tasks: Vec<Task<()>>,
|
||||
}
|
||||
|
||||
trait CacheableCommand: Any + Send + Sync {
|
||||
@@ -648,12 +708,9 @@ pub enum SessionEvent {
|
||||
StackTrace,
|
||||
Variables,
|
||||
Threads,
|
||||
CapabilitiesLoaded,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum SessionStateEvent {
|
||||
Running,
|
||||
pub(super) enum SessionStateEvent {
|
||||
Shutdown,
|
||||
Restart,
|
||||
}
|
||||
@@ -665,140 +722,80 @@ impl EventEmitter<SessionStateEvent> for Session {}
|
||||
// remote side will only send breakpoint updates when it is a breakpoint created by that peer
|
||||
// BreakpointStore notifies session on breakpoint changes
|
||||
impl Session {
|
||||
pub(crate) fn new(
|
||||
pub(crate) fn local(
|
||||
breakpoint_store: Entity<BreakpointStore>,
|
||||
worktree: WeakEntity<Worktree>,
|
||||
session_id: SessionId,
|
||||
parent_session: Option<Entity<Session>>,
|
||||
template: DebugTaskDefinition,
|
||||
start_debugging_requests_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>,
|
||||
cx: &mut App,
|
||||
) -> Entity<Self> {
|
||||
cx.new::<Self>(|cx| {
|
||||
cx.subscribe(&breakpoint_store, |this, _, event, cx| match event {
|
||||
BreakpointStoreEvent::BreakpointsUpdated(path, reason) => {
|
||||
if let Some(local) = (!this.ignore_breakpoints)
|
||||
.then(|| this.as_local_mut())
|
||||
.flatten()
|
||||
{
|
||||
local
|
||||
.send_breakpoints_from_path(path.clone(), *reason, cx)
|
||||
.detach();
|
||||
};
|
||||
}
|
||||
BreakpointStoreEvent::BreakpointsCleared(paths) => {
|
||||
if let Some(local) = (!this.ignore_breakpoints)
|
||||
.then(|| this.as_local_mut())
|
||||
.flatten()
|
||||
{
|
||||
local.unset_breakpoints_from_paths(paths, cx).detach();
|
||||
}
|
||||
}
|
||||
BreakpointStoreEvent::ActiveDebugLineChanged => {}
|
||||
})
|
||||
.detach();
|
||||
|
||||
let this = Self {
|
||||
mode: Mode::Building,
|
||||
id: session_id,
|
||||
child_session_ids: HashSet::default(),
|
||||
parent_session,
|
||||
capabilities: Capabilities::default(),
|
||||
ignore_breakpoints: false,
|
||||
variables: Default::default(),
|
||||
stack_frames: Default::default(),
|
||||
thread_states: ThreadStates::default(),
|
||||
output_token: OutputToken(0),
|
||||
output: circular_buffer::CircularBuffer::boxed(),
|
||||
requests: HashMap::default(),
|
||||
modules: Vec::default(),
|
||||
loaded_sources: Vec::default(),
|
||||
threads: IndexMap::default(),
|
||||
background_tasks: Vec::default(),
|
||||
locations: Default::default(),
|
||||
is_session_terminated: false,
|
||||
exception_breakpoints: Default::default(),
|
||||
definition: template,
|
||||
start_debugging_requests_tx,
|
||||
};
|
||||
|
||||
this
|
||||
})
|
||||
}
|
||||
|
||||
pub fn worktree(&self) -> Option<Entity<Worktree>> {
|
||||
match &self.mode {
|
||||
Mode::Building => None,
|
||||
Mode::Running(local_mode) => local_mode.worktree.upgrade(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn boot(
|
||||
&mut self,
|
||||
binary: DebugAdapterBinary,
|
||||
worktree: Entity<Worktree>,
|
||||
breakpoint_store: Entity<BreakpointStore>,
|
||||
dap_store: WeakEntity<DapStore>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let (message_tx, mut message_rx) = futures::channel::mpsc::unbounded();
|
||||
let (initialized_tx, initialized_rx) = futures::channel::oneshot::channel();
|
||||
let session_id = self.session_id();
|
||||
config: DebugTaskDefinition,
|
||||
start_debugging_requests_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>,
|
||||
initialized_tx: oneshot::Sender<()>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<Entity<Self>>> {
|
||||
let (message_tx, message_rx) = futures::channel::mpsc::unbounded();
|
||||
|
||||
let background_tasks = vec![cx.spawn(async move |this: WeakEntity<Session>, cx| {
|
||||
let mut initialized_tx = Some(initialized_tx);
|
||||
while let Some(message) = message_rx.next().await {
|
||||
if let Message::Event(event) = message {
|
||||
if let Events::Initialized(_) = *event {
|
||||
if let Some(tx) = initialized_tx.take() {
|
||||
tx.send(()).ok();
|
||||
}
|
||||
} else {
|
||||
let Ok(_) = this.update(cx, |session, cx| {
|
||||
session.handle_dap_event(event, cx);
|
||||
}) else {
|
||||
break;
|
||||
};
|
||||
}
|
||||
} else {
|
||||
let Ok(Ok(_)) = this.update(cx, |this, _| {
|
||||
this.start_debugging_requests_tx
|
||||
.unbounded_send((session_id, message))
|
||||
}) else {
|
||||
break;
|
||||
};
|
||||
}
|
||||
}
|
||||
})];
|
||||
self.background_tasks = background_tasks;
|
||||
let id = self.id;
|
||||
let parent_session = self.parent_session.clone();
|
||||
|
||||
cx.spawn(async move |this, cx| {
|
||||
cx.spawn(async move |cx| {
|
||||
let mode = LocalMode::new(
|
||||
id,
|
||||
parent_session,
|
||||
worktree.downgrade(),
|
||||
session_id,
|
||||
parent_session.clone(),
|
||||
worktree,
|
||||
breakpoint_store.clone(),
|
||||
config.clone(),
|
||||
binary,
|
||||
message_tx,
|
||||
cx.clone(),
|
||||
)
|
||||
.await?;
|
||||
this.update(cx, |this, cx| {
|
||||
this.mode = Mode::Running(mode);
|
||||
cx.emit(SessionStateEvent::Running);
|
||||
})?;
|
||||
|
||||
this.update(cx, |session, cx| session.request_initialize(cx))?
|
||||
.await?;
|
||||
|
||||
this.update(cx, |session, cx| {
|
||||
session.initialize_sequence(initialized_rx, dap_store.clone(), cx)
|
||||
})?
|
||||
.await
|
||||
cx.new(|cx| {
|
||||
create_local_session(
|
||||
breakpoint_store,
|
||||
session_id,
|
||||
parent_session,
|
||||
start_debugging_requests_tx,
|
||||
initialized_tx,
|
||||
message_rx,
|
||||
mode,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn remote(
|
||||
session_id: SessionId,
|
||||
client: AnyProtoClient,
|
||||
upstream_project_id: u64,
|
||||
ignore_breakpoints: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
mode: Mode::Remote(RemoteConnection {
|
||||
_adapter_name: SharedString::new(""), // todo(debugger) we need to pipe in the right values to deserialize the debugger pane layout
|
||||
_client: client,
|
||||
_upstream_project_id: upstream_project_id,
|
||||
}),
|
||||
id: session_id,
|
||||
child_session_ids: HashSet::default(),
|
||||
parent_id: None,
|
||||
capabilities: Capabilities::default(),
|
||||
ignore_breakpoints,
|
||||
variables: Default::default(),
|
||||
stack_frames: Default::default(),
|
||||
thread_states: ThreadStates::default(),
|
||||
output_token: OutputToken(0),
|
||||
output: circular_buffer::CircularBuffer::boxed(),
|
||||
requests: HashMap::default(),
|
||||
modules: Vec::default(),
|
||||
loaded_sources: Vec::default(),
|
||||
threads: IndexMap::default(),
|
||||
_background_tasks: Vec::default(),
|
||||
locations: Default::default(),
|
||||
is_session_terminated: false,
|
||||
exception_breakpoints: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_id(&self) -> SessionId {
|
||||
self.id
|
||||
}
|
||||
@@ -815,14 +812,8 @@ impl Session {
|
||||
self.child_session_ids.remove(&session_id);
|
||||
}
|
||||
|
||||
pub fn parent_id(&self, cx: &App) -> Option<SessionId> {
|
||||
self.parent_session
|
||||
.as_ref()
|
||||
.map(|session| session.read(cx).id)
|
||||
}
|
||||
|
||||
pub fn parent_session(&self) -> Option<&Entity<Self>> {
|
||||
self.parent_session.as_ref()
|
||||
pub fn parent_id(&self) -> Option<SessionId> {
|
||||
self.parent_id
|
||||
}
|
||||
|
||||
pub fn capabilities(&self) -> &Capabilities {
|
||||
@@ -830,35 +821,35 @@ impl Session {
|
||||
}
|
||||
|
||||
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())),
|
||||
}
|
||||
let Mode::Local(local_mode) = &self.mode else {
|
||||
panic!("Session is not local");
|
||||
};
|
||||
local_mode
|
||||
.root_binary
|
||||
.clone()
|
||||
.unwrap_or_else(|| Arc::new(local_mode.binary.clone()))
|
||||
}
|
||||
|
||||
pub fn binary(&self) -> &DebugAdapterBinary {
|
||||
let Mode::Running(local_mode) = &self.mode else {
|
||||
let Mode::Local(local_mode) = &self.mode else {
|
||||
panic!("Session is not local");
|
||||
};
|
||||
&local_mode.binary
|
||||
}
|
||||
|
||||
pub fn adapter_name(&self) -> SharedString {
|
||||
self.definition.adapter.clone().into()
|
||||
match &self.mode {
|
||||
Mode::Local(local_mode) => local_mode.definition.adapter.clone().into(),
|
||||
Mode::Remote(remote_mode) => remote_mode._adapter_name.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn label(&self) -> String {
|
||||
self.definition.label.clone()
|
||||
}
|
||||
|
||||
pub fn definition(&self) -> DebugTaskDefinition {
|
||||
self.definition.clone()
|
||||
pub fn configuration(&self) -> Option<DebugTaskDefinition> {
|
||||
if let Mode::Local(local_mode) = &self.mode {
|
||||
Some(local_mode.definition.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_terminated(&self) -> bool {
|
||||
@@ -866,33 +857,31 @@ impl Session {
|
||||
}
|
||||
|
||||
pub fn is_local(&self) -> bool {
|
||||
matches!(self.mode, Mode::Running(_))
|
||||
matches!(self.mode, Mode::Local(_))
|
||||
}
|
||||
|
||||
pub fn as_local_mut(&mut self) -> Option<&mut LocalMode> {
|
||||
match &mut self.mode {
|
||||
Mode::Running(local_mode) => Some(local_mode),
|
||||
Mode::Building => None,
|
||||
Mode::Local(local_mode) => Some(local_mode),
|
||||
Mode::Remote(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_local(&self) -> Option<&LocalMode> {
|
||||
match &self.mode {
|
||||
Mode::Running(local_mode) => Some(local_mode),
|
||||
Mode::Building => None,
|
||||
Mode::Local(local_mode) => Some(local_mode),
|
||||
Mode::Remote(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn request_initialize(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
|
||||
let adapter_id = self.definition.adapter.clone();
|
||||
let request = Initialize { adapter_id };
|
||||
match &self.mode {
|
||||
Mode::Running(local_mode) => {
|
||||
let capabilities = local_mode.request(request, cx.background_executor().clone());
|
||||
Mode::Local(local_mode) => {
|
||||
let capabilities = local_mode.clone().request_initialization(cx);
|
||||
|
||||
cx.spawn(async move |this, cx| {
|
||||
let capabilities = capabilities.await?;
|
||||
this.update(cx, |session, cx| {
|
||||
this.update(cx, |session, _| {
|
||||
session.capabilities = capabilities;
|
||||
let filters = session
|
||||
.capabilities
|
||||
@@ -906,13 +895,12 @@ impl Session {
|
||||
.entry(filter.filter.clone())
|
||||
.or_insert_with(|| (filter, default));
|
||||
}
|
||||
cx.emit(SessionEvent::CapabilitiesLoaded);
|
||||
})?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
Mode::Building => Task::ready(Err(anyhow!(
|
||||
"Cannot send initialize request, task still building"
|
||||
Mode::Remote(_) => Task::ready(Err(anyhow!(
|
||||
"Cannot send initialize request from remote session"
|
||||
))),
|
||||
}
|
||||
}
|
||||
@@ -924,14 +912,10 @@ impl Session {
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
match &self.mode {
|
||||
Mode::Running(local_mode) => local_mode.initialize_sequence(
|
||||
&self.capabilities,
|
||||
&self.definition,
|
||||
initialize_rx,
|
||||
dap_store,
|
||||
cx,
|
||||
),
|
||||
Mode::Building => Task::ready(Err(anyhow!("cannot initialize, still building"))),
|
||||
Mode::Local(local_mode) => {
|
||||
local_mode.initialize_sequence(&self.capabilities, initialize_rx, dap_store, cx)
|
||||
}
|
||||
Mode::Remote(_) => Task::ready(Err(anyhow!("cannot initialize remote session"))),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -942,7 +926,7 @@ impl Session {
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
match &mut self.mode {
|
||||
Mode::Running(local_mode) => {
|
||||
Mode::Local(local_mode) => {
|
||||
if !matches!(
|
||||
self.thread_states.thread_state(active_thread_id),
|
||||
Some(ThreadStatus::Stopped)
|
||||
@@ -965,7 +949,7 @@ impl Session {
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
Mode::Building => {}
|
||||
Mode::Remote(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -999,13 +983,13 @@ impl Session {
|
||||
body: Option<serde_json::Value>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let Some(local_session) = self.as_local() else {
|
||||
let Some(local_session) = self.as_local().cloned() else {
|
||||
unreachable!("Cannot respond to remote client");
|
||||
};
|
||||
let client = local_session.client.clone();
|
||||
|
||||
cx.background_spawn(async move {
|
||||
client
|
||||
local_session
|
||||
.client
|
||||
.send_message(Message::Response(Response {
|
||||
body,
|
||||
success,
|
||||
@@ -1194,6 +1178,7 @@ impl Session {
|
||||
|
||||
let task = Self::request_inner::<Arc<T>>(
|
||||
&self.capabilities,
|
||||
self.id,
|
||||
&self.mode,
|
||||
command,
|
||||
process_result,
|
||||
@@ -1214,6 +1199,7 @@ impl Session {
|
||||
|
||||
fn request_inner<T: DapCommand + PartialEq + Eq + Hash>(
|
||||
capabilities: &Capabilities,
|
||||
session_id: SessionId,
|
||||
mode: &Mode,
|
||||
request: T,
|
||||
process_result: impl FnOnce(
|
||||
@@ -1239,7 +1225,7 @@ impl Session {
|
||||
});
|
||||
}
|
||||
|
||||
let request = mode.request_dap(request, cx);
|
||||
let request = mode.request_dap(session_id, request, cx);
|
||||
cx.spawn(async move |this, cx| {
|
||||
let result = request.await;
|
||||
this.update(cx, |this, cx| process_result(this, result, cx))
|
||||
@@ -1259,7 +1245,14 @@ impl Session {
|
||||
+ 'static,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Option<T::Response>> {
|
||||
Self::request_inner(&self.capabilities, &self.mode, request, process_result, cx)
|
||||
Self::request_inner(
|
||||
&self.capabilities,
|
||||
self.id,
|
||||
&self.mode,
|
||||
request,
|
||||
process_result,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
|
||||
fn invalidate_command_type<Command: DapCommand>(&mut self) {
|
||||
@@ -1576,8 +1569,8 @@ impl Session {
|
||||
|
||||
pub fn adapter_client(&self) -> Option<Arc<DebugAdapterClient>> {
|
||||
match self.mode {
|
||||
Mode::Running(ref local) => Some(local.client.clone()),
|
||||
Mode::Building => None,
|
||||
Mode::Local(ref local) => Some(local.client.clone()),
|
||||
Mode::Remote(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1943,3 +1936,83 @@ impl Session {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_local_session(
|
||||
breakpoint_store: Entity<BreakpointStore>,
|
||||
session_id: SessionId,
|
||||
parent_session: Option<Entity<Session>>,
|
||||
start_debugging_requests_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>,
|
||||
initialized_tx: oneshot::Sender<()>,
|
||||
mut message_rx: futures::channel::mpsc::UnboundedReceiver<Message>,
|
||||
mode: LocalMode,
|
||||
cx: &mut Context<Session>,
|
||||
) -> Session {
|
||||
let _background_tasks = vec![cx.spawn(async move |this: WeakEntity<Session>, cx| {
|
||||
let mut initialized_tx = Some(initialized_tx);
|
||||
while let Some(message) = message_rx.next().await {
|
||||
if let Message::Event(event) = message {
|
||||
if let Events::Initialized(_) = *event {
|
||||
if let Some(tx) = initialized_tx.take() {
|
||||
tx.send(()).ok();
|
||||
}
|
||||
} else {
|
||||
let Ok(_) = this.update(cx, |session, cx| {
|
||||
session.handle_dap_event(event, cx);
|
||||
}) else {
|
||||
break;
|
||||
};
|
||||
}
|
||||
} else {
|
||||
let Ok(_) = start_debugging_requests_tx.unbounded_send((session_id, message))
|
||||
else {
|
||||
break;
|
||||
};
|
||||
}
|
||||
}
|
||||
})];
|
||||
|
||||
cx.subscribe(&breakpoint_store, |this, _, event, cx| match event {
|
||||
BreakpointStoreEvent::BreakpointsUpdated(path, reason) => {
|
||||
if let Some(local) = (!this.ignore_breakpoints)
|
||||
.then(|| this.as_local_mut())
|
||||
.flatten()
|
||||
{
|
||||
local
|
||||
.send_breakpoints_from_path(path.clone(), *reason, cx)
|
||||
.detach();
|
||||
};
|
||||
}
|
||||
BreakpointStoreEvent::BreakpointsCleared(paths) => {
|
||||
if let Some(local) = (!this.ignore_breakpoints)
|
||||
.then(|| this.as_local_mut())
|
||||
.flatten()
|
||||
{
|
||||
local.unset_breakpoints_from_paths(paths, cx).detach();
|
||||
}
|
||||
}
|
||||
BreakpointStoreEvent::ActiveDebugLineChanged => {}
|
||||
})
|
||||
.detach();
|
||||
|
||||
Session {
|
||||
mode: Mode::Local(mode),
|
||||
id: session_id,
|
||||
child_session_ids: HashSet::default(),
|
||||
parent_id: parent_session.map(|session| session.read(cx).id),
|
||||
variables: Default::default(),
|
||||
capabilities: Capabilities::default(),
|
||||
thread_states: ThreadStates::default(),
|
||||
output_token: OutputToken(0),
|
||||
ignore_breakpoints: false,
|
||||
output: circular_buffer::CircularBuffer::boxed(),
|
||||
requests: HashMap::default(),
|
||||
modules: Vec::default(),
|
||||
loaded_sources: Vec::default(),
|
||||
threads: IndexMap::default(),
|
||||
stack_frames: IndexMap::default(),
|
||||
locations: Default::default(),
|
||||
exception_breakpoints: Default::default(),
|
||||
_background_tasks,
|
||||
is_session_terminated: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +1,68 @@
|
||||
use std::{path::Path, sync::Arc};
|
||||
|
||||
use dap::client::DebugAdapterClient;
|
||||
use gpui::{App, AppContext, Subscription};
|
||||
use anyhow::Result;
|
||||
use dap::{DebugRequest, client::DebugAdapterClient};
|
||||
use gpui::{App, AppContext, Entity, Subscription, Task};
|
||||
use task::DebugTaskDefinition;
|
||||
|
||||
use super::session::{Session, SessionStateEvent};
|
||||
use crate::Project;
|
||||
|
||||
use super::session::Session;
|
||||
|
||||
pub fn intercept_debug_sessions<T: Fn(&Arc<DebugAdapterClient>) + 'static>(
|
||||
cx: &mut gpui::TestAppContext,
|
||||
configure: T,
|
||||
) -> Subscription {
|
||||
cx.update(|cx| {
|
||||
let configure = Arc::new(configure);
|
||||
cx.observe_new::<Session>(move |_, _, cx| {
|
||||
let configure = configure.clone();
|
||||
cx.subscribe_self(move |session, event, cx| {
|
||||
let configure = configure.clone();
|
||||
if matches!(event, SessionStateEvent::Running) {
|
||||
let client = session.adapter_client().unwrap();
|
||||
register_default_handlers(session, &client, cx);
|
||||
configure(&client);
|
||||
cx.background_spawn(async move {
|
||||
client
|
||||
.fake_event(dap::messages::Events::Initialized(
|
||||
Some(Default::default()),
|
||||
))
|
||||
.await
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
cx.observe_new::<Session>(move |session, _, cx| {
|
||||
let client = session.adapter_client().unwrap();
|
||||
register_default_handlers(session, &client, cx);
|
||||
configure(&client);
|
||||
cx.background_spawn(async move {
|
||||
client
|
||||
.fake_event(dap::messages::Events::Initialized(Some(Default::default())))
|
||||
.await
|
||||
})
|
||||
.detach();
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn start_debug_session_with<T: Fn(&Arc<DebugAdapterClient>) + 'static>(
|
||||
project: &Entity<Project>,
|
||||
cx: &mut gpui::TestAppContext,
|
||||
config: DebugTaskDefinition,
|
||||
configure: T,
|
||||
) -> Task<Result<Entity<Session>>> {
|
||||
let subscription = intercept_debug_sessions(cx, configure);
|
||||
let task = project.update(cx, |project, cx| project.start_debug_session(config, cx));
|
||||
cx.spawn(async move |_| {
|
||||
let result = task.await;
|
||||
drop(subscription);
|
||||
result
|
||||
})
|
||||
}
|
||||
|
||||
pub fn start_debug_session<T: Fn(&Arc<DebugAdapterClient>) + 'static>(
|
||||
project: &Entity<Project>,
|
||||
cx: &mut gpui::TestAppContext,
|
||||
configure: T,
|
||||
) -> Task<Result<Entity<Session>>> {
|
||||
start_debug_session_with(
|
||||
project,
|
||||
cx,
|
||||
DebugTaskDefinition {
|
||||
adapter: "fake-adapter".to_string(),
|
||||
request: DebugRequest::Launch(Default::default()),
|
||||
label: "test".to_string(),
|
||||
initialize_args: None,
|
||||
tcp_connection: None,
|
||||
stop_on_entry: None,
|
||||
},
|
||||
configure,
|
||||
)
|
||||
}
|
||||
|
||||
fn register_default_handlers(session: &Session, client: &Arc<DebugAdapterClient>, cx: &mut App) {
|
||||
client.on_request::<dap::requests::Initialize, _>(move |_, _| Ok(Default::default()));
|
||||
let paths = session
|
||||
|
||||
@@ -25,6 +25,7 @@ mod environment;
|
||||
use buffer_diff::BufferDiff;
|
||||
pub use environment::{EnvironmentErrorMessage, ProjectEnvironmentEvent};
|
||||
use git_store::{Repository, RepositoryId};
|
||||
use task::DebugTaskDefinition;
|
||||
pub mod search_history;
|
||||
mod yarn;
|
||||
|
||||
@@ -38,13 +39,17 @@ use client::{
|
||||
};
|
||||
use clock::ReplicaId;
|
||||
|
||||
use dap::client::DebugAdapterClient;
|
||||
use dap::{
|
||||
adapters::{DebugAdapterBinary, TcpArguments},
|
||||
client::DebugAdapterClient,
|
||||
};
|
||||
|
||||
use collections::{BTreeSet, HashMap, HashSet};
|
||||
use debounced_delay::DebouncedDelay;
|
||||
use debugger::{
|
||||
breakpoint_store::BreakpointStore,
|
||||
dap_store::{DapStore, DapStoreEvent},
|
||||
session::Session,
|
||||
};
|
||||
pub use environment::ProjectEnvironment;
|
||||
#[cfg(test)]
|
||||
@@ -92,6 +97,7 @@ use snippet::Snippet;
|
||||
use snippet_provider::SnippetProvider;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
net::Ipv4Addr,
|
||||
ops::Range,
|
||||
path::{Component, Path, PathBuf},
|
||||
pin::pin,
|
||||
@@ -101,7 +107,7 @@ use std::{
|
||||
};
|
||||
|
||||
use task_store::TaskStore;
|
||||
use terminals::Terminals;
|
||||
use terminals::{SshCommand, Terminals, wrap_for_ssh};
|
||||
use text::{Anchor, BufferId};
|
||||
use toolchain_store::EmptyToolchainStore;
|
||||
use util::{
|
||||
@@ -1066,9 +1072,8 @@ impl Project {
|
||||
let dap_store = cx.new(|cx| {
|
||||
DapStore::new_ssh(
|
||||
SSH_PROJECT_ID,
|
||||
ssh.clone(),
|
||||
ssh_proto.clone(),
|
||||
breakpoint_store.clone(),
|
||||
worktree_store.clone(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -1253,7 +1258,6 @@ impl Project {
|
||||
remote_id,
|
||||
client.clone().into(),
|
||||
breakpoint_store.clone(),
|
||||
worktree_store.clone(),
|
||||
cx,
|
||||
)
|
||||
})?;
|
||||
@@ -1459,6 +1463,79 @@ impl Project {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_debug_session(
|
||||
&mut self,
|
||||
definition: DebugTaskDefinition,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Entity<Session>>> {
|
||||
let Some(worktree) = self.worktrees(cx).find(|tree| tree.read(cx).is_visible()) else {
|
||||
return Task::ready(Err(anyhow!("Failed to find a worktree")));
|
||||
};
|
||||
|
||||
let ssh_client = self.ssh_client().clone();
|
||||
|
||||
let result = cx.spawn(async move |this, cx| {
|
||||
let mut binary = this
|
||||
.update(cx, |this, cx| {
|
||||
this.dap_store.update(cx, |dap_store, cx| {
|
||||
dap_store.get_debug_adapter_binary(definition.clone(), cx)
|
||||
})
|
||||
})?
|
||||
.await?;
|
||||
|
||||
if let Some(ssh_client) = ssh_client {
|
||||
let mut ssh_command = ssh_client.update(cx, |ssh, _| {
|
||||
anyhow::Ok(SshCommand {
|
||||
arguments: ssh
|
||||
.ssh_args()
|
||||
.ok_or_else(|| anyhow!("SSH arguments not found"))?,
|
||||
})
|
||||
})??;
|
||||
|
||||
let mut connection = None;
|
||||
if let Some(c) = binary.connection {
|
||||
let local_bind_addr = Ipv4Addr::new(127, 0, 0, 1);
|
||||
let port = dap::transport::TcpTransport::unused_port(local_bind_addr).await?;
|
||||
|
||||
ssh_command.add_port_forwarding(port, c.host.to_string(), c.port);
|
||||
connection = Some(TcpArguments {
|
||||
port: c.port,
|
||||
host: local_bind_addr,
|
||||
timeout: c.timeout,
|
||||
})
|
||||
}
|
||||
|
||||
let (program, args) = wrap_for_ssh(
|
||||
&ssh_command,
|
||||
Some((&binary.command, &binary.arguments)),
|
||||
binary.cwd.as_deref(),
|
||||
binary.envs,
|
||||
None,
|
||||
);
|
||||
|
||||
binary = DebugAdapterBinary {
|
||||
command: program,
|
||||
arguments: args,
|
||||
envs: HashMap::default(),
|
||||
cwd: None,
|
||||
connection,
|
||||
request_args: binary.request_args,
|
||||
}
|
||||
};
|
||||
|
||||
let ret = this
|
||||
.update(cx, |project, cx| {
|
||||
project.dap_store.update(cx, |dap_store, cx| {
|
||||
dap_store.new_session(binary, definition, worktree.downgrade(), None, cx)
|
||||
})
|
||||
})?
|
||||
.1
|
||||
.await;
|
||||
ret
|
||||
});
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub async fn example(
|
||||
root_paths: impl IntoIterator<Item = &Path>,
|
||||
|
||||
@@ -331,15 +331,19 @@ impl ProjectPanel {
|
||||
cx.subscribe(&project, |this, project, event, cx| match event {
|
||||
project::Event::ActiveEntryChanged(Some(entry_id)) => {
|
||||
if ProjectPanelSettings::get_global(cx).auto_reveal_entries {
|
||||
this.reveal_entry(project.clone(), *entry_id, true, cx);
|
||||
this.reveal_entry(project.clone(), *entry_id, true, cx).ok();
|
||||
}
|
||||
}
|
||||
project::Event::ActiveEntryChanged(None) => {
|
||||
this.marked_entries.clear();
|
||||
}
|
||||
project::Event::RevealInProjectPanel(entry_id) => {
|
||||
this.reveal_entry(project.clone(), *entry_id, false, cx);
|
||||
cx.emit(PanelEvent::Activate);
|
||||
if let Some(()) = this
|
||||
.reveal_entry(project.clone(), *entry_id, false, cx)
|
||||
.log_err()
|
||||
{
|
||||
cx.emit(PanelEvent::Activate);
|
||||
}
|
||||
}
|
||||
project::Event::ActivateProjectPanel => {
|
||||
cx.emit(PanelEvent::Activate);
|
||||
@@ -4422,7 +4426,7 @@ impl ProjectPanel {
|
||||
entry_id: ProjectEntryId,
|
||||
skip_ignored: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
) -> Result<()> {
|
||||
if let Some(worktree) = project.read(cx).worktree_for_entry(entry_id, cx) {
|
||||
let worktree = worktree.read(cx);
|
||||
if skip_ignored
|
||||
@@ -4430,7 +4434,9 @@ impl ProjectPanel {
|
||||
.entry_for_id(entry_id)
|
||||
.map_or(true, |entry| entry.is_ignored && !entry.is_always_included)
|
||||
{
|
||||
return;
|
||||
return Err(anyhow!(
|
||||
"can't reveal an ignored entry in the project panel"
|
||||
));
|
||||
}
|
||||
|
||||
let worktree_id = worktree.id();
|
||||
@@ -4443,6 +4449,11 @@ impl ProjectPanel {
|
||||
});
|
||||
self.autoscroll(cx);
|
||||
cx.notify();
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"can't reveal a non-existent entry in the project panel"
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1023,6 +1023,7 @@ impl Vim {
|
||||
}
|
||||
|
||||
pub fn cursor_shape(&self, cx: &mut App) -> CursorShape {
|
||||
let cursor_shape = VimSettings::get_global(cx).cursor_shape;
|
||||
match self.mode {
|
||||
Mode::Normal => {
|
||||
if let Some(operator) = self.operator_stack.last() {
|
||||
@@ -1040,18 +1041,18 @@ impl Vim {
|
||||
_ => CursorShape::Underline,
|
||||
}
|
||||
} else {
|
||||
// No operator active -> Block cursor
|
||||
CursorShape::Block
|
||||
cursor_shape.normal.unwrap_or(CursorShape::Block)
|
||||
}
|
||||
}
|
||||
Mode::Replace => CursorShape::Underline,
|
||||
Mode::HelixNormal | Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
|
||||
CursorShape::Block
|
||||
Mode::HelixNormal => cursor_shape.normal.unwrap_or(CursorShape::Block),
|
||||
Mode::Replace => cursor_shape.replace.unwrap_or(CursorShape::Underline),
|
||||
Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
|
||||
cursor_shape.visual.unwrap_or(CursorShape::Block)
|
||||
}
|
||||
Mode::Insert => {
|
||||
Mode::Insert => cursor_shape.insert.unwrap_or({
|
||||
let editor_settings = EditorSettings::get_global(cx);
|
||||
editor_settings.cursor_shape.unwrap_or_default()
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1693,6 +1694,27 @@ pub enum UseSystemClipboard {
|
||||
OnYank,
|
||||
}
|
||||
|
||||
/// The settings for cursor shape.
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
struct CursorShapeSettings {
|
||||
/// Cursor shape for the normal mode.
|
||||
///
|
||||
/// Default: block
|
||||
pub normal: Option<CursorShape>,
|
||||
/// Cursor shape for the replace mode.
|
||||
///
|
||||
/// Default: underline
|
||||
pub replace: Option<CursorShape>,
|
||||
/// Cursor shape for the visual mode.
|
||||
///
|
||||
/// Default: block
|
||||
pub visual: Option<CursorShape>,
|
||||
/// Cursor shape for the insert mode.
|
||||
///
|
||||
/// The default value follows the primary cursor_shape.
|
||||
pub insert: Option<CursorShape>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct VimSettings {
|
||||
pub default_mode: Mode,
|
||||
@@ -1702,6 +1724,7 @@ struct VimSettings {
|
||||
pub use_smartcase_find: bool,
|
||||
pub custom_digraphs: HashMap<String, Arc<str>>,
|
||||
pub highlight_on_yank_duration: u64,
|
||||
pub cursor_shape: CursorShapeSettings,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
|
||||
@@ -1713,6 +1736,7 @@ struct VimSettingsContent {
|
||||
pub use_smartcase_find: Option<bool>,
|
||||
pub custom_digraphs: Option<HashMap<String, Arc<str>>>,
|
||||
pub highlight_on_yank_duration: Option<u64>,
|
||||
pub cursor_shape: Option<CursorShapeSettings>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
|
||||
@@ -1771,6 +1795,7 @@ impl Settings for VimSettings {
|
||||
highlight_on_yank_duration: settings
|
||||
.highlight_on_yank_duration
|
||||
.ok_or_else(Self::missing_default)?,
|
||||
cursor_shape: settings.cursor_shape.ok_or_else(Self::missing_default)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use anyhow::{Result, anyhow};
|
||||
use gpui::{Context, Task};
|
||||
use project::TaskSourceKind;
|
||||
use remote::ConnectionState;
|
||||
use task::{DebugTaskDefinition, ResolvedTask, SpawnInTerminal, TaskContext, TaskTemplate};
|
||||
use task::{ResolvedTask, SpawnInTerminal, TaskContext, TaskTemplate};
|
||||
use ui::Window;
|
||||
|
||||
use crate::Workspace;
|
||||
@@ -109,26 +109,14 @@ impl Workspace {
|
||||
debug_config.definition
|
||||
};
|
||||
|
||||
workspace.update_in(cx, |workspace, window, cx| {
|
||||
workspace.start_debug_session(config, window, cx);
|
||||
})?;
|
||||
|
||||
project
|
||||
.update(cx, |project, cx| project.start_debug_session(config, cx))?
|
||||
.await?;
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
pub fn start_debug_session(
|
||||
&mut self,
|
||||
definition: DebugTaskDefinition,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let Some(provider) = self.debugger_provider.as_mut() {
|
||||
provider.start_session(definition, window, cx)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawn_in_terminal(
|
||||
self: &mut Workspace,
|
||||
spawn_in_terminal: SpawnInTerminal,
|
||||
|
||||
@@ -96,7 +96,7 @@ use std::{
|
||||
sync::{Arc, LazyLock, Weak, atomic::AtomicUsize},
|
||||
time::Duration,
|
||||
};
|
||||
use task::{DebugTaskDefinition, SpawnInTerminal};
|
||||
use task::SpawnInTerminal;
|
||||
use theme::{ActiveTheme, SystemAppearance, ThemeSettings};
|
||||
pub use toolbar::{Toolbar, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
|
||||
pub use ui;
|
||||
@@ -139,10 +139,6 @@ pub trait TerminalProvider {
|
||||
) -> Task<Result<ExitStatus>>;
|
||||
}
|
||||
|
||||
pub trait DebuggerProvider {
|
||||
fn start_session(&self, definition: DebugTaskDefinition, window: &mut Window, cx: &mut App);
|
||||
}
|
||||
|
||||
actions!(
|
||||
workspace,
|
||||
[
|
||||
@@ -864,7 +860,6 @@ pub struct Workspace {
|
||||
on_prompt_for_new_path: Option<PromptForNewPath>,
|
||||
on_prompt_for_open_path: Option<PromptForOpenPath>,
|
||||
terminal_provider: Option<Box<dyn TerminalProvider>>,
|
||||
debugger_provider: Option<Box<dyn DebuggerProvider>>,
|
||||
serializable_items_tx: UnboundedSender<Box<dyn SerializableItemHandle>>,
|
||||
serialized_ssh_project: Option<SerializedSshProject>,
|
||||
_items_serializer: Task<Result<()>>,
|
||||
@@ -1191,7 +1186,6 @@ impl Workspace {
|
||||
on_prompt_for_new_path: None,
|
||||
on_prompt_for_open_path: None,
|
||||
terminal_provider: None,
|
||||
debugger_provider: None,
|
||||
serializable_items_tx,
|
||||
_items_serializer,
|
||||
session_id: Some(session_id),
|
||||
@@ -1711,10 +1705,6 @@ impl Workspace {
|
||||
self.terminal_provider = Some(Box::new(provider));
|
||||
}
|
||||
|
||||
pub fn set_debugger_provider(&mut self, provider: impl DebuggerProvider + 'static) {
|
||||
self.debugger_provider = Some(Box::new(provider));
|
||||
}
|
||||
|
||||
pub fn serialized_ssh_project(&self) -> Option<SerializedSshProject> {
|
||||
self.serialized_ssh_project.clone()
|
||||
}
|
||||
|
||||
@@ -444,7 +444,7 @@ fn initialize_panels(
|
||||
window,
|
||||
async move |workspace: gpui::WeakEntity<Workspace>,
|
||||
cx: &mut AsyncWindowContext| {
|
||||
let debug_panel = DebugPanel::load(workspace.clone(), cx).await?;
|
||||
let debug_panel = DebugPanel::load(workspace.clone(), cx.clone()).await?;
|
||||
workspace.update_in(cx, |workspace, window, cx| {
|
||||
workspace.add_panel(debug_panel, window, cx);
|
||||
})?;
|
||||
|
||||
Reference in New Issue
Block a user