Move requests into client (#112)

* Move most of the requests into the client state itself.

Co-authored-by: Anthony Eid <hello@anthonyeid.me>

* 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 <piotr@zed.dev>

---------

Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
Co-authored-by: Anthony Eid <hello@anthonyeid.me>
Co-authored-by: Anthony Eid <56899983+Anthony-Eid@users.noreply.github.com>
Co-authored-by: Piotr Osiewicz <piotr@zed.dev>
This commit is contained in:
Remco Smits
2025-02-13 22:25:32 +01:00
committed by GitHub
parent 661e5b0335
commit ecfc0ef12d
26 changed files with 2386 additions and 1766 deletions

1
Cargo.lock generated
View File

@@ -10228,6 +10228,7 @@ dependencies = [
"gpui",
"http_client",
"image",
"indexmap",
"itertools 0.14.0",
"language",
"log",

View File

@@ -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
});

View File

@@ -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,

View File

@@ -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,
}
}
}

View File

@@ -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(),

View File

@@ -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<DapStore>,
window: &mut Window,
cx: &mut Context<Self>,
) -> 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);
});

View File

@@ -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<OutputGroup>,
console: Entity<Editor>,
query_bar: Entity<Editor>,
dap_store: Entity<DapStore>,
session: Entity<DebugSession>,
client_id: DebugAdapterClientId,
_subscriptions: Vec<Subscription>,
@@ -47,8 +43,7 @@ pub struct Console {
impl Console {
pub fn new(
session: Entity<DebugSession>,
client_id: &DebugAdapterClientId,
dap_store: Entity<DapStore>,
client_id: DebugAdapterClientId,
stack_frame_list: Entity<StackFrameList>,
variable_list: Entity<VariableList>,
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<Self>) -> bool {
self.dap_store.read(cx).as_local().is_some()
fn is_local(&self, _cx: &Context<Self>) -> 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<Self>) {
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<Self>) -> 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<Editor>,
) -> gpui::Task<gpui::Result<Vec<project::Completion>>> {
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,

View File

@@ -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<Workspace>,
_subscriptions: Vec<Subscription>,
message_queue: HashMap<DebugAdapterClientId, VecDeque<OutputEvent>>,
thread_states: BTreeMap<(DebugAdapterClientId, u64), Entity<ThreadState>>,
thread_states: BTreeMap<(DebugAdapterClientId, ThreadId), Entity<ThreadState>>,
}
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<Self>,
) -> Option<Entity<DebugPanelItem>> {
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::<DebugPanelItem>().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<Value>,
cx: &mut Context<Self>,
@@ -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<Value>,
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<Self>,
) {
@@ -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<Self>,
) {
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<Capabilities>,
cx: &mut Context<Self>,
) {
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<Self>,
) {
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<Self>,
) {
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<Self>,
) {
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<Self>,
) {
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<TerminatedEvent>,
cx: &mut Context<Self>,
) {
@@ -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>,
) {
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<Self>,
) {
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::<DebugPanelItem>());
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<Self>) {
@@ -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<Self>,
) {
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<Self>,
) {
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<Self>,
) {
@@ -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));
}
}

View File

@@ -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<Console>,
focus_handle: FocusHandle,
remote_id: Option<ViewId>,
dap_store: Entity<DapStore>,
session: Entity<DebugSession>,
show_console_indicator: bool,
module_list: Entity<ModuleList>,
@@ -82,10 +78,9 @@ impl DebugPanelItem {
#[allow(clippy::too_many_arguments)]
pub fn new(
session: Entity<DebugSession>,
client_id: &DebugAdapterClientId,
thread_id: u64,
client_id: DebugAdapterClientId,
thread_id: ThreadId,
thread_state: Entity<ThreadState>,
dap_store: Entity<DapStore>,
debug_panel: &Entity<DebugPanel>,
workspace: WeakEntity<Workspace>,
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>) {
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<Self>,
) {
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<Self>,
) {
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<Self>,
) {
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<Self>,
) {
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<Self>,
// ) {
// 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<DebugSession> {
&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<Self>) -> Option<Capabilities> {
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<Self>) {
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::<Editor>(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::<Editor>(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::<editor::DebugCurrentRowHighlight>();
// if let Some(editor) = editor {
// editor.update(cx, |editor, cx| {
// editor.clear_row_highlights::<editor::DebugCurrentRowHighlight>();
cx.notify();
});
}
})
.ok();
}
// cx.notify();
// });
// }
// })
// .ok();
// }
}
pub fn go_to_current_stack_frame(&self, window: &mut Window, cx: &mut Context<Self>) {
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>) {
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>) {
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>) {
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>) {
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>) {
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>) {
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>) {
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>) {
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>) {
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<Self>) {
@@ -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,
)))
}

