Correctly refetch/invalidate stackframes/scopes/variables (#55)
* Correctly invalidate and refetch data * Fix show current stack frame after invalidating * Remove not used return value * Clean up * Don't send SelectedStackFrameChanged when id is still the same * Only update the active debug line if we got the editor
This commit is contained in:
@@ -136,10 +136,8 @@ impl LogStore {
|
||||
io_kind: IoKind,
|
||||
message: &str,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Option<()> {
|
||||
) {
|
||||
self.add_debug_client_message(client_id, io_kind, message.to_string(), cx);
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn on_adapter_log(
|
||||
@@ -148,10 +146,8 @@ impl LogStore {
|
||||
io_kind: IoKind,
|
||||
message: &str,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Option<()> {
|
||||
) {
|
||||
self.add_debug_client_log(client_id, io_kind, message.to_string(), cx);
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
pub fn add_project(&mut self, project: &Model<Project>, cx: &mut ModelContext<Self>) {
|
||||
|
||||
@@ -121,9 +121,7 @@ impl Console {
|
||||
console.add_message(&response.result, cx);
|
||||
|
||||
console.variable_list.update(cx, |variable_list, cx| {
|
||||
variable_list
|
||||
.refetch_existing_variables(cx)
|
||||
.detach_and_log_err(cx);
|
||||
variable_list.invalidate(cx);
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -33,6 +33,7 @@ pub struct StackFrameList {
|
||||
workspace: WeakView<Workspace>,
|
||||
client_id: DebugAdapterClientId,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
fetch_stack_frames_task: Option<Task<Result<()>>>,
|
||||
}
|
||||
|
||||
impl StackFrameList {
|
||||
@@ -65,6 +66,7 @@ impl StackFrameList {
|
||||
client_id: *client_id,
|
||||
workspace: workspace.clone(),
|
||||
dap_store: dap_store.clone(),
|
||||
fetch_stack_frames_task: None,
|
||||
stack_frames: Default::default(),
|
||||
current_stack_frame_id: Default::default(),
|
||||
}
|
||||
@@ -86,31 +88,34 @@ impl StackFrameList {
|
||||
) {
|
||||
match event {
|
||||
Stopped { go_to_stack_frame } => {
|
||||
self.fetch_stack_frames(*go_to_stack_frame, cx)
|
||||
.detach_and_log_err(cx);
|
||||
self.fetch_stack_frames(*go_to_stack_frame, cx);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn fetch_stack_frames(
|
||||
&self,
|
||||
go_to_stack_frame: bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
pub fn invalidate(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.fetch_stack_frames(true, cx);
|
||||
}
|
||||
|
||||
fn fetch_stack_frames(&mut self, go_to_stack_frame: bool, cx: &mut ViewContext<Self>) {
|
||||
let task = self.dap_store.update(cx, |store, cx| {
|
||||
store.stack_frames(&self.client_id, self.thread_id, cx)
|
||||
});
|
||||
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
self.fetch_stack_frames_task = Some(cx.spawn(|this, mut cx| async move {
|
||||
let mut stack_frames = task.await?;
|
||||
|
||||
let task = this.update(&mut cx, |this, cx| {
|
||||
std::mem::swap(&mut this.stack_frames, &mut stack_frames);
|
||||
|
||||
let previous_stack_frame_id = this.current_stack_frame_id;
|
||||
if let Some(stack_frame) = this.stack_frames.first() {
|
||||
this.current_stack_frame_id = stack_frame.id;
|
||||
cx.emit(StackFrameListEvent::SelectedStackFrameChanged);
|
||||
|
||||
if previous_stack_frame_id != this.current_stack_frame_id {
|
||||
cx.emit(StackFrameListEvent::SelectedStackFrameChanged);
|
||||
}
|
||||
}
|
||||
|
||||
this.list.reset(this.stack_frames.len());
|
||||
@@ -129,8 +134,10 @@ impl StackFrameList {
|
||||
task.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
this.update(&mut cx, |this, _| {
|
||||
this.fetch_stack_frames_task.take();
|
||||
})
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn go_to_stack_frame(&mut self, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
|
||||
@@ -151,19 +158,21 @@ impl StackFrameList {
|
||||
return Task::ready(Err(anyhow!("Project path not found")));
|
||||
};
|
||||
|
||||
self.dap_store.update(cx, |store, cx| {
|
||||
store.set_active_debug_line(&project_path, row, column, cx);
|
||||
});
|
||||
|
||||
cx.spawn({
|
||||
let workspace = self.workspace.clone();
|
||||
move |_, mut cx| async move {
|
||||
move |this, mut cx| async move {
|
||||
let task = workspace.update(&mut cx, |workspace, cx| {
|
||||
workspace.open_path_preview(project_path, None, false, true, cx)
|
||||
workspace.open_path_preview(project_path.clone(), None, false, true, cx)
|
||||
})?;
|
||||
|
||||
let editor = task.await?.downcast::<Editor>().unwrap();
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.dap_store.update(cx, |store, cx| {
|
||||
store.set_active_debug_line(&project_path, row, column, cx);
|
||||
})
|
||||
})?;
|
||||
|
||||
workspace.update(&mut cx, |_, cx| {
|
||||
editor.update(cx, |editor, cx| editor.go_to_active_debug_line(cx))
|
||||
})
|
||||
|
||||
@@ -18,7 +18,7 @@ use std::{
|
||||
};
|
||||
use ui::{prelude::*, ContextMenu, ListItem};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct VariableContainer {
|
||||
pub container_reference: u64,
|
||||
pub variable: Variable,
|
||||
@@ -35,6 +35,12 @@ pub struct SetVariableState {
|
||||
parent_variables_reference: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
enum OpenEntry {
|
||||
Scope { name: String },
|
||||
Variable { name: String, depth: usize },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum VariableListEntry {
|
||||
Scope(Scope),
|
||||
@@ -51,22 +57,64 @@ pub enum VariableListEntry {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ScopeVariableIndex {
|
||||
fetched_ids: HashSet<u64>,
|
||||
variables: Vec<VariableContainer>,
|
||||
}
|
||||
|
||||
impl ScopeVariableIndex {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
variables: Vec::new(),
|
||||
fetched_ids: HashSet::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fetched(&self, container_reference: &u64) -> bool {
|
||||
self.fetched_ids.contains(container_reference)
|
||||
}
|
||||
|
||||
/// All the variables should have the same depth and the same container reference
|
||||
pub fn add_variables(&mut self, container_reference: u64, variables: Vec<VariableContainer>) {
|
||||
let position = self
|
||||
.variables
|
||||
.iter()
|
||||
.position(|v| v.variable.variables_reference == container_reference);
|
||||
|
||||
self.fetched_ids.insert(container_reference);
|
||||
|
||||
if let Some(position) = position {
|
||||
self.variables.splice(position + 1..=position, variables);
|
||||
} else {
|
||||
self.variables.extend(variables);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.variables.is_empty()
|
||||
}
|
||||
|
||||
pub fn variables(&self) -> &[VariableContainer] {
|
||||
&self.variables
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VariableList {
|
||||
list: ListState,
|
||||
dap_store: Model<DapStore>,
|
||||
focus_handle: FocusHandle,
|
||||
dap_store: Model<DapStore>,
|
||||
open_entries: Vec<OpenEntry>,
|
||||
client_id: DebugAdapterClientId,
|
||||
open_entries: Vec<SharedString>,
|
||||
scopes: HashMap<u64, Vec<Scope>>,
|
||||
set_variable_editor: View<Editor>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
fetched_variable_ids: HashSet<u64>,
|
||||
stack_frame_list: View<StackFrameList>,
|
||||
set_variable_state: Option<SetVariableState>,
|
||||
entries: HashMap<u64, Vec<VariableListEntry>>,
|
||||
fetch_variables_task: Option<Task<Result<()>>>,
|
||||
// (stack_frame_id, scope.variables_reference) -> variables
|
||||
variables: BTreeMap<(u64, u64), Vec<VariableContainer>>,
|
||||
// (stack_frame_id, scope_id) -> VariableIndex
|
||||
variables: BTreeMap<(u64, u64), ScopeVariableIndex>,
|
||||
open_context_menu: Option<(View<ContextMenu>, Point<Pixels>, Subscription)>,
|
||||
}
|
||||
|
||||
@@ -116,7 +164,6 @@ impl VariableList {
|
||||
entries: Default::default(),
|
||||
variables: Default::default(),
|
||||
open_entries: Default::default(),
|
||||
fetched_variable_ids: Default::default(),
|
||||
stack_frame_list: stack_frame_list.clone(),
|
||||
}
|
||||
}
|
||||
@@ -142,7 +189,7 @@ impl VariableList {
|
||||
|
||||
self.variables
|
||||
.range((stack_frame_id, u64::MIN)..(stack_frame_id, u64::MAX))
|
||||
.flat_map(|(_, containers)| containers.iter().cloned())
|
||||
.flat_map(|(_, containers)| containers.variables.iter().cloned())
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -163,10 +210,9 @@ impl VariableList {
|
||||
scope,
|
||||
variable,
|
||||
has_children,
|
||||
container_reference: parent_variables_reference,
|
||||
container_reference,
|
||||
} => self.render_variable(
|
||||
ix,
|
||||
*parent_variables_reference,
|
||||
*container_reference,
|
||||
variable,
|
||||
scope,
|
||||
*depth,
|
||||
@@ -176,7 +222,7 @@ impl VariableList {
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle_entry_collapsed(&mut self, entry_id: &SharedString, cx: &mut ViewContext<Self>) {
|
||||
fn toggle_entry(&mut self, entry_id: &OpenEntry, cx: &mut ViewContext<Self>) {
|
||||
match self.open_entries.binary_search(&entry_id) {
|
||||
Ok(ix) => {
|
||||
self.open_entries.remove(ix);
|
||||
@@ -207,25 +253,35 @@ impl VariableList {
|
||||
|
||||
let mut entries: Vec<VariableListEntry> = Vec::default();
|
||||
for scope in scopes {
|
||||
let Some(variables) = self
|
||||
let Some(index) = self
|
||||
.variables
|
||||
.get(&(stack_frame_id, scope.variables_reference))
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if variables.is_empty() {
|
||||
if index.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if open_first_scope && entries.is_empty() {
|
||||
self.open_entries.push(scope_entry_id(scope));
|
||||
let scope_open_entry_id = OpenEntry::Scope {
|
||||
name: scope.name.clone(),
|
||||
};
|
||||
|
||||
if open_first_scope
|
||||
&& entries.is_empty()
|
||||
&& self
|
||||
.open_entries
|
||||
.binary_search(&scope_open_entry_id)
|
||||
.is_err()
|
||||
{
|
||||
self.open_entries.push(scope_open_entry_id.clone());
|
||||
}
|
||||
entries.push(VariableListEntry::Scope(scope.clone()));
|
||||
|
||||
if self
|
||||
.open_entries
|
||||
.binary_search(&scope_entry_id(scope))
|
||||
.binary_search(&scope_open_entry_id)
|
||||
.is_err()
|
||||
{
|
||||
continue;
|
||||
@@ -233,7 +289,7 @@ impl VariableList {
|
||||
|
||||
let mut depth_check: Option<usize> = None;
|
||||
|
||||
for variable_container in variables {
|
||||
for variable_container in index.variables().iter() {
|
||||
let depth = variable_container.depth;
|
||||
let variable = &variable_container.variable;
|
||||
let container_reference = variable_container.container_reference;
|
||||
@@ -248,7 +304,10 @@ impl VariableList {
|
||||
|
||||
if self
|
||||
.open_entries
|
||||
.binary_search(&variable_entry_id(scope, variable, depth))
|
||||
.binary_search(&OpenEntry::Variable {
|
||||
name: variable.name.clone(),
|
||||
depth,
|
||||
})
|
||||
.is_err()
|
||||
{
|
||||
if depth_check.is_none() || depth_check.is_some_and(|d| d > depth) {
|
||||
@@ -285,79 +344,134 @@ impl VariableList {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn fetch_nested_variables(
|
||||
&self,
|
||||
variables_reference: u64,
|
||||
depth: usize,
|
||||
open_entries: &Vec<OpenEntry>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Task<Result<Vec<VariableContainer>>> {
|
||||
cx.spawn({
|
||||
let open_entries = open_entries.clone();
|
||||
|this, mut cx| async move {
|
||||
let variables_task = this.update(&mut cx, |this, cx| {
|
||||
this.dap_store.update(cx, |store, cx| {
|
||||
store.variables(&this.client_id, variables_reference, cx)
|
||||
})
|
||||
})?;
|
||||
|
||||
let mut variables = Vec::new();
|
||||
|
||||
for variable in variables_task.await? {
|
||||
variables.push(VariableContainer {
|
||||
variable: variable.clone(),
|
||||
container_reference: variables_reference,
|
||||
depth,
|
||||
});
|
||||
|
||||
if open_entries
|
||||
.binary_search(&&OpenEntry::Variable {
|
||||
name: variable.name.clone(),
|
||||
depth,
|
||||
})
|
||||
.is_ok()
|
||||
{
|
||||
let task = this.update(&mut cx, |this, cx| {
|
||||
this.fetch_nested_variables(
|
||||
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,
|
||||
open_entries: &Vec<OpenEntry>,
|
||||
cx: &mut ViewContext<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({
|
||||
let open_entries = open_entries.clone();
|
||||
|this, mut cx| async move {
|
||||
let mut variables = HashMap::new();
|
||||
|
||||
let scopes = scopes_task.await?;
|
||||
|
||||
for scope in scopes.iter() {
|
||||
let variables_task = this.update(&mut cx, |this, cx| {
|
||||
this.fetch_nested_variables(scope.variables_reference, 1, &open_entries, cx)
|
||||
})?;
|
||||
|
||||
variables.insert(scope.variables_reference, variables_task.await?);
|
||||
}
|
||||
|
||||
Ok((scopes, variables))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn fetch_variables(&mut self, cx: &mut ViewContext<Self>) {
|
||||
let stack_frames = self.stack_frame_list.read(cx).stack_frames().clone();
|
||||
|
||||
self.fetch_variables_task = Some(cx.spawn(|this, mut cx| async move {
|
||||
let mut scope_tasks = Vec::with_capacity(stack_frames.len());
|
||||
let mut tasks = Vec::with_capacity(stack_frames.len());
|
||||
|
||||
let open_entries = this.update(&mut cx, |this, _| {
|
||||
this.open_entries
|
||||
.iter()
|
||||
.filter(|e| matches!(e, OpenEntry::Variable { .. }))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
})?;
|
||||
|
||||
for stack_frame in stack_frames.clone().into_iter() {
|
||||
let stack_frame_scopes_task = this.update(&mut cx, |this, cx| {
|
||||
this.dap_store.update(cx, |store, cx| {
|
||||
store.scopes(&this.client_id, stack_frame.id, cx)
|
||||
})
|
||||
let task = this.update(&mut cx, |this, cx| {
|
||||
this.fetch_variables_for_stack_frame(stack_frame.id, &open_entries, cx)
|
||||
});
|
||||
|
||||
scope_tasks.push(async move {
|
||||
anyhow::Ok((stack_frame.id, stack_frame_scopes_task?.await?))
|
||||
});
|
||||
tasks.push(
|
||||
cx.background_executor()
|
||||
.spawn(async move { anyhow::Ok((stack_frame.id, task?.await?)) }),
|
||||
);
|
||||
}
|
||||
|
||||
let mut stack_frame_tasks = Vec::with_capacity(scope_tasks.len());
|
||||
for (stack_frame_id, scopes) in try_join_all(scope_tasks).await? {
|
||||
let variable_tasks = this.update(&mut cx, |this, cx| {
|
||||
this.dap_store.update(cx, |store, cx| {
|
||||
let mut tasks = Vec::with_capacity(scopes.len());
|
||||
|
||||
for scope in scopes {
|
||||
let variables_task =
|
||||
store.variables(&this.client_id, scope.variables_reference, cx);
|
||||
tasks.push(async move { anyhow::Ok((scope, variables_task.await?)) });
|
||||
}
|
||||
|
||||
tasks
|
||||
})
|
||||
})?;
|
||||
|
||||
stack_frame_tasks.push(async move {
|
||||
anyhow::Ok((stack_frame_id, try_join_all(variable_tasks).await?))
|
||||
});
|
||||
}
|
||||
|
||||
let result = try_join_all(stack_frame_tasks).await?;
|
||||
let result = try_join_all(tasks).await?;
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.variables.clear();
|
||||
this.scopes.clear();
|
||||
this.fetched_variable_ids.clear();
|
||||
let mut new_variables = BTreeMap::new();
|
||||
let mut new_scopes = HashMap::new();
|
||||
|
||||
for (stack_frame_id, scopes) in result {
|
||||
for (scope, variables) in scopes {
|
||||
this.scopes
|
||||
.entry(stack_frame_id)
|
||||
.or_default()
|
||||
.push(scope.clone());
|
||||
for (stack_frame_id, (scopes, variables)) in result {
|
||||
new_scopes.insert(stack_frame_id, scopes);
|
||||
|
||||
this.fetched_variable_ids.insert(scope.variables_reference);
|
||||
for (scope_id, variables) in variables.into_iter() {
|
||||
let mut variable_index = ScopeVariableIndex::new();
|
||||
variable_index.add_variables(scope_id, variables);
|
||||
|
||||
this.variables.insert(
|
||||
(stack_frame_id, scope.variables_reference),
|
||||
variables
|
||||
.into_iter()
|
||||
.map(|v| VariableContainer {
|
||||
container_reference: scope.variables_reference,
|
||||
variable: v,
|
||||
depth: 1,
|
||||
})
|
||||
.collect::<Vec<VariableContainer>>(),
|
||||
);
|
||||
new_variables.insert((stack_frame_id, scope_id), variable_index);
|
||||
}
|
||||
}
|
||||
|
||||
this.build_entries(true, false, cx);
|
||||
std::mem::swap(&mut this.variables, &mut new_variables);
|
||||
std::mem::swap(&mut this.scopes, &mut new_scopes);
|
||||
|
||||
this.build_entries(true, true, cx);
|
||||
|
||||
this.fetch_variables_task.take();
|
||||
|
||||
cx.notify();
|
||||
})
|
||||
}));
|
||||
}
|
||||
@@ -476,7 +590,7 @@ impl VariableList {
|
||||
});
|
||||
|
||||
let Some(state) = self.set_variable_state.take() else {
|
||||
return cx.notify();
|
||||
return;
|
||||
};
|
||||
|
||||
if new_variable_value == state.value
|
||||
@@ -508,67 +622,18 @@ impl VariableList {
|
||||
|
||||
set_value_task?.await?;
|
||||
|
||||
this.update(&mut cx, |this, cx| this.refetch_existing_variables(cx))?
|
||||
.await?;
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.build_entries(false, true, cx);
|
||||
this.invalidate(cx);
|
||||
})
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
pub fn refetch_existing_variables(&mut self, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
|
||||
let mut scope_tasks = Vec::with_capacity(self.variables.len());
|
||||
|
||||
for ((stack_frame_id, scope_id), variable_containers) in self.variables.clone().into_iter()
|
||||
{
|
||||
let mut variable_tasks = Vec::with_capacity(variable_containers.len());
|
||||
|
||||
for variable_container in variable_containers {
|
||||
let fetch_variables_task = self.dap_store.update(cx, |store, cx| {
|
||||
store.variables(&self.client_id, variable_container.container_reference, cx)
|
||||
});
|
||||
|
||||
variable_tasks.push(async move {
|
||||
let depth = variable_container.depth;
|
||||
let container_reference = variable_container.container_reference;
|
||||
|
||||
anyhow::Ok(
|
||||
fetch_variables_task
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(move |variable| VariableContainer {
|
||||
container_reference,
|
||||
variable,
|
||||
depth,
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
scope_tasks.push(async move {
|
||||
anyhow::Ok((
|
||||
(stack_frame_id, scope_id),
|
||||
try_join_all(variable_tasks).await?,
|
||||
))
|
||||
});
|
||||
}
|
||||
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let updated_variables = try_join_all(scope_tasks).await?;
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
for (entry_id, variable_containers) in updated_variables {
|
||||
for variables in variable_containers {
|
||||
this.variables.insert(entry_id, variables);
|
||||
}
|
||||
}
|
||||
|
||||
this.build_entries(false, true, cx);
|
||||
})
|
||||
})
|
||||
pub fn invalidate(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.stack_frame_list.update(cx, |stack_frame_list, cx| {
|
||||
stack_frame_list.invalidate(cx);
|
||||
});
|
||||
}
|
||||
|
||||
fn render_set_variable_editor(
|
||||
@@ -590,11 +655,13 @@ impl VariableList {
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn on_toggle_variable(
|
||||
&mut self,
|
||||
ix: usize,
|
||||
variable_id: &SharedString,
|
||||
scope_id: u64,
|
||||
entry_id: &OpenEntry,
|
||||
variable_reference: u64,
|
||||
depth: usize,
|
||||
has_children: bool,
|
||||
disclosed: Option<bool>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
@@ -603,73 +670,52 @@ impl VariableList {
|
||||
return;
|
||||
}
|
||||
|
||||
// if we already opened the variable/we already fetched it
|
||||
// we can just toggle it because we already have the nested variable
|
||||
if disclosed.unwrap_or(true) || self.fetched_variable_ids.contains(&variable_reference) {
|
||||
return self.toggle_entry_collapsed(&variable_id, cx);
|
||||
}
|
||||
|
||||
let stack_frame_id = self.stack_frame_list.read(cx).current_stack_frame_id();
|
||||
|
||||
let Some(entries) = self.entries.get(&stack_frame_id) else {
|
||||
let Some(index) = self.variables.get(&(stack_frame_id, scope_id)) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(entry) = entries.get(ix) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let VariableListEntry::Variable { scope, depth, .. } = entry {
|
||||
let variable_id = variable_id.clone();
|
||||
let scope = scope.clone();
|
||||
let depth = *depth;
|
||||
|
||||
let fetch_variables_task = self.dap_store.update(cx, |store, cx| {
|
||||
store.variables(&self.client_id, variable_reference, cx)
|
||||
});
|
||||
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let new_variables = fetch_variables_task.await?;
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let Some(variables) = this
|
||||
.variables
|
||||
.get_mut(&(stack_frame_id, scope.variables_reference))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let position = variables.iter().position(|v| {
|
||||
variable_entry_id(&scope, &v.variable, v.depth) == variable_id
|
||||
});
|
||||
|
||||
if let Some(position) = position {
|
||||
variables.splice(
|
||||
position + 1..position + 1,
|
||||
new_variables
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|variable| VariableContainer {
|
||||
container_reference: variable_reference,
|
||||
variable,
|
||||
depth: depth + 1,
|
||||
}),
|
||||
);
|
||||
|
||||
this.fetched_variable_ids.insert(variable_reference);
|
||||
}
|
||||
|
||||
this.toggle_entry_collapsed(&variable_id, cx);
|
||||
})
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
// if we already opened the variable/we already fetched it
|
||||
// we can just toggle it because we already have the nested variable
|
||||
if disclosed.unwrap_or(true) || index.fetched(&variable_reference) {
|
||||
return self.toggle_entry(&entry_id, cx);
|
||||
}
|
||||
|
||||
let fetch_variables_task = self.dap_store.update(cx, |store, cx| {
|
||||
store.variables(&self.client_id, variable_reference, cx)
|
||||
});
|
||||
|
||||
let entry_id = entry_id.clone();
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let new_variables = fetch_variables_task.await?;
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let Some(index) = this.variables.get_mut(&(stack_frame_id, scope_id)) else {
|
||||
return;
|
||||
};
|
||||
|
||||
index.add_variables(
|
||||
variable_reference,
|
||||
new_variables
|
||||
.into_iter()
|
||||
.map(|variable| VariableContainer {
|
||||
container_reference: variable_reference,
|
||||
variable,
|
||||
depth: depth + 1,
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
this.toggle_entry(&entry_id, cx);
|
||||
})
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn render_variable(
|
||||
&self,
|
||||
ix: usize,
|
||||
parent_variables_reference: u64,
|
||||
variable: &Variable,
|
||||
scope: &Scope,
|
||||
@@ -677,61 +723,68 @@ impl VariableList {
|
||||
has_children: bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> AnyElement {
|
||||
let scope_id = scope.variables_reference;
|
||||
let variable_reference = variable.variables_reference;
|
||||
let variable_id = variable_entry_id(scope, variable, depth);
|
||||
|
||||
let disclosed = has_children.then(|| {
|
||||
self.open_entries
|
||||
.binary_search(&variable_entry_id(scope, variable, depth))
|
||||
.is_ok()
|
||||
});
|
||||
let entry_id = OpenEntry::Variable {
|
||||
name: variable.name.clone(),
|
||||
depth,
|
||||
};
|
||||
let disclosed = has_children.then(|| self.open_entries.binary_search(&entry_id).is_ok());
|
||||
|
||||
div()
|
||||
.id(variable_id.clone())
|
||||
.id(SharedString::from(format!(
|
||||
"variable-{}-{}-{}",
|
||||
scope.variables_reference, variable.name, depth
|
||||
)))
|
||||
.group("")
|
||||
.h_4()
|
||||
.size_full()
|
||||
.child(
|
||||
ListItem::new(variable_id.clone())
|
||||
.indent_level(depth + 1)
|
||||
.indent_step_size(px(20.))
|
||||
.always_show_disclosure_icon(true)
|
||||
.toggle(disclosed)
|
||||
.on_toggle(cx.listener(move |this, _, cx| {
|
||||
this.on_toggle_variable(
|
||||
ix,
|
||||
&variable_id,
|
||||
variable_reference,
|
||||
has_children,
|
||||
disclosed,
|
||||
ListItem::new(SharedString::from(format!(
|
||||
"variable-item-{}-{}-{}",
|
||||
scope.variables_reference, variable.name, depth
|
||||
)))
|
||||
.indent_level(depth + 1)
|
||||
.indent_step_size(px(20.))
|
||||
.always_show_disclosure_icon(true)
|
||||
.toggle(disclosed)
|
||||
.on_toggle(cx.listener(move |this, _, cx| {
|
||||
this.on_toggle_variable(
|
||||
scope_id,
|
||||
&entry_id,
|
||||
variable_reference,
|
||||
depth,
|
||||
has_children,
|
||||
disclosed,
|
||||
cx,
|
||||
)
|
||||
}))
|
||||
.on_secondary_mouse_down(cx.listener({
|
||||
let scope = scope.clone();
|
||||
let variable = variable.clone();
|
||||
move |this, event: &MouseDownEvent, cx| {
|
||||
this.deploy_variable_context_menu(
|
||||
parent_variables_reference,
|
||||
&scope,
|
||||
&variable,
|
||||
event.position,
|
||||
cx,
|
||||
)
|
||||
}))
|
||||
.on_secondary_mouse_down(cx.listener({
|
||||
let scope = scope.clone();
|
||||
let variable = variable.clone();
|
||||
move |this, event: &MouseDownEvent, cx| {
|
||||
this.deploy_variable_context_menu(
|
||||
parent_variables_reference,
|
||||
&scope,
|
||||
&variable,
|
||||
event.position,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}))
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.text_ui_sm(cx)
|
||||
.child(variable.name.clone())
|
||||
.child(
|
||||
div()
|
||||
.text_ui_xs(cx)
|
||||
.text_color(cx.theme().colors().text_muted)
|
||||
.child(variable.value.replace("\n", " ").clone()),
|
||||
),
|
||||
),
|
||||
}
|
||||
}))
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.text_ui_sm(cx)
|
||||
.child(variable.name.clone())
|
||||
.child(
|
||||
div()
|
||||
.text_ui_xs(cx)
|
||||
.text_color(cx.theme().colors().text_muted)
|
||||
.child(variable.value.replace("\n", " ").clone()),
|
||||
),
|
||||
),
|
||||
)
|
||||
.into_any()
|
||||
}
|
||||
@@ -739,8 +792,10 @@ impl VariableList {
|
||||
fn render_scope(&self, scope: &Scope, cx: &mut ViewContext<Self>) -> AnyElement {
|
||||
let element_id = scope.variables_reference;
|
||||
|
||||
let scope_id = scope_entry_id(scope);
|
||||
let disclosed = self.open_entries.binary_search(&scope_id).is_ok();
|
||||
let entry_id = OpenEntry::Scope {
|
||||
name: scope.name.clone(),
|
||||
};
|
||||
let disclosed = self.open_entries.binary_search(&entry_id).is_ok();
|
||||
|
||||
div()
|
||||
.id(element_id as usize)
|
||||
@@ -749,15 +804,16 @@ impl VariableList {
|
||||
.w_full()
|
||||
.h_full()
|
||||
.child(
|
||||
ListItem::new(scope_id.clone())
|
||||
.indent_level(1)
|
||||
.indent_step_size(px(20.))
|
||||
.always_show_disclosure_icon(true)
|
||||
.toggle(disclosed)
|
||||
.on_toggle(
|
||||
cx.listener(move |this, _, cx| this.toggle_entry_collapsed(&scope_id, cx)),
|
||||
)
|
||||
.child(div().text_ui(cx).w_full().child(scope.name.clone())),
|
||||
ListItem::new(SharedString::from(format!(
|
||||
"scope-{}",
|
||||
scope.variables_reference
|
||||
)))
|
||||
.indent_level(1)
|
||||
.indent_step_size(px(20.))
|
||||
.always_show_disclosure_icon(true)
|
||||
.toggle(disclosed)
|
||||
.on_toggle(cx.listener(move |this, _, cx| this.toggle_entry(&entry_id, cx)))
|
||||
.child(div().text_ui(cx).w_full().child(scope.name.clone())),
|
||||
)
|
||||
.into_any()
|
||||
}
|
||||
@@ -789,13 +845,184 @@ impl Render for VariableList {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn variable_entry_id(scope: &Scope, variable: &Variable, depth: usize) -> SharedString {
|
||||
SharedString::from(format!(
|
||||
"variable-{}-{}-{}",
|
||||
scope.variables_reference, variable.name, depth
|
||||
))
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn scope_entry_id(scope: &Scope) -> SharedString {
|
||||
SharedString::from(format!("scope-{}", scope.variables_reference))
|
||||
#[test]
|
||||
fn test_add_initial_variables_to_index() {
|
||||
let mut index = ScopeVariableIndex::new();
|
||||
|
||||
assert_eq!(index.variables(), &[]);
|
||||
|
||||
let variable1 = VariableContainer {
|
||||
variable: Variable {
|
||||
name: "First variable".into(),
|
||||
value: "First variable".into(),
|
||||
type_: None,
|
||||
presentation_hint: None,
|
||||
evaluate_name: None,
|
||||
variables_reference: 0,
|
||||
named_variables: None,
|
||||
indexed_variables: None,
|
||||
memory_reference: None,
|
||||
},
|
||||
depth: 1,
|
||||
container_reference: 1,
|
||||
};
|
||||
|
||||
let variable2 = VariableContainer {
|
||||
variable: Variable {
|
||||
name: "Second variable with child".into(),
|
||||
value: "Second variable with child".into(),
|
||||
type_: None,
|
||||
presentation_hint: None,
|
||||
evaluate_name: None,
|
||||
variables_reference: 2,
|
||||
named_variables: None,
|
||||
indexed_variables: None,
|
||||
memory_reference: None,
|
||||
},
|
||||
depth: 1,
|
||||
container_reference: 1,
|
||||
};
|
||||
|
||||
let variable3 = VariableContainer {
|
||||
variable: Variable {
|
||||
name: "Third variable".into(),
|
||||
value: "Third variable".into(),
|
||||
type_: None,
|
||||
presentation_hint: None,
|
||||
evaluate_name: None,
|
||||
variables_reference: 0,
|
||||
named_variables: None,
|
||||
indexed_variables: None,
|
||||
memory_reference: None,
|
||||
},
|
||||
depth: 1,
|
||||
container_reference: 1,
|
||||
};
|
||||
|
||||
index.add_variables(
|
||||
1,
|
||||
vec![variable1.clone(), variable2.clone(), variable3.clone()],
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
index.variables(),
|
||||
&[variable1.clone(), variable2.clone(), variable3.clone()]
|
||||
);
|
||||
}
|
||||
|
||||
/// This covers when you click on a variable that has a nested variable
|
||||
/// We correctly insert the variables right after the variable you clicked on
|
||||
#[test]
|
||||
fn test_add_sub_variables_to_index() {
|
||||
let mut index = ScopeVariableIndex::new();
|
||||
|
||||
assert_eq!(index.variables(), &[]);
|
||||
|
||||
let variable1 = VariableContainer {
|
||||
variable: Variable {
|
||||
name: "First variable".into(),
|
||||
value: "First variable".into(),
|
||||
type_: None,
|
||||
presentation_hint: None,
|
||||
evaluate_name: None,
|
||||
variables_reference: 0,
|
||||
named_variables: None,
|
||||
indexed_variables: None,
|
||||
memory_reference: None,
|
||||
},
|
||||
depth: 1,
|
||||
container_reference: 1,
|
||||
};
|
||||
|
||||
let variable2 = VariableContainer {
|
||||
variable: Variable {
|
||||
name: "Second variable with child".into(),
|
||||
value: "Second variable with child".into(),
|
||||
type_: None,
|
||||
presentation_hint: None,
|
||||
evaluate_name: None,
|
||||
variables_reference: 2,
|
||||
named_variables: None,
|
||||
indexed_variables: None,
|
||||
memory_reference: None,
|
||||
},
|
||||
depth: 1,
|
||||
container_reference: 1,
|
||||
};
|
||||
|
||||
let variable3 = VariableContainer {
|
||||
variable: Variable {
|
||||
name: "Third variable".into(),
|
||||
value: "Third variable".into(),
|
||||
type_: None,
|
||||
presentation_hint: None,
|
||||
evaluate_name: None,
|
||||
variables_reference: 0,
|
||||
named_variables: None,
|
||||
indexed_variables: None,
|
||||
memory_reference: None,
|
||||
},
|
||||
depth: 1,
|
||||
container_reference: 1,
|
||||
};
|
||||
|
||||
index.add_variables(
|
||||
1,
|
||||
vec![variable1.clone(), variable2.clone(), variable3.clone()],
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
index.variables(),
|
||||
&[variable1.clone(), variable2.clone(), variable3.clone()]
|
||||
);
|
||||
|
||||
let variable4 = VariableContainer {
|
||||
variable: Variable {
|
||||
name: "Fourth variable".into(),
|
||||
value: "Fourth variable".into(),
|
||||
type_: None,
|
||||
presentation_hint: None,
|
||||
evaluate_name: None,
|
||||
variables_reference: 0,
|
||||
named_variables: None,
|
||||
indexed_variables: None,
|
||||
memory_reference: None,
|
||||
},
|
||||
depth: 1,
|
||||
container_reference: 1,
|
||||
};
|
||||
|
||||
let variable5 = VariableContainer {
|
||||
variable: Variable {
|
||||
name: "Five variable".into(),
|
||||
value: "Five variable".into(),
|
||||
type_: None,
|
||||
presentation_hint: None,
|
||||
evaluate_name: None,
|
||||
variables_reference: 0,
|
||||
named_variables: None,
|
||||
indexed_variables: None,
|
||||
memory_reference: None,
|
||||
},
|
||||
depth: 1,
|
||||
container_reference: 1,
|
||||
};
|
||||
|
||||
index.add_variables(2, vec![variable4.clone(), variable5.clone()]);
|
||||
|
||||
assert_eq!(
|
||||
index.variables(),
|
||||
&[
|
||||
variable1.clone(),
|
||||
variable2.clone(),
|
||||
variable4.clone(),
|
||||
variable5.clone(),
|
||||
variable3.clone(),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user