From ecfc0ef12dc0307fcd0601bf8fe006723975dae7 Mon Sep 17 00:00:00 2001 From: Remco Smits Date: Thu, 13 Feb 2025 22:25:32 +0100 Subject: [PATCH] Move requests into client (#112) * Move most of the requests into the client state itself. Co-authored-by: Anthony Eid * WIP * Fix some errors and create new errors * More teardown * Fix detach requests * Move set variable value to dap command * Fix dap command error and add evaluate command * FIx more compiler errors * Fix more compiler errors * Clipppyyyy * FIx more * One more * Fix one more * Use threadId from project instead u64 * Mostly fix stack frame list * More compile errors * More * WIP transfer completions to dap command Co-Authored-By: Anthony Eid <56899983+Anthony-Eid@users.noreply.github.com> Co-Authored-By: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> * Finish console completions DapCommand impl * Get Zed to build !! * Fix test compile errors: The debugger tests will still fail * Add threads reqeust to debug session Co-authored-by: Piotr Osiewicz --------- Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Co-authored-by: Anthony Eid Co-authored-by: Anthony Eid <56899983+Anthony-Eid@users.noreply.github.com> Co-authored-by: Piotr Osiewicz --- Cargo.lock | 1 + crates/collab/src/tests/debug_panel_tests.rs | 32 +- crates/dap/src/client.rs | 5 +- crates/dap/src/proto_conversions.rs | 152 +++- crates/debugger_tools/src/dap_log.rs | 15 +- crates/debugger_ui/src/attach_modal.rs | 12 +- crates/debugger_ui/src/console.rs | 145 ++- crates/debugger_ui/src/debugger_panel.rs | 195 ++-- crates/debugger_ui/src/debugger_panel_item.rs | 386 ++++---- crates/debugger_ui/src/loaded_source_list.rs | 6 +- crates/debugger_ui/src/module_list.rs | 6 +- crates/debugger_ui/src/stack_frame_list.rs | 251 +++--- .../debugger_ui/src/tests/debugger_panel.rs | 19 +- .../debugger_ui/src/tests/stack_frame_list.rs | 45 +- crates/debugger_ui/src/tests/variable_list.rs | 80 +- crates/debugger_ui/src/variable_list.rs | 343 ++----- crates/editor/src/editor_tests.rs | 2 +- crates/project/Cargo.toml | 1 + crates/project/src/debugger/dap_command.rs | 644 ++++++++++--- crates/project/src/debugger/dap_session.rs | 853 +++++++++++++++--- crates/project/src/debugger/dap_store.rs | 756 +++------------- crates/project/src/project.rs | 6 +- crates/proto/proto/zed.proto | 167 +++- crates/proto/src/proto.rs | 26 +- crates/remote_server/src/headless_project.rs | 2 +- crates/zed/src/zed.rs | 2 +- 26 files changed, 2386 insertions(+), 1766 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 94d8414491..e4d62324db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10228,6 +10228,7 @@ dependencies = [ "gpui", "http_client", "image", + "indexmap", "itertools 0.14.0", "language", "log", diff --git a/crates/collab/src/tests/debug_panel_tests.rs b/crates/collab/src/tests/debug_panel_tests.rs index 9379442b99..2da39f55e7 100644 --- a/crates/collab/src/tests/debug_panel_tests.rs +++ b/crates/collab/src/tests/debug_panel_tests.rs @@ -269,7 +269,7 @@ async fn test_debug_panel_item_opens_on_remote( debug_panel.update(cx, |this, cx| this.pane().unwrap().read(cx).items_len()) ); assert_eq!(client.id(), active_debug_panel_item.read(cx).client_id()); - assert_eq!(1, active_debug_panel_item.read(cx).thread_id()); + assert_eq!(1, active_debug_panel_item.read(cx).thread_id().0); }); let shutdown_client = host_project.update(host_cx, |project, cx| { @@ -368,7 +368,7 @@ async fn test_active_debug_panel_item_set_on_join_project( debug_panel.update(cx, |this, cx| this.pane().unwrap().read(cx).items_len()) ); assert_eq!(client.id(), active_debug_panel_item.read(cx).client_id()); - assert_eq!(1, active_debug_panel_item.read(cx).thread_id()); + assert_eq!(1, active_debug_panel_item.read(cx).thread_id().0); }); let shutdown_client = host_project.update(host_cx, |project, cx| { @@ -481,7 +481,7 @@ async fn test_debug_panel_remote_button_presses( debug_panel.update(cx, |this, cx| this.pane().unwrap().read(cx).items_len()) ); assert_eq!(client.id(), active_debug_panel_item.read(cx).client_id()); - assert_eq!(1, active_debug_panel_item.read(cx).thread_id()); + assert_eq!(1, active_debug_panel_item.read(cx).thread_id().0); active_debug_panel_item }); @@ -496,7 +496,7 @@ async fn test_debug_panel_remote_button_presses( debug_panel.update(cx, |this, cx| this.pane().unwrap().read(cx).items_len()) ); assert_eq!(client.id(), active_debug_panel_item.read(cx).client_id()); - assert_eq!(1, active_debug_panel_item.read(cx).thread_id()); + assert_eq!(1, active_debug_panel_item.read(cx).thread_id().0); active_debug_panel_item }); @@ -1251,7 +1251,9 @@ async fn test_module_list( debug_panel_item.update(cx, |item, cx| { assert_eq!( true, - item.capabilities(cx).supports_modules_request.unwrap(), + item.capabilities(cx) + .and_then(|caps| caps.supports_modules_request) + .unwrap(), "Local supports modules request should be true" ); @@ -1279,7 +1281,9 @@ async fn test_module_list( debug_panel_item.update(cx, |item, cx| { assert_eq!( true, - item.capabilities(cx).supports_modules_request.unwrap(), + item.capabilities(cx) + .and_then(|caps| caps.supports_modules_request) + .unwrap(), "Remote capabilities supports modules request should be true" ); let remote_module_list = item.module_list().update(cx, |list, cx| list.modules(cx)); @@ -1310,7 +1314,9 @@ async fn test_module_list( debug_panel_item.update(cx, |item, cx| { assert_eq!( true, - item.capabilities(cx).supports_modules_request.unwrap(), + item.capabilities(cx) + .and_then(|caps| caps.supports_modules_request) + .unwrap(), "Remote (mid session join) capabilities supports modules request should be true" ); let remote_module_list = item.module_list().update(cx, |list, cx| list.modules(cx)); @@ -1950,9 +1956,7 @@ async fn test_ignore_breakpoints( let session_id = debug_panel.update(cx, |this, cx| { this.dap_store() .read(cx) - .as_remote() - .unwrap() - .session_by_client_id(&client.id()) + .session_by_client_id(client.id()) .unwrap() .read(cx) .id() @@ -1966,7 +1970,7 @@ async fn test_ignore_breakpoints( ); assert_eq!(false, breakpoints_ignored); assert_eq!(client.id(), active_debug_panel_item.read(cx).client_id()); - assert_eq!(1, active_debug_panel_item.read(cx).thread_id()); + assert_eq!(1, active_debug_panel_item.read(cx).thread_id().0); active_debug_panel_item }); @@ -2004,7 +2008,7 @@ async fn test_ignore_breakpoints( active_debug_panel_item.read(cx).are_breakpoints_ignored(cx) ); assert_eq!(client.id(), active_debug_panel_item.read(cx).client_id()); - assert_eq!(1, active_debug_panel_item.read(cx).thread_id()); + assert_eq!(1, active_debug_panel_item.read(cx).thread_id().0); active_debug_panel_item }); @@ -2064,7 +2068,7 @@ async fn test_ignore_breakpoints( assert_eq!(true, breakpoints_ignored); assert_eq!(client.id(), debug_panel.client_id()); - assert_eq!(1, debug_panel.thread_id()); + assert_eq!(1, debug_panel.thread_id().0); }); client @@ -2135,7 +2139,7 @@ async fn test_ignore_breakpoints( debug_panel.update(cx, |this, cx| this.pane().unwrap().read(cx).items_len()) ); assert_eq!(client.id(), active_debug_panel_item.read(cx).client_id()); - assert_eq!(1, active_debug_panel_item.read(cx).thread_id()); + assert_eq!(1, active_debug_panel_item.read(cx).thread_id().0); active_debug_panel_item }); diff --git a/crates/dap/src/client.rs b/crates/dap/src/client.rs index 2d78a453b7..9499a83c20 100644 --- a/crates/dap/src/client.rs +++ b/crates/dap/src/client.rs @@ -27,11 +27,11 @@ const DAP_REQUEST_TIMEOUT: Duration = Duration::from_secs(12); #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] -pub struct DebugAdapterClientId(pub usize); +pub struct DebugAdapterClientId(pub u32); impl DebugAdapterClientId { pub fn from_proto(client_id: u64) -> Self { - Self(client_id as usize) + Self(client_id as u32) } pub fn to_proto(&self) -> u64 { @@ -39,6 +39,7 @@ impl DebugAdapterClientId { } } +/// Represents a connection to the debug adapter process, either via stdout/stdin or a socket. pub struct DebugAdapterClient { id: DebugAdapterClientId, sequence_count: AtomicU64, diff --git a/crates/dap/src/proto_conversions.rs b/crates/dap/src/proto_conversions.rs index 440cc182d2..153247dfc9 100644 --- a/crates/dap/src/proto_conversions.rs +++ b/crates/dap/src/proto_conversions.rs @@ -1,7 +1,8 @@ use anyhow::{anyhow, Result}; use client::proto::{ - self, DapChecksum, DapChecksumAlgorithm, DapModule, DapScope, DapScopePresentationHint, - DapSource, DapSourcePresentationHint, DapStackFrame, DapVariable, SetDebugClientCapabilities, + self, DapChecksum, DapChecksumAlgorithm, DapEvaluateContext, DapModule, DapScope, + DapScopePresentationHint, DapSource, DapSourcePresentationHint, DapStackFrame, DapVariable, + SetDebugClientCapabilities, }; use dap_types::{ Capabilities, OutputEventCategory, OutputEventGroup, ScopePresentationHint, Source, @@ -490,3 +491,150 @@ impl ProtoConversion for dap_types::OutputEventGroup { } } } + +impl ProtoConversion for dap_types::CompletionItem { + type ProtoType = proto::DapCompletionItem; + type Output = Self; + + fn to_proto(&self) -> Self::ProtoType { + proto::DapCompletionItem { + label: self.label.clone(), + text: self.text.clone(), + detail: self.detail.clone(), + typ: self + .type_ + .as_ref() + .map(ProtoConversion::to_proto) + .map(|typ| typ.into()), + start: self.start, + length: self.length, + selection_start: self.selection_start, + selection_length: self.selection_length, + sort_text: self.sort_text.clone(), + } + } + + fn from_proto(payload: Self::ProtoType) -> Self { + let typ = payload.typ(); // todo(debugger): This might be a potential issue/bug because it defaults to a type when it's None + + Self { + label: payload.label, + detail: payload.detail, + sort_text: payload.sort_text, + text: payload.text.clone(), + type_: Some(dap_types::CompletionItemType::from_proto(typ)), + start: payload.start, + length: payload.length, + selection_start: payload.selection_start, + selection_length: payload.selection_length, + } + } +} + +impl ProtoConversion for dap_types::EvaluateArgumentsContext { + type ProtoType = DapEvaluateContext; + type Output = Self; + + fn to_proto(&self) -> Self::ProtoType { + match self { + dap_types::EvaluateArgumentsContext::Variables => { + proto::DapEvaluateContext::EvaluateVariables + } + dap_types::EvaluateArgumentsContext::Watch => proto::DapEvaluateContext::Watch, + dap_types::EvaluateArgumentsContext::Hover => proto::DapEvaluateContext::Hover, + dap_types::EvaluateArgumentsContext::Repl => proto::DapEvaluateContext::Repl, + dap_types::EvaluateArgumentsContext::Clipboard => proto::DapEvaluateContext::Clipboard, + dap_types::EvaluateArgumentsContext::Unknown => { + proto::DapEvaluateContext::EvaluateUnknown + } + _ => proto::DapEvaluateContext::EvaluateUnknown, + } + } + + fn from_proto(payload: Self::ProtoType) -> Self { + match payload { + proto::DapEvaluateContext::EvaluateVariables => { + dap_types::EvaluateArgumentsContext::Variables + } + proto::DapEvaluateContext::Watch => dap_types::EvaluateArgumentsContext::Watch, + proto::DapEvaluateContext::Hover => dap_types::EvaluateArgumentsContext::Hover, + proto::DapEvaluateContext::Repl => dap_types::EvaluateArgumentsContext::Repl, + proto::DapEvaluateContext::Clipboard => dap_types::EvaluateArgumentsContext::Clipboard, + proto::DapEvaluateContext::EvaluateUnknown => { + dap_types::EvaluateArgumentsContext::Unknown + } + } + } +} + +impl ProtoConversion for dap_types::CompletionItemType { + type ProtoType = proto::DapCompletionItemType; + type Output = Self; + + fn to_proto(&self) -> Self::ProtoType { + match self { + dap_types::CompletionItemType::Class => proto::DapCompletionItemType::Class, + dap_types::CompletionItemType::Color => proto::DapCompletionItemType::Color, + dap_types::CompletionItemType::Constructor => proto::DapCompletionItemType::Constructor, + dap_types::CompletionItemType::Customcolor => proto::DapCompletionItemType::Customcolor, + dap_types::CompletionItemType::Enum => proto::DapCompletionItemType::Enum, + dap_types::CompletionItemType::Field => proto::DapCompletionItemType::Field, + dap_types::CompletionItemType::File => proto::DapCompletionItemType::CompletionItemFile, + dap_types::CompletionItemType::Function => proto::DapCompletionItemType::Function, + dap_types::CompletionItemType::Interface => proto::DapCompletionItemType::Interface, + dap_types::CompletionItemType::Keyword => proto::DapCompletionItemType::Keyword, + dap_types::CompletionItemType::Method => proto::DapCompletionItemType::Method, + dap_types::CompletionItemType::Module => proto::DapCompletionItemType::Module, + dap_types::CompletionItemType::Property => proto::DapCompletionItemType::Property, + dap_types::CompletionItemType::Reference => proto::DapCompletionItemType::Reference, + dap_types::CompletionItemType::Snippet => proto::DapCompletionItemType::Snippet, + dap_types::CompletionItemType::Text => proto::DapCompletionItemType::Text, + dap_types::CompletionItemType::Unit => proto::DapCompletionItemType::Unit, + dap_types::CompletionItemType::Value => proto::DapCompletionItemType::Value, + dap_types::CompletionItemType::Variable => proto::DapCompletionItemType::Variable, + } + } + + fn from_proto(payload: Self::ProtoType) -> Self { + match payload { + proto::DapCompletionItemType::Class => dap_types::CompletionItemType::Class, + proto::DapCompletionItemType::Color => dap_types::CompletionItemType::Color, + proto::DapCompletionItemType::CompletionItemFile => dap_types::CompletionItemType::File, + proto::DapCompletionItemType::Constructor => dap_types::CompletionItemType::Constructor, + proto::DapCompletionItemType::Customcolor => dap_types::CompletionItemType::Customcolor, + proto::DapCompletionItemType::Enum => dap_types::CompletionItemType::Enum, + proto::DapCompletionItemType::Field => dap_types::CompletionItemType::Field, + proto::DapCompletionItemType::Function => dap_types::CompletionItemType::Function, + proto::DapCompletionItemType::Interface => dap_types::CompletionItemType::Interface, + proto::DapCompletionItemType::Keyword => dap_types::CompletionItemType::Keyword, + proto::DapCompletionItemType::Method => dap_types::CompletionItemType::Method, + proto::DapCompletionItemType::Module => dap_types::CompletionItemType::Module, + proto::DapCompletionItemType::Property => dap_types::CompletionItemType::Property, + proto::DapCompletionItemType::Reference => dap_types::CompletionItemType::Reference, + proto::DapCompletionItemType::Snippet => dap_types::CompletionItemType::Snippet, + proto::DapCompletionItemType::Text => dap_types::CompletionItemType::Text, + proto::DapCompletionItemType::Unit => dap_types::CompletionItemType::Unit, + proto::DapCompletionItemType::Value => dap_types::CompletionItemType::Value, + proto::DapCompletionItemType::Variable => dap_types::CompletionItemType::Variable, + } + } +} + +impl ProtoConversion for dap_types::Thread { + type ProtoType = proto::DapThread; + type Output = Self; + + fn to_proto(&self) -> Self::ProtoType { + proto::DapThread { + id: self.id, + name: self.name.clone(), + } + } + + fn from_proto(payload: Self::ProtoType) -> Self { + Self { + id: payload.id, + name: payload.name, + } + } +} diff --git a/crates/debugger_tools/src/dap_log.rs b/crates/debugger_tools/src/dap_log.rs index 3e2f70cbba..14f20ffd4b 100644 --- a/crates/debugger_tools/src/dap_log.rs +++ b/crates/debugger_tools/src/dap_log.rs @@ -174,9 +174,13 @@ impl LogStore { this.add_debug_client( *client_id, project.update(cx, |project, cx| { - project - .dap_store() - .update(cx, |store, cx| store.client_by_id(client_id, cx)) + project.dap_store().update(cx, |store, cx| { + store.client_by_id(client_id, cx).and_then( + |(session, client)| { + Some((session, client.read(cx).adapter_client()?)) + }, + ) + }) }), ); } @@ -587,9 +591,8 @@ impl DapLogView { session_name: session.read(cx).name(), clients: { let mut clients = session - .read(cx) - .as_local()? - .clients() + .read_with(cx, |session, cx| session.clients(cx)) + .iter() .map(|client| DapMenuSubItem { client_id: client.id(), client_name: client.adapter_id(), diff --git a/crates/debugger_ui/src/attach_modal.rs b/crates/debugger_ui/src/attach_modal.rs index b3aa65d5a3..98fe4c91be 100644 --- a/crates/debugger_ui/src/attach_modal.rs +++ b/crates/debugger_ui/src/attach_modal.rs @@ -53,14 +53,14 @@ pub(crate) struct AttachModal { impl AttachModal { pub fn new( session_id: &DebugSessionId, - client_id: &DebugAdapterClientId, + client_id: DebugAdapterClientId, dap_store: Entity, window: &mut Window, cx: &mut Context, ) -> Self { let picker = cx.new(|cx| { Picker::uniform_list( - AttachModalDelegate::new(*session_id, *client_id, dap_store), + AttachModalDelegate::new(*session_id, client_id, dap_store), window, cx, ) @@ -130,8 +130,10 @@ impl PickerDelegate for AttachModalDelegate { if let Some(processes) = this.delegate.candidates.clone() { processes } else { - let Some((_, client)) = this.delegate.dap_store.update(cx, |store, cx| { - store.client_by_id(&this.delegate.client_id, cx) + let Some(client) = this.delegate.dap_store.update(cx, |store, cx| { + store + .client_by_id(&this.delegate.client_id, cx) + .and_then(|(_, client)| client.read(cx).adapter_client()) }) else { return Vec::new(); }; @@ -222,7 +224,7 @@ impl PickerDelegate for AttachModalDelegate { self.dap_store.update(cx, |store, cx| { store - .attach(&self.session_id, &self.client_id, candidate.pid, cx) + .attach(&self.session_id, self.client_id, candidate.pid, cx) .detach_and_log_err(cx); }); diff --git a/crates/debugger_ui/src/console.rs b/crates/debugger_ui/src/console.rs index 3af5404567..c0d2275fd7 100644 --- a/crates/debugger_ui/src/console.rs +++ b/crates/debugger_ui/src/console.rs @@ -2,27 +2,24 @@ use crate::{ stack_frame_list::{StackFrameList, StackFrameListEvent}, variable_list::VariableList, }; -use dap::{ - client::DebugAdapterClientId, proto_conversions::ProtoConversion, OutputEvent, OutputEventGroup, -}; +use anyhow::anyhow; +use dap::{client::DebugAdapterClientId, OutputEvent, OutputEventGroup}; use editor::{ display_map::{Crease, CreaseId}, Anchor, CompletionProvider, Editor, EditorElement, EditorStyle, FoldPlaceholder, }; use fuzzy::StringMatchCandidate; use gpui::{Context, Entity, Render, Subscription, Task, TextStyle, WeakEntity}; -use language::{Buffer, CodeLabel, LanguageServerId, ToOffsetUtf16}; +use language::{Buffer, CodeLabel, LanguageServerId}; use menu::Confirm; use project::{ - debugger::{dap_session::DebugSession, dap_store::DapStore}, + debugger::dap_session::{CompletionsQuery, DebugSession}, Completion, }; -use rpc::proto; use settings::Settings; -use std::{cell::RefCell, collections::HashMap, rc::Rc, sync::Arc}; +use std::{cell::RefCell, collections::HashMap, rc::Rc, sync::Arc, usize}; use theme::ThemeSettings; use ui::{prelude::*, ButtonLike, Disclosure, ElevationIndex}; -use util::ResultExt; pub struct OutputGroup { pub start: Anchor, @@ -36,7 +33,6 @@ pub struct Console { groups: Vec, console: Entity, query_bar: Entity, - dap_store: Entity, session: Entity, client_id: DebugAdapterClientId, _subscriptions: Vec, @@ -47,8 +43,7 @@ pub struct Console { impl Console { pub fn new( session: Entity, - client_id: &DebugAdapterClientId, - dap_store: Entity, + client_id: DebugAdapterClientId, stack_frame_list: Entity, variable_list: Entity, window: &mut Window, @@ -91,12 +86,11 @@ impl Console { Self { session, console, - dap_store, query_bar, variable_list, _subscriptions, stack_frame_list, - client_id: *client_id, + client_id, groups: Vec::default(), } } @@ -111,8 +105,9 @@ impl Console { &self.query_bar } - fn is_local(&self, cx: &Context) -> bool { - self.dap_store.read(cx).as_local().is_some() + fn is_local(&self, _cx: &Context) -> bool { + // todo(debugger): Fix this function + true } fn handle_stack_frame_list_events( @@ -128,21 +123,6 @@ impl Console { } pub fn add_message(&mut self, event: OutputEvent, window: &mut Window, cx: &mut Context) { - if let Some((client, project_id)) = self.dap_store.read(cx).downstream_client() { - client - .send(proto::UpdateDebugAdapter { - project_id: *project_id, - client_id: self.client_id.to_proto(), - thread_id: None, - session_id: self.session.read(cx).id().to_proto(), - variant: Some(proto::update_debug_adapter::Variant::OutputEvent( - event.to_proto(), - )), - }) - .log_err(); - return; - } - self.console.update(cx, |console, cx| { let output = event.output.trim_end().to_string(); @@ -254,45 +234,49 @@ impl Console { expression }); - let evaluate_task = self.dap_store.update(cx, |store, cx| { - store.evaluate( - &self.client_id, - self.stack_frame_list.read(cx).current_stack_frame_id(), + let Some(client_state) = self.session.read(cx).client_state(self.client_id) else { + return; + }; + + client_state.update(cx, |state, cx| { + state.evaluate( expression, - dap::EvaluateArgumentsContext::Variables, + Some(dap::EvaluateArgumentsContext::Variables), + Some(self.stack_frame_list.read(cx).current_stack_frame_id()), None, cx, - ) + ); }); - let weak_console = cx.weak_entity(); + // TODO(debugger): make this work again + // let weak_console = cx.weak_entity(); - window - .spawn(cx, |mut cx| async move { - let response = evaluate_task.await?; + // window + // .spawn(cx, |mut cx| async move { + // let response = evaluate_task.await?; - weak_console.update_in(&mut cx, |console, window, cx| { - console.add_message( - OutputEvent { - category: None, - output: response.result, - group: None, - variables_reference: Some(response.variables_reference), - source: None, - line: None, - column: None, - data: None, - }, - window, - cx, - ); + // weak_console.update_in(&mut cx, |console, window, cx| { + // console.add_message( + // OutputEvent { + // category: None, + // output: response.result, + // group: None, + // variables_reference: Some(response.variables_reference), + // source: None, + // line: None, + // column: None, + // data: None, + // }, + // window, + // cx, + // ); - console.variable_list.update(cx, |variable_list, cx| { - variable_list.invalidate(window, cx); - }) - }) - }) - .detach_and_log_err(cx); + // console.variable_list.update(cx, |variable_list, cx| { + // variable_list.invalidate(window, cx); + // }) + // }) + // }) + // .detach_and_log_err(cx); } fn render_console(&self, cx: &Context) -> impl IntoElement { @@ -383,9 +367,10 @@ impl CompletionProvider for ConsoleQueryBarCompletionProvider { let support_completions = console .read(cx) - .dap_store + .session .read(cx) - .capabilities_by_id(&console.read(cx).client_id, cx) + .client_state(console.read(cx).client_id) + .map(|state| state.read(cx).capabilities()) .map(|caps| caps.supports_completions_request) .flatten() .unwrap_or_default(); @@ -470,7 +455,6 @@ impl ConsoleQueryBarCompletionProvider { }); let query = buffer.read(cx).text(); - let start_position = buffer.read(cx).anchor_before(0); cx.spawn(|_, cx| async move { let matches = fuzzy::match_strings( @@ -489,14 +473,14 @@ impl ConsoleQueryBarCompletionProvider { let variable_value = variables.get(&string_match.string)?; Some(project::Completion { - old_range: start_position..buffer_position, + old_range: buffer_position..buffer_position, new_text: string_match.string.clone(), label: CodeLabel { filter_range: 0..string_match.string.len(), text: format!("{} {}", string_match.string.clone(), variable_value), runs: Vec::new(), }, - server_id: LanguageServerId(0), // TODO debugger: read from client + server_id: LanguageServerId(usize::MAX), documentation: None, lsp_completion: Default::default(), confirm: None, @@ -514,20 +498,21 @@ impl ConsoleQueryBarCompletionProvider { buffer_position: language::Anchor, cx: &mut Context, ) -> gpui::Task>> { - let text = buffer.read(cx).text(); - let start_position = buffer.read(cx).anchor_before(0); - let snapshot = buffer.read(cx).snapshot(); + let client_id = console.read(cx).client_id; let completion_task = console.update(cx, |console, cx| { - console.dap_store.update(cx, |store, cx| { - store.completions( - &console.client_id, - console.stack_frame_list.read(cx).current_stack_frame_id(), - text, - buffer_position.to_offset_utf16(&snapshot).0 as u64, - cx, - ) - }) + if let Some(client_state) = console.session.read(cx).client_state(client_id) { + client_state.update(cx, |state, cx| { + let frame_id = Some(console.stack_frame_list.read(cx).current_stack_frame_id()); + + state.completions( + CompletionsQuery::new(buffer.read(cx), buffer_position, frame_id), + cx, + ) + }) + } else { + Task::ready(Err(anyhow!("failed to fetch completions"))) + } }); cx.background_executor().spawn(async move { @@ -535,14 +520,14 @@ impl ConsoleQueryBarCompletionProvider { .await? .iter() .map(|completion| project::Completion { - old_range: start_position..buffer_position, + old_range: buffer_position..buffer_position, // TODO(debugger): change this new_text: completion.text.clone().unwrap_or(completion.label.clone()), label: CodeLabel { filter_range: 0..completion.label.len(), text: completion.label.clone(), runs: Vec::new(), }, - server_id: LanguageServerId(0), // TODO debugger: read from client + server_id: LanguageServerId(usize::MAX), documentation: None, lsp_completion: Default::default(), confirm: None, diff --git a/crates/debugger_ui/src/debugger_panel.rs b/crates/debugger_ui/src/debugger_panel.rs index fab1f9d27e..7ba512592b 100644 --- a/crates/debugger_ui/src/debugger_panel.rs +++ b/crates/debugger_ui/src/debugger_panel.rs @@ -16,8 +16,10 @@ use gpui::{ Focusable, Subscription, Task, WeakEntity, }; use project::{ - debugger::dap_session::DebugSessionId, - debugger::dap_store::{DapStore, DapStoreEvent}, + debugger::{ + dap_session::{DebugSessionId, ThreadId}, + dap_store::{DapStore, DapStoreEvent}, + }, terminals::TerminalKind, }; use rpc::proto::{self, SetDebuggerPanelItem, UpdateDebugAdapter}; @@ -118,7 +120,7 @@ pub struct DebugPanel { workspace: WeakEntity, _subscriptions: Vec, message_queue: HashMap>, - thread_states: BTreeMap<(DebugAdapterClientId, u64), Entity>, + thread_states: BTreeMap<(DebugAdapterClientId, ThreadId), Entity>, } impl DebugPanel { @@ -157,7 +159,7 @@ impl DebugPanel { cx.subscribe_in(&project, window, { move |this: &mut Self, _, event, window, cx| match event { project::Event::DebugClientStarted((session_id, client_id)) => { - this.handle_debug_client_started(session_id, client_id, window, cx); + this.handle_debug_client_started(session_id, *client_id, window, cx); } project::Event::DebugClientEvent { session_id, @@ -166,14 +168,14 @@ impl DebugPanel { } => match message { Message::Event(event) => { this.handle_debug_client_events( - session_id, client_id, event, window, cx, + session_id, *client_id, event, window, cx, ); } Message::Request(request) => { if StartDebugging::COMMAND == request.command { this.handle_start_debugging_request( session_id, - client_id, + *client_id, request.seq, request.arguments.clone(), cx, @@ -181,7 +183,7 @@ impl DebugPanel { } else if RunInTerminal::COMMAND == request.command { this.handle_run_in_terminal_request( session_id, - client_id, + *client_id, request.seq, request.arguments.clone(), window, @@ -335,8 +337,8 @@ impl DebugPanel { pub fn debug_panel_item_by_client( &self, - client_id: &DebugAdapterClientId, - thread_id: u64, + client_id: DebugAdapterClientId, + thread_id: ThreadId, cx: &mut Context, ) -> Option> { self.pane @@ -346,7 +348,7 @@ impl DebugPanel { .find(|item| { let item = item.read(cx); - &item.client_id() == client_id && item.thread_id() == thread_id + item.client_id() == client_id && item.thread_id() == thread_id }) } @@ -362,17 +364,23 @@ impl DebugPanel { let thread_panel = item.downcast::().unwrap(); let thread_id = thread_panel.read(cx).thread_id(); - let session_id = thread_panel.read(cx).session().read(cx).id(); let client_id = thread_panel.read(cx).client_id(); self.thread_states.remove(&(client_id, thread_id)); cx.notify(); - self.dap_store.update(cx, |store, cx| { - store - .terminate_threads(&session_id, &client_id, Some(vec![thread_id; 1]), cx) - .detach() + let Some(client_state) = thread_panel + .read(cx) + .session() + .read(cx) + .client_state(client_id) + else { + return; + }; + + client_state.update(cx, |state, cx| { + state.terminate_threads(Some(vec![thread_id; 1]), cx); }); } pane::Event::Remove { .. } => cx.emit(PanelEvent::Close), @@ -405,7 +413,7 @@ impl DebugPanel { fn handle_start_debugging_request( &mut self, session_id: &DebugSessionId, - client_id: &DebugAdapterClientId, + client_id: DebugAdapterClientId, seq: u64, request_args: Option, cx: &mut Context, @@ -426,7 +434,7 @@ impl DebugPanel { fn handle_run_in_terminal_request( &mut self, session_id: &DebugSessionId, - client_id: &DebugAdapterClientId, + client_id: DebugAdapterClientId, seq: u64, request_args: Option, window: &mut Window, @@ -519,7 +527,7 @@ impl DebugPanel { }); let session_id = *session_id; - let client_id = *client_id; + cx.spawn(|this, mut cx| async move { // Ensure a response is always sent, even in error cases, // to maintain proper communication with the debug adapter @@ -577,14 +585,7 @@ impl DebugPanel { let respond_task = this.update(&mut cx, |this, cx| { this.dap_store.update(cx, |store, cx| { - store.respond_to_run_in_terminal( - &session_id, - &client_id, - success, - seq, - body, - cx, - ) + store.respond_to_run_in_terminal(&session_id, client_id, success, seq, body, cx) }) }); @@ -596,7 +597,7 @@ impl DebugPanel { fn handle_debug_client_started( &self, session_id: &DebugSessionId, - client_id: &DebugAdapterClientId, + client_id: DebugAdapterClientId, window: &mut Window, cx: &mut Context, ) { @@ -610,14 +611,12 @@ impl DebugPanel { }; let session_id = *session_id; - let client_id = *client_id; let workspace = self.workspace.clone(); let request_type = session.configuration().request.clone(); cx.spawn_in(window, |this, mut cx| async move { let task = this.update(&mut cx, |this, cx| { - this.dap_store.update(cx, |store, cx| { - store.initialize(&session_id, &client_id, cx) - }) + this.dap_store + .update(cx, |store, cx| store.initialize(&session_id, client_id, cx)) })?; task.await?; @@ -626,7 +625,7 @@ impl DebugPanel { DebugRequestType::Launch => { let task = this.update(&mut cx, |this, cx| { this.dap_store - .update(cx, |store, cx| store.launch(&session_id, &client_id, cx)) + .update(cx, |store, cx| store.launch(&session_id, client_id, cx)) }); task?.await @@ -635,7 +634,7 @@ impl DebugPanel { if let Some(process_id) = config.process_id { let task = this.update(&mut cx, |this, cx| { this.dap_store.update(cx, |store, cx| { - store.attach(&session_id, &client_id, process_id, cx) + store.attach(&session_id, client_id, process_id, cx) }) })?; @@ -646,7 +645,7 @@ impl DebugPanel { workspace.toggle_modal(window, cx, |window, cx| { AttachModal::new( &session_id, - &client_id, + client_id, this.dap_store.clone(), window, cx, @@ -680,28 +679,28 @@ impl DebugPanel { fn handle_debug_client_events( &mut self, session_id: &DebugSessionId, - client_id: &DebugAdapterClientId, + client_id: DebugAdapterClientId, event: &Events, window: &mut Window, cx: &mut Context, ) { match event { Events::Initialized(event) => { - self.handle_initialized_event(&session_id, &client_id, event, cx) + self.handle_initialized_event(&session_id, client_id, event, cx) } Events::Stopped(event) => { - self.handle_stopped_event(&session_id, &client_id, event, window, cx) + self.handle_stopped_event(&session_id, client_id, event, window, cx) } - Events::Continued(event) => self.handle_continued_event(&client_id, event, cx), - Events::Exited(event) => self.handle_exited_event(&client_id, event, cx), + Events::Continued(event) => self.handle_continued_event(client_id, event, cx), + Events::Exited(event) => self.handle_exited_event(client_id, event, cx), Events::Terminated(event) => { - self.handle_terminated_event(&session_id, &client_id, event, cx) + self.handle_terminated_event(&session_id, client_id, event, cx) } - Events::Thread(event) => self.handle_thread_event(&client_id, event, cx), - Events::Output(event) => self.handle_output_event(&client_id, event, cx), + Events::Thread(event) => self.handle_thread_event(client_id, event, cx), + Events::Output(event) => self.handle_output_event(client_id, event, cx), Events::Breakpoint(_) => {} - Events::Module(event) => self.handle_module_event(&client_id, event, cx), - Events::LoadedSource(event) => self.handle_loaded_source_event(&client_id, event, cx), + Events::Module(event) => self.handle_module_event(client_id, event, cx), + Events::LoadedSource(event) => self.handle_loaded_source_event(client_id, event, cx), Events::Capabilities(event) => { self.handle_capabilities_changed_event(session_id, client_id, event, cx); } @@ -718,26 +717,25 @@ impl DebugPanel { fn handle_initialized_event( &mut self, session_id: &DebugSessionId, - client_id: &DebugAdapterClientId, + client_id: DebugAdapterClientId, capabilities: &Option, cx: &mut Context, ) { if let Some(capabilities) = capabilities { self.dap_store.update(cx, |store, cx| { - store.update_capabilities_for_client(&session_id, &client_id, capabilities, cx); + store.update_capabilities_for_client(&session_id, client_id, capabilities, cx); }); - cx.emit(DebugPanelEvent::CapabilitiesChanged(*client_id)); + cx.emit(DebugPanelEvent::CapabilitiesChanged(client_id)); } let session_id = *session_id; - let client_id = *client_id; cx.spawn(|this, mut cx| async move { this.update(&mut cx, |debug_panel, cx| { debug_panel.workspace.update(cx, |workspace, cx| { workspace.project().update(cx, |project, cx| { - project.initial_send_breakpoints(&session_id, &client_id, cx) + project.initial_send_breakpoints(&session_id, client_id, cx) }) }) })?? @@ -746,7 +744,7 @@ impl DebugPanel { this.update(&mut cx, |debug_panel, cx| { debug_panel .dap_store - .update(cx, |store, cx| store.configuration_done(&client_id, cx)) + .update(cx, |store, cx| store.configuration_done(client_id, cx)) })? .await }) @@ -755,22 +753,22 @@ impl DebugPanel { fn handle_continued_event( &mut self, - client_id: &DebugAdapterClientId, + client_id: DebugAdapterClientId, event: &ContinuedEvent, cx: &mut Context, ) { - cx.emit(DebugPanelEvent::Continued((*client_id, event.clone()))); + cx.emit(DebugPanelEvent::Continued((client_id, event.clone()))); } fn handle_stopped_event( &mut self, session_id: &DebugSessionId, - client_id: &DebugAdapterClientId, + client_id: DebugAdapterClientId, event: &StoppedEvent, window: &mut Window, cx: &mut Context, ) { - let Some(thread_id) = event.thread_id else { + let Some(thread_id) = event.thread_id.map(|thread_id| ThreadId(thread_id)) else { return; }; @@ -783,8 +781,6 @@ impl DebugPanel { return; // this can/should never happen }; - let client_id = *client_id; - cx.spawn_in(window, { let event = event.clone(); |this, mut cx| async move { @@ -800,17 +796,16 @@ impl DebugPanel { thread_state.status = ThreadStatus::Stopped; }); - let existing_item = this.debug_panel_item_by_client(&client_id, thread_id, cx); + let existing_item = this.debug_panel_item_by_client(client_id, thread_id, cx); if existing_item.is_none() { let debug_panel = cx.entity(); this.pane.update(cx, |pane, cx| { let tab = cx.new(|cx| { DebugPanelItem::new( session, - &client_id, + client_id, thread_id, thread_state, - this.dap_store.clone(), &debug_panel, this.workspace.clone(), window, @@ -858,13 +853,13 @@ impl DebugPanel { fn handle_thread_event( &mut self, - client_id: &DebugAdapterClientId, + client_id: DebugAdapterClientId, event: &ThreadEvent, cx: &mut Context, ) { - let thread_id = event.thread_id; + let thread_id = ThreadId(event.thread_id); - if let Some(thread_state) = self.thread_states.get(&(*client_id, thread_id)) { + if let Some(thread_state) = self.thread_states.get(&(client_id, thread_id)) { if !thread_state.read(cx).stopped && event.reason == ThreadEventReason::Exited { const MESSAGE: &'static str = "Debug session exited without hitting breakpoints\n\nTry adding a breakpoint, or define the correct path mapping for your debugger."; @@ -876,26 +871,26 @@ impl DebugPanel { if event.reason == ThreadEventReason::Started { self.thread_states - .insert((*client_id, thread_id), cx.new(|_| ThreadState::default())); + .insert((client_id, thread_id), cx.new(|_| ThreadState::default())); } - cx.emit(DebugPanelEvent::Thread((*client_id, event.clone()))); + cx.emit(DebugPanelEvent::Thread((client_id, event.clone()))); cx.notify(); } fn handle_exited_event( &mut self, - client_id: &DebugAdapterClientId, + client_id: DebugAdapterClientId, _: &ExitedEvent, cx: &mut Context, ) { - cx.emit(DebugPanelEvent::Exited(*client_id)); + cx.emit(DebugPanelEvent::Exited(client_id)); } fn handle_terminated_event( &mut self, session_id: &DebugSessionId, - client_id: &DebugAdapterClientId, + client_id: DebugAdapterClientId, event: &Option, cx: &mut Context, ) { @@ -903,7 +898,7 @@ impl DebugPanel { for (_, thread_state) in self .thread_states - .range_mut(&(*client_id, u64::MIN)..&(*client_id, u64::MAX)) + .range_mut(&(client_id, ThreadId::MIN)..&(client_id, ThreadId::MAX)) { thread_state.update(cx, |thread_state, cx| { thread_state.status = ThreadStatus::Ended; @@ -917,9 +912,17 @@ impl DebugPanel { .as_ref() .is_some_and(|v| v.as_bool().unwrap_or(true)) { - store - .restart(&client_id, restart_args, cx) - .detach_and_log_err(cx); + let Some(session) = store.session_by_client_id(client_id) else { + return; + }; + + let Some(client_state) = session.read(cx).client_state(client_id) else { + return; + }; + + client_state.update(cx, |state, cx| { + state.restart(restart_args, cx); + }); } else { store .shutdown_session(&session_id, cx) @@ -927,21 +930,21 @@ impl DebugPanel { } }); - cx.emit(DebugPanelEvent::Terminated(*client_id)); + cx.emit(DebugPanelEvent::Terminated(client_id)); } fn handle_output_event( &mut self, - client_id: &DebugAdapterClientId, + client_id: DebugAdapterClientId, event: &OutputEvent, cx: &mut Context, ) { self.message_queue - .entry(*client_id) + .entry(client_id) .or_default() .push_back(event.clone()); - cx.emit(DebugPanelEvent::Output((*client_id, event.clone()))); + cx.emit(DebugPanelEvent::Output((client_id, event.clone()))); } fn on_dap_store_event( @@ -973,7 +976,7 @@ impl DebugPanel { ) { if let Some(thread_state) = self.thread_states.get_mut(&( DebugAdapterClientId::from_proto(update.client_id), - update.thread_id, + ThreadId(update.thread_id), )) { thread_state.update(cx, |thread_state, _| { thread_state.status = ThreadStatus::from_proto(update.status()); @@ -986,11 +989,11 @@ impl DebugPanel { pub(crate) fn handle_debug_adapter_update( &mut self, update: &UpdateDebugAdapter, - window: &mut Window, + _window: &mut Window, cx: &mut Context, ) { let client_id = DebugAdapterClientId::from_proto(update.client_id); - let thread_id = update.thread_id; + let thread_id = update.thread_id.map(|thread_id| ThreadId(thread_id)); let active_item = self .pane @@ -998,7 +1001,7 @@ impl DebugPanel { .active_item() .and_then(|item| item.downcast::()); - let search = self + let _search = self .pane .read(cx) .items() @@ -1020,15 +1023,16 @@ impl DebugPanel { } }); - if let Some((debug_panel_item, is_active_item)) = search { - debug_panel_item.update(cx, |this, cx| { - this.update_adapter(update, window, cx); + // if let Some((debug_panel_item, is_active_item)) = search { + // TODO(debugger): make this work again + // debug_panel_item.update(cx, |this, cx| { + // this.update_adapter(update, window, cx); - if is_active_item { - this.go_to_current_stack_frame(window, cx); - } - }); - } + // if is_active_item { + // this.go_to_current_stack_frame(window, cx); + // } + // }); + // } } fn handle_remote_has_initialized(&mut self, window: &mut Window, cx: &mut Context) { @@ -1055,7 +1059,7 @@ impl DebugPanel { }; let client_id = DebugAdapterClientId::from_proto(payload.client_id); - let thread_id = payload.thread_id; + let thread_id = ThreadId(payload.thread_id); let thread_state = payload.thread_state.clone().unwrap(); let thread_state = cx.new(|_| ThreadState::from_proto(thread_state)); @@ -1079,10 +1083,9 @@ impl DebugPanel { let debug_panel_item = cx.new(|cx| { DebugPanelItem::new( session, - &client_id, + client_id, thread_id, thread_state, - self.dap_store.clone(), &debug_panel, self.workspace.clone(), window, @@ -1118,26 +1121,26 @@ impl DebugPanel { fn handle_module_event( &mut self, - client_id: &DebugAdapterClientId, + client_id: DebugAdapterClientId, event: &ModuleEvent, cx: &mut Context, ) { - cx.emit(DebugPanelEvent::Module((*client_id, event.clone()))); + cx.emit(DebugPanelEvent::Module((client_id, event.clone()))); } fn handle_loaded_source_event( &mut self, - client_id: &DebugAdapterClientId, + client_id: DebugAdapterClientId, event: &LoadedSourceEvent, cx: &mut Context, ) { - cx.emit(DebugPanelEvent::LoadedSource((*client_id, event.clone()))); + cx.emit(DebugPanelEvent::LoadedSource((client_id, event.clone()))); } fn handle_capabilities_changed_event( &mut self, session_id: &DebugSessionId, - client_id: &DebugAdapterClientId, + client_id: DebugAdapterClientId, event: &CapabilitiesEvent, cx: &mut Context, ) { @@ -1145,7 +1148,7 @@ impl DebugPanel { store.update_capabilities_for_client(session_id, client_id, &event.capabilities, cx); }); - cx.emit(DebugPanelEvent::CapabilitiesChanged(*client_id)); + cx.emit(DebugPanelEvent::CapabilitiesChanged(client_id)); } } diff --git a/crates/debugger_ui/src/debugger_panel_item.rs b/crates/debugger_ui/src/debugger_panel_item.rs index 8625c244bb..46f8787c01 100644 --- a/crates/debugger_ui/src/debugger_panel_item.rs +++ b/crates/debugger_ui/src/debugger_panel_item.rs @@ -5,24 +5,21 @@ use crate::module_list::ModuleList; use crate::stack_frame_list::{StackFrameList, StackFrameListEvent}; use crate::variable_list::VariableList; -use dap::proto_conversions::{self, ProtoConversion}; use dap::{ client::DebugAdapterClientId, debugger_settings::DebuggerSettings, Capabilities, ContinuedEvent, LoadedSourceEvent, ModuleEvent, OutputEvent, OutputEventCategory, StoppedEvent, ThreadEvent, }; -use editor::Editor; use gpui::{ AnyElement, App, Entity, EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity, }; -use project::debugger::{dap_session::DebugSession, dap_store::DapStore}; -use rpc::proto::{self, DebuggerThreadStatus, PeerId, SetDebuggerPanelItem, UpdateDebugAdapter}; +use project::debugger::dap_session::{DebugSession, ThreadId}; +use rpc::proto::{self, DebuggerThreadStatus, PeerId, SetDebuggerPanelItem}; use settings::Settings; use ui::{prelude::*, Indicator, Tooltip}; -use util::ResultExt as _; use workspace::{ item::{self, Item, ItemEvent}, - FollowableItem, ItemHandle, ViewId, Workspace, + FollowableItem, ViewId, Workspace, }; #[derive(Debug)] @@ -60,11 +57,10 @@ impl ThreadItem { } pub struct DebugPanelItem { - thread_id: u64, + thread_id: ThreadId, console: Entity, focus_handle: FocusHandle, remote_id: Option, - dap_store: Entity, session: Entity, show_console_indicator: bool, module_list: Entity, @@ -82,10 +78,9 @@ impl DebugPanelItem { #[allow(clippy::too_many_arguments)] pub fn new( session: Entity, - client_id: &DebugAdapterClientId, - thread_id: u64, + client_id: DebugAdapterClientId, + thread_id: ThreadId, thread_state: Entity, - dap_store: Entity, debug_panel: &Entity, workspace: WeakEntity, window: &mut Window, @@ -93,13 +88,9 @@ impl DebugPanelItem { ) -> Self { let focus_handle = cx.focus_handle(); - let this = cx.entity(); - let stack_frame_list = cx.new(|cx| { StackFrameList::new( workspace.clone(), - &this, - dap_store.clone(), session.clone(), client_id, thread_id, @@ -112,23 +103,20 @@ impl DebugPanelItem { VariableList::new( session.clone(), client_id, - dap_store.clone(), stack_frame_list.clone(), window, cx, ) }); - let module_list = cx.new(|cx| ModuleList::new(session.clone(), &client_id, cx)); + let module_list = cx.new(|cx| ModuleList::new(session.clone(), client_id, cx)); - let loaded_source_list = - cx.new(|cx| LoadedSourceList::new(session.clone(), &client_id, cx)); + let loaded_source_list = cx.new(|cx| LoadedSourceList::new(session.clone(), client_id, cx)); let console = cx.new(|cx| { Console::new( session.clone(), client_id, - dap_store.clone(), stack_frame_list.clone(), variable_list.clone(), window, @@ -188,7 +176,6 @@ impl DebugPanelItem { session, console, thread_id, - dap_store, workspace, module_list, thread_state, @@ -198,33 +185,12 @@ impl DebugPanelItem { remote_id: None, stack_frame_list, loaded_source_list, - client_id: *client_id, + client_id, show_console_indicator: false, active_thread_item: ThreadItem::Variables, } } - pub(crate) fn to_proto(&self, project_id: u64, cx: &App) -> SetDebuggerPanelItem { - let thread_state = Some(self.thread_state.read(cx).to_proto()); - let variable_list = Some(self.variable_list.read(cx).to_proto()); - let stack_frame_list = Some(self.stack_frame_list.read(cx).to_proto()); - - SetDebuggerPanelItem { - project_id, - session_id: self.session.read(cx).id().to_proto(), - client_id: self.client_id.to_proto(), - thread_id: self.thread_id, - console: None, - module_list: None, - active_thread_item: self.active_thread_item.to_proto().into(), - thread_state, - variable_list, - stack_frame_list, - loaded_source_list: None, - session_name: self.session.read(cx).name(), - } - } - pub(crate) fn from_proto(&mut self, state: &SetDebuggerPanelItem, cx: &mut Context) { self.thread_state.update(cx, |thread_state, _| { let (status, stopped) = state @@ -240,12 +206,6 @@ impl DebugPanelItem { self.active_thread_item = ThreadItem::from_proto(state.active_thread_item()); - if let Some(stack_frame_list) = state.stack_frame_list.as_ref() { - self.stack_frame_list.update(cx, |this, cx| { - this.set_from_proto(stack_frame_list.clone(), cx); - }); - } - if let Some(variable_list_state) = state.variable_list.as_ref() { self.variable_list .update(cx, |this, cx| this.set_from_proto(variable_list_state, cx)); @@ -269,7 +229,7 @@ impl DebugPanelItem { } } - fn should_skip_event(&self, client_id: &DebugAdapterClientId, thread_id: u64) -> bool { + fn should_skip_event(&self, client_id: &DebugAdapterClientId, thread_id: ThreadId) -> bool { thread_id != self.thread_id || *client_id != self.client_id } @@ -279,7 +239,7 @@ impl DebugPanelItem { event: &ContinuedEvent, cx: &mut Context, ) { - if self.should_skip_event(client_id, event.thread_id) { + if self.should_skip_event(client_id, ThreadId(event.thread_id)) { return; } @@ -293,21 +253,20 @@ impl DebugPanelItem { go_to_stack_frame: bool, cx: &mut Context, ) { - if self.should_skip_event(client_id, event.thread_id.unwrap_or(self.thread_id)) { + if self.should_skip_event( + client_id, + event.thread_id.map(ThreadId).unwrap_or(self.thread_id), + ) { return; } if let Some(client_state) = self.session.read(cx).client_state(*client_id) { - client_state.update(cx, |state, cx| state.invalidate(cx)); + client_state.update(cx, |state, cx| { + state.invalidate(cx); + }); } cx.emit(DebugPanelItemEvent::Stopped { go_to_stack_frame }); - - if let Some((downstream_client, project_id)) = self.dap_store.read(cx).downstream_client() { - downstream_client - .send(self.to_proto(*project_id, cx)) - .log_err(); - } } fn handle_thread_event( @@ -316,7 +275,7 @@ impl DebugPanelItem { event: &ThreadEvent, cx: &mut Context, ) { - if self.should_skip_event(client_id, event.thread_id) { + if self.should_skip_event(client_id, ThreadId(event.thread_id)) { return; } @@ -393,9 +352,10 @@ impl DebugPanelItem { self.update_thread_state_status(ThreadStatus::Stopped, cx); - self.dap_store.update(cx, |store, cx| { - store.remove_active_debug_line_for_client(client_id, cx); - }); + // TODO(debugger): make this work again + // self.dap_store.update(cx, |store, cx| { + // store.remove_active_debug_line_for_client(client_id, cx); + // }); cx.emit(DebugPanelItemEvent::Close); } @@ -411,10 +371,6 @@ impl DebugPanelItem { self.update_thread_state_status(ThreadStatus::Exited, cx); - self.dap_store.update(cx, |store, cx| { - store.remove_active_debug_line_for_client(client_id, cx); - }); - cx.emit(DebugPanelItemEvent::Close); } @@ -427,56 +383,42 @@ impl DebugPanelItem { return; } - // notify the view that the capabilities have changed cx.notify(); - - if let Some((downstream_client, project_id)) = self.dap_store.read(cx).downstream_client() { - if let Some(caps) = self.dap_store.read(cx).capabilities_by_id(client_id, cx) { - let message = proto_conversions::capabilities_to_proto( - &caps, - *project_id, - self.session.read(cx).id().to_proto(), - self.client_id.to_proto(), - ); - - downstream_client.send(message).log_err(); - } - } } - pub(crate) fn update_adapter( - &mut self, - update: &UpdateDebugAdapter, - window: &mut Window, - cx: &mut Context, - ) { - if let Some(update_variant) = update.variant.as_ref() { - match update_variant { - proto::update_debug_adapter::Variant::StackFrameList(stack_frame_list) => { - self.stack_frame_list.update(cx, |this, cx| { - this.set_from_proto(stack_frame_list.clone(), cx); - }) - } - proto::update_debug_adapter::Variant::ThreadState(thread_state) => { - self.thread_state.update(cx, |this, _| { - *this = ThreadState::from_proto(thread_state.clone()); - }) - } - proto::update_debug_adapter::Variant::VariableList(variable_list) => self - .variable_list - .update(cx, |this, cx| this.set_from_proto(variable_list, cx)), - proto::update_debug_adapter::Variant::AddToVariableList(variables_to_add) => self - .variable_list - .update(cx, |this, _| this.add_variables(variables_to_add.clone())), - proto::update_debug_adapter::Variant::Modules(_) => {} - proto::update_debug_adapter::Variant::OutputEvent(output_event) => { - self.console.update(cx, |this, cx| { - this.add_message(OutputEvent::from_proto(output_event.clone()), window, cx); - }) - } - } - } - } + // pub(crate) fn update_adapter( + // &mut self, + // update: &UpdateDebugAdapter, + // window: &mut Window, + // cx: &mut Context, + // ) { + // if let Some(update_variant) = update.variant.as_ref() { + // match update_variant { + // proto::update_debug_adapter::Variant::StackFrameList(stack_frame_list) => { + // self.stack_frame_list.update(cx, |this, cx| { + // this.set_from_proto(stack_frame_list.clone(), cx); + // }) + // } + // proto::update_debug_adapter::Variant::ThreadState(thread_state) => { + // self.thread_state.update(cx, |this, _| { + // *this = ThreadState::from_proto(thread_state.clone()); + // }) + // } + // proto::update_debug_adapter::Variant::VariableList(variable_list) => self + // .variable_list + // .update(cx, |this, cx| this.set_from_proto(variable_list, cx)), + // proto::update_debug_adapter::Variant::AddToVariableList(variables_to_add) => self + // .variable_list + // .update(cx, |this, _| this.add_variables(variables_to_add.clone())), + // proto::update_debug_adapter::Variant::Modules(_) => {} + // proto::update_debug_adapter::Variant::OutputEvent(output_event) => { + // self.console.update(cx, |this, cx| { + // this.add_message(OutputEvent::from_proto(output_event.clone()), window, cx); + // }) + // } + // } + // } + // } pub fn session(&self) -> &Entity { &self.session @@ -486,7 +428,7 @@ impl DebugPanelItem { self.client_id } - pub fn thread_id(&self) -> u64 { + pub fn thread_id(&self) -> ThreadId { self.thread_id } @@ -527,41 +469,43 @@ impl DebugPanelItem { } pub fn capabilities(&self, cx: &mut Context) -> Option { - self.dap_store + self.session() .read(cx) - .capabilities_by_id(&self.client_id, cx) + .client_state(self.client_id) + .map(|state| state.read(cx).capabilities().clone()) } fn clear_highlights(&self, cx: &mut Context) { - if let Some((_, project_path, _)) = self.dap_store.read(cx).active_debug_line() { - self.workspace - .update(cx, |workspace, cx| { - let editor = workspace - .items_of_type::(cx) - .find(|editor| Some(project_path.clone()) == editor.project_path(cx)); + // TODO(debugger): make this work again + // if let Some((_, project_path, _)) = self.dap_store.read(cx).active_debug_line() { + // self.workspace + // .update(cx, |workspace, cx| { + // let editor = workspace + // .items_of_type::(cx) + // .find(|editor| Some(project_path.clone()) == editor.project_path(cx)); - if let Some(editor) = editor { - editor.update(cx, |editor, cx| { - editor.clear_row_highlights::(); + // if let Some(editor) = editor { + // editor.update(cx, |editor, cx| { + // editor.clear_row_highlights::(); - cx.notify(); - }); - } - }) - .ok(); - } + // cx.notify(); + // }); + // } + // }) + // .ok(); + // } } pub fn go_to_current_stack_frame(&self, window: &mut Window, cx: &mut Context) { self.stack_frame_list.update(cx, |stack_frame_list, cx| { if let Some(stack_frame) = stack_frame_list - .stack_frames() + .stack_frames(cx) .iter() - .find(|frame| frame.id == stack_frame_list.current_stack_frame_id()) + .find(|frame| frame.dap.id == stack_frame_list.current_stack_frame_id()) .cloned() { stack_frame_list - .select_stack_frame(&stack_frame, true, window, cx) + .select_stack_frame(&stack_frame.dap, true, window, cx) .detach_and_log_err(cx); } }); @@ -603,134 +547,110 @@ impl DebugPanelItem { } pub fn continue_thread(&mut self, cx: &mut Context) { - self.update_thread_state_status(ThreadStatus::Running, cx); - - let task = self.dap_store.update(cx, |store, cx| { - store.continue_thread(&self.client_id, self.thread_id, cx) - }); - - cx.spawn(|this, mut cx| async move { - if task.await.log_err().is_none() { - this.update(&mut cx, |debug_panel_item, cx| { - debug_panel_item.update_thread_state_status(ThreadStatus::Stopped, cx); - }) - .log_err(); - } - }) - .detach(); + self.session() + .read(cx) + .client_state(self.client_id) + .map(|entity| { + entity.update(cx, |state, cx| { + state.continue_thread(self.thread_id, cx); + }); + }); } pub fn step_over(&mut self, cx: &mut Context) { - self.update_thread_state_status(ThreadStatus::Running, cx); let granularity = DebuggerSettings::get_global(cx).stepping_granularity; - let task = self.dap_store.update(cx, |store, cx| { - store.step_over(&self.client_id, self.thread_id, granularity, cx) - }); - - cx.spawn(|this, mut cx| async move { - if task.await.log_err().is_none() { - this.update(&mut cx, |debug_panel_item, cx| { - debug_panel_item.update_thread_state_status(ThreadStatus::Stopped, cx); - }) - .log_err(); - } - }) - .detach(); + self.session() + .read(cx) + .client_state(self.client_id) + .map(|entity| { + entity.update(cx, |state, cx| { + state.step_over(self.thread_id, granularity, cx); + }); + }); } pub fn step_in(&mut self, cx: &mut Context) { - self.update_thread_state_status(ThreadStatus::Running, cx); let granularity = DebuggerSettings::get_global(cx).stepping_granularity; - let task = self.dap_store.update(cx, |store, cx| { - store.step_in(&self.client_id, self.thread_id, granularity, cx) - }); - - cx.spawn(|this, mut cx| async move { - if task.await.log_err().is_none() { - this.update(&mut cx, |debug_panel_item, cx| { - debug_panel_item.update_thread_state_status(ThreadStatus::Stopped, cx); - }) - .log_err(); - } - }) - .detach(); + self.session() + .read(cx) + .client_state(self.client_id) + .map(|entity| { + entity.update(cx, |state, cx| { + state.step_in(self.thread_id, granularity, cx); + }); + }); } pub fn step_out(&mut self, cx: &mut Context) { - self.update_thread_state_status(ThreadStatus::Running, cx); let granularity = DebuggerSettings::get_global(cx).stepping_granularity; - let task = self.dap_store.update(cx, |store, cx| { - store.step_out(&self.client_id, self.thread_id, granularity, cx) - }); - - cx.spawn(|this, mut cx| async move { - if task.await.log_err().is_none() { - this.update(&mut cx, |debug_panel_item, cx| { - debug_panel_item.update_thread_state_status(ThreadStatus::Stopped, cx); - }) - .log_err(); - } - }) - .detach(); + self.session() + .read(cx) + .client_state(self.client_id) + .map(|entity| { + entity.update(cx, |state, cx| { + state.step_out(self.thread_id, granularity, cx); + }); + }); } pub fn step_back(&mut self, cx: &mut Context) { - self.update_thread_state_status(ThreadStatus::Running, cx); let granularity = DebuggerSettings::get_global(cx).stepping_granularity; - let task = self.dap_store.update(cx, |store, cx| { - store.step_back(&self.client_id, self.thread_id, granularity, cx) - }); - - cx.spawn(|this, mut cx| async move { - if task.await.log_err().is_none() { - this.update(&mut cx, |debug_panel_item, cx| { - debug_panel_item.update_thread_state_status(ThreadStatus::Stopped, cx); - }) - .log_err(); - } - }) - .detach(); + self.session() + .read(cx) + .client_state(self.client_id) + .map(|entity| { + entity.update(cx, |state, cx| { + state.step_back(self.thread_id, granularity, cx); + }); + }); } pub fn restart_client(&self, cx: &mut Context) { - self.dap_store.update(cx, |store, cx| { - store - .restart(&self.client_id, None, cx) - .detach_and_log_err(cx); - }); + self.session() + .read(cx) + .client_state(self.client_id) + .map(|entity| { + entity.update(cx, |state, cx| { + state.restart(None, cx); + }); + }); } pub fn pause_thread(&self, cx: &mut Context) { - self.dap_store.update(cx, |store, cx| { - store - .pause_thread(&self.client_id, self.thread_id, cx) - .detach_and_log_err(cx) - }); + self.session() + .read(cx) + .client_state(self.client_id) + .map(|entity| { + entity.update(cx, |state, cx| { + state.pause_thread(self.thread_id, cx); + }); + }); } pub fn stop_thread(&self, cx: &mut Context) { - self.dap_store.update(cx, |store, cx| { - store - .terminate_threads( - &self.session.read(cx).id(), - &self.client_id, - Some(vec![self.thread_id; 1]), - cx, - ) - .detach_and_log_err(cx) - }); + self.session() + .read(cx) + .client_state(self.client_id) + .map(|entity| { + entity.update(cx, |state, cx| { + state.terminate_threads(Some(vec![self.thread_id; 1]), cx); + }); + }); } pub fn disconnect_client(&self, cx: &mut Context) { - self.dap_store.update(cx, |store, cx| { - store - .disconnect_client(&self.client_id, cx) - .detach_and_log_err(cx); - }); + self.session() + .read(cx) + .client_state(self.client_id) + .map(|entity| { + entity.update(cx, |state, cx| { + state.disconnect_client(cx); + }); + }); } pub fn toggle_ignore_breakpoints(&mut self, cx: &mut Context) { @@ -760,7 +680,7 @@ impl Item for DebugPanelItem { Label::new(format!( "{} - Thread {}", self.session.read(cx).name(), - self.thread_id + self.thread_id.0 )) .color(if params.selected { Color::Default @@ -774,7 +694,7 @@ impl Item for DebugPanelItem { Some(SharedString::from(format!( "{} Thread {} - {:?}", self.session.read(cx).name(), - self.thread_id, + self.thread_id.0, self.thread_state.read(cx).status, ))) } diff --git a/crates/debugger_ui/src/loaded_source_list.rs b/crates/debugger_ui/src/loaded_source_list.rs index 091edc95f6..84296c728d 100644 --- a/crates/debugger_ui/src/loaded_source_list.rs +++ b/crates/debugger_ui/src/loaded_source_list.rs @@ -15,7 +15,7 @@ pub struct LoadedSourceList { impl LoadedSourceList { pub fn new( session: Entity, - client_id: &DebugAdapterClientId, + client_id: DebugAdapterClientId, cx: &mut Context, ) -> Self { let weak_entity = cx.weak_entity(); @@ -35,7 +35,7 @@ impl LoadedSourceList { }, ); - let client_state = session.read(cx).client_state(*client_id).unwrap(); + let client_state = session.read(cx).client_state(client_id).unwrap(); let _subscription = cx.observe(&client_state, |loaded_source_list, state, cx| { let len = state.update(cx, |state, cx| state.loaded_sources(cx).len()); @@ -48,7 +48,7 @@ impl LoadedSourceList { session, focus_handle, _subscription, - client_id: *client_id, + client_id, } } diff --git a/crates/debugger_ui/src/module_list.rs b/crates/debugger_ui/src/module_list.rs index 074cccd65f..00484d2e89 100644 --- a/crates/debugger_ui/src/module_list.rs +++ b/crates/debugger_ui/src/module_list.rs @@ -14,7 +14,7 @@ pub struct ModuleList { impl ModuleList { pub fn new( session: Entity, - client_id: &DebugAdapterClientId, + client_id: DebugAdapterClientId, cx: &mut Context, ) -> Self { let weak_entity = cx.weak_entity(); @@ -32,7 +32,7 @@ impl ModuleList { }, ); - let client_state = session.read(cx).client_state(*client_id).unwrap(); + let client_state = session.read(cx).client_state(client_id).unwrap(); let _subscription = cx.observe(&client_state, |module_list, state, cx| { let modules_len = state.update(cx, |state, cx| state.modules(cx).len()); @@ -46,7 +46,7 @@ impl ModuleList { session, focus_handle, _subscription, - client_id: *client_id, + client_id, } } diff --git a/crates/debugger_ui/src/stack_frame_list.rs b/crates/debugger_ui/src/stack_frame_list.rs index 37d5e59396..b8197234de 100644 --- a/crates/debugger_ui/src/stack_frame_list.rs +++ b/crates/debugger_ui/src/stack_frame_list.rs @@ -2,22 +2,15 @@ use std::path::Path; use anyhow::{anyhow, Result}; use dap::client::DebugAdapterClientId; -use dap::proto_conversions::ProtoConversion; -use dap::StackFrame; use gpui::{ list, AnyElement, Entity, EventEmitter, FocusHandle, Focusable, ListState, Subscription, Task, WeakEntity, }; -use project::debugger::{dap_session::DebugSession, dap_store::DapStore}; +use project::debugger::dap_session::{DebugSession, StackFrame, ThreadId}; use project::ProjectPath; -use rpc::proto::{DebuggerStackFrameList, UpdateDebugAdapter}; use ui::{prelude::*, Tooltip}; -use util::ResultExt; use workspace::Workspace; -use crate::debugger_panel_item::DebugPanelItemEvent::Stopped; -use crate::debugger_panel_item::{self, DebugPanelItem}; - pub type StackFrameId = u64; #[derive(Debug)] @@ -27,36 +20,31 @@ pub enum StackFrameListEvent { } pub struct StackFrameList { - thread_id: u64, list: ListState, + thread_id: ThreadId, focus_handle: FocusHandle, - dap_store: Entity, + _subscription: Subscription, session: Entity, - stack_frames: Vec, entries: Vec, workspace: WeakEntity, client_id: DebugAdapterClientId, - _subscriptions: Vec, current_stack_frame_id: StackFrameId, fetch_stack_frames_task: Option>>, } #[derive(Debug, PartialEq, Eq)] pub enum StackFrameEntry { - Normal(StackFrame), - Collapsed(Vec), + Normal(dap::StackFrame), + Collapsed(Vec), } impl StackFrameList { - #[allow(clippy::too_many_arguments)] pub fn new( workspace: WeakEntity, - debug_panel_item: &Entity, - dap_store: Entity, session: Entity, - client_id: &DebugAdapterClientId, - thread_id: u64, - window: &Window, + client_id: DebugAdapterClientId, + thread_id: ThreadId, + _window: &Window, cx: &mut Context, ) -> Self { let weak_entity = cx.weak_entity(); @@ -76,68 +64,59 @@ impl StackFrameList { }, ); - let _subscriptions = vec![cx.subscribe_in( - debug_panel_item, - window, - Self::handle_debug_panel_item_event, - )]; + let client_state = session.read(cx).client_state(client_id).unwrap(); + + let _subscription = cx.observe(&client_state, |stack_frame_list, state, cx| { + let _frame_len = state.update(cx, |state, cx| { + state.stack_frames(stack_frame_list.thread_id, cx).len() + }); + + stack_frame_list.build_entries(cx); + }); Self { list, session, workspace, - dap_store, thread_id, + client_id, focus_handle, - _subscriptions, - client_id: *client_id, + _subscription, entries: Default::default(), fetch_stack_frames_task: None, - stack_frames: Default::default(), current_stack_frame_id: Default::default(), } } - pub(crate) fn thread_id(&self) -> u64 { + pub(crate) fn thread_id(&self) -> ThreadId { self.thread_id } - pub(crate) fn to_proto(&self) -> DebuggerStackFrameList { - DebuggerStackFrameList { - thread_id: self.thread_id, - client_id: self.client_id.to_proto(), - current_stack_frame: self.current_stack_frame_id, - stack_frames: self.stack_frames.to_proto(), - } - } - - pub(crate) fn set_from_proto( - &mut self, - stack_frame_list: DebuggerStackFrameList, - cx: &mut Context, - ) { - self.thread_id = stack_frame_list.thread_id; - self.client_id = DebugAdapterClientId::from_proto(stack_frame_list.client_id); - self.current_stack_frame_id = stack_frame_list.current_stack_frame; - self.stack_frames = Vec::from_proto(stack_frame_list.stack_frames); - - self.build_entries(); - cx.notify(); - } - #[cfg(any(test, feature = "test-support"))] pub fn entries(&self) -> &Vec { &self.entries } - pub fn stack_frames(&self) -> &Vec { - &self.stack_frames + pub fn stack_frames(&self, cx: &mut App) -> Vec { + self.session + .read(cx) + .client_state(self.client_id) + .map(|state| state.update(cx, |client, cx| client.stack_frames(self.thread_id, cx))) + .unwrap_or_default() } - pub fn first_stack_frame_id(&self) -> u64 { - self.stack_frames + #[cfg(any(test, feature = "test-support"))] + pub fn dap_stack_frames(&self, cx: &mut App) -> Vec { + self.stack_frames(cx) + .into_iter() + .map(|stack_frame| stack_frame.dap.clone()) + .collect() + } + + pub fn get_main_stack_frame_id(&self, cx: &mut Context) -> u64 { + self.stack_frames(cx) .first() - .map(|stack_frame| stack_frame.id) + .map(|stack_frame| stack_frame.dap.id) .unwrap_or(0) } @@ -145,33 +124,14 @@ impl StackFrameList { self.current_stack_frame_id } - fn handle_debug_panel_item_event( - &mut self, - _: &Entity, - event: &debugger_panel_item::DebugPanelItemEvent, - window: &mut Window, - cx: &mut Context, - ) { - match event { - Stopped { go_to_stack_frame } => { - self.fetch_stack_frames(*go_to_stack_frame, window, cx); - } - _ => {} - } - } - - pub fn invalidate(&mut self, window: &mut Window, cx: &mut Context) { - self.fetch_stack_frames(true, window, cx); - } - - fn build_entries(&mut self) { + fn build_entries(&mut self, cx: &mut Context) { let mut entries = Vec::new(); let mut collapsed_entries = Vec::new(); - for stack_frame in &self.stack_frames { - match stack_frame.presentation_hint { + for stack_frame in &self.stack_frames(cx) { + match stack_frame.dap.presentation_hint { Some(dap::StackFramePresentationHint::Deemphasize) => { - collapsed_entries.push(stack_frame.clone()); + collapsed_entries.push(stack_frame.dap.clone()); } _ => { let collapsed_entries = std::mem::take(&mut collapsed_entries); @@ -179,7 +139,7 @@ impl StackFrameList { entries.push(StackFrameEntry::Collapsed(collapsed_entries.clone())); } - entries.push(StackFrameEntry::Normal(stack_frame.clone())); + entries.push(StackFrameEntry::Normal(stack_frame.dap.clone())); } } } @@ -191,54 +151,55 @@ impl StackFrameList { std::mem::swap(&mut self.entries, &mut entries); self.list.reset(self.entries.len()); + cx.notify(); } - fn fetch_stack_frames( - &mut self, - go_to_stack_frame: bool, - window: &Window, - cx: &mut Context, - ) { - // If this is a remote debug session we never need to fetch stack frames ourselves - // because the host will fetch and send us stack frames whenever there's a stop event - if self.dap_store.read(cx).as_remote().is_some() { - return; - } + // fn fetch_stack_frames( + // &mut self, + // go_to_stack_frame: bool, + // window: &Window, + // cx: &mut Context, + // ) { + // // If this is a remote debug session we never need to fetch stack frames ourselves + // // because the host will fetch and send us stack frames whenever there's a stop event + // if self.dap_store.read(cx).as_remote().is_some() { + // return; + // } - let task = self.dap_store.update(cx, |store, cx| { - store.stack_frames(&self.client_id, self.thread_id, cx) - }); + // let task = self.dap_store.update(cx, |store, cx| { + // store.stack_frames(&self.client_id, self.thread_id, cx) + // }); - self.fetch_stack_frames_task = Some(cx.spawn_in(window, |this, mut cx| async move { - let mut stack_frames = task.await?; + // self.fetch_stack_frames_task = Some(cx.spawn_in(window, |this, mut cx| async move { + // let mut stack_frames = task.await?; - let task = this.update_in(&mut cx, |this, window, cx| { - std::mem::swap(&mut this.stack_frames, &mut stack_frames); + // let task = this.update_in(&mut cx, |this, window, cx| { + // std::mem::swap(&mut this.stack_frames, &mut stack_frames); - this.build_entries(); + // this.build_entries(); - cx.emit(StackFrameListEvent::StackFramesUpdated); + // cx.emit(StackFrameListEvent::StackFramesUpdated); - let stack_frame = this - .stack_frames - .first() - .cloned() - .ok_or_else(|| anyhow!("No stack frame found to select"))?; + // let stack_frame = this + // .stack_frames + // .first() + // .cloned() + // .ok_or_else(|| anyhow!("No stack frame found to select"))?; - anyhow::Ok(this.select_stack_frame(&stack_frame, go_to_stack_frame, window, cx)) - })?; + // anyhow::Ok(this.select_stack_frame(&stack_frame, go_to_stack_frame, window, cx)) + // })?; - task?.await?; + // task?.await?; - this.update(&mut cx, |this, _| { - this.fetch_stack_frames_task.take(); - }) - })); - } + // this.update(&mut cx, |this, _| { + // this.fetch_stack_frames_task.take(); + // }) + // })); + // } pub fn select_stack_frame( &mut self, - stack_frame: &StackFrame, + stack_frame: &dap::StackFrame, go_to_stack_frame: bool, window: &Window, cx: &mut Context, @@ -250,20 +211,6 @@ impl StackFrameList { )); cx.notify(); - if let Some((client, id)) = self.dap_store.read(cx).downstream_client() { - let request = UpdateDebugAdapter { - client_id: self.client_id.to_proto(), - session_id: self.session.read(cx).id().to_proto(), - project_id: *id, - thread_id: Some(self.thread_id), - variant: Some(rpc::proto::update_debug_adapter::Variant::StackFrameList( - self.to_proto(), - )), - }; - - client.send(request).log_err(); - } - if !go_to_stack_frame { return Task::ready(Ok(())); }; @@ -291,18 +238,21 @@ impl StackFrameList { })?? .await?; - this.update(&mut cx, |this, cx| { - this.dap_store.update(cx, |store, cx| { - store.set_active_debug_line(&client_id, &project_path, row, cx); - }) - }) + Ok(()) + + // TODO(debugger): make this work again + // this.update(&mut cx, |this, cx| { + // this.dap_store.update(cx, |store, cx| { + // store.set_active_debug_line(client_id, &project_path, row, cx); + // }) + // }) } }) } - pub fn project_path_from_stack_frame( + fn project_path_from_stack_frame( &self, - stack_frame: &StackFrame, + stack_frame: &dap::StackFrame, cx: &mut Context, ) -> Option { let path = stack_frame.source.as_ref().and_then(|s| s.path.as_ref())?; @@ -317,14 +267,18 @@ impl StackFrameList { } pub fn restart_stack_frame(&mut self, stack_frame_id: u64, cx: &mut Context) { - self.dap_store.update(cx, |store, cx| { - store - .restart_stack_frame(&self.client_id, stack_frame_id, cx) - .detach_and_log_err(cx); - }); + if let Some(client_state) = self.session.read(cx).client_state(self.client_id) { + client_state.update(cx, |state, cx| { + state.restart_stack_frame(stack_frame_id, cx) + }); + } } - fn render_normal_entry(&self, stack_frame: &StackFrame, cx: &mut Context) -> AnyElement { + fn render_normal_entry( + &self, + stack_frame: &dap::StackFrame, + cx: &mut Context, + ) -> AnyElement { let source = stack_frame.source.clone(); let is_selected_frame = stack_frame.id == self.current_stack_frame_id; @@ -335,11 +289,10 @@ impl StackFrameList { ); let supports_frame_restart = self - .dap_store + .session .read(cx) - .capabilities_by_id(&self.client_id, cx) - .map(|caps| caps.supports_restart_frame) - .flatten() + .client_state(self.client_id) + .and_then(|client| client.read(cx).capabilities().supports_restart_frame) .unwrap_or_default(); let origin = stack_frame @@ -442,7 +395,7 @@ impl StackFrameList { pub fn expand_collapsed_entry( &mut self, ix: usize, - stack_frames: &Vec, + stack_frames: &Vec, cx: &mut Context, ) { self.entries.splice( @@ -458,7 +411,7 @@ impl StackFrameList { fn render_collapsed_entry( &self, ix: usize, - stack_frames: &Vec, + stack_frames: &Vec, cx: &mut Context, ) -> AnyElement { let first_stack_frame = &stack_frames[0]; diff --git a/crates/debugger_ui/src/tests/debugger_panel.rs b/crates/debugger_ui/src/tests/debugger_panel.rs index 07a08e9ecf..1f14d0f718 100644 --- a/crates/debugger_ui/src/tests/debugger_panel.rs +++ b/crates/debugger_ui/src/tests/debugger_panel.rs @@ -15,7 +15,7 @@ use editor::{ }; use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext}; use project::{ - dap_store::{Breakpoint, BreakpointEditAction, BreakpointKind}, + debugger::dap_store::{Breakpoint, BreakpointEditAction, BreakpointKind}, FakeFs, Project, }; use serde_json::json; @@ -125,7 +125,7 @@ async fn test_basic_show_debug_panel(executor: BackgroundExecutor, cx: &mut Test debug_panel.update(cx, |this, cx| this.pane().unwrap().read(cx).items_len()) ); assert_eq!(client.id(), active_debug_panel_item.read(cx).client_id()); - assert_eq!(1, active_debug_panel_item.read(cx).thread_id()); + assert_eq!(1, active_debug_panel_item.read(cx).thread_id().0); }) .unwrap(); @@ -248,7 +248,7 @@ async fn test_we_can_only_have_one_panel_per_debug_thread( debug_panel.update(cx, |this, cx| this.pane().unwrap().read(cx).items_len()) ); assert_eq!(client.id(), active_debug_panel_item.read(cx).client_id()); - assert_eq!(1, active_debug_panel_item.read(cx).thread_id()); + assert_eq!(1, active_debug_panel_item.read(cx).thread_id().0); }) .unwrap(); @@ -279,7 +279,7 @@ async fn test_we_can_only_have_one_panel_per_debug_thread( debug_panel.update(cx, |this, cx| this.pane().unwrap().read(cx).items_len()) ); assert_eq!(client.id(), active_debug_panel_item.read(cx).client_id()); - assert_eq!(1, active_debug_panel_item.read(cx).thread_id()); + assert_eq!(1, active_debug_panel_item.read(cx).thread_id().0); }) .unwrap(); @@ -402,7 +402,7 @@ async fn test_client_can_open_multiple_thread_panels( debug_panel.update(cx, |this, cx| this.pane().unwrap().read(cx).items_len()) ); assert_eq!(client.id(), active_debug_panel_item.read(cx).client_id()); - assert_eq!(1, active_debug_panel_item.read(cx).thread_id()); + assert_eq!(1, active_debug_panel_item.read(cx).thread_id().0); }) .unwrap(); @@ -433,7 +433,7 @@ async fn test_client_can_open_multiple_thread_panels( debug_panel.update(cx, |this, cx| this.pane().unwrap().read(cx).items_len()) ); assert_eq!(client.id(), active_debug_panel_item.read(cx).client_id()); - assert_eq!(2, active_debug_panel_item.read(cx).thread_id()); + assert_eq!(2, active_debug_panel_item.read(cx).thread_id().0); }) .unwrap(); @@ -749,7 +749,7 @@ async fn test_handle_start_debugging_reverse_request( cx.run_until_parked(); project.update(cx, |_, cx| { - assert_eq!(2, session.read(cx).as_local().unwrap().clients_len()); + assert_eq!(2, session.read(cx).clients_len()); }); assert!( send_response.load(std::sync::atomic::Ordering::SeqCst), @@ -759,9 +759,10 @@ async fn test_handle_start_debugging_reverse_request( let second_client = project.update(cx, |_, cx| { session .read(cx) - .as_local() + .client_state(DebugAdapterClientId(1)) .unwrap() - .client_by_id(&DebugAdapterClientId(1)) + .read(cx) + .adapter_client() .unwrap() }); diff --git a/crates/debugger_ui/src/tests/stack_frame_list.rs b/crates/debugger_ui/src/tests/stack_frame_list.rs index 31c922c042..9413b8f696 100644 --- a/crates/debugger_ui/src/tests/stack_frame_list.rs +++ b/crates/debugger_ui/src/tests/stack_frame_list.rs @@ -162,10 +162,19 @@ async fn test_fetch_initial_stack_frames_and_go_to_stack_frame( .unwrap(); active_debug_panel_item.update(cx, |debug_panel_item, cx| { - let stack_frame_list = debug_panel_item.stack_frame_list().read(cx); + let (stack_frame_list, stack_frame_id) = + debug_panel_item.stack_frame_list().update(cx, |list, cx| { + ( + list.stack_frames(cx) + .into_iter() + .map(|frame| frame.dap) + .collect::>(), + list.current_stack_frame_id(), + ) + }); - assert_eq!(1, stack_frame_list.current_stack_frame_id()); - assert_eq!(stack_frames, stack_frame_list.stack_frames().clone()); + assert_eq!(1, stack_frame_id); + assert_eq!(stack_frames, stack_frame_list); }); }) .unwrap(); @@ -324,10 +333,19 @@ async fn test_select_stack_frame(executor: BackgroundExecutor, cx: &mut TestAppC .unwrap(); active_debug_panel_item.update(cx, |debug_panel_item, cx| { - let stack_frame_list = debug_panel_item.stack_frame_list().read(cx); + let (stack_frame_list, stack_frame_id) = + debug_panel_item.stack_frame_list().update(cx, |list, cx| { + ( + list.stack_frames(cx) + .into_iter() + .map(|frame| frame.dap) + .collect::>(), + list.current_stack_frame_id(), + ) + }); - assert_eq!(1, stack_frame_list.current_stack_frame_id()); - assert_eq!(stack_frames, stack_frame_list.stack_frames().clone()); + assert_eq!(1, stack_frame_id); + assert_eq!(stack_frames, stack_frame_list); }); let editors = workspace.items_of_type::(cx).collect::>(); @@ -383,10 +401,19 @@ async fn test_select_stack_frame(executor: BackgroundExecutor, cx: &mut TestAppC .unwrap(); active_debug_panel_item.update(cx, |debug_panel_item, cx| { - let stack_frame_list = debug_panel_item.stack_frame_list().read(cx); + let (stack_frame_list, stack_frame_id) = + debug_panel_item.stack_frame_list().update(cx, |list, cx| { + ( + list.stack_frames(cx) + .into_iter() + .map(|frame| frame.dap) + .collect::>(), + list.current_stack_frame_id(), + ) + }); - assert_eq!(2, stack_frame_list.current_stack_frame_id()); - assert_eq!(stack_frames, stack_frame_list.stack_frames().clone()); + assert_eq!(2, stack_frame_id); + assert_eq!(stack_frames, stack_frame_list); }); let editors = workspace.items_of_type::(cx).collect::>(); diff --git a/crates/debugger_ui/src/tests/variable_list.rs b/crates/debugger_ui/src/tests/variable_list.rs index 941350b7b4..9173b6c586 100644 --- a/crates/debugger_ui/src/tests/variable_list.rs +++ b/crates/debugger_ui/src/tests/variable_list.rs @@ -191,10 +191,19 @@ async fn test_basic_fetch_initial_scope_and_variables( cx.run_until_parked(); active_debug_panel_item(workspace, cx).update(cx, |debug_panel_item, cx| { - let stack_frame_list = debug_panel_item.stack_frame_list().read(cx); + let (stack_frame_list, stack_frame_id) = + debug_panel_item.stack_frame_list().update(cx, |list, cx| { + ( + list.stack_frames(cx) + .into_iter() + .map(|frame| frame.dap) + .collect::>(), + list.current_stack_frame_id(), + ) + }); - assert_eq!(1, stack_frame_list.current_stack_frame_id()); - assert_eq!(stack_frames, stack_frame_list.stack_frames().clone()); + assert_eq!(1, stack_frame_id); + assert_eq!(stack_frames, stack_frame_list); debug_panel_item .variable_list() @@ -444,10 +453,19 @@ async fn test_fetch_variables_for_multiple_scopes( cx.run_until_parked(); active_debug_panel_item(workspace, cx).update(cx, |debug_panel_item, cx| { - let stack_frame_list = debug_panel_item.stack_frame_list().read(cx); + let (stack_frame_list, stack_frame_id) = + debug_panel_item.stack_frame_list().update(cx, |list, cx| { + ( + list.stack_frames(cx) + .into_iter() + .map(|frame| frame.dap) + .collect::>(), + list.current_stack_frame_id(), + ) + }); - assert_eq!(1, stack_frame_list.current_stack_frame_id()); - assert_eq!(stack_frames, stack_frame_list.stack_frames().clone()); + assert_eq!(1, stack_frame_id); + assert_eq!(stack_frames, stack_frame_list); debug_panel_item .variable_list() @@ -1324,11 +1342,21 @@ async fn test_it_only_fetches_scopes_and_variables_for_the_first_stack_frame( cx.run_until_parked(); active_debug_panel_item(workspace, cx).update(cx, |debug_panel_item, cx| { - let stack_frame_list = debug_panel_item.stack_frame_list().read(cx); - let variable_list = debug_panel_item.variable_list().read(cx); + let (stack_frame_list, stack_frame_id) = + debug_panel_item.stack_frame_list().update(cx, |list, cx| { + ( + list.stack_frames(cx) + .into_iter() + .map(|frame| frame.dap) + .collect::>(), + list.current_stack_frame_id(), + ) + }); - assert_eq!(1, stack_frame_list.current_stack_frame_id()); - assert_eq!(stack_frames, stack_frame_list.stack_frames().clone()); + assert_eq!(1, stack_frame_id); + assert_eq!(stack_frames, stack_frame_list); + + let variable_list = debug_panel_item.variable_list().read(cx); assert_eq!( frame_1_variables @@ -1560,11 +1588,21 @@ async fn test_it_fetches_scopes_variables_when_you_select_a_stack_frame( cx.run_until_parked(); active_debug_panel_item(workspace, cx).update(cx, |debug_panel_item, cx| { - let stack_frame_list = debug_panel_item.stack_frame_list().read(cx); + let (stack_frame_list, stack_frame_id) = + debug_panel_item.stack_frame_list().update(cx, |list, cx| { + ( + list.stack_frames(cx) + .into_iter() + .map(|frame| frame.dap) + .collect::>(), + list.current_stack_frame_id(), + ) + }); + let variable_list = debug_panel_item.variable_list().read(cx); - assert_eq!(1, stack_frame_list.current_stack_frame_id()); - assert_eq!(stack_frames, stack_frame_list.stack_frames().clone()); + assert_eq!(1, stack_frame_id); + assert_eq!(stack_frames, stack_frame_list); assert_eq!( frame_1_variables @@ -1663,11 +1701,21 @@ async fn test_it_fetches_scopes_variables_when_you_select_a_stack_frame( cx.run_until_parked(); active_debug_panel_item(workspace, cx).update(cx, |debug_panel_item, cx| { - let stack_frame_list = debug_panel_item.stack_frame_list().read(cx); + let (stack_frame_list, stack_frame_id) = + debug_panel_item.stack_frame_list().update(cx, |list, cx| { + ( + list.stack_frames(cx) + .into_iter() + .map(|frame| frame.dap) + .collect::>(), + list.current_stack_frame_id(), + ) + }); + let variable_list = debug_panel_item.variable_list().read(cx); - assert_eq!(2, stack_frame_list.current_stack_frame_id()); - assert_eq!(stack_frames, stack_frame_list.stack_frames().clone()); + assert_eq!(2, stack_frame_id); + assert_eq!(stack_frames, stack_frame_list); assert_eq!( frame_1_variables diff --git a/crates/debugger_ui/src/variable_list.rs b/crates/debugger_ui/src/variable_list.rs index 08b7ba5524..be222e0190 100644 --- a/crates/debugger_ui/src/variable_list.rs +++ b/crates/debugger_ui/src/variable_list.rs @@ -10,10 +10,10 @@ use gpui::{ FocusHandle, Focusable, Hsla, ListOffset, ListState, MouseDownEvent, Point, Subscription, Task, }; use menu::{Confirm, SelectFirst, SelectLast, SelectNext, SelectPrev}; -use project::debugger::{dap_session::DebugSession, dap_store::DapStore}; +use project::debugger::dap_session::DebugSession; use rpc::proto::{ - self, DebuggerScopeVariableIndex, DebuggerVariableContainer, UpdateDebugAdapter, - VariableListScopes, VariableListVariables, + self, DebuggerScopeVariableIndex, DebuggerVariableContainer, VariableListScopes, + VariableListVariables, }; use std::{ collections::{BTreeMap, HashMap, HashSet}, @@ -329,7 +329,6 @@ type ScopeId = u64; pub struct VariableList { list: ListState, focus_handle: FocusHandle, - dap_store: Entity, open_entries: Vec, session: Entity, client_id: DebugAdapterClientId, @@ -348,8 +347,7 @@ pub struct VariableList { impl VariableList { pub fn new( session: Entity, - client_id: &DebugAdapterClientId, - dap_store: Entity, + client_id: DebugAdapterClientId, stack_frame_list: Entity, window: &mut Window, cx: &mut Context, @@ -387,13 +385,12 @@ impl VariableList { Self { list, session, - dap_store, focus_handle, _subscriptions, selection: None, stack_frame_list, set_variable_editor, - client_id: *client_id, + client_id, open_context_menu: None, set_variable_state: None, fetch_variables_task: None, @@ -426,11 +423,7 @@ impl VariableList { }) .collect(); - proto::DebuggerVariableList { - scopes, - variables, - added_variables: vec![], - } + proto::DebuggerVariableList { scopes, variables } } pub(crate) fn set_from_proto( @@ -465,44 +458,10 @@ impl VariableList { }) .collect(); - for variables in state.added_variables.iter() { - self.add_variables(variables.clone()); - } - self.build_entries(true, cx); cx.notify(); } - pub(crate) fn add_variables(&mut self, variables_to_add: proto::AddToVariableList) { - let variables: Vec = Vec::from_proto(variables_to_add.variables); - let variable_id = variables_to_add.variable_id; - let stack_frame_id = variables_to_add.stack_frame_id; - let scope_id = variables_to_add.scope_id; - let key = (stack_frame_id, scope_id); - - if let Some(depth) = self.variables.get(&key).and_then(|containers| { - containers - .variables - .iter() - .find(|container| container.variable.variables_reference == variable_id) - .map(|container| container.depth + 1usize) - }) { - if let Some(index) = self.variables.get_mut(&key) { - index.add_variables( - variable_id, - variables - .into_iter() - .map(|var| VariableContainer { - container_reference: variable_id, - variable: var, - depth, - }) - .collect(), - ); - } - } - } - fn handle_stack_frame_list_events( &mut self, _: Entity, @@ -526,35 +485,34 @@ impl VariableList { stack_frame_id: StackFrameId, cx: &mut Context, ) { - if self.scopes.contains_key(&stack_frame_id) { - return self.build_entries(true, cx); - } + // if self.scopes.contains_key(&stack_frame_id) { + // return self.build_entries(true, cx); + // } - self.fetch_variables_task = Some(cx.spawn(|this, mut cx| async move { - let task = this.update(&mut cx, |variable_list, cx| { - variable_list.fetch_variables_for_stack_frame(stack_frame_id, cx) - })?; + // self.fetch_variables_task = Some(cx.spawn(|this, mut cx| async move { + // let task = this.update(&mut cx, |variable_list, cx| { + // variable_list.fetch_variables_for_stack_frame(stack_frame_id, cx) + // })?; - let (scopes, variables) = task.await?; + // let (scopes, variables) = task.await?; - this.update(&mut cx, |variable_list, cx| { - variable_list.scopes.insert(stack_frame_id, scopes); + // this.update(&mut cx, |variable_list, cx| { + // variable_list.scopes.insert(stack_frame_id, scopes); - for (scope_id, variables) in variables.into_iter() { - let mut variable_index = ScopeVariableIndex::new(); - variable_index.add_variables(scope_id, variables); + // for (scope_id, variables) in variables.into_iter() { + // let mut variable_index = ScopeVariableIndex::new(); + // variable_index.add_variables(scope_id, variables); - variable_list - .variables - .insert((stack_frame_id, scope_id), variable_index); - } + // variable_list + // .variables + // .insert((stack_frame_id, scope_id), variable_index); + // } - variable_list.build_entries(true, cx); - variable_list.send_update_proto_message(cx); + // variable_list.build_entries(true, cx); - variable_list.fetch_variables_task.take(); - }) - })); + // variable_list.fetch_variables_task.take(); + // }) + // })); } #[cfg(any(test, feature = "test-support"))] @@ -595,7 +553,9 @@ impl VariableList { } pub fn completion_variables(&self, cx: &mut Context) -> Vec { - let stack_frame_id = self.stack_frame_list.read(cx).first_stack_frame_id(); + let stack_frame_id = self + .stack_frame_list + .update(cx, |this, cx| this.get_main_stack_frame_id(cx)); self.variables .range((stack_frame_id, u64::MIN)..(stack_frame_id, u64::MAX)) @@ -665,24 +625,27 @@ impl VariableList { return self.toggle_entry(&entry_id, cx); } - let fetch_variables_task = self.dap_store.update(cx, |store, cx| { - let thread_id = self.stack_frame_list.read(cx).thread_id(); - store.variables( - &self.client_id, - thread_id, - stack_frame_id, - scope_id, - self.session.read(cx).id(), - variable.variables_reference, - cx, - ) - }); + // let fetch_variables_task = self.dap_store.update(cx, |store, cx| { + // let thread_id = self.stack_frame_list.read(cx).thread_id(); + // store.variables( + // &self.client_id, + // thread_id, + // stack_frame_id, + // scope_id, + // self.session.read(cx).id(), + // variable.variables_reference, + // cx, + // ) + // }); + let fetch_variables_task = Task::ready(anyhow::Result::Err(anyhow!( + "Toggling variables isn't supported yet (dued to refactor)" + ))); let container_reference = variable.variables_reference; let entry_id = entry_id.clone(); self.fetch_variables_task = Some(cx.spawn(|this, mut cx| async move { - let new_variables = fetch_variables_task.await?; + let new_variables: Vec = fetch_variables_task.await?; this.update(&mut cx, |this, cx| { let Some(index) = this.variables.get_mut(&(stack_frame_id, scope_id)) else { @@ -867,119 +830,7 @@ impl VariableList { open_entries: &Vec, cx: &mut Context, ) -> Task>> { - let stack_frame_list = self.stack_frame_list.read(cx); - let thread_id = stack_frame_list.thread_id(); - let stack_frame_id = stack_frame_list.current_stack_frame_id(); - - let variables_task = self.dap_store.update(cx, |store, cx| { - store.variables( - &self.client_id, - thread_id, - stack_frame_id, - scope.variables_reference, - self.session.read(cx).id(), - container_reference, - cx, - ) - }); - - cx.spawn({ - let scope = scope.clone(); - let open_entries = open_entries.clone(); - |this, mut cx| async move { - let mut variables = Vec::new(); - - for variable in variables_task.await? { - variables.push(VariableContainer { - depth, - container_reference, - variable: variable.clone(), - }); - - if open_entries - .binary_search(&OpenEntry::Variable { - depth, - name: variable.name, - scope_name: scope.name.clone(), - }) - .is_ok() - { - let task = this.update(&mut cx, |this, cx| { - this.fetch_nested_variables( - &scope, - variable.variables_reference, - depth + 1, - &open_entries, - cx, - ) - })?; - - variables.extend(task.await?); - } - } - - anyhow::Ok(variables) - } - }) - } - - fn fetch_variables_for_stack_frame( - &self, - stack_frame_id: u64, - cx: &mut Context, - ) -> Task, HashMap>)>> { - let scopes_task = self.dap_store.update(cx, |store, cx| { - store.scopes(&self.client_id, stack_frame_id, cx) - }); - - cx.spawn({ - |this, mut cx| async move { - let mut variables = HashMap::new(); - - let scopes = scopes_task.await?; - - let open_entries = this.read_with(&cx, |variable_list, _| { - variable_list - .open_entries - .iter() - .filter(|entry| matches!(entry, OpenEntry::Variable { .. })) - .cloned() - .collect::>() - })?; - - for scope in scopes.iter() { - let variables_task = this.update(&mut cx, |this, cx| { - this.fetch_nested_variables( - scope, - scope.variables_reference, - 1, - &open_entries, - cx, - ) - })?; - - variables.insert(scope.variables_reference, variables_task.await?); - } - - Ok((scopes, variables)) - } - }) - } - - fn send_update_proto_message(&self, cx: &mut Context) { - if let Some((client, project_id)) = self.dap_store.read(cx).downstream_client() { - let request = UpdateDebugAdapter { - client_id: self.client_id.to_proto(), - session_id: self.session.read(cx).id().to_proto(), - thread_id: Some(self.stack_frame_list.read(cx).thread_id()), - project_id: *project_id, - variant: Some(rpc::proto::update_debug_adapter::Variant::VariableList( - self.to_proto(), - )), - }; - - client.send(request).log_err(); - }; + Task::ready(Ok(vec![])) } fn deploy_variable_context_menu( @@ -991,14 +842,20 @@ impl VariableList { window: &mut Window, cx: &mut Context, ) { - let Some(caps) = self - .dap_store + let Some((support_set_variable, support_clipboard_context)) = self + .session .read(cx) - .capabilities_by_id(&self.client_id, cx) + .client_state(self.client_id) + .map(|state| state.read(cx).capabilities()) + .map(|caps| { + ( + caps.supports_set_variable.unwrap_or_default(), + caps.supports_clipboard_context.unwrap_or_default(), + ) + }) else { return; }; - let support_set_variable = caps.supports_set_variable.unwrap_or_default(); let this = cx.entity(); @@ -1016,36 +873,26 @@ impl VariableList { let evaluate_name = variable.evaluate_name.clone(); window.handler_for(&this.clone(), move |this, _window, cx| { - this.dap_store.update(cx, |dap_store, cx| { - if dap_store - .capabilities_by_id(&this.client_id, cx) - .map(|caps| caps.supports_clipboard_context) - .flatten() - .unwrap_or_default() - { - let task = dap_store.evaluate( - &this.client_id, - this.stack_frame_list.read(cx).current_stack_frame_id(), + if support_clipboard_context { + let Some(client_state) = this.session.read(cx).client_state(this.client_id) + else { + return; + }; + + client_state.update(cx, |state, cx| { + state.evaluate( evaluate_name.clone().unwrap_or(variable_name.clone()), - dap::EvaluateArgumentsContext::Clipboard, + Some(dap::EvaluateArgumentsContext::Clipboard), + Some(this.stack_frame_list.read(cx).current_stack_frame_id()), source.clone(), cx, ); - - cx.spawn(|_, cx| async move { - let response = task.await?; - - cx.update(|cx| { - cx.write_to_clipboard(ClipboardItem::new_string( - response.result, - )) - }) - }) - .detach_and_log_err(cx); - } else { - cx.write_to_clipboard(ClipboardItem::new_string(variable_value.clone())) - } - }); + }); + // TODO(debugger): make this work again: + // cx.write_to_clipboard(ClipboardItem::new_string(response.result)); + } else { + cx.write_to_clipboard(ClipboardItem::new_string(variable_value.clone())) + } }) }) .when_some( @@ -1126,46 +973,28 @@ impl VariableList { new_variable_value }); - let Some(state) = self.set_variable_state.take() else { + let Some(set_variable_state) = self.set_variable_state.take() else { return; }; - if new_variable_value == state.value - || state.stack_frame_id != self.stack_frame_list.read(cx).current_stack_frame_id() + if new_variable_value == set_variable_state.value + || set_variable_state.stack_frame_id + != self.stack_frame_list.read(cx).current_stack_frame_id() { return cx.notify(); } - let set_value_task = self.dap_store.update(cx, |store, cx| { - store.set_variable_value( - &self.client_id, - state.stack_frame_id, - state.parent_variables_reference, - state.name, + let Some(client_state) = self.session.read(cx).client_state(self.client_id) else { + return; + }; + + client_state.update(cx, |state, cx| { + state.set_variable_value( + set_variable_state.parent_variables_reference, + set_variable_state.name, new_variable_value, - state.evaluate_name, cx, - ) - }); - - cx.spawn_in(window, |this, mut cx| async move { - set_value_task.await?; - - this.update_in(&mut cx, |this, window, cx| { - this.build_entries(false, cx); - this.invalidate(window, cx); - }) - }) - .detach_and_log_err(cx); - } - - pub fn invalidate(&mut self, window: &mut Window, cx: &mut Context) { - self.variables.clear(); - self.scopes.clear(); - self.entries.clear(); - - self.stack_frame_list.update(cx, |stack_frame_list, cx| { - stack_frame_list.invalidate(window, cx); + ); }); } diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 6c1d8b8a5a..b220bb5151 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -29,7 +29,7 @@ use parking_lot::Mutex; use pretty_assertions::{assert_eq, assert_ne}; use project::FakeFs; use project::{ - dap_store::BreakpointKind, + debugger::dap_store::BreakpointKind, lsp_command::SIGNATURE_HELP_HIGHLIGHT_CURRENT, project_settings::{LspSettings, ProjectSettings}, }; diff --git a/crates/project/Cargo.toml b/crates/project/Cargo.toml index 5c83fd04b4..10be9c3c55 100644 --- a/crates/project/Cargo.toml +++ b/crates/project/Cargo.toml @@ -44,6 +44,7 @@ globset.workspace = true gpui.workspace = true http_client.workspace = true itertools.workspace = true +indexmap.workspace = true language.workspace = true log.workspace = true lsp.workspace = true diff --git a/crates/project/src/debugger/dap_command.rs b/crates/project/src/debugger/dap_command.rs index cc1b1430b8..b4b2e7f542 100644 --- a/crates/project/src/debugger/dap_command.rs +++ b/crates/project/src/debugger/dap_command.rs @@ -5,14 +5,13 @@ use dap::{ client::DebugAdapterClientId, proto_conversions::ProtoConversion, requests::{Continue, Next}, - Capabilities, ContinueArguments, NextArguments, StepInArguments, StepOutArguments, - SteppingGranularity, ValueFormat, Variable, VariablesArgumentsFilter, + Capabilities, ContinueArguments, NextArguments, SetVariableResponse, StepInArguments, + StepOutArguments, SteppingGranularity, ValueFormat, Variable, VariablesArgumentsFilter, }; -use gpui::{AsyncApp, WeakEntity}; use rpc::proto; use util::ResultExt; -use super::{dap_session::DebugSessionId, dap_store::DapStore}; +use super::dap_session::DebugSessionId; pub(crate) trait DapCommand: 'static + Send + Sync + std::fmt::Debug { type Response: 'static + Send + std::fmt::Debug; @@ -21,28 +20,18 @@ pub(crate) trait DapCommand: 'static + Send + Sync + std::fmt::Debug { fn is_supported(&self, capabilities: &Capabilities) -> bool; - fn handle_response( - &self, - _dap_store: WeakEntity, - _client_id: &DebugAdapterClientId, - response: Result, - _cx: &mut AsyncApp, - ) -> Result { - response - } - fn client_id_from_proto(request: &Self::ProtoRequest) -> DebugAdapterClientId; fn from_proto(request: &Self::ProtoRequest) -> Self; fn to_proto( &self, - debug_client_id: &DebugAdapterClientId, + debug_client_id: DebugAdapterClientId, upstream_project_id: u64, ) -> Self::ProtoRequest; fn response_to_proto( - debug_client_id: &DebugAdapterClientId, + debug_client_id: DebugAdapterClientId, message: Self::Response, ) -> ::Response; @@ -78,14 +67,14 @@ impl DapCommand for Arc { fn to_proto( &self, - debug_client_id: &DebugAdapterClientId, + debug_client_id: DebugAdapterClientId, upstream_project_id: u64, ) -> Self::ProtoRequest { T::to_proto(self, debug_client_id, upstream_project_id) } fn response_to_proto( - debug_client_id: &DebugAdapterClientId, + debug_client_id: DebugAdapterClientId, message: Self::Response, ) -> ::Response { T::response_to_proto(debug_client_id, message) @@ -161,7 +150,7 @@ impl DapCommand for NextCommand { } fn response_to_proto( - _debug_client_id: &DebugAdapterClientId, + _debug_client_id: DebugAdapterClientId, _message: Self::Response, ) -> ::Response { proto::Ack {} @@ -169,7 +158,7 @@ impl DapCommand for NextCommand { fn to_proto( &self, - debug_client_id: &DebugAdapterClientId, + debug_client_id: DebugAdapterClientId, upstream_project_id: u64, ) -> proto::DapNextRequest { proto::DapNextRequest { @@ -235,7 +224,7 @@ impl DapCommand for StepInCommand { } fn response_to_proto( - _debug_client_id: &DebugAdapterClientId, + _debug_client_id: DebugAdapterClientId, _message: Self::Response, ) -> ::Response { proto::Ack {} @@ -243,7 +232,7 @@ impl DapCommand for StepInCommand { fn to_proto( &self, - debug_client_id: &DebugAdapterClientId, + debug_client_id: DebugAdapterClientId, upstream_project_id: u64, ) -> proto::DapStepInRequest { proto::DapStepInRequest { @@ -294,36 +283,6 @@ impl DapCommand for StepOutCommand { true } - fn handle_response( - &self, - dap_store: WeakEntity, - client_id: &DebugAdapterClientId, - response: Result, - cx: &mut AsyncApp, - ) -> Result { - if response.is_ok() { - dap_store - .update(cx, |this, cx| { - if let Some((client, project_id)) = this.downstream_client() { - let thread_message = proto::UpdateThreadStatus { - project_id: *project_id, - client_id: client_id.to_proto(), - thread_id: self.inner.thread_id, - status: proto::DebuggerThreadStatus::Running.into(), - }; - - cx.emit(super::dap_store::DapStoreEvent::UpdateThreadStatus( - thread_message.clone(), - )); - - client.send(thread_message).log_err(); - } - }) - .log_err(); - } - response - } - fn client_id_from_proto(request: &Self::ProtoRequest) -> DebugAdapterClientId { DebugAdapterClientId::from_proto(request.client_id) } @@ -341,7 +300,7 @@ impl DapCommand for StepOutCommand { } fn response_to_proto( - _debug_client_id: &DebugAdapterClientId, + _debug_client_id: DebugAdapterClientId, _message: Self::Response, ) -> ::Response { proto::Ack {} @@ -349,7 +308,7 @@ impl DapCommand for StepOutCommand { fn to_proto( &self, - debug_client_id: &DebugAdapterClientId, + debug_client_id: DebugAdapterClientId, upstream_project_id: u64, ) -> proto::DapStepOutRequest { proto::DapStepOutRequest { @@ -415,7 +374,7 @@ impl DapCommand for StepBackCommand { } fn response_to_proto( - _debug_client_id: &DebugAdapterClientId, + _debug_client_id: DebugAdapterClientId, _message: Self::Response, ) -> ::Response { proto::Ack {} @@ -423,7 +382,7 @@ impl DapCommand for StepBackCommand { fn to_proto( &self, - debug_client_id: &DebugAdapterClientId, + debug_client_id: DebugAdapterClientId, upstream_project_id: u64, ) -> proto::DapStepBackRequest { proto::DapStepBackRequest { @@ -472,43 +431,13 @@ impl DapCommand for ContinueCommand { true } - fn handle_response( - &self, - dap_store: WeakEntity, - client_id: &DebugAdapterClientId, - response: Result, - cx: &mut AsyncApp, - ) -> Result { - if response.is_ok() { - dap_store - .update(cx, |this, cx| { - if let Some((client, project_id)) = this.downstream_client() { - let thread_message = proto::UpdateThreadStatus { - project_id: *project_id, - client_id: client_id.to_proto(), - thread_id: self.args.thread_id, - status: proto::DebuggerThreadStatus::Running.into(), - }; - - cx.emit(super::dap_store::DapStoreEvent::UpdateThreadStatus( - thread_message.clone(), - )); - - client.send(thread_message).log_err(); - } - }) - .log_err(); - } - response - } - fn client_id_from_proto(request: &Self::ProtoRequest) -> DebugAdapterClientId { DebugAdapterClientId::from_proto(request.client_id) } fn to_proto( &self, - debug_client_id: &DebugAdapterClientId, + debug_client_id: DebugAdapterClientId, upstream_project_id: u64, ) -> proto::DapContinueRequest { proto::DapContinueRequest { @@ -549,7 +478,7 @@ impl DapCommand for ContinueCommand { } fn response_to_proto( - debug_client_id: &DebugAdapterClientId, + debug_client_id: DebugAdapterClientId, message: Self::Response, ) -> ::Response { proto::DapContinueResponse { @@ -585,7 +514,7 @@ impl DapCommand for PauseCommand { fn to_proto( &self, - debug_client_id: &DebugAdapterClientId, + debug_client_id: DebugAdapterClientId, upstream_project_id: u64, ) -> proto::DapPauseRequest { proto::DapPauseRequest { @@ -596,7 +525,7 @@ impl DapCommand for PauseCommand { } fn response_to_proto( - _debug_client_id: &DebugAdapterClientId, + _debug_client_id: DebugAdapterClientId, _message: Self::Response, ) -> ::Response { proto::Ack {} @@ -653,7 +582,7 @@ impl DapCommand for DisconnectCommand { fn to_proto( &self, - debug_client_id: &DebugAdapterClientId, + debug_client_id: DebugAdapterClientId, upstream_project_id: u64, ) -> proto::DapDisconnectRequest { proto::DapDisconnectRequest { @@ -666,7 +595,7 @@ impl DapCommand for DisconnectCommand { } fn response_to_proto( - _debug_client_id: &DebugAdapterClientId, + _debug_client_id: DebugAdapterClientId, _message: Self::Response, ) -> ::Response { proto::Ack {} @@ -727,7 +656,7 @@ impl DapCommand for TerminateThreadsCommand { fn to_proto( &self, - debug_client_id: &DebugAdapterClientId, + debug_client_id: DebugAdapterClientId, upstream_project_id: u64, ) -> proto::DapTerminateThreadsRequest { proto::DapTerminateThreadsRequest { @@ -738,7 +667,7 @@ impl DapCommand for TerminateThreadsCommand { } fn response_to_proto( - _debug_client_id: &DebugAdapterClientId, + _debug_client_id: DebugAdapterClientId, _message: Self::Response, ) -> ::Response { proto::Ack {} @@ -791,7 +720,7 @@ impl DapCommand for TerminateCommand { fn to_proto( &self, - debug_client_id: &DebugAdapterClientId, + debug_client_id: DebugAdapterClientId, upstream_project_id: u64, ) -> proto::DapTerminateRequest { proto::DapTerminateRequest { @@ -802,7 +731,7 @@ impl DapCommand for TerminateCommand { } fn response_to_proto( - _debug_client_id: &DebugAdapterClientId, + _debug_client_id: DebugAdapterClientId, _message: Self::Response, ) -> ::Response { proto::Ack {} @@ -857,7 +786,7 @@ impl DapCommand for RestartCommand { fn to_proto( &self, - debug_client_id: &DebugAdapterClientId, + debug_client_id: DebugAdapterClientId, upstream_project_id: u64, ) -> proto::DapRestartRequest { let raw_args = serde_json::to_vec(&self.raw).log_err().unwrap_or_default(); @@ -870,7 +799,7 @@ impl DapCommand for RestartCommand { } fn response_to_proto( - _debug_client_id: &DebugAdapterClientId, + _debug_client_id: DebugAdapterClientId, _message: Self::Response, ) -> ::Response { proto::Ack {} @@ -900,7 +829,6 @@ impl DapCommand for RestartCommand { #[derive(Debug, Hash, PartialEq, Eq)] pub struct VariablesCommand { pub stack_frame_id: u64, - pub scope_id: u64, pub thread_id: u64, pub variables_reference: u64, pub session_id: DebugSessionId, @@ -919,40 +847,6 @@ impl DapCommand for VariablesCommand { true } - fn handle_response( - &self, - dap_store: WeakEntity, - client_id: &DebugAdapterClientId, - response: Result, - cx: &mut AsyncApp, - ) -> Result { - let variables = response?; - - dap_store.update(cx, |this, _| { - if let Some((downstream_clients, project_id)) = this.downstream_client() { - let update = proto::UpdateDebugAdapter { - project_id: *project_id, - session_id: self.session_id.to_proto(), - client_id: client_id.to_proto(), - thread_id: Some(self.thread_id), - variant: Some(proto::update_debug_adapter::Variant::AddToVariableList( - proto::AddToVariableList { - scope_id: self.scope_id, - stack_frame_id: self.stack_frame_id, - variable_id: self.variables_reference, - variables: variables.to_proto(), - }, - )), - }; - - downstream_clients.send(update.clone()).log_err(); - // cx.emit(crate::dap_store::DapStoreEvent::UpdateDebugAdapter(update)); - } - })?; - - Ok(variables) - } - fn client_id_from_proto(request: &Self::ProtoRequest) -> DebugAdapterClientId { DebugAdapterClientId::from_proto(request.client_id) } @@ -976,7 +870,7 @@ impl DapCommand for VariablesCommand { fn to_proto( &self, - debug_client_id: &DebugAdapterClientId, + debug_client_id: DebugAdapterClientId, upstream_project_id: u64, ) -> Self::ProtoRequest { proto::VariablesRequest { @@ -985,7 +879,6 @@ impl DapCommand for VariablesCommand { thread_id: self.thread_id, session_id: self.session_id.to_proto(), stack_frame_id: self.stack_frame_id, - scope_id: self.scope_id, variables_reference: self.variables_reference, filter: None, start: self.start, @@ -999,7 +892,6 @@ impl DapCommand for VariablesCommand { thread_id: request.thread_id, session_id: DebugSessionId::from_proto(request.session_id), stack_frame_id: request.stack_frame_id, - scope_id: request.scope_id, variables_reference: request.variables_reference, filter: None, start: request.start, @@ -1009,7 +901,7 @@ impl DapCommand for VariablesCommand { } fn response_to_proto( - debug_client_id: &DebugAdapterClientId, + debug_client_id: DebugAdapterClientId, message: Self::Response, ) -> ::Response { proto::DapVariables { @@ -1026,6 +918,94 @@ impl DapCommand for VariablesCommand { } } +#[derive(Debug, Hash, PartialEq, Eq)] +pub(crate) struct SetVariableValueCommand { + pub name: String, + pub value: String, + pub variables_reference: u64, +} + +impl DapCommand for SetVariableValueCommand { + type Response = SetVariableResponse; + type DapRequest = dap::requests::SetVariable; + type ProtoRequest = proto::DapSetVariableValueRequest; + + fn is_supported(&self, capabilities: &Capabilities) -> bool { + capabilities.supports_set_variable.unwrap_or_default() + } + + fn client_id_from_proto(request: &Self::ProtoRequest) -> DebugAdapterClientId { + DebugAdapterClientId::from_proto(request.client_id) + } + + fn to_dap(&self) -> ::Arguments { + dap::SetVariableArguments { + format: None, + name: self.name.clone(), + value: self.value.clone(), + variables_reference: self.variables_reference, + } + } + + fn response_from_dap( + &self, + message: ::Response, + ) -> Result { + Ok(message) + } + + fn to_proto( + &self, + debug_client_id: DebugAdapterClientId, + upstream_project_id: u64, + ) -> Self::ProtoRequest { + proto::DapSetVariableValueRequest { + project_id: upstream_project_id, + client_id: debug_client_id.to_proto(), + variables_reference: self.variables_reference, + value: self.value.clone(), + name: self.name.clone(), + } + } + + fn from_proto(request: &Self::ProtoRequest) -> Self { + Self { + variables_reference: request.variables_reference, + name: request.name.clone(), + value: request.value.clone(), + } + } + + fn response_to_proto( + debug_client_id: DebugAdapterClientId, + message: Self::Response, + ) -> ::Response { + proto::DapSetVariableValueResponse { + client_id: debug_client_id.to_proto(), + value: message.value, + variable_type: message.type_, + named_variables: message.named_variables, + variables_reference: message.variables_reference, + indexed_variables: message.indexed_variables, + memory_reference: message.memory_reference, + } + } + + fn response_from_proto( + &self, + message: ::Response, + ) -> Result { + Ok(SetVariableResponse { + value: message.value, + type_: message.variable_type, + variables_reference: message.variables_reference, + named_variables: message.named_variables, + indexed_variables: message.indexed_variables, + memory_reference: message.memory_reference, + }) + } +} + #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub(crate) struct RestartStackFrameCommand { pub stack_frame_id: u64, @@ -1052,7 +1032,7 @@ impl DapCommand for RestartStackFrameCommand { fn to_proto( &self, - debug_client_id: &DebugAdapterClientId, + debug_client_id: DebugAdapterClientId, upstream_project_id: u64, ) -> proto::DapRestartStackFrameRequest { proto::DapRestartStackFrameRequest { @@ -1063,7 +1043,7 @@ impl DapCommand for RestartStackFrameCommand { } fn response_to_proto( - _debug_client_id: &DebugAdapterClientId, + _debug_client_id: DebugAdapterClientId, _message: Self::Response, ) -> ::Response { proto::Ack {} @@ -1112,7 +1092,7 @@ impl DapCommand for ModulesCommand { fn to_proto( &self, - debug_client_id: &DebugAdapterClientId, + debug_client_id: DebugAdapterClientId, upstream_project_id: u64, ) -> proto::DapModulesRequest { proto::DapModulesRequest { @@ -1122,7 +1102,7 @@ impl DapCommand for ModulesCommand { } fn response_to_proto( - debug_client_id: &DebugAdapterClientId, + debug_client_id: DebugAdapterClientId, message: Self::Response, ) -> ::Response { proto::DapModulesResponse { @@ -1184,7 +1164,7 @@ impl DapCommand for LoadedSourcesCommand { fn to_proto( &self, - debug_client_id: &DebugAdapterClientId, + debug_client_id: DebugAdapterClientId, upstream_project_id: u64, ) -> proto::DapLoadedSourcesRequest { proto::DapLoadedSourcesRequest { @@ -1194,7 +1174,7 @@ impl DapCommand for LoadedSourcesCommand { } fn response_to_proto( - debug_client_id: &DebugAdapterClientId, + debug_client_id: DebugAdapterClientId, message: Self::Response, ) -> ::Response { proto::DapLoadedSourcesResponse { @@ -1228,3 +1208,381 @@ impl DapCommand for LoadedSourcesCommand { .collect()) } } + +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub(crate) struct StackTraceCommand { + pub thread_id: u64, + pub start_frame: Option, + pub levels: Option, +} + +impl DapCommand for StackTraceCommand { + type Response = Vec; + type DapRequest = dap::requests::StackTrace; + type ProtoRequest = proto::DapStackTraceRequest; + + fn to_dap(&self) -> ::Arguments { + dap::StackTraceArguments { + thread_id: self.thread_id, + start_frame: self.start_frame, + levels: self.levels, + format: None, + } + } + + fn response_from_dap( + &self, + message: ::Response, + ) -> Result { + Ok(message.stack_frames) + } + + fn is_supported(&self, _capabilities: &Capabilities) -> bool { + true + } + + fn to_proto( + &self, + debug_client_id: DebugAdapterClientId, + upstream_project_id: u64, + ) -> Self::ProtoRequest { + proto::DapStackTraceRequest { + project_id: upstream_project_id, + client_id: debug_client_id.to_proto(), + thread_id: self.thread_id, + start_frame: self.start_frame, + stack_trace_levels: self.levels, + } + } + + fn from_proto(request: &Self::ProtoRequest) -> Self { + Self { + thread_id: request.thread_id, + start_frame: request.start_frame, + levels: request.stack_trace_levels, + } + } + + fn client_id_from_proto(request: &Self::ProtoRequest) -> DebugAdapterClientId { + DebugAdapterClientId::from_proto(request.client_id) + } + + fn response_from_proto( + &self, + message: ::Response, + ) -> Result { + Ok(message + .frames + .into_iter() + .map(dap::StackFrame::from_proto) + .collect()) + } + + fn response_to_proto( + _debug_client_id: DebugAdapterClientId, + message: Self::Response, + ) -> ::Response { + proto::DapStackTraceResponse { + frames: message.to_proto(), + } + } +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub(crate) struct ScopesCommand { + pub thread_id: u64, + pub stack_frame_id: u64, +} + +impl DapCommand for ScopesCommand { + type Response = Vec; + type DapRequest = dap::requests::Scopes; + type ProtoRequest = proto::DapScopesRequest; + + fn to_dap(&self) -> ::Arguments { + dap::ScopesArguments { + frame_id: self.stack_frame_id, + } + } + + fn response_from_dap( + &self, + message: ::Response, + ) -> Result { + Ok(message.scopes) + } + + fn is_supported(&self, _capabilities: &Capabilities) -> bool { + true + } + + fn to_proto( + &self, + debug_client_id: DebugAdapterClientId, + upstream_project_id: u64, + ) -> Self::ProtoRequest { + proto::DapScopesRequest { + project_id: upstream_project_id, + client_id: debug_client_id.to_proto(), + thread_id: self.thread_id, + stack_frame_id: self.stack_frame_id, + } + } + + fn from_proto(request: &Self::ProtoRequest) -> Self { + Self { + thread_id: request.thread_id, + stack_frame_id: request.stack_frame_id, + } + } + + fn client_id_from_proto(request: &Self::ProtoRequest) -> DebugAdapterClientId { + DebugAdapterClientId::from_proto(request.client_id) + } + + fn response_from_proto( + &self, + message: ::Response, + ) -> Result { + Ok(Vec::from_proto(message.scopes)) + } + + fn response_to_proto( + _debug_client_id: DebugAdapterClientId, + message: Self::Response, + ) -> ::Response { + proto::DapScopesResponse { + scopes: message.to_proto(), + } + } +} + +impl DapCommand for super::dap_session::CompletionsQuery { + type Response = dap::CompletionsResponse; + type DapRequest = dap::requests::Completions; + type ProtoRequest = proto::DapCompletionRequest; + + fn to_dap(&self) -> ::Arguments { + dap::CompletionsArguments { + text: self.query.clone(), + frame_id: self.frame_id, + column: self.column, + line: None, + } + } + + fn response_from_dap( + &self, + message: ::Response, + ) -> Result { + Ok(message) + } + + fn is_supported(&self, capabilities: &Capabilities) -> bool { + capabilities + .supports_completions_request + .unwrap_or_default() + } + + fn to_proto( + &self, + debug_client_id: DebugAdapterClientId, + upstream_project_id: u64, + ) -> Self::ProtoRequest { + proto::DapCompletionRequest { + client_id: debug_client_id.to_proto(), + project_id: upstream_project_id, + frame_id: self.frame_id, + query: self.query.clone(), + column: self.column, + line: self.line.map(u64::from), + } + } + + fn client_id_from_proto(request: &Self::ProtoRequest) -> DebugAdapterClientId { + DebugAdapterClientId::from_proto(request.client_id) + } + + fn from_proto(request: &Self::ProtoRequest) -> Self { + Self { + query: request.query.clone(), + frame_id: request.frame_id, + column: request.column, + line: request.line, + } + } + + fn response_from_proto( + &self, + message: ::Response, + ) -> Result { + Ok(dap::CompletionsResponse { + targets: Vec::from_proto(message.completions), + }) + } + + fn response_to_proto( + _debug_client_id: DebugAdapterClientId, + message: Self::Response, + ) -> ::Response { + proto::DapCompletionResponse { + client_id: _debug_client_id.to_proto(), + completions: message.targets.to_proto(), + } + } +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub(crate) struct EvaluateCommand { + pub expression: String, + pub frame_id: Option, + pub context: Option, + pub source: Option, +} + +impl DapCommand for EvaluateCommand { + type Response = dap::EvaluateResponse; + type DapRequest = dap::requests::Evaluate; + type ProtoRequest = proto::DapEvaluateRequest; + + fn to_dap(&self) -> ::Arguments { + dap::EvaluateArguments { + expression: self.expression.clone(), + frame_id: self.frame_id, + context: self.context.clone(), + source: self.source.clone(), + line: None, + column: None, + format: None, + } + } + + fn response_from_dap( + &self, + message: ::Response, + ) -> Result { + Ok(message) + } + + fn is_supported(&self, _capabilities: &Capabilities) -> bool { + true + } + + fn to_proto( + &self, + debug_client_id: DebugAdapterClientId, + upstream_project_id: u64, + ) -> Self::ProtoRequest { + proto::DapEvaluateRequest { + client_id: debug_client_id.to_proto(), + project_id: upstream_project_id, + expression: self.expression.clone(), + frame_id: self.frame_id, + context: self + .context + .clone() + .map(|context| context.to_proto().into()), + } + } + + fn client_id_from_proto(request: &Self::ProtoRequest) -> DebugAdapterClientId { + DebugAdapterClientId::from_proto(request.client_id) + } + + fn from_proto(request: &Self::ProtoRequest) -> Self { + Self { + expression: request.expression.clone(), + frame_id: request.frame_id, + context: Some(dap::EvaluateArgumentsContext::from_proto(request.context())), + source: None, + } + } + + fn response_from_proto( + &self, + message: ::Response, + ) -> Result { + Ok(dap::EvaluateResponse { + result: message.result.clone(), + type_: message.evaluate_type.clone(), + presentation_hint: None, + variables_reference: message.variable_reference, + named_variables: message.named_variables, + indexed_variables: message.indexed_variables, + memory_reference: message.memory_reference.clone(), + }) + } + + fn response_to_proto( + _debug_client_id: DebugAdapterClientId, + message: Self::Response, + ) -> ::Response { + proto::DapEvaluateResponse { + result: message.result, + evaluate_type: message.type_, + variable_reference: message.variables_reference, + named_variables: message.named_variables, + indexed_variables: message.indexed_variables, + memory_reference: message.memory_reference, + } + } +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub(crate) struct ThreadsCommand; + +impl DapCommand for ThreadsCommand { + type Response = Vec; + type DapRequest = dap::requests::Threads; + type ProtoRequest = proto::DapThreadsRequest; + + fn to_dap(&self) -> ::Arguments { + () + } + + fn response_from_dap( + &self, + message: ::Response, + ) -> Result { + Ok(message.threads) + } + + fn is_supported(&self, _capabilities: &Capabilities) -> bool { + true + } + + fn to_proto( + &self, + debug_client_id: DebugAdapterClientId, + upstream_project_id: u64, + ) -> Self::ProtoRequest { + proto::DapThreadsRequest { + project_id: upstream_project_id, + client_id: debug_client_id.to_proto(), + } + } + + fn from_proto(_request: &Self::ProtoRequest) -> Self { + Self {} + } + + fn client_id_from_proto(request: &Self::ProtoRequest) -> DebugAdapterClientId { + DebugAdapterClientId::from_proto(request.client_id) + } + + fn response_from_proto( + &self, + message: ::Response, + ) -> Result { + Ok(Vec::from_proto(message.threads)) + } + + fn response_to_proto( + _debug_client_id: DebugAdapterClientId, + message: Self::Response, + ) -> ::Response { + proto::DapThreadsResponse { + threads: message.to_proto(), + } + } +} diff --git a/crates/project/src/debugger/dap_session.rs b/crates/project/src/debugger/dap_session.rs index 1d671f4800..ce9bb72ffd 100644 --- a/crates/project/src/debugger/dap_session.rs +++ b/crates/project/src/debugger/dap_session.rs @@ -1,7 +1,23 @@ -use collections::{BTreeMap, HashMap}; -use dap::{Capabilities, Module, Source}; +use super::dap_command::{ + self, ContinueCommand, DapCommand, DisconnectCommand, EvaluateCommand, NextCommand, + PauseCommand, RestartCommand, RestartStackFrameCommand, ScopesCommand, SetVariableValueCommand, + StepBackCommand, StepCommand, StepInCommand, StepOutCommand, TerminateCommand, + TerminateThreadsCommand, VariablesCommand, +}; +use anyhow::{anyhow, Result}; +use collections::{BTreeMap, HashMap, IndexMap}; +use dap::client::{DebugAdapterClient, DebugAdapterClientId}; +use dap::requests::Request; +use dap::{ + Capabilities, ContinueArguments, EvaluateArgumentsContext, Module, Source, SteppingGranularity, +}; use futures::{future::Shared, FutureExt}; -use gpui::{AppContext, Context, Entity, Task, WeakEntity}; +use gpui::{App, AppContext, Context, Entity, Task}; +use rpc::AnyProtoClient; +use serde_json::Value; +use std::borrow::Borrow; +use std::collections::btree_map::Entry as BTreeMapEntry; +use std::u64; use std::{ any::Any, collections::hash_map::Entry, @@ -9,14 +25,9 @@ use std::{ sync::Arc, }; use task::DebugAdapterConfig; +use text::{PointUtf16, ToPointUtf16}; use util::ResultExt; -use super::{ - dap_command::{self, DapCommand}, - dap_store::DapStore, -}; -use dap::client::{DebugAdapterClient, DebugAdapterClientId}; - #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] pub struct DebugSessionId(pub usize); @@ -31,23 +42,58 @@ impl DebugSessionId { } } -#[derive(Copy, Clone, PartialEq, PartialOrd)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd, Ord, Eq)] #[repr(transparent)] -struct ThreadId(u64); +pub struct ThreadId(pub u64); -struct Variable { - _variable: dap::Variable, - _variables: Vec, +impl ThreadId { + pub const MIN: ThreadId = ThreadId(u64::MIN); + pub const MAX: ThreadId = ThreadId(u64::MAX); } -struct Scope { - _scope: dap::Scope, - _variables: Vec, +#[derive(Clone)] +pub struct Variable { + dap: dap::Variable, + variables: Vec, } -struct StackFrame { - _stack_frame: dap::StackFrame, - _scopes: Vec, +impl From for Variable { + fn from(dap: dap::Variable) -> Self { + Self { + dap, + variables: vec![], + } + } +} + +#[derive(Clone)] +pub struct Scope { + pub dap: dap::Scope, + pub variables: Vec, +} + +impl From for Scope { + fn from(scope: dap::Scope) -> Self { + Self { + dap: scope, + variables: vec![], + } + } +} + +#[derive(Clone)] +pub struct StackFrame { + pub dap: dap::StackFrame, + pub scopes: Vec, +} + +impl From for StackFrame { + fn from(stack_frame: dap::StackFrame) -> Self { + Self { + scopes: vec![], + dap: stack_frame, + } + } } #[derive(Copy, Clone, Default, PartialEq, Eq)] @@ -59,20 +105,140 @@ pub enum ThreadStatus { Ended, } -struct Thread { - _thread: dap::Thread, - _stack_frames: Vec, +pub struct Thread { + dap: dap::Thread, + stack_frames: Vec, _status: ThreadStatus, _has_stopped: bool, } -pub struct DebugAdapterClientState { - dap_store: WeakEntity, +impl From for Thread { + fn from(dap: dap::Thread) -> Self { + Self { + dap, + stack_frames: vec![], + _status: ThreadStatus::default(), + _has_stopped: false, + } + } +} + +type UpstreamProjectId = u64; + +pub struct RemoteConnection { + client: AnyProtoClient, + upstream_project_id: UpstreamProjectId, +} + +impl RemoteConnection { + fn send_proto_client_request( + &self, + request: R, + client_id: DebugAdapterClientId, + cx: &mut App, + ) -> Task> { + let message = request.to_proto(client_id, self.upstream_project_id); + let upstream_client = self.client.clone(); + cx.background_executor().spawn(async move { + let response = upstream_client.request(message).await?; + request.response_from_proto(response) + }) + } + fn request_remote( + &self, + request: R, + client_id: DebugAdapterClientId, + cx: &mut App, + ) -> Task> + where + ::Response: 'static, + ::Arguments: 'static + Send, + { + return self.send_proto_client_request::(request, client_id, cx); + } +} + +pub enum Mode { + Local(Arc), + Remote(RemoteConnection), +} + +impl From for Mode { + fn from(value: RemoteConnection) -> Self { + Self::Remote(value) + } +} + +impl From> for Mode { + fn from(client: Arc) -> Self { + Mode::Local(client) + } +} + +impl Mode { + fn request_local( + connection: &Arc, + caps: &Capabilities, + request: R, + cx: &mut Context, + ) -> Task> + where + ::Response: 'static, + ::Arguments: 'static + Send, + { + if !request.is_supported(&caps) { + return Task::ready(Err(anyhow!( + "Request {} is not supported", + R::DapRequest::COMMAND + ))); + } + + let request = Arc::new(request); + + let request_clone = request.clone(); + let connection = connection.clone(); + let request_task = cx.background_executor().spawn(async move { + let args = request_clone.to_dap(); + connection.request::(args).await + }); + + cx.background_executor().spawn(async move { + let response = request.response_from_dap(request_task.await?); + response + }) + } + + fn request_dap( + &self, + caps: &Capabilities, + client_id: DebugAdapterClientId, + request: R, + cx: &mut Context, + ) -> Task> + where + ::Response: 'static, + ::Arguments: 'static + Send, + { + match self { + Mode::Local(debug_adapter_client) => { + Self::request_local(&debug_adapter_client, caps, request, cx) + } + Mode::Remote(remote_connection) => { + remote_connection.request_remote(request, client_id, cx) + } + } + } +} + +/// Represents a current state of a single debug adapter and provides ways to mutate it. +pub struct Client { + mode: Mode, + pub(super) capabilities: Capabilities, client_id: DebugAdapterClientId, modules: Vec, loaded_sources: Vec, - _threads: BTreeMap, + threads: IndexMap, requests: HashMap>>>, } @@ -127,7 +293,35 @@ impl Hash for RequestSlot { } } -impl DebugAdapterClientState { +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct CompletionsQuery { + pub query: String, + pub column: u64, + pub line: Option, + pub frame_id: Option, +} + +impl CompletionsQuery { + pub fn new( + buffer: &language::Buffer, + cursor_position: language::Anchor, + frame_id: Option, + ) -> Self { + let PointUtf16 { row, column } = cursor_position.to_point_utf16(&buffer.snapshot()); + Self { + query: buffer.text(), + column: column as u64, + frame_id, + line: Some(row as u64), + } + } +} + +impl Client { + pub fn capabilities(&self) -> &Capabilities { + &self.capabilities + } + pub(crate) fn _wait_for_request( &self, request: R, @@ -136,48 +330,102 @@ impl DebugAdapterClientState { self.requests.get(&request_slot).cloned() } - /// Ensure that there's a request in flight for the given command, and if not, send it. - fn request( + /// Ensure that there's a request in flight for the given command, and if not, send it. Use this to run requests that are idempotent. + fn fetch( &mut self, request: T, - process_result: impl FnOnce(&mut Self, T::Response) + 'static + Send + Sync, + process_result: impl FnOnce(&mut Self, &T::Response, &mut Context) + 'static, cx: &mut Context, ) { if let Entry::Vacant(vacant) = self.requests.entry(request.into()) { let command = vacant.key().0.clone().as_any_arc().downcast::().unwrap(); - if let Ok(request) = self.dap_store.update(cx, |dap_store, cx| { - dap_store.request_dap(&self.client_id, command, cx) - }) { - let task = cx - .spawn(|this, mut cx| async move { - let result = request.await.log_err()?; - this.update(&mut cx, |this, cx| { - process_result(this, result); - cx.notify(); - }) - .log_err() - }) - .shared(); + let task = Self::request_inner::>( + &self.capabilities, + self.client_id, + &self.mode, + command, + process_result, + cx, + ); + let task = cx + .background_executor() + .spawn(async move { + let _ = task.await?; + Some(()) + }) + .shared(); - vacant.insert(task); - } + vacant.insert(task); } } + fn request_inner( + capabilities: &Capabilities, + client_id: DebugAdapterClientId, + mode: &Mode, + request: T, + process_result: impl FnOnce(&mut Self, &T::Response, &mut Context) + 'static, + cx: &mut Context, + ) -> Task> { + let request = mode.request_dap(&capabilities, client_id, request, cx); + cx.spawn(|this, mut cx| async move { + let result = request.await.log_err()?; + this.update(&mut cx, |this, cx| { + process_result(this, &result, cx); + }) + .log_err(); + Some(result) + }) + } + + fn request( + &self, + request: T, + process_result: impl FnOnce(&mut Self, &T::Response, &mut Context) + 'static, + cx: &mut Context, + ) -> Task> { + Self::request_inner( + &self.capabilities, + self.client_id, + &self.mode, + request, + process_result, + cx, + ) + } + pub fn invalidate(&mut self, cx: &mut Context) { self.requests.clear(); self.modules.clear(); self.loaded_sources.clear(); - cx.notify(); } + pub fn threads(&mut self, cx: &mut Context) -> Vec { + self.fetch( + dap_command::ThreadsCommand, + |this, result, cx| { + this.threads.extend( + result + .iter() + .map(|thread| (ThreadId(thread.id), Thread::from(thread.clone()))), + ); + }, + cx, + ); + self.threads + .values() + .map(|thread| thread.dap.clone()) + .collect() + } + pub fn modules(&mut self, cx: &mut Context) -> &[Module] { - self.request( + self.fetch( dap_command::ModulesCommand, - |this, result| { - this.modules = result; + |this, result, cx| { + this.modules = result.clone(); + cx.notify(); }, cx, ); @@ -198,16 +446,224 @@ impl DebugAdapterClientState { } pub fn loaded_sources(&mut self, cx: &mut Context) -> &[Source] { - self.request( + self.fetch( dap_command::LoadedSourcesCommand, - |this, result| { - this.loaded_sources = result; + |this, result, cx| { + this.loaded_sources = result.clone(); + cx.notify(); }, cx, ); &self.loaded_sources } + fn empty_response(&mut self, _: &(), _cx: &mut Context) {} + + pub fn pause_thread(&mut self, thread_id: ThreadId, cx: &mut Context) { + self.request( + PauseCommand { + thread_id: thread_id.0, + }, + Self::empty_response, + cx, + ) + .detach(); + } + + pub fn restart_stack_frame(&mut self, stack_frame_id: u64, cx: &mut Context) { + self.request( + RestartStackFrameCommand { stack_frame_id }, + Self::empty_response, + cx, + ) + .detach(); + } + + pub fn restart(&mut self, args: Option, cx: &mut Context) { + if self.capabilities.supports_restart_request.unwrap_or(false) { + self.request( + RestartCommand { + raw: args.unwrap_or(Value::Null), + }, + Self::empty_response, + cx, + ) + .detach(); + } else { + self.request( + DisconnectCommand { + restart: Some(false), + terminate_debuggee: Some(true), + suspend_debuggee: Some(false), + }, + Self::empty_response, + cx, + ) + .detach(); + } + } + + fn shutdown(&mut self, cx: &mut Context) { + if self + .capabilities + .supports_terminate_request + .unwrap_or_default() + { + self.request( + TerminateCommand { + restart: Some(false), + }, + Self::empty_response, + cx, + ) + .detach(); + } else { + self.request( + DisconnectCommand { + restart: Some(false), + terminate_debuggee: Some(true), + suspend_debuggee: Some(false), + }, + Self::empty_response, + cx, + ) + .detach(); + } + } + + pub fn completions( + &mut self, + query: CompletionsQuery, + cx: &mut Context, + ) -> Task>> { + let task = self.request(query, |_, _, _| {}, cx); + + cx.background_executor().spawn(async move { + anyhow::Ok( + task.await + .map(|response| response.targets) + .ok_or_else(|| anyhow!("failed to fetch completions"))?, + ) + }) + } + + pub fn continue_thread(&mut self, thread_id: ThreadId, cx: &mut Context) { + self.request( + ContinueCommand { + args: ContinueArguments { + thread_id: thread_id.0, + single_thread: Some(true), + }, + }, + |_, _, _| {}, // todo: what do we do about the payload here? + cx, + ) + .detach(); + } + + pub fn adapter_client(&self) -> Option> { + match self.mode { + Mode::Local(ref adapter_client) => Some(adapter_client.clone()), + Mode::Remote(_) => None, + } + } + + pub fn step_over( + &mut self, + thread_id: ThreadId, + granularity: SteppingGranularity, + cx: &mut Context, + ) { + let supports_single_thread_execution_requests = + self.capabilities.supports_single_thread_execution_requests; + let supports_stepping_granularity = self + .capabilities + .supports_stepping_granularity + .unwrap_or_default(); + + let command = NextCommand { + inner: StepCommand { + thread_id: thread_id.0, + granularity: supports_stepping_granularity.then(|| granularity), + single_thread: supports_single_thread_execution_requests, + }, + }; + + self.request(command, Self::empty_response, cx).detach(); + } + + pub fn step_in( + &self, + thread_id: ThreadId, + granularity: SteppingGranularity, + cx: &mut Context, + ) { + let supports_single_thread_execution_requests = + self.capabilities.supports_single_thread_execution_requests; + let supports_stepping_granularity = self + .capabilities + .supports_stepping_granularity + .unwrap_or_default(); + + let command = StepInCommand { + inner: StepCommand { + thread_id: thread_id.0, + granularity: supports_stepping_granularity.then(|| granularity), + single_thread: supports_single_thread_execution_requests, + }, + }; + + self.request(command, Self::empty_response, cx).detach(); + } + + pub fn step_out( + &self, + thread_id: ThreadId, + granularity: SteppingGranularity, + cx: &mut Context, + ) { + let supports_single_thread_execution_requests = + self.capabilities.supports_single_thread_execution_requests; + let supports_stepping_granularity = self + .capabilities + .supports_stepping_granularity + .unwrap_or_default(); + + let command = StepOutCommand { + inner: StepCommand { + thread_id: thread_id.0, + granularity: supports_stepping_granularity.then(|| granularity), + single_thread: supports_single_thread_execution_requests, + }, + }; + + self.request(command, Self::empty_response, cx).detach(); + } + + pub fn step_back( + &self, + thread_id: ThreadId, + granularity: SteppingGranularity, + cx: &mut Context, + ) { + let supports_single_thread_execution_requests = + self.capabilities.supports_single_thread_execution_requests; + let supports_stepping_granularity = self + .capabilities + .supports_stepping_granularity + .unwrap_or_default(); + + let command = StepBackCommand { + inner: StepCommand { + thread_id: thread_id.0, + granularity: supports_stepping_granularity.then(|| granularity), + single_thread: supports_single_thread_execution_requests, + }, + }; + + self.request(command, Self::empty_response, cx).detach(); + } + pub fn handle_loaded_source_event( &mut self, event: &dap::LoadedSourceEvent, @@ -241,12 +697,205 @@ impl DebugAdapterClientState { } cx.notify(); } + + pub fn stack_frames(&mut self, thread_id: ThreadId, cx: &mut Context) -> Vec { + self.fetch( + super::dap_command::StackTraceCommand { + thread_id: thread_id.0, + start_frame: None, + levels: None, + }, + move |this, stack_frames, cx| { + let entry = this.threads.entry(thread_id).and_modify(|thread| { + thread.stack_frames = stack_frames.iter().cloned().map(From::from).collect(); + }); + debug_assert!( + matches!(entry, indexmap::map::Entry::Occupied(_)), + "Sent request for thread_id that doesn't exist" + ); + + cx.notify(); + }, + cx, + ); + + self.threads + .get(&thread_id) + .map(|thread| thread.stack_frames.clone()) + .unwrap_or_default() + } + + pub fn scopes( + &mut self, + thread_id: ThreadId, + stack_frame_id: u64, + cx: &mut Context, + ) -> Vec { + self.fetch( + ScopesCommand { + thread_id: thread_id.0, + stack_frame_id, + }, + move |this, scopes, cx| { + this.threads.entry(thread_id).and_modify(|thread| { + if let Some(stack_frame) = thread + .stack_frames + .iter_mut() + .find(|frame| frame.dap.id == stack_frame_id) + { + stack_frame.scopes = scopes.iter().cloned().map(From::from).collect(); + cx.notify(); + } + }); + }, + cx, + ); + self.threads + .get(&thread_id) + .and_then(|thread| { + thread.stack_frames.iter().find_map(|stack_frame| { + (stack_frame.dap.id == stack_frame_id).then(|| stack_frame.scopes.clone()) + }) + }) + .unwrap_or_default() + } + + fn find_scope( + &mut self, + thread_id: ThreadId, + stack_frame_id: u64, + variables_reference: u64, + ) -> Option<&mut Scope> { + self.threads.get_mut(&thread_id).and_then(|thread| { + let stack_frame = thread + .stack_frames + .iter_mut() + .find(|stack_frame| (stack_frame.dap.id == stack_frame_id))?; + stack_frame + .scopes + .iter_mut() + .find(|scope| scope.dap.variables_reference == variables_reference) + }) + } + + #[allow(clippy::too_many_arguments)] + pub fn variables( + &mut self, + thread_id: ThreadId, + stack_frame_id: u64, + session_id: DebugSessionId, + variables_reference: u64, + cx: &mut Context, + ) -> Vec { + let command = VariablesCommand { + stack_frame_id, + session_id, + thread_id: thread_id.0, + variables_reference, + filter: None, + start: None, + count: None, + format: None, + }; + + self.fetch( + command, + move |this, variables, cx| { + if let Some(scope) = this.find_scope(thread_id, stack_frame_id, variables_reference) + { + // This is only valid if scope.variable[x].ref_id == variables_reference + // otherwise we have to search the tree for the right index to add variables too + // todo(debugger): Fix this ^ + scope.variables = variables.iter().cloned().map(From::from).collect(); + cx.notify(); + } + }, + cx, + ); + + self.find_scope(thread_id, stack_frame_id, variables_reference) + .map(|scope| scope.variables.clone()) + .unwrap_or_default() + } + + pub fn set_variable_value( + &mut self, + variables_reference: u64, + name: String, + value: String, + cx: &mut Context, + ) { + if self.capabilities.supports_set_variable.unwrap_or_default() { + self.request( + SetVariableValueCommand { + name, + value, + variables_reference, + }, + |this, _response, cx| { + this.invalidate(cx); + }, + cx, + ) + .detach() + } + } + + pub fn evaluate( + &mut self, + expression: String, + context: Option, + frame_id: Option, + source: Option, + cx: &mut Context, + ) { + self.request( + EvaluateCommand { + expression, + context, + frame_id, + source, + }, + |this, _response, cx| { + this.invalidate(cx); + }, + cx, + ) + .detach() + } + + pub fn disconnect_client(&mut self, cx: &mut Context) { + let command = DisconnectCommand { + restart: Some(false), + terminate_debuggee: Some(true), + suspend_debuggee: Some(false), + }; + + self.request(command, Self::empty_response, cx).detach() + } + + pub fn terminate_threads(&mut self, thread_ids: Option>, cx: &mut Context) { + if self + .capabilities + .supports_terminate_threads_request + .unwrap_or_default() + { + self.request( + TerminateThreadsCommand { + thread_ids: thread_ids.map(|ids| ids.into_iter().map(|id| id.0).collect()), + }, + Self::empty_response, + cx, + ) + .detach(); + } + } } pub struct DebugSession { id: DebugSessionId, mode: DebugSessionMode, - pub(super) states: HashMap>, + pub(super) states: BTreeMap>, ignore_breakpoints: bool, } @@ -257,7 +906,6 @@ pub enum DebugSessionMode { pub struct LocalDebugSession { configuration: DebugAdapterConfig, - clients: HashMap>, } impl LocalDebugSession { @@ -273,42 +921,6 @@ impl LocalDebugSession { f(&mut self.configuration); cx.notify(); } - - fn add_client(&mut self, client: Arc, cx: &mut Context) { - self.clients.insert(client.id(), client); - cx.notify(); - } - - pub fn remove_client( - &mut self, - client_id: &DebugAdapterClientId, - cx: &mut Context, - ) -> Option> { - let client = self.clients.remove(client_id); - cx.notify(); - - client - } - - pub fn client_by_id( - &self, - client_id: &DebugAdapterClientId, - ) -> Option> { - self.clients.get(client_id).cloned() - } - - #[cfg(any(test, feature = "test-support"))] - pub fn clients_len(&self) -> usize { - self.clients.len() - } - - pub fn clients(&self) -> impl Iterator> + '_ { - self.clients.values().cloned() - } - - pub fn client_ids(&self) -> impl Iterator + '_ { - self.clients.keys().cloned() - } } pub struct RemoteDebugSession { @@ -320,11 +932,8 @@ impl DebugSession { Self { id, ignore_breakpoints: false, - states: HashMap::default(), - mode: DebugSessionMode::Local(LocalDebugSession { - configuration, - clients: HashMap::default(), - }), + states: BTreeMap::default(), + mode: DebugSessionMode::Local(LocalDebugSession { configuration }), } } @@ -346,7 +955,7 @@ impl DebugSession { Self { id, ignore_breakpoints, - states: HashMap::default(), + states: BTreeMap::default(), mode: DebugSessionMode::Remote(RemoteDebugSession { label }), } } @@ -371,38 +980,64 @@ impl DebugSession { cx.notify(); } - pub fn client_state( - &self, - client_id: DebugAdapterClientId, - ) -> Option> { + pub fn client_state(&self, client_id: DebugAdapterClientId) -> Option> { self.states.get(&client_id).cloned() } + pub(super) fn client_ids(&self) -> impl Iterator + '_ { + self.states.keys().copied() + } + + pub fn clients(&self, cx: &App) -> Vec> { + self.states + .values() + .filter_map(|state| state.read(cx).adapter_client()) + .collect() + } + pub fn add_client( &mut self, - client: Option>, + client: impl Into, client_id: DebugAdapterClientId, - weak_dap: WeakEntity, cx: &mut Context, ) { if !self.states.contains_key(&client_id) { - let state = cx.new(|_cx| DebugAdapterClientState { - dap_store: weak_dap, + let mode = client.into(); + let state = cx.new(|_cx| Client { client_id, modules: Vec::default(), loaded_sources: Vec::default(), - _threads: BTreeMap::default(), + threads: IndexMap::default(), requests: HashMap::default(), capabilities: Default::default(), + mode, }); self.states.insert(client_id, state); } + } - if let Some(client) = client { - self.as_local_mut() - .expect("Client can only exist on local Zed instances") - .add_client(client, cx); + pub(crate) fn client_by_id( + &self, + client_id: impl Borrow, + ) -> Option> { + self.states.get(client_id.borrow()).cloned() + } + + pub(crate) fn shutdown_client( + &mut self, + client_id: DebugAdapterClientId, + cx: &mut Context, + ) { + if let Some(client) = self.states.remove(&client_id) { + client.update(cx, |this, cx| { + this.shutdown(cx); + }) } } + + #[cfg(any(test, feature = "test-support"))] + pub fn clients_len(&self) -> usize { + self.states.len() + } } diff --git a/crates/project/src/debugger/dap_store.rs b/crates/project/src/debugger/dap_store.rs index b44ace8048..017764355b 100644 --- a/crates/project/src/debugger/dap_store.rs +++ b/crates/project/src/debugger/dap_store.rs @@ -1,33 +1,30 @@ use super::{ - dap_command::{ - ContinueCommand, DapCommand, DisconnectCommand, NextCommand, PauseCommand, RestartCommand, - RestartStackFrameCommand, StepBackCommand, StepCommand, StepInCommand, StepOutCommand, - TerminateCommand, TerminateThreadsCommand, VariablesCommand, - }, - dap_session::{DebugSession, DebugSessionId}, + // Will need to uncomment this once we implement rpc message handler again + // dap_command::{ + // ContinueCommand, DapCommand, DisconnectCommand, NextCommand, PauseCommand, RestartCommand, + // RestartStackFrameCommand, StepBackCommand, StepCommand, StepInCommand, StepOutCommand, + // TerminateCommand, TerminateThreadsCommand, VariablesCommand, + // }, + dap_command::DapCommand, + dap_session::{self, DebugSession, DebugSessionId}, }; use crate::{project_settings::ProjectSettings, ProjectEnvironment, ProjectItem as _, ProjectPath}; use anyhow::{anyhow, bail, Context as _, Result}; use async_trait::async_trait; use collections::HashMap; -use dap::ContinueResponse; use dap::{ adapters::{DapDelegate, DapStatus, DebugAdapter, DebugAdapterBinary, DebugAdapterName}, client::{DebugAdapterClient, DebugAdapterClientId}, messages::{Message, Response}, requests::{ - Attach, Completions, ConfigurationDone, Disconnect, Evaluate, Initialize, Launch, - LoadedSources, Modules, Request as _, RunInTerminal, Scopes, SetBreakpoints, SetExpression, - SetVariable, StackTrace, StartDebugging, Terminate, + Attach, Completions, Evaluate, Initialize, Launch, Request as _, RunInTerminal, + SetBreakpoints, SetExpression, SetVariable, StartDebugging, }, - AttachRequestArguments, Capabilities, CompletionItem, CompletionsArguments, - ConfigurationDoneArguments, ContinueArguments, DisconnectArguments, ErrorResponse, + AttachRequestArguments, Capabilities, CompletionItem, CompletionsArguments, ErrorResponse, EvaluateArguments, EvaluateArgumentsContext, EvaluateResponse, InitializeRequestArguments, - InitializeRequestArgumentsPathFormat, LaunchRequestArguments, LoadedSourcesArguments, Module, - ModulesArguments, Scope, ScopesArguments, SetBreakpointsArguments, SetExpressionArguments, - SetVariableArguments, Source, SourceBreakpoint, StackFrame, StackTraceArguments, - StartDebuggingRequestArguments, StartDebuggingRequestArgumentsRequest, SteppingGranularity, - TerminateArguments, Variable, + InitializeRequestArgumentsPathFormat, LaunchRequestArguments, SetBreakpointsArguments, + SetExpressionArguments, SetVariableArguments, Source, SourceBreakpoint, + StartDebuggingRequestArguments, StartDebuggingRequestArgumentsRequest, }; use dap_adapters::build_adapter; use fs::Fs; @@ -47,8 +44,8 @@ use rpc::{ use serde_json::Value; use settings::{Settings as _, WorktreeId}; use smol::lock::Mutex; -use std::collections::VecDeque; use std::{ + borrow::Borrow, collections::{BTreeMap, HashSet}, ffi::OsStr, hash::{Hash, Hasher}, @@ -58,6 +55,7 @@ use std::{ Arc, }, }; +use std::{collections::VecDeque, sync::atomic::AtomicU32}; use task::{AttachConfig, DebugAdapterConfig, DebugRequestType}; use text::Point; use util::{merge_json_value_into, ResultExt as _}; @@ -92,7 +90,7 @@ pub enum DapStoreMode { pub struct LocalDapStore { fs: Arc, node_runtime: NodeRuntime, - next_client_id: AtomicUsize, + next_client_id: AtomicU32, next_session_id: AtomicUsize, http_client: Arc, environment: Entity, @@ -142,18 +140,19 @@ impl DapStore { client.add_entity_message_handler(Self::handle_ignore_breakpoint_state); client.add_entity_message_handler(Self::handle_session_has_shutdown); - client.add_entity_request_handler(Self::handle_dap_command::); - client.add_entity_request_handler(Self::handle_dap_command::); - client.add_entity_request_handler(Self::handle_dap_command::); - client.add_entity_request_handler(Self::handle_dap_command::); - client.add_entity_request_handler(Self::handle_dap_command::); - client.add_entity_request_handler(Self::handle_dap_command::); - client.add_entity_request_handler(Self::handle_dap_command::); - client.add_entity_request_handler(Self::handle_dap_command::); - client.add_entity_request_handler(Self::handle_dap_command::); - client.add_entity_request_handler(Self::handle_dap_command::); - client.add_entity_request_handler(Self::handle_dap_command::); - client.add_entity_request_handler(Self::handle_dap_command::); + // todo(debugger): Reenable these after we finish handle_dap_command refactor + // client.add_entity_request_handler(Self::handle_dap_command::); + // client.add_entity_request_handler(Self::handle_dap_command::); + // client.add_entity_request_handler(Self::handle_dap_command::); + // client.add_entity_request_handler(Self::handle_dap_command::); + // client.add_entity_request_handler(Self::handle_dap_command::); + // client.add_entity_request_handler(Self::handle_dap_command::); + // client.add_entity_request_handler(Self::handle_dap_command::); + // client.add_entity_request_handler(Self::handle_dap_command::); + // client.add_entity_request_handler(Self::handle_dap_command::); + // client.add_entity_request_handler(Self::handle_dap_command::); + // client.add_entity_request_handler(Self::handle_dap_command::); + // client.add_entity_request_handler(Self::handle_dap_command::); client.add_entity_request_handler(Self::handle_shutdown_session_request); } @@ -293,29 +292,32 @@ impl DapStore { pub fn session_by_client_id( &self, - client_id: &DebugAdapterClientId, + client_id: impl Borrow, ) -> Option> { self.sessions - .get(self.client_by_session.get(client_id)?) + .get(self.client_by_session.get(client_id.borrow())?) .cloned() } pub fn client_by_id( &self, - client_id: &DebugAdapterClientId, + client_id: impl Borrow, cx: &Context, - ) -> Option<(Entity, Arc)> { - let local_session = self.session_by_client_id(client_id)?; - let client = local_session.read(cx).as_local()?.client_by_id(client_id)?; + ) -> Option<(Entity, Entity)> { + let client_id = client_id.borrow(); + let session = self.session_by_client_id(client_id)?; - Some((local_session, client)) + let client = session.read(cx).client_by_id(*client_id)?; + + Some((session, client)) } pub fn capabilities_by_id( &self, - client_id: &DebugAdapterClientId, + client_id: impl Borrow, cx: &App, ) -> Option { + let client_id = client_id.borrow(); self.session_by_client_id(client_id).and_then(|session| { session .read(cx) @@ -327,13 +329,13 @@ impl DapStore { pub fn update_capabilities_for_client( &mut self, session_id: &DebugSessionId, - client_id: &DebugAdapterClientId, + client_id: DebugAdapterClientId, capabilities: &Capabilities, cx: &mut Context, ) { if let Some((client, _)) = self.client_by_id(client_id, cx) { client.update(cx, |this, cx| { - if let Some(state) = this.client_state(*client_id) { + if let Some(state) = this.client_state(client_id) { state.update(cx, |this, _| { this.capabilities = this.capabilities.merge(capabilities.clone()); }); @@ -584,10 +586,9 @@ impl DapStore { store.client_by_session.insert(client_id, session_id); let session = store.session_by_id(&session_id).unwrap(); - let weak_dap = cx.weak_entity(); session.update(cx, |session, cx| { - session.add_client(Some(Arc::new(client)), client_id, weak_dap, cx); + session.add_client(Arc::new(client), client_id, cx); let local_session = session .as_local_mut() .expect("Only local sessions should attempt to reconnect"); @@ -735,10 +736,8 @@ impl DapStore { }; this.update(&mut cx, |store, cx| { - let weak_dap = cx.weak_entity(); - session.update(cx, |session, cx| { - session.add_client(Some(client.clone()), client.id(), weak_dap, cx); + session.add_client(client.clone(), client.id(), cx); }); let client_id = client.id(); @@ -757,10 +756,13 @@ impl DapStore { pub fn initialize( &mut self, session_id: &DebugSessionId, - client_id: &DebugAdapterClientId, + client_id: DebugAdapterClientId, cx: &mut Context, ) -> Task> { - let Some((_, client)) = self.client_by_id(client_id, cx) else { + let Some(client) = self + .client_by_id(client_id, cx) + .and_then(|(_, client)| client.read(cx).adapter_client()) + else { return Task::ready(Err(anyhow!( "Could not find debug client: {:?} for session {:?}", client_id, @@ -769,7 +771,6 @@ impl DapStore { }; let session_id = *session_id; - let client_id = *client_id; cx.spawn(|this, mut cx| async move { let capabilities = client @@ -794,18 +795,49 @@ impl DapStore { .await?; this.update(&mut cx, |store, cx| { - store.update_capabilities_for_client(&session_id, &client_id, &capabilities, cx); + store.update_capabilities_for_client(&session_id, client_id, &capabilities, cx); }) }) } + pub fn configuration_done( + &self, + client_id: DebugAdapterClientId, + cx: &mut Context, + ) -> Task> { + let Some(client) = self + .client_by_id(client_id, cx) + .and_then(|(_, client)| client.read(cx).adapter_client()) + else { + return Task::ready(Err(anyhow!("Could not find client: {:?}", client_id))); + }; + + if self + .capabilities_by_id(client_id, cx) + .map(|caps| caps.supports_configuration_done_request) + .flatten() + .unwrap_or_default() + { + cx.background_executor().spawn(async move { + client + .request::(dap::ConfigurationDoneArguments) + .await + }) + } else { + Task::ready(Ok(())) + } + } + pub fn launch( &mut self, session_id: &DebugSessionId, - client_id: &DebugAdapterClientId, + client_id: DebugAdapterClientId, cx: &mut Context, ) -> Task> { - let Some((session, client)) = self.client_by_id(client_id, cx) else { + let Some((session, client)) = self + .client_by_id(client_id, cx) + .and_then(|(session, client)| Some((session, client.read(cx).adapter_client()?))) + else { return Task::ready(Err(anyhow!( "Could not find debug client: {:?} for session {:?}", client_id, @@ -844,11 +876,14 @@ impl DapStore { pub fn attach( &mut self, session_id: &DebugSessionId, - client_id: &DebugAdapterClientId, + client_id: DebugAdapterClientId, process_id: u32, cx: &mut Context, ) -> Task> { - let Some((session, client)) = self.client_by_id(client_id, cx) else { + let Some((session, client)) = self + .client_by_id(client_id, cx) + .and_then(|(session, client)| Some((session, client.read(cx).adapter_client()?))) + else { return Task::ready(Err(anyhow!( "Could not find debug client: {:?} for session {:?}", client_id, @@ -884,156 +919,18 @@ impl DapStore { }) } - pub fn modules( - &mut self, - client_id: &DebugAdapterClientId, - cx: &mut Context, - ) -> Task>> { - let Some((_, client)) = self.client_by_id(client_id, cx) else { - return Task::ready(Err(anyhow!("Client was not found"))); - }; - - if !self - .capabilities_by_id(client_id, cx) - .map(|caps| caps.supports_modules_request) - .flatten() - .unwrap_or_default() - { - return Task::ready(Ok(Vec::default())); - } - - cx.background_executor().spawn(async move { - Ok(client - .request::(ModulesArguments { - start_module: None, - module_count: None, - }) - .await? - .modules) - }) - } - - pub fn loaded_sources( - &mut self, - client_id: &DebugAdapterClientId, - cx: &mut Context, - ) -> Task>> { - let Some((_, client)) = self.client_by_id(client_id, cx) else { - return Task::ready(Err(anyhow!("Client was not found"))); - }; - - if !self - .capabilities_by_id(client_id, cx) - .map(|caps| caps.supports_loaded_sources_request) - .flatten() - .unwrap_or_default() - { - return Task::ready(Ok(Vec::default())); - } - - cx.background_executor().spawn(async move { - Ok(client - .request::(LoadedSourcesArguments {}) - .await? - .sources) - }) - } - - pub fn stack_frames( - &mut self, - client_id: &DebugAdapterClientId, - thread_id: u64, - cx: &mut Context, - ) -> Task>> { - let Some((_, client)) = self.client_by_id(client_id, cx) else { - return Task::ready(Err(anyhow!("Client was not found"))); - }; - - cx.background_executor().spawn(async move { - Ok(client - .request::(StackTraceArguments { - thread_id, - start_frame: None, - levels: None, - format: None, - }) - .await? - .stack_frames) - }) - } - - pub fn restart_stack_frame( - &mut self, - client_id: &DebugAdapterClientId, - stack_frame_id: u64, - cx: &mut Context, - ) -> Task> { - if !self - .capabilities_by_id(client_id, cx) - .map(|caps| caps.supports_restart_frame) - .flatten() - .unwrap_or_default() - { - return Task::ready(Ok(())); - } - - self.request_dap(client_id, RestartStackFrameCommand { stack_frame_id }, cx) - } - - pub fn scopes( - &mut self, - client_id: &DebugAdapterClientId, - stack_frame_id: u64, - cx: &mut Context, - ) -> Task>> { - let Some((_, client)) = self.client_by_id(client_id, cx) else { - return Task::ready(Err(anyhow!("Client was not found"))); - }; - - cx.background_executor().spawn(async move { - Ok(client - .request::(ScopesArguments { - frame_id: stack_frame_id, - }) - .await? - .scopes) - }) - } - - pub fn configuration_done( - &self, - client_id: &DebugAdapterClientId, - cx: &mut Context, - ) -> Task> { - let Some((_, client)) = self.client_by_id(client_id, cx) else { - return Task::ready(Err(anyhow!("Could not find client: {:?}", client_id))); - }; - - if self - .capabilities_by_id(client_id, cx) - .map(|caps| caps.supports_configuration_done_request) - .flatten() - .unwrap_or_default() - { - cx.background_executor().spawn(async move { - client - .request::(ConfigurationDoneArguments) - .await - }) - } else { - Task::ready(Ok(())) - } - } - pub fn respond_to_start_debugging( &mut self, session_id: &DebugSessionId, - client_id: &DebugAdapterClientId, + client_id: DebugAdapterClientId, seq: u64, args: Option, cx: &mut Context, ) -> Task> { - let Some((session, client)) = self.client_by_id(client_id, cx) else { + let Some((session, client)) = self + .client_by_id(client_id, cx) + .and_then(|(session, client)| Some((session, client.read(cx).adapter_client()?))) + else { return Task::ready(Err(anyhow!( "Could not find debug client: {:?} for session {:?}", client_id, @@ -1158,13 +1055,16 @@ impl DapStore { pub fn respond_to_run_in_terminal( &self, session_id: &DebugSessionId, - client_id: &DebugAdapterClientId, + client_id: DebugAdapterClientId, success: bool, seq: u64, body: Option, cx: &mut Context, ) -> Task> { - let Some((_, client)) = self.client_by_id(client_id, cx) else { + let Some(client) = self + .client_by_id(client_id, cx) + .and_then(|(_, client)| client.read(cx).adapter_client()) + else { return Task::ready(Err(anyhow!( "Could not find debug client: {:?} for session {:?}", client_id, @@ -1185,231 +1085,6 @@ impl DapStore { }) } - pub fn continue_thread( - &self, - client_id: &DebugAdapterClientId, - thread_id: u64, - cx: &mut Context, - ) -> Task> { - let command = ContinueCommand { - args: ContinueArguments { - thread_id, - single_thread: Some(true), - }, - }; - - self.request_dap(client_id, command, cx) - } - - pub(crate) fn request_dap( - &self, - client_id: &DebugAdapterClientId, - request: R, - cx: &mut Context, - ) -> Task> - where - ::Response: 'static, - ::Arguments: 'static + Send, - { - if let Some((upstream_client, upstream_project_id)) = self.upstream_client() { - return self.send_proto_client_request::( - upstream_client, - upstream_project_id, - client_id, - request, - cx, - ); - } - - let Some((session, client)) = self.client_by_id(client_id, cx) else { - return Task::ready(Err(anyhow!("Could not find client: {:?}", client_id))); - }; - - let Some(caps) = session - .read(cx) - .client_state(*client_id) - .map(|state| state.read(cx).capabilities.clone()) - else { - return Task::ready(Err(anyhow!("Could not find client: {:?}", client_id))); - }; - if !request.is_supported(&caps) { - return Task::ready(Err(anyhow!( - "Request {} is not supported", - R::DapRequest::COMMAND - ))); - } - - let client_id = *client_id; - let request = Arc::new(request); - - let request_clone = request.clone(); - let request_task = cx.background_executor().spawn(async move { - let args = request_clone.to_dap(); - client.request::(args).await - }); - - cx.spawn(|this, mut cx| async move { - let response = request.response_from_dap(request_task.await?); - request.handle_response(this, &client_id, response, &mut cx) - }) - } - - fn send_proto_client_request( - &self, - upstream_client: AnyProtoClient, - upstream_project_id: u64, - client_id: &DebugAdapterClientId, - request: R, - cx: &mut Context, - ) -> Task> { - let message = request.to_proto(&client_id, upstream_project_id); - cx.background_executor().spawn(async move { - let response = upstream_client.request(message).await?; - request.response_from_proto(response) - }) - } - - pub fn step_over( - &self, - client_id: &DebugAdapterClientId, - thread_id: u64, - granularity: SteppingGranularity, - cx: &mut Context, - ) -> Task> { - let Some(capabilities) = self.capabilities_by_id(client_id, cx) else { - return Task::ready(Err(anyhow!("Could not find client: {:?}", client_id))); - }; - let supports_single_thread_execution_requests = capabilities - .supports_single_thread_execution_requests - .unwrap_or_default(); - let supports_stepping_granularity = capabilities - .supports_stepping_granularity - .unwrap_or_default(); - - let command = NextCommand { - inner: StepCommand { - thread_id, - granularity: supports_stepping_granularity.then(|| granularity), - single_thread: supports_single_thread_execution_requests.then(|| true), - }, - }; - - self.request_dap(client_id, command, cx) - } - - pub fn step_in( - &self, - client_id: &DebugAdapterClientId, - thread_id: u64, - granularity: SteppingGranularity, - cx: &mut Context, - ) -> Task> { - let Some(capabilities) = self.capabilities_by_id(client_id, cx) else { - return Task::ready(Err(anyhow!("Could not find client: {:?}", client_id))); - }; - let supports_single_thread_execution_requests = capabilities - .supports_single_thread_execution_requests - .unwrap_or_default(); - let supports_stepping_granularity = capabilities - .supports_stepping_granularity - .unwrap_or_default(); - - let command = StepInCommand { - inner: StepCommand { - thread_id, - granularity: supports_stepping_granularity.then(|| granularity), - single_thread: supports_single_thread_execution_requests.then(|| true), - }, - }; - - self.request_dap(client_id, command, cx) - } - - pub fn step_out( - &self, - client_id: &DebugAdapterClientId, - thread_id: u64, - granularity: SteppingGranularity, - cx: &mut Context, - ) -> Task> { - let Some(capabilities) = self.capabilities_by_id(client_id, cx) else { - return Task::ready(Err(anyhow!("Could not find client: {:?}", client_id))); - }; - let supports_single_thread_execution_requests = capabilities - .supports_single_thread_execution_requests - .unwrap_or_default(); - let supports_stepping_granularity = capabilities - .supports_stepping_granularity - .unwrap_or_default(); - - let command = StepOutCommand { - inner: StepCommand { - thread_id, - granularity: supports_stepping_granularity.then(|| granularity), - single_thread: supports_single_thread_execution_requests.then(|| true), - }, - }; - - self.request_dap(client_id, command, cx) - } - - pub fn step_back( - &self, - client_id: &DebugAdapterClientId, - thread_id: u64, - granularity: SteppingGranularity, - cx: &mut Context, - ) -> Task> { - let Some(capabilities) = self.capabilities_by_id(client_id, cx) else { - return Task::ready(Err(anyhow!("Could not find client: {:?}", client_id))); - }; - if !capabilities.supports_step_back.unwrap_or_default() { - return Task::ready(Ok(())); - } - - let supports_single_thread_execution_requests = capabilities - .supports_single_thread_execution_requests - .unwrap_or_default(); - let supports_stepping_granularity = capabilities - .supports_stepping_granularity - .unwrap_or_default(); - - let command = StepBackCommand { - inner: StepCommand { - thread_id, - granularity: supports_stepping_granularity.then(|| granularity), - single_thread: supports_single_thread_execution_requests.then(|| true), - }, - }; - - self.request_dap(client_id, command, cx) - } - #[allow(clippy::too_many_arguments)] - pub fn variables( - &self, - client_id: &DebugAdapterClientId, - thread_id: u64, - stack_frame_id: u64, - scope_id: u64, - session_id: DebugSessionId, - variables_reference: u64, - cx: &mut Context, - ) -> Task>> { - let command = VariablesCommand { - stack_frame_id, - scope_id, - session_id, - thread_id, - variables_reference, - filter: None, - start: None, - count: None, - format: None, - }; - - self.request_dap(&client_id, command, cx) - } - pub fn evaluate( &self, client_id: &DebugAdapterClientId, @@ -1419,7 +1094,10 @@ impl DapStore { source: Option, cx: &mut Context, ) -> Task> { - let Some((_, client)) = self.client_by_id(client_id, cx) else { + let Some(client) = self + .client_by_id(client_id, cx) + .and_then(|(_, client)| client.read(cx).adapter_client()) + else { return Task::ready(Err(anyhow!("Could not find client: {:?}", client_id))); }; @@ -1446,7 +1124,10 @@ impl DapStore { completion_column: u64, cx: &mut Context, ) -> Task>> { - let Some((_, client)) = self.client_by_id(client_id, cx) else { + let Some(client) = self + .client_by_id(client_id, cx) + .and_then(|(_, client)| client.read(cx).adapter_client()) + else { return Task::ready(Err(anyhow!("Could not find client: {:?}", client_id))); }; @@ -1474,7 +1155,10 @@ impl DapStore { evaluate_name: Option, cx: &mut Context, ) -> Task> { - let Some((_, client)) = self.client_by_id(client_id, cx) else { + let Some(client) = self + .client_by_id(client_id, cx) + .and_then(|(_, client)| client.read(cx).adapter_client()) + else { return Task::ready(Err(anyhow!("Could not find client: {:?}", client_id))); }; @@ -1509,76 +1193,10 @@ impl DapStore { }) } - pub fn pause_thread( - &mut self, - client_id: &DebugAdapterClientId, - thread_id: u64, - cx: &mut Context, - ) -> Task> { - self.request_dap(client_id, PauseCommand { thread_id }, cx) - } - - pub fn terminate_threads( - &mut self, - session_id: &DebugSessionId, - client_id: &DebugAdapterClientId, - thread_ids: Option>, - cx: &mut Context, - ) -> Task> { - if self - .capabilities_by_id(client_id, cx) - .map(|caps| caps.supports_terminate_threads_request) - .flatten() - .unwrap_or_default() - { - self.request_dap(client_id, TerminateThreadsCommand { thread_ids }, cx) - } else { - self.shutdown_session(session_id, cx) - } - } - - pub fn disconnect_client( - &mut self, - client_id: &DebugAdapterClientId, - cx: &mut Context, - ) -> Task> { - let command = DisconnectCommand { - restart: Some(false), - terminate_debuggee: Some(true), - suspend_debuggee: Some(false), - }; - - self.request_dap(client_id, command, cx) - } - - pub fn restart( - &mut self, - client_id: &DebugAdapterClientId, - args: Option, - cx: &mut Context, - ) -> Task> { - let supports_restart = self - .capabilities_by_id(client_id, cx) - .map(|caps| caps.supports_restart_request) - .flatten() - .unwrap_or_default(); - - if supports_restart { - let command = RestartCommand { - raw: args.unwrap_or(Value::Null), - }; - - self.request_dap(client_id, command, cx) - } else { - let command = DisconnectCommand { - restart: Some(false), - terminate_debuggee: Some(true), - suspend_debuggee: Some(false), - }; - - self.request_dap(client_id, command, cx) - } - } + // .. get the client and what not + // let _ = client.modules(); // This can fire a request to a dap adapter or be a cheap getter. + // client.wait_for_request(request::Modules); // This ensures that the request that we've fired off runs to completions + // let returned_value = client.modules(); // this is a cheap getter. pub fn shutdown_sessions(&mut self, cx: &mut Context) -> Task<()> { let Some(_) = self.as_local() else { @@ -1633,89 +1251,12 @@ impl DapStore { return Task::ready(Err(anyhow!("Could not find session: {:?}", session_id))); }; - let Some(local_session) = session.read(cx).as_local() else { - return Task::ready(Err(anyhow!( - "Cannot shutdown session on remote side: {:?}", - session_id - ))); - }; - - let mut tasks = Vec::new(); - for client in local_session.clients().collect::>() { - tasks.push(self.shutdown_client(&session, client, cx)); - } - - if let Some((downstream_client, project_id)) = self.downstream_client.as_ref() { - downstream_client - .send(proto::DebuggerSessionEnded { - project_id: *project_id, - session_id: session_id.to_proto(), - }) - .log_err(); - } - - cx.background_executor().spawn(async move { - futures::future::join_all(tasks).await; - Ok(()) - }) - } - - fn shutdown_client( - &mut self, - session: &Entity, - client: Arc, - cx: &mut Context, - ) -> Task> { - let client_id = client.id(); - - cx.emit(DapStoreEvent::DebugClientShutdown(client_id)); - let Some(capabilities) = self.session_by_client_id(&client_id).and_then(|session| { - session - .read(cx) - .client_state(client_id) - .map(|state| state.read(cx).capabilities.clone()) - }) else { - return Task::ready(Err(anyhow!("Client not found"))); - }; - let session = session.clone(); - self.client_by_session.remove(&client_id); - if let Some((downstream_client, project_id)) = self.downstream_client.as_ref() { - downstream_client - .send(proto::ShutdownDebugClient { - session_id: session.read(cx).id().to_proto(), - client_id: client_id.to_proto(), - project_id: *project_id, - }) - .log_err(); - } - - cx.spawn(|_, mut cx| async move { - if capabilities.supports_terminate_request.unwrap_or_default() { - let _ = client - .request::(TerminateArguments { - restart: Some(false), - }) - .await - .log_err(); - } else { - let _ = client - .request::(DisconnectArguments { - restart: Some(false), - terminate_debuggee: Some(true), - suspend_debuggee: Some(false), - }) - .await - .log_err(); - } - - client.shutdown().await?; - - let _ = session.update(&mut cx, |this, _| { - this.states.remove(&client_id); + for client_id in session.read(cx).client_ids().collect::>() { + session.update(cx, |this, cx| { + this.shutdown_client(client_id, cx); }); - - Ok(()) - }) + } + Task::ready(Ok(())) } pub fn request_active_debug_sessions(&mut self, cx: &mut Context) { @@ -1766,7 +1307,7 @@ impl DapStore { self.update_capabilities_for_client( &session_id, - &client, + client, &dap::proto_conversions::capabilities_from_proto( &debug_client.capabilities.unwrap_or_default(), ), @@ -1834,7 +1375,7 @@ impl DapStore { let client_id = T::client_id_from_proto(&envelope.payload); let _state = this.update(&mut cx, |this, cx| { - this.session_by_client_id(&client_id)? + this.session_by_client_id(client_id)? .read(cx) .client_state(client_id)? .read(cx) @@ -1844,27 +1385,27 @@ impl DapStore { todo!() } - async fn handle_dap_command( - this: Entity, - envelope: TypedEnvelope, - mut cx: AsyncApp, - ) -> Result<::Response> - where - ::Arguments: Send, - ::Response: Send, - { - let _sender_id = envelope.original_sender_id().unwrap_or_default(); - let client_id = T::client_id_from_proto(&envelope.payload); + // async fn handle_dap_command( + // this: Entity, + // envelope: TypedEnvelope, + // mut cx: AsyncApp, + // ) -> Result<::Response> + // where + // ::Arguments: Send, + // ::Response: Send, + // { + // let _sender_id = envelope.original_sender_id().unwrap_or_default(); + // let client_id = T::client_id_from_proto(&envelope.payload); - let request = T::from_proto(&envelope.payload); - let response = this - .update(&mut cx, |this, cx| { - this.request_dap::(&client_id, request, cx) - })? - .await?; + // let request = T::from_proto(&envelope.payload); + // let response = this + // .update(&mut cx, |this, cx| { + // this.request_dap::(&client_id, request, cx) + // })? + // .await?; - Ok(T::response_to_proto(&client_id, response)) - } + // Ok(T::response_to_proto(&client_id, response)) + // } async fn handle_synchronize_breakpoints( this: Entity, @@ -1939,7 +1480,7 @@ impl DapStore { this.update(&mut cx, |dap_store, cx| { dap_store.update_capabilities_for_client( &DebugSessionId::from_proto(envelope.payload.session_id), - &DebugAdapterClientId::from_proto(envelope.payload.client_id), + DebugAdapterClientId::from_proto(envelope.payload.client_id), &dap::proto_conversions::capabilities_from_proto(&envelope.payload), cx, ); @@ -1954,7 +1495,7 @@ impl DapStore { this.update(&mut cx, |dap_store, cx| { let client_id = DebugAdapterClientId::from_proto(envelope.payload.client_id); - dap_store.session_by_client_id(&client_id).map(|state| { + dap_store.session_by_client_id(client_id).map(|state| { state.update(cx, |this, _| { this.states.remove(&client_id); }) @@ -2056,14 +1597,17 @@ impl DapStore { pub fn send_breakpoints( &self, - client_id: &DebugAdapterClientId, + client_id: DebugAdapterClientId, absolute_file_path: Arc, mut breakpoints: Vec, ignore: bool, source_changed: bool, cx: &Context, ) -> Task> { - let Some((_, client)) = self.client_by_id(client_id, cx) else { + let Some(client) = self + .client_by_id(client_id, cx) + .and_then(|(_, client)| client.read(cx).adapter_client()) + else { return Task::ready(Err(anyhow!("Could not find client: {:?}", client_id))); }; @@ -2121,9 +1665,9 @@ impl DapStore { { let session = session.read(cx); let ignore_breakpoints = session.ignore_breakpoints(); - for client in session.as_local().unwrap().clients().collect::>() { + for client_id in session.client_ids().collect::>() { tasks.push(self.send_breakpoints( - &client.id(), + client_id, Arc::from(absolute_path.clone()), source_breakpoints.clone(), ignore_breakpoints, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 1673721749..565a5a5bcb 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1311,7 +1311,7 @@ impl Project { pub fn initial_send_breakpoints( &self, session_id: &DebugSessionId, - client_id: &DebugAdapterClientId, + client_id: DebugAdapterClientId, cx: &mut Context, ) -> Task<()> { let mut tasks = Vec::new(); @@ -1439,7 +1439,7 @@ impl Project { project .toggle_ignore_breakpoints( &DebugSessionId::from_proto(envelope.payload.session_id), - &DebugAdapterClientId::from_proto(envelope.payload.client_id), + DebugAdapterClientId::from_proto(envelope.payload.client_id), cx, ) .detach_and_log_err(cx); @@ -1450,7 +1450,7 @@ impl Project { pub fn toggle_ignore_breakpoints( &self, session_id: &DebugSessionId, - client_id: &DebugAdapterClientId, + client_id: DebugAdapterClientId, cx: &mut Context, ) -> Task> { let tasks = self.dap_store.update(cx, |store, cx| { diff --git a/crates/proto/proto/zed.proto b/crates/proto/proto/zed.proto index e871715f0b..1489618e21 100644 --- a/crates/proto/proto/zed.proto +++ b/crates/proto/proto/zed.proto @@ -355,7 +355,19 @@ message Envelope { DapLoadedSourcesRequest dap_loaded_sources_request = 331; DapLoadedSourcesResponse dap_loaded_sources_response = 332; ActiveDebugSessionsRequest active_debug_sessions_request = 333; - ActiveDebugSessionsResponse active_debug_sessions_response = 334; // current max + ActiveDebugSessionsResponse active_debug_sessions_response = 334; + DapStackTraceRequest dap_stack_trace_request = 335; + DapStackTraceResponse dap_stack_trace_response = 336; + DapScopesRequest dap_scopes_request = 337; + DapScopesResponse dap_scopes_response = 338; + DapSetVariableValueRequest dap_set_variable_value_request = 339; + DapSetVariableValueResponse dap_set_variable_value_response = 340; + DapEvaluateRequest dap_evaluate_request = 341; + DapEvaluateResponse dap_evaluate_response = 342; + DapCompletionRequest dap_completion_request = 343; + DapCompletionResponse dap_completion_response = 344; + DapThreadsRequest dap_threads_request = 345; + DapThreadsResponse dap_threads_response = 346; // current max } reserved 87 to 88; @@ -2708,7 +2720,6 @@ message VariableListVariables { message DebuggerVariableList { repeated VariableListScopes scopes = 1; repeated VariableListVariables variables = 2; - repeated AddToVariableList added_variables = 3; } enum VariablesArgumentsFilter { @@ -2726,20 +2737,13 @@ message VariablesRequest { uint64 thread_id = 3; uint64 session_id = 4; uint64 stack_frame_id = 5; - uint64 scope_id = 6; - uint64 variables_reference = 7; - optional VariablesArgumentsFilter filter = 8; - optional uint64 start = 9; - optional uint64 count = 10; - optional ValueFormat format = 11; + uint64 variables_reference = 6; + optional VariablesArgumentsFilter filter = 7; + optional uint64 start = 8; + optional uint64 count = 9; + optional ValueFormat format = 10; } -message AddToVariableList { - uint64 variable_id = 1; - uint64 stack_frame_id = 2; - uint64 scope_id = 3; - repeated DapVariable variables = 4; -} message DebuggerStackFrameList { uint64 thread_id = 1; @@ -2754,6 +2758,110 @@ enum SteppingGranularity { Instruction = 2; } +enum DapEvaluateContext { + Repl = 0; + Watch = 1; + Hover = 2; + Clipboard = 3; + EvaluateVariables = 4; + EvaluateUnknown = 5; +} + +message DapEvaluateRequest { + uint64 project_id = 1; + uint64 client_id = 2; + string expression = 3; + optional uint64 frame_id = 4; + optional DapEvaluateContext context = 5; +} + +message DapEvaluateResponse { + string result = 1; + optional string evaluate_type = 2; + uint64 variable_reference = 3; + optional uint64 named_variables = 4; + optional uint64 indexed_variables = 5; + optional string memory_reference = 6; +} + + +message DapCompletionRequest { + uint64 project_id = 1; + uint64 client_id = 2; + string query = 3; + optional uint64 frame_id = 4; + optional uint64 line = 5; + uint64 column = 6; +} + +enum DapCompletionItemType { + Method = 0; + Function = 1; + Constructor = 2; + Field = 3; + Variable = 4; + Class = 5; + Interface = 6; + Module = 7; + Property = 8; + Unit = 9; + Value = 10; + Enum = 11; + Keyword = 12; + Snippet = 13; + Text = 14; + Color = 15; + CompletionItemFile = 16; + Reference = 17; + Customcolor = 19; +} + +message DapCompletionItem { + string label = 1; + optional string text = 2; + optional string sort_text = 3; + optional string detail = 4; + optional DapCompletionItemType typ = 5; + optional uint64 start = 6; + optional uint64 length = 7; + optional uint64 selection_start = 8; + optional uint64 selection_length = 9; +} + +message DapCompletionResponse { + uint64 client_id = 1; + repeated DapCompletionItem completions = 2; +} + +message DapScopesRequest { + uint64 project_id = 1; + uint64 client_id = 2; + uint64 thread_id = 3; + uint64 stack_frame_id = 4; +} + +message DapScopesResponse { + repeated DapScope scopes = 1; +} + +message DapSetVariableValueRequest { + uint64 project_id = 1; + uint64 client_id = 2; + string name = 3; + string value = 4; + uint64 variables_reference = 5; +} + +message DapSetVariableValueResponse { + uint64 client_id = 1; + string value = 2; + optional string variable_type = 3; + optional uint64 variables_reference = 4; + optional uint64 named_variables = 5; + optional uint64 indexed_variables = 6; + optional string memory_reference = 7; +} + message DapPauseRequest { uint64 project_id = 1; uint64 client_id = 2; @@ -2774,6 +2882,15 @@ message DapTerminateThreadsRequest { repeated uint64 thread_ids = 3; } +message DapThreadsRequest { + uint64 project_id = 1; + uint64 client_id = 2; +} + +message DapThreadsResponse { + repeated DapThread threads = 1; +} + message DapTerminateRequest { uint64 project_id = 1; uint64 client_id = 2; @@ -2874,6 +2991,18 @@ message DapLoadedSourcesResponse { repeated DapSource sources = 2; } +message DapStackTraceRequest { + uint64 project_id = 1; + uint64 client_id = 2; + uint64 thread_id = 3; + optional uint64 start_frame = 4; + optional uint64 stack_trace_levels = 5; +} + +message DapStackTraceResponse { + repeated DapStackFrame frames = 1; +} + message DapStackFrame { uint64 id = 1; string name = 2; @@ -2926,9 +3055,8 @@ message UpdateDebugAdapter { DebuggerThreadState thread_state = 5; DebuggerStackFrameList stack_frame_list = 6; DebuggerVariableList variable_list = 7; - AddToVariableList add_to_variable_list = 8; - DebuggerModuleList modules = 9; - DapOutputEvent output_event = 10; + DebuggerModuleList modules = 8; + DapOutputEvent output_event = 9; } } @@ -2952,6 +3080,11 @@ message DapVariable { optional string memory_reference = 9; } +message DapThread { + uint64 id = 1; + string name = 2; +} + message DapScope { string name = 1; optional DapScopePresentationHint presentation_hint = 2; diff --git a/crates/proto/src/proto.rs b/crates/proto/src/proto.rs index dc8c2fe297..d7fc560522 100644 --- a/crates/proto/src/proto.rs +++ b/crates/proto/src/proto.rs @@ -476,6 +476,18 @@ messages!( (DebuggerSessionEnded, Background), (ActiveDebugSessionsRequest, Foreground), (ActiveDebugSessionsResponse, Foreground), + (DapStackTraceRequest, Background), + (DapStackTraceResponse, Background), + (DapScopesRequest, Background), + (DapScopesResponse, Background), + (DapSetVariableValueRequest, Background), + (DapSetVariableValueResponse, Background), + (DapEvaluateRequest, Background), + (DapEvaluateResponse, Background), + (DapCompletionRequest, Background), + (DapCompletionResponse, Background), + (DapThreadsRequest, Background), + (DapThreadsResponse, Background), ); request_messages!( @@ -627,7 +639,13 @@ request_messages!( (DapRestartStackFrameRequest, Ack), (DapShutdownSession, Ack), (VariablesRequest, DapVariables), - (ActiveDebugSessionsRequest, ActiveDebugSessionsResponse) + (ActiveDebugSessionsRequest, ActiveDebugSessionsResponse), + (DapStackTraceRequest, DapStackTraceResponse), + (DapScopesRequest, DapScopesResponse), + (DapSetVariableValueRequest, DapSetVariableValueResponse), + (DapEvaluateRequest, DapEvaluateResponse), + (DapCompletionRequest, DapCompletionResponse), + (DapThreadsRequest, DapThreadsResponse), ); entity_messages!( @@ -748,6 +766,12 @@ entity_messages!( ToggleIgnoreBreakpoints, DebuggerSessionEnded, ActiveDebugSessionsRequest, + DapStackTraceRequest, + DapScopesRequest, + DapSetVariableValueRequest, + DapEvaluateRequest, + DapCompletionRequest, + DapThreadsRequest, ); entity_messages!( diff --git a/crates/remote_server/src/headless_project.rs b/crates/remote_server/src/headless_project.rs index 3a8549bdb0..2c84862c24 100644 --- a/crates/remote_server/src/headless_project.rs +++ b/crates/remote_server/src/headless_project.rs @@ -9,7 +9,7 @@ use language::{proto::serialize_operation, Buffer, BufferEvent, LanguageRegistry use node_runtime::NodeRuntime; use project::{ buffer_store::{BufferStore, BufferStoreEvent}, - dap_store::DapStore, + debugger::dap_store::DapStore, git::GitStore, project_settings::SettingsObserver, search::SearchQuery, diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 37377ce401..ce658982b0 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -4208,7 +4208,7 @@ mod tests { repl::init(app_state.fs.clone(), cx); repl::notebook::init(cx); tasks_ui::init(cx); - project::dap_store::DapStore::init(&app_state.client.clone().into()); + project::debugger::dap_store::DapStore::init(&app_state.client.clone().into()); debugger_ui::init(cx); initialize_workspace(app_state.clone(), prompt_builder, cx); search::init(cx);