View File

@@ -15,7 +15,7 @@ pub struct LoadedSourceList {
impl LoadedSourceList {
pub fn new(
session: Entity<DebugSession>,
client_id: &DebugAdapterClientId,
client_id: DebugAdapterClientId,
cx: &mut Context<Self>,
) -> 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,
}
}

View File

@@ -14,7 +14,7 @@ pub struct ModuleList {
impl ModuleList {
pub fn new(
session: Entity<DebugSession>,
client_id: &DebugAdapterClientId,
client_id: DebugAdapterClientId,
cx: &mut Context<Self>,
) -> 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,
}
}

View File

@@ -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<DapStore>,
_subscription: Subscription,
session: Entity<DebugSession>,
stack_frames: Vec<StackFrame>,
entries: Vec<StackFrameEntry>,
workspace: WeakEntity<Workspace>,
client_id: DebugAdapterClientId,
_subscriptions: Vec<Subscription>,
current_stack_frame_id: StackFrameId,
fetch_stack_frames_task: Option<Task<Result<()>>>,
}
#[derive(Debug, PartialEq, Eq)]
pub enum StackFrameEntry {
Normal(StackFrame),
Collapsed(Vec<StackFrame>),
Normal(dap::StackFrame),
Collapsed(Vec<dap::StackFrame>),
}
impl StackFrameList {
#[allow(clippy::too_many_arguments)]
pub fn new(
workspace: WeakEntity<Workspace>,
debug_panel_item: &Entity<DebugPanelItem>,
dap_store: Entity<DapStore>,
session: Entity<DebugSession>,
client_id: &DebugAdapterClientId,
thread_id: u64,
window: &Window,
client_id: DebugAdapterClientId,
thread_id: ThreadId,
_window: &Window,
cx: &mut Context<Self>,
) -> 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>,
) {
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<StackFrameEntry> {
&self.entries
}
pub fn stack_frames(&self) -> &Vec<StackFrame> {
&self.stack_frames
pub fn stack_frames(&self, cx: &mut App) -> Vec<StackFrame> {
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<dap::StackFrame> {
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<Self>) -> 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<DebugPanelItem>,
event: &debugger_panel_item::DebugPanelItemEvent,
window: &mut Window,
cx: &mut Context<Self>,
) {
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>) {
self.fetch_stack_frames(true, window, cx);
}
fn build_entries(&mut self) {
fn build_entries(&mut self, cx: &mut Context<Self>) {
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<Self>,
) {
// 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<Self>,
// ) {
// // 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<Self>,
@@ -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<Self>,
) -> Option<ProjectPath> {
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>) {
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<Self>) -> AnyElement {
fn render_normal_entry(
&self,
stack_frame: &dap::StackFrame,
cx: &mut Context<Self>,
) -> 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<StackFrame>,
stack_frames: &Vec<dap::StackFrame>,
cx: &mut Context<Self>,
) {
self.entries.splice(
@@ -458,7 +411,7 @@ impl StackFrameList {
fn render_collapsed_entry(
&self,
ix: usize,
stack_frames: &Vec<StackFrame>,
stack_frames: &Vec<dap::StackFrame>,
cx: &mut Context<Self>,
) -> AnyElement {
let first_stack_frame = &stack_frames[0];

View File

@@ -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()
});

View File

@@ -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::<Vec<_>>(),
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::<Vec<_>>(),
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::<Editor>(cx).collect::<Vec<_>>();
@@ -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::<Vec<_>>(),
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::<Editor>(cx).collect::<Vec<_>>();

View File

@@ -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::<Vec<_>>(),
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::<Vec<_>>(),
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::<Vec<_>>(),
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::<Vec<_>>(),
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::<Vec<_>>(),
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

View File

@@ -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<DapStore>,
open_entries: Vec<OpenEntry>,
session: Entity<DebugSession>,
client_id: DebugAdapterClientId,
@@ -348,8 +347,7 @@ pub struct VariableList {
impl VariableList {
pub fn new(
session: Entity<DebugSession>,
client_id: &DebugAdapterClientId,
dap_store: Entity<DapStore>,
client_id: DebugAdapterClientId,
stack_frame_list: Entity<StackFrameList>,
window: &mut Window,
cx: &mut Context<Self>,
@@ -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<Variable> = 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<StackFrameList>,
@@ -526,35 +485,34 @@ impl VariableList {
stack_frame_id: StackFrameId,
cx: &mut Context<Self>,
) {
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<Self>) -> Vec<VariableContainer> {
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<Variable> = 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<OpenEntry>,
cx: &mut Context<Self>,
) -> Task<Result<Vec<VariableContainer>>> {
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<Self>,
) -> Task<Result<(Vec<Scope>, HashMap<u64, Vec<VariableContainer>>)>> {
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::<Vec<_>>()
})?;
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<Self>) {
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<Self>,
) {
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>) {
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);
);
});
}

View File

@@ -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},
};

View File

@@ -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

View File

@@ -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<DapStore>,
_client_id: &DebugAdapterClientId,
response: Result<Self::Response>,
_cx: &mut AsyncApp,
) -> Result<Self::Response> {
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,
) -> <Self::ProtoRequest as proto::RequestMessage>::Response;
@@ -78,14 +67,14 @@ impl<T: DapCommand> DapCommand for Arc<T> {
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,
) -> <Self::ProtoRequest as proto::RequestMessage>::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,
) -> <Self::ProtoRequest as proto::RequestMessage>::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,
) -> <Self::ProtoRequest as proto::RequestMessage>::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<DapStore>,
client_id: &DebugAdapterClientId,
response: Result<Self::Response>,
cx: &mut AsyncApp,
) -> Result<Self::Response> {
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,
) -> <Self::ProtoRequest as proto::RequestMessage>::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,
) -> <Self::ProtoRequest as proto::RequestMessage>::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<DapStore>,
client_id: &DebugAdapterClientId,
response: Result<Self::Response>,
cx: &mut AsyncApp,
) -> Result<Self::Response> {
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,
) -> <Self::ProtoRequest as proto::RequestMessage>::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,
) -> <Self::ProtoRequest as proto::RequestMessage>::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,
) -> <Self::ProtoRequest as proto::RequestMessage>::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,
) -> <Self::ProtoRequest as proto::RequestMessage>::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,
) -> <Self::ProtoRequest as proto::RequestMessage>::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,
) -> <Self::ProtoRequest as proto::RequestMessage>::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<DapStore>,
client_id: &DebugAdapterClientId,
response: Result<Self::Response>,
cx: &mut AsyncApp,
) -> Result<Self::Response> {
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,
) -> <Self::ProtoRequest as proto::RequestMessage>::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) -> <Self::DapRequest as dap::requests::Request>::Arguments {
dap::SetVariableArguments {
format: None,
name: self.name.clone(),
value: self.value.clone(),
variables_reference: self.variables_reference,
}
}
fn response_from_dap(
&self,
message: <Self::DapRequest as dap::requests::Request>::Response,
) -> Result<Self::Response> {
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,
) -> <Self::ProtoRequest as proto::RequestMessage>::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: <Self::ProtoRequest as proto::RequestMessage>::Response,
) -> Result<Self::Response> {
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,
) -> <Self::ProtoRequest as proto::RequestMessage>::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,
) -> <Self::ProtoRequest as proto::RequestMessage>::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,
) -> <Self::ProtoRequest as proto::RequestMessage>::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<u64>,
pub levels: Option<u64>,
}
impl DapCommand for StackTraceCommand {
type Response = Vec<dap::StackFrame>;
type DapRequest = dap::requests::StackTrace;
type ProtoRequest = proto::DapStackTraceRequest;
fn to_dap(&self) -> <Self::DapRequest as dap::requests::Request>::Arguments {
dap::StackTraceArguments {
thread_id: self.thread_id,
start_frame: self.start_frame,
levels: self.levels,
format: None,
}
}
fn response_from_dap(
&self,
message: <Self::DapRequest as dap::requests::Request>::Response,
) -> Result<Self::Response> {
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: <Self::ProtoRequest as proto::RequestMessage>::Response,
) -> Result<Self::Response> {
Ok(message
.frames
.into_iter()
.map(dap::StackFrame::from_proto)
.collect())
}
fn response_to_proto(
_debug_client_id: DebugAdapterClientId,
message: Self::Response,
) -> <Self::ProtoRequest as proto::RequestMessage>::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<dap::Scope>;
type DapRequest = dap::requests::Scopes;
type ProtoRequest = proto::DapScopesRequest;
fn to_dap(&self) -> <Self::DapRequest as dap::requests::Request>::Arguments {
dap::ScopesArguments {
frame_id: self.stack_frame_id,
}
}
fn response_from_dap(
&self,
message: <Self::DapRequest as dap::requests::Request>::Response,
) -> Result<Self::Response> {
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: <Self::ProtoRequest as proto::RequestMessage>::Response,
) -> Result<Self::Response> {
Ok(Vec::from_proto(message.scopes))
}
fn response_to_proto(
_debug_client_id: DebugAdapterClientId,
message: Self::Response,
) -> <Self::ProtoRequest as proto::RequestMessage>::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) -> <Self::DapRequest as dap::requests::Request>::Arguments {
dap::CompletionsArguments {
text: self.query.clone(),
frame_id: self.frame_id,
column: self.column,
line: None,
}
}
fn response_from_dap(
&self,
message: <Self::DapRequest as dap::requests::Request>::Response,
) -> Result<Self::Response> {
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: <Self::ProtoRequest as proto::RequestMessage>::Response,
) -> Result<Self::Response> {
Ok(dap::CompletionsResponse {
targets: Vec::from_proto(message.completions),
})
}
fn response_to_proto(
_debug_client_id: DebugAdapterClientId,
message: Self::Response,
) -> <Self::ProtoRequest as proto::RequestMessage>::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<u64>,
pub context: Option<dap::EvaluateArgumentsContext>,
pub source: Option<dap::Source>,
}
impl DapCommand for EvaluateCommand {
type Response = dap::EvaluateResponse;
type DapRequest = dap::requests::Evaluate;
type ProtoRequest = proto::DapEvaluateRequest;
fn to_dap(&self) -> <Self::DapRequest as dap::requests::Request>::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: <Self::DapRequest as dap::requests::Request>::Response,
) -> Result<Self::Response> {
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: <Self::ProtoRequest as proto::RequestMessage>::Response,
) -> Result<Self::Response> {
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,
) -> <Self::ProtoRequest as proto::RequestMessage>::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<dap::Thread>;
type DapRequest = dap::requests::Threads;
type ProtoRequest = proto::DapThreadsRequest;
fn to_dap(&self) -> <Self::DapRequest as dap::requests::Request>::Arguments {
()
}
fn response_from_dap(
&self,
message: <Self::DapRequest as dap::requests::Request>::Response,
) -> Result<Self::Response> {
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: <Self::ProtoRequest as proto::RequestMessage>::Response,
) -> Result<Self::Response> {
Ok(Vec::from_proto(message.threads))
}
fn response_to_proto(
_debug_client_id: DebugAdapterClientId,
message: Self::Response,
) -> <Self::ProtoRequest as proto::RequestMessage>::Response {
proto::DapThreadsResponse {
threads: message.to_proto(),
}
}
}

View File

@@ -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<Variable>,
impl ThreadId {
pub const MIN: ThreadId = ThreadId(u64::MIN);
pub const MAX: ThreadId = ThreadId(u64::MAX);
}
struct Scope {
_scope: dap::Scope,
_variables: Vec<Variable>,
#[derive(Clone)]
pub struct Variable {
dap: dap::Variable,
variables: Vec<Variable>,
}
struct StackFrame {
_stack_frame: dap::StackFrame,
_scopes: Vec<Scope>,
impl From<dap::Variable> for Variable {
fn from(dap: dap::Variable) -> Self {
Self {
dap,
variables: vec![],
}
}
}
#[derive(Clone)]
pub struct Scope {
pub dap: dap::Scope,
pub variables: Vec<Variable>,
}
impl From<dap::Scope> 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<Scope>,
}
impl From<dap::StackFrame> 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<StackFrame>,
pub struct Thread {
dap: dap::Thread,
stack_frames: Vec<StackFrame>,
_status: ThreadStatus,
_has_stopped: bool,
}
pub struct DebugAdapterClientState {
dap_store: WeakEntity<DapStore>,
impl From<dap::Thread> 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<R: DapCommand>(
&self,
request: R,
client_id: DebugAdapterClientId,
cx: &mut App,
) -> Task<Result<R::Response>> {
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<R: DapCommand>(
&self,
request: R,
client_id: DebugAdapterClientId,
cx: &mut App,
) -> Task<Result<R::Response>>
where
<R::DapRequest as dap::requests::Request>::Response: 'static,
<R::DapRequest as dap::requests::Request>::Arguments: 'static + Send,
{
return self.send_proto_client_request::<R>(request, client_id, cx);
}
}
pub enum Mode {
Local(Arc<DebugAdapterClient>),
Remote(RemoteConnection),
}
impl From<RemoteConnection> for Mode {
fn from(value: RemoteConnection) -> Self {
Self::Remote(value)
}
}
impl From<Arc<DebugAdapterClient>> for Mode {
fn from(client: Arc<DebugAdapterClient>) -> Self {
Mode::Local(client)
}
}
impl Mode {
fn request_local<R: DapCommand>(
connection: &Arc<DebugAdapterClient>,
caps: &Capabilities,
request: R,
cx: &mut Context<Client>,
) -> Task<Result<R::Response>>
where
<R::DapRequest as dap::requests::Request>::Response: 'static,
<R::DapRequest as dap::requests::Request>::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::<R::DapRequest>(args).await
});
cx.background_executor().spawn(async move {
let response = request.response_from_dap(request_task.await?);
response
})
}
fn request_dap<R: DapCommand>(
&self,
caps: &Capabilities,
client_id: DebugAdapterClientId,
request: R,
cx: &mut Context<Client>,
) -> Task<Result<R::Response>>
where
<R::DapRequest as dap::requests::Request>::Response: 'static,
<R::DapRequest as dap::requests::Request>::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<dap::Module>,
loaded_sources: Vec<dap::Source>,
_threads: BTreeMap<ThreadId, Thread>,
threads: IndexMap<ThreadId, Thread>,
requests: HashMap<RequestSlot, Shared<Task<Option<()>>>>,
}
@@ -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<u64>,
pub frame_id: Option<u64>,
}
impl CompletionsQuery {
pub fn new(
buffer: &language::Buffer,
cursor_position: language::Anchor,
frame_id: Option<u64>,
) -> 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<R: DapCommand + PartialEq + Eq + Hash>(
&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<T: DapCommand + PartialEq + Eq + Hash>(
/// 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<T: DapCommand + PartialEq + Eq + Hash>(
&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<Self>) + 'static,
cx: &mut Context<Self>,
) {
if let Entry::Vacant(vacant) = self.requests.entry(request.into()) {
let command = vacant.key().0.clone().as_any_arc().downcast::<T>().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::<Arc<T>>(
&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<T: DapCommand + PartialEq + Eq + Hash>(
capabilities: &Capabilities,
client_id: DebugAdapterClientId,
mode: &Mode,
request: T,
process_result: impl FnOnce(&mut Self, &T::Response, &mut Context<Self>) + 'static,
cx: &mut Context<Self>,
) -> Task<Option<T::Response>> {
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<T: DapCommand + PartialEq + Eq + Hash>(
&self,
request: T,
process_result: impl FnOnce(&mut Self, &T::Response, &mut Context<Self>) + 'static,
cx: &mut Context<Self>,
) -> Task<Option<T::Response>> {
Self::request_inner(
&self.capabilities,
self.client_id,
&self.mode,
request,
process_result,
cx,
)
}
pub fn invalidate(&mut self, cx: &mut Context<Self>) {
self.requests.clear();
self.modules.clear();
self.loaded_sources.clear();
cx.notify();
}
pub fn threads(&mut self, cx: &mut Context<Self>) -> Vec<dap::Thread> {
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<Self>) -> &[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<Self>) -> &[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<Self>) {}
pub fn pause_thread(&mut self, thread_id: ThreadId, cx: &mut Context<Self>) {
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>) {
self.request(
RestartStackFrameCommand { stack_frame_id },
Self::empty_response,
cx,
)
.detach();
}
pub fn restart(&mut self, args: Option<Value>, cx: &mut Context<Self>) {
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<Self>) {
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<Self>,
) -> Task<Result<Vec<dap::CompletionItem>>> {
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>) {
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<Arc<DebugAdapterClient>> {
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<Self>,
) {
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<Self>,
) {
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<Self>,
) {
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<Self>,
) {
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<Self>) -> Vec<StackFrame> {
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<Self>,
) -> Vec<Scope> {
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<Self>,
) -> Vec<Variable> {
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<Self>,
) {
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<EvaluateArgumentsContext>,
frame_id: Option<u64>,
source: Option<Source>,
cx: &mut Context<Self>,
) {
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<Self>) {
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<Vec<ThreadId>>, cx: &mut Context<Self>) {
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<DebugAdapterClientId, Entity<DebugAdapterClientState>>,
pub(super) states: BTreeMap<DebugAdapterClientId, Entity<Client>>,
ignore_breakpoints: bool,
}
@@ -257,7 +906,6 @@ pub enum DebugSessionMode {
pub struct LocalDebugSession {
configuration: DebugAdapterConfig,
clients: HashMap<DebugAdapterClientId, Arc<DebugAdapterClient>>,
}
impl LocalDebugSession {
@@ -273,42 +921,6 @@ impl LocalDebugSession {
f(&mut self.configuration);
cx.notify();
}
fn add_client(&mut self, client: Arc<DebugAdapterClient>, cx: &mut Context<DebugSession>) {
self.clients.insert(client.id(), client);
cx.notify();
}
pub fn remove_client(
&mut self,
client_id: &DebugAdapterClientId,
cx: &mut Context<DebugSession>,
) -> Option<Arc<DebugAdapterClient>> {
let client = self.clients.remove(client_id);
cx.notify();
client
}
pub fn client_by_id(
&self,
client_id: &DebugAdapterClientId,
) -> Option<Arc<DebugAdapterClient>> {
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<Item = Arc<DebugAdapterClient>> + '_ {
self.clients.values().cloned()
}
pub fn client_ids(&self) -> impl Iterator<Item = DebugAdapterClientId> + '_ {
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<Entity<DebugAdapterClientState>> {
pub fn client_state(&self, client_id: DebugAdapterClientId) -> Option<Entity<Client>> {
self.states.get(&client_id).cloned()
}
pub(super) fn client_ids(&self) -> impl Iterator<Item = DebugAdapterClientId> + '_ {
self.states.keys().copied()
}
pub fn clients(&self, cx: &App) -> Vec<Arc<DebugAdapterClient>> {
self.states
.values()
.filter_map(|state| state.read(cx).adapter_client())
.collect()
}
pub fn add_client(
&mut self,
client: Option<Arc<DebugAdapterClient>>,
client: impl Into<Mode>,
client_id: DebugAdapterClientId,
weak_dap: WeakEntity<DapStore>,
cx: &mut Context<DebugSession>,
) {
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<DebugAdapterClientId>,
) -> Option<Entity<Client>> {
self.states.get(client_id.borrow()).cloned()
}
pub(crate) fn shutdown_client(
&mut self,
client_id: DebugAdapterClientId,
cx: &mut Context<Self>,
) {
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()
}
}

View File

@@ -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<dyn Fs>,
node_runtime: NodeRuntime,
next_client_id: AtomicUsize,
next_client_id: AtomicU32,
next_session_id: AtomicUsize,
http_client: Arc<dyn HttpClient>,
environment: Entity<ProjectEnvironment>,
@@ -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::<NextCommand>);
client.add_entity_request_handler(Self::handle_dap_command::<StepInCommand>);
client.add_entity_request_handler(Self::handle_dap_command::<StepOutCommand>);
client.add_entity_request_handler(Self::handle_dap_command::<StepBackCommand>);
client.add_entity_request_handler(Self::handle_dap_command::<ContinueCommand>);
client.add_entity_request_handler(Self::handle_dap_command::<PauseCommand>);
client.add_entity_request_handler(Self::handle_dap_command::<DisconnectCommand>);
client.add_entity_request_handler(Self::handle_dap_command::<TerminateThreadsCommand>);
client.add_entity_request_handler(Self::handle_dap_command::<TerminateCommand>);
client.add_entity_request_handler(Self::handle_dap_command::<RestartCommand>);
client.add_entity_request_handler(Self::handle_dap_command::<VariablesCommand>);
client.add_entity_request_handler(Self::handle_dap_command::<RestartStackFrameCommand>);
// todo(debugger): Reenable these after we finish handle_dap_command refactor
// client.add_entity_request_handler(Self::handle_dap_command::<NextCommand>);
// client.add_entity_request_handler(Self::handle_dap_command::<StepInCommand>);
// client.add_entity_request_handler(Self::handle_dap_command::<StepOutCommand>);
// client.add_entity_request_handler(Self::handle_dap_command::<StepBackCommand>);
// client.add_entity_request_handler(Self::handle_dap_command::<ContinueCommand>);
// client.add_entity_request_handler(Self::handle_dap_command::<PauseCommand>);
// client.add_entity_request_handler(Self::handle_dap_command::<DisconnectCommand>);
// client.add_entity_request_handler(Self::handle_dap_command::<TerminateThreadsCommand>);
// client.add_entity_request_handler(Self::handle_dap_command::<TerminateCommand>);
// client.add_entity_request_handler(Self::handle_dap_command::<RestartCommand>);
// client.add_entity_request_handler(Self::handle_dap_command::<VariablesCommand>);
// client.add_entity_request_handler(Self::handle_dap_command::<RestartStackFrameCommand>);
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<DebugAdapterClientId>,
) -> Option<Entity<DebugSession>> {
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<DebugAdapterClientId>,
cx: &Context<Self>,
) -> Option<(Entity<DebugSession>, Arc<DebugAdapterClient>)> {
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<DebugSession>, Entity<dap_session::Client>)> {
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<DebugAdapterClientId>,
cx: &App,
) -> Option<Capabilities> {
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<Self>,
) {
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<Self>,
) -> Task<Result<()>> {
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<Self>,
) -> Task<Result<()>> {
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::requests::ConfigurationDone>(dap::ConfigurationDoneArguments)
.await
})
} else {
Task::ready(Ok(()))
}
}
pub fn launch(
&mut self,
session_id: &DebugSessionId,
client_id: &DebugAdapterClientId,
client_id: DebugAdapterClientId,
cx: &mut Context<Self>,
) -> Task<Result<()>> {
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<Self>,
) -> Task<Result<()>> {
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<Self>,
) -> Task<Result<Vec<Module>>> {
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::<Modules>(ModulesArguments {
start_module: None,
module_count: None,
})
.await?
.modules)
})
}
pub fn loaded_sources(
&mut self,
client_id: &DebugAdapterClientId,
cx: &mut Context<Self>,
) -> Task<Result<Vec<Source>>> {
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::<LoadedSources>(LoadedSourcesArguments {})
.await?
.sources)
})
}
pub fn stack_frames(
&mut self,
client_id: &DebugAdapterClientId,
thread_id: u64,
cx: &mut Context<Self>,
) -> Task<Result<Vec<StackFrame>>> {
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::<StackTrace>(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<Self>,
) -> Task<Result<()>> {
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<Self>,
) -> Task<Result<Vec<Scope>>> {
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::<Scopes>(ScopesArguments {
frame_id: stack_frame_id,
})
.await?
.scopes)
})
}
pub fn configuration_done(
&self,
client_id: &DebugAdapterClientId,
cx: &mut Context<Self>,
) -> Task<Result<()>> {
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::<ConfigurationDone>(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<StartDebuggingRequestArguments>,
cx: &mut Context<Self>,
) -> Task<Result<()>> {
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<Value>,
cx: &mut Context<Self>,
) -> Task<Result<()>> {
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<Self>,
) -> Task<Result<ContinueResponse>> {
let command = ContinueCommand {
args: ContinueArguments {
thread_id,
single_thread: Some(true),
},
};
self.request_dap(client_id, command, cx)
}
pub(crate) fn request_dap<R: DapCommand>(
&self,
client_id: &DebugAdapterClientId,
request: R,
cx: &mut Context<Self>,
) -> Task<Result<R::Response>>
where
<R::DapRequest as dap::requests::Request>::Response: 'static,
<R::DapRequest as dap::requests::Request>::Arguments: 'static + Send,
{
if let Some((upstream_client, upstream_project_id)) = self.upstream_client() {
return self.send_proto_client_request::<R>(
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::<R::DapRequest>(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<R: DapCommand>(
&self,
upstream_client: AnyProtoClient,
upstream_project_id: u64,
client_id: &DebugAdapterClientId,
request: R,
cx: &mut Context<Self>,
) -> Task<Result<R::Response>> {
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<Self>,
) -> Task<Result<()>> {
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<Self>,
) -> Task<Result<()>> {
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<Self>,
) -> Task<Result<()>> {
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<Self>,
) -> Task<Result<()>> {
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<Self>,
) -> Task<Result<Vec<Variable>>> {
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<Source>,
cx: &mut Context<Self>,
) -> Task<Result<EvaluateResponse>> {
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<Self>,
) -> Task<Result<Vec<CompletionItem>>> {
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<String>,
cx: &mut Context<Self>,
) -> Task<Result<()>> {
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<Self>,
) -> Task<Result<()>> {
self.request_dap(client_id, PauseCommand { thread_id }, cx)
}
pub fn terminate_threads(
&mut self,
session_id: &DebugSessionId,
client_id: &DebugAdapterClientId,
thread_ids: Option<Vec<u64>>,
cx: &mut Context<Self>,
) -> Task<Result<()>> {
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<Self>,
) -> Task<Result<()>> {
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<Value>,
cx: &mut Context<Self>,
) -> Task<Result<()>> {
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<Self>) -> 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::<Vec<_>>() {
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<DebugSession>,
client: Arc<DebugAdapterClient>,
cx: &mut Context<Self>,
) -> Task<Result<()>> {
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::<Terminate>(TerminateArguments {
restart: Some(false),
})
.await
.log_err();
} else {
let _ = client
.request::<Disconnect>(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::<Vec<_>>() {
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<Self>) {
@@ -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<T: DapCommand>(
this: Entity<Self>,
envelope: TypedEnvelope<T::ProtoRequest>,
mut cx: AsyncApp,
) -> Result<<T::ProtoRequest as proto::RequestMessage>::Response>
where
<T::DapRequest as dap::requests::Request>::Arguments: Send,
<T::DapRequest as dap::requests::Request>::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<T: DapCommand>(
// this: Entity<Self>,
// envelope: TypedEnvelope<T::ProtoRequest>,
// mut cx: AsyncApp,
// ) -> Result<<T::ProtoRequest as proto::RequestMessage>::Response>
// where
// <T::DapRequest as dap::requests::Request>::Arguments: Send,
// <T::DapRequest as dap::requests::Request>::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::<T>(&client_id, request, cx)
})?
.await?;
// let request = T::from_proto(&envelope.payload);
// let response = this
// .update(&mut cx, |this, cx| {
// this.request_dap::<T>(&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<Self>,
@@ -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<Path>,
mut breakpoints: Vec<SourceBreakpoint>,
ignore: bool,
source_changed: bool,
cx: &Context<Self>,
) -> Task<Result<()>> {
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::<Vec<_>>() {
for client_id in session.client_ids().collect::<Vec<_>>() {
tasks.push(self.send_breakpoints(
&client.id(),
client_id,
Arc::from(absolute_path.clone()),
source_breakpoints.clone(),
ignore_breakpoints,

View File

@@ -1311,7 +1311,7 @@ impl Project {
pub fn initial_send_breakpoints(
&self,
session_id: &DebugSessionId,
client_id: &DebugAdapterClientId,
client_id: DebugAdapterClientId,
cx: &mut Context<Self>,
) -> 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<Self>,
) -> Task<Result<()>> {
let tasks = self.dap_store.update(cx, |store, cx| {

View File

@@ -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;

View File

@@ -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!(

View File

@@ -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,

View File

@@ -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);