debugger: Improve appearance of session list for JavaScript debugging (#34322)
This PR updates the debugger panel's session list to be more useful in some cases that are commonly hit when using the JavaScript adapter. We make two adjustments, which only apply to JavaScript sessions: - For a child session that's the only child of a root session, we collapse it with its parent. This imitates what VS Code does in the "call stack" view for JavaScript sessions. - When a session has exactly one thread, we label the session with that thread's name, instead of the session label provided by the DAP. VS Code also makes this adjustment, which surfaces more useful information when working with browser sessions. Closes #33072 Release Notes: - debugger: Improved the appearance of JavaScript sessions in the debug panel's session list. --------- Co-authored-by: Julia <julia@zed.dev> Co-authored-by: Remco Smits <djsmits12@gmail.com>
This commit is contained in:
parent
13ddd5e4cb
commit
a8cc927303
11 changed files with 392 additions and 236 deletions
|
@ -448,7 +448,7 @@ impl ActivityIndicator {
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
),
|
),
|
||||||
message: format!("Debug: {}", session.read(cx).adapter()),
|
message: format!("Debug: {}", session.read(cx).adapter()),
|
||||||
tooltip_message: Some(session.read(cx).label().to_string()),
|
tooltip_message: session.read(cx).label().map(|label| label.to_string()),
|
||||||
on_click: None,
|
on_click: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -378,6 +378,14 @@ pub trait DebugAdapter: 'static + Send + Sync {
|
||||||
fn label_for_child_session(&self, _args: &StartDebuggingRequestArguments) -> Option<String> {
|
fn label_for_child_session(&self, _args: &StartDebuggingRequestArguments) -> Option<String> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn compact_child_session(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prefer_thread_name(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
|
|
@ -534,6 +534,14 @@ impl DebugAdapter for JsDebugAdapter {
|
||||||
.filter(|name| !name.is_empty())?;
|
.filter(|name| !name.is_empty())?;
|
||||||
Some(label.to_owned())
|
Some(label.to_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn compact_child_session(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prefer_thread_name(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn normalize_task_type(task_type: &mut Value) {
|
fn normalize_task_type(task_type: &mut Value) {
|
||||||
|
|
|
@ -399,7 +399,8 @@ impl LogStore {
|
||||||
state.insert(DebugAdapterState::new(
|
state.insert(DebugAdapterState::new(
|
||||||
id.session_id,
|
id.session_id,
|
||||||
adapter_name,
|
adapter_name,
|
||||||
session_label,
|
session_label
|
||||||
|
.unwrap_or_else(|| format!("Session {} (child)", id.session_id.0).into()),
|
||||||
has_adapter_logs,
|
has_adapter_logs,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ use crate::{
|
||||||
ToggleExpandItem, ToggleSessionPicker, ToggleThreadPicker, persistence, spawn_task_or_modal,
|
ToggleExpandItem, ToggleSessionPicker, ToggleThreadPicker, persistence, spawn_task_or_modal,
|
||||||
};
|
};
|
||||||
use anyhow::{Context as _, Result, anyhow};
|
use anyhow::{Context as _, Result, anyhow};
|
||||||
|
use collections::IndexMap;
|
||||||
use dap::adapters::DebugAdapterName;
|
use dap::adapters::DebugAdapterName;
|
||||||
use dap::debugger_settings::DebugPanelDockPosition;
|
use dap::debugger_settings::DebugPanelDockPosition;
|
||||||
use dap::{
|
use dap::{
|
||||||
|
@ -26,7 +27,7 @@ use text::ToPoint as _;
|
||||||
|
|
||||||
use itertools::Itertools as _;
|
use itertools::Itertools as _;
|
||||||
use language::Buffer;
|
use language::Buffer;
|
||||||
use project::debugger::session::{Session, SessionStateEvent};
|
use project::debugger::session::{Session, SessionQuirks, SessionStateEvent};
|
||||||
use project::{DebugScenarioContext, Fs, ProjectPath, TaskSourceKind, WorktreeId};
|
use project::{DebugScenarioContext, Fs, ProjectPath, TaskSourceKind, WorktreeId};
|
||||||
use project::{Project, debugger::session::ThreadStatus};
|
use project::{Project, debugger::session::ThreadStatus};
|
||||||
use rpc::proto::{self};
|
use rpc::proto::{self};
|
||||||
|
@ -63,13 +64,14 @@ pub enum DebugPanelEvent {
|
||||||
|
|
||||||
pub struct DebugPanel {
|
pub struct DebugPanel {
|
||||||
size: Pixels,
|
size: Pixels,
|
||||||
sessions: Vec<Entity<DebugSession>>,
|
|
||||||
active_session: Option<Entity<DebugSession>>,
|
active_session: Option<Entity<DebugSession>>,
|
||||||
project: Entity<Project>,
|
project: Entity<Project>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
context_menu: Option<(Entity<ContextMenu>, Point<Pixels>, Subscription)>,
|
context_menu: Option<(Entity<ContextMenu>, Point<Pixels>, Subscription)>,
|
||||||
debug_scenario_scheduled_last: bool,
|
debug_scenario_scheduled_last: bool,
|
||||||
|
pub(crate) sessions_with_children:
|
||||||
|
IndexMap<Entity<DebugSession>, Vec<WeakEntity<DebugSession>>>,
|
||||||
pub(crate) thread_picker_menu_handle: PopoverMenuHandle<ContextMenu>,
|
pub(crate) thread_picker_menu_handle: PopoverMenuHandle<ContextMenu>,
|
||||||
pub(crate) session_picker_menu_handle: PopoverMenuHandle<ContextMenu>,
|
pub(crate) session_picker_menu_handle: PopoverMenuHandle<ContextMenu>,
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
|
@ -100,7 +102,7 @@ impl DebugPanel {
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
size: px(300.),
|
size: px(300.),
|
||||||
sessions: vec![],
|
sessions_with_children: Default::default(),
|
||||||
active_session: None,
|
active_session: None,
|
||||||
focus_handle,
|
focus_handle,
|
||||||
breakpoint_list: BreakpointList::new(
|
breakpoint_list: BreakpointList::new(
|
||||||
|
@ -138,8 +140,9 @@ impl DebugPanel {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn sessions(&self) -> Vec<Entity<DebugSession>> {
|
#[cfg(test)]
|
||||||
self.sessions.clone()
|
pub(crate) fn sessions(&self) -> impl Iterator<Item = Entity<DebugSession>> {
|
||||||
|
self.sessions_with_children.keys().cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn active_session(&self) -> Option<Entity<DebugSession>> {
|
pub fn active_session(&self) -> Option<Entity<DebugSession>> {
|
||||||
|
@ -185,12 +188,20 @@ impl DebugPanel {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
let dap_store = self.project.read(cx).dap_store();
|
let dap_store = self.project.read(cx).dap_store();
|
||||||
|
let Some(adapter) = DapRegistry::global(cx).adapter(&scenario.adapter) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let quirks = SessionQuirks {
|
||||||
|
compact: adapter.compact_child_session(),
|
||||||
|
prefer_thread_name: adapter.prefer_thread_name(),
|
||||||
|
};
|
||||||
let session = dap_store.update(cx, |dap_store, cx| {
|
let session = dap_store.update(cx, |dap_store, cx| {
|
||||||
dap_store.new_session(
|
dap_store.new_session(
|
||||||
scenario.label.clone(),
|
Some(scenario.label.clone()),
|
||||||
DebugAdapterName(scenario.adapter.clone()),
|
DebugAdapterName(scenario.adapter.clone()),
|
||||||
task_context.clone(),
|
task_context.clone(),
|
||||||
None,
|
None,
|
||||||
|
quirks,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
@ -363,14 +374,15 @@ impl DebugPanel {
|
||||||
};
|
};
|
||||||
|
|
||||||
let dap_store_handle = self.project.read(cx).dap_store().clone();
|
let dap_store_handle = self.project.read(cx).dap_store().clone();
|
||||||
let label = curr_session.read(cx).label().clone();
|
let label = curr_session.read(cx).label();
|
||||||
|
let quirks = curr_session.read(cx).quirks();
|
||||||
let adapter = curr_session.read(cx).adapter().clone();
|
let adapter = curr_session.read(cx).adapter().clone();
|
||||||
let binary = curr_session.read(cx).binary().cloned().unwrap();
|
let binary = curr_session.read(cx).binary().cloned().unwrap();
|
||||||
let task_context = curr_session.read(cx).task_context().clone();
|
let task_context = curr_session.read(cx).task_context().clone();
|
||||||
|
|
||||||
let curr_session_id = curr_session.read(cx).session_id();
|
let curr_session_id = curr_session.read(cx).session_id();
|
||||||
self.sessions
|
self.sessions_with_children
|
||||||
.retain(|session| session.read(cx).session_id(cx) != curr_session_id);
|
.retain(|session, _| session.read(cx).session_id(cx) != curr_session_id);
|
||||||
let task = dap_store_handle.update(cx, |dap_store, cx| {
|
let task = dap_store_handle.update(cx, |dap_store, cx| {
|
||||||
dap_store.shutdown_session(curr_session_id, cx)
|
dap_store.shutdown_session(curr_session_id, cx)
|
||||||
});
|
});
|
||||||
|
@ -379,7 +391,7 @@ impl DebugPanel {
|
||||||
task.await.log_err();
|
task.await.log_err();
|
||||||
|
|
||||||
let (session, task) = dap_store_handle.update(cx, |dap_store, cx| {
|
let (session, task) = dap_store_handle.update(cx, |dap_store, cx| {
|
||||||
let session = dap_store.new_session(label, adapter, task_context, None, cx);
|
let session = dap_store.new_session(label, adapter, task_context, None, quirks, cx);
|
||||||
|
|
||||||
let task = session.update(cx, |session, cx| {
|
let task = session.update(cx, |session, cx| {
|
||||||
session.boot(binary, worktree, dap_store_handle.downgrade(), cx)
|
session.boot(binary, worktree, dap_store_handle.downgrade(), cx)
|
||||||
|
@ -425,6 +437,7 @@ impl DebugPanel {
|
||||||
let dap_store_handle = self.project.read(cx).dap_store().clone();
|
let dap_store_handle = self.project.read(cx).dap_store().clone();
|
||||||
let label = self.label_for_child_session(&parent_session, request, cx);
|
let label = self.label_for_child_session(&parent_session, request, cx);
|
||||||
let adapter = parent_session.read(cx).adapter().clone();
|
let adapter = parent_session.read(cx).adapter().clone();
|
||||||
|
let quirks = parent_session.read(cx).quirks();
|
||||||
let Some(mut binary) = parent_session.read(cx).binary().cloned() else {
|
let Some(mut binary) = parent_session.read(cx).binary().cloned() else {
|
||||||
log::error!("Attempted to start a child-session without a binary");
|
log::error!("Attempted to start a child-session without a binary");
|
||||||
return;
|
return;
|
||||||
|
@ -438,6 +451,7 @@ impl DebugPanel {
|
||||||
adapter,
|
adapter,
|
||||||
task_context,
|
task_context,
|
||||||
Some(parent_session.clone()),
|
Some(parent_session.clone()),
|
||||||
|
quirks,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -463,8 +477,8 @@ impl DebugPanel {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
let Some(session) = self
|
let Some(session) = self
|
||||||
.sessions
|
.sessions_with_children
|
||||||
.iter()
|
.keys()
|
||||||
.find(|other| entity_id == other.entity_id())
|
.find(|other| entity_id == other.entity_id())
|
||||||
.cloned()
|
.cloned()
|
||||||
else {
|
else {
|
||||||
|
@ -498,15 +512,14 @@ impl DebugPanel {
|
||||||
}
|
}
|
||||||
session.update(cx, |session, cx| session.shutdown(cx)).ok();
|
session.update(cx, |session, cx| session.shutdown(cx)).ok();
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.sessions.retain(|other| entity_id != other.entity_id());
|
this.retain_sessions(|other| entity_id != other.entity_id());
|
||||||
|
|
||||||
if let Some(active_session_id) = this
|
if let Some(active_session_id) = this
|
||||||
.active_session
|
.active_session
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|session| session.entity_id())
|
.map(|session| session.entity_id())
|
||||||
{
|
{
|
||||||
if active_session_id == entity_id {
|
if active_session_id == entity_id {
|
||||||
this.active_session = this.sessions.first().cloned();
|
this.active_session = this.sessions_with_children.keys().next().cloned();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cx.notify()
|
cx.notify()
|
||||||
|
@ -976,8 +989,8 @@ impl DebugPanel {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
if let Some(session) = self
|
if let Some(session) = self
|
||||||
.sessions
|
.sessions_with_children
|
||||||
.iter()
|
.keys()
|
||||||
.find(|session| session.read(cx).session_id(cx) == session_id)
|
.find(|session| session.read(cx).session_id(cx) == session_id)
|
||||||
{
|
{
|
||||||
self.activate_session(session.clone(), window, cx);
|
self.activate_session(session.clone(), window, cx);
|
||||||
|
@ -990,7 +1003,7 @@ impl DebugPanel {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
debug_assert!(self.sessions.contains(&session_item));
|
debug_assert!(self.sessions_with_children.contains_key(&session_item));
|
||||||
session_item.focus_handle(cx).focus(window);
|
session_item.focus_handle(cx).focus(window);
|
||||||
session_item.update(cx, |this, cx| {
|
session_item.update(cx, |this, cx| {
|
||||||
this.running_state().update(cx, |this, cx| {
|
this.running_state().update(cx, |this, cx| {
|
||||||
|
@ -1261,18 +1274,27 @@ impl DebugPanel {
|
||||||
parent_session: &Entity<Session>,
|
parent_session: &Entity<Session>,
|
||||||
request: &StartDebuggingRequestArguments,
|
request: &StartDebuggingRequestArguments,
|
||||||
cx: &mut Context<'_, Self>,
|
cx: &mut Context<'_, Self>,
|
||||||
) -> SharedString {
|
) -> Option<SharedString> {
|
||||||
let adapter = parent_session.read(cx).adapter();
|
let adapter = parent_session.read(cx).adapter();
|
||||||
if let Some(adapter) = DapRegistry::global(cx).adapter(&adapter) {
|
if let Some(adapter) = DapRegistry::global(cx).adapter(&adapter) {
|
||||||
if let Some(label) = adapter.label_for_child_session(request) {
|
if let Some(label) = adapter.label_for_child_session(request) {
|
||||||
return label.into();
|
return Some(label.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut label = parent_session.read(cx).label().clone();
|
None
|
||||||
if !label.ends_with("(child)") {
|
}
|
||||||
label = format!("{label} (child)").into();
|
|
||||||
|
fn retain_sessions(&mut self, keep: impl Fn(&Entity<DebugSession>) -> bool) {
|
||||||
|
self.sessions_with_children
|
||||||
|
.retain(|session, _| keep(session));
|
||||||
|
for children in self.sessions_with_children.values_mut() {
|
||||||
|
children.retain(|child| {
|
||||||
|
let Some(child) = child.upgrade() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
keep(&child)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
label
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1302,11 +1324,11 @@ async fn register_session_inner(
|
||||||
let serialized_layout = persistence::get_serialized_layout(adapter_name).await;
|
let serialized_layout = persistence::get_serialized_layout(adapter_name).await;
|
||||||
let debug_session = this.update_in(cx, |this, window, cx| {
|
let debug_session = this.update_in(cx, |this, window, cx| {
|
||||||
let parent_session = this
|
let parent_session = this
|
||||||
.sessions
|
.sessions_with_children
|
||||||
.iter()
|
.keys()
|
||||||
.find(|p| Some(p.read(cx).session_id(cx)) == session.read(cx).parent_id(cx))
|
.find(|p| Some(p.read(cx).session_id(cx)) == session.read(cx).parent_id(cx))
|
||||||
.cloned();
|
.cloned();
|
||||||
this.sessions.retain(|session| {
|
this.retain_sessions(|session| {
|
||||||
!session
|
!session
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.running_state()
|
.running_state()
|
||||||
|
@ -1337,13 +1359,23 @@ async fn register_session_inner(
|
||||||
)
|
)
|
||||||
.detach();
|
.detach();
|
||||||
let insert_position = this
|
let insert_position = this
|
||||||
.sessions
|
.sessions_with_children
|
||||||
.iter()
|
.keys()
|
||||||
.position(|session| Some(session) == parent_session.as_ref())
|
.position(|session| Some(session) == parent_session.as_ref())
|
||||||
.map(|position| position + 1)
|
.map(|position| position + 1)
|
||||||
.unwrap_or(this.sessions.len());
|
.unwrap_or(this.sessions_with_children.len());
|
||||||
// Maintain topological sort order of sessions
|
// Maintain topological sort order of sessions
|
||||||
this.sessions.insert(insert_position, debug_session.clone());
|
let (_, old) = this.sessions_with_children.insert_before(
|
||||||
|
insert_position,
|
||||||
|
debug_session.clone(),
|
||||||
|
Default::default(),
|
||||||
|
);
|
||||||
|
debug_assert!(old.is_none());
|
||||||
|
if let Some(parent_session) = parent_session {
|
||||||
|
this.sessions_with_children
|
||||||
|
.entry(parent_session)
|
||||||
|
.and_modify(|children| children.push(debug_session.downgrade()));
|
||||||
|
}
|
||||||
|
|
||||||
debug_session
|
debug_session
|
||||||
})?;
|
})?;
|
||||||
|
@ -1383,7 +1415,7 @@ impl Panel for DebugPanel {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
if position.axis() != self.position(window, cx).axis() {
|
if position.axis() != self.position(window, cx).axis() {
|
||||||
self.sessions.iter().for_each(|session_item| {
|
self.sessions_with_children.keys().for_each(|session_item| {
|
||||||
session_item.update(cx, |item, cx| {
|
session_item.update(cx, |item, cx| {
|
||||||
item.running_state()
|
item.running_state()
|
||||||
.update(cx, |state, _| state.invert_axies())
|
.update(cx, |state, _| state.invert_axies())
|
||||||
|
|
|
@ -1,16 +1,82 @@
|
||||||
use std::time::Duration;
|
use std::{rc::Rc, time::Duration};
|
||||||
|
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use gpui::{Animation, AnimationExt as _, Entity, Transformation, percentage};
|
use gpui::{Animation, AnimationExt as _, Entity, Transformation, WeakEntity, percentage};
|
||||||
use project::debugger::session::{ThreadId, ThreadStatus};
|
use project::debugger::session::{ThreadId, ThreadStatus};
|
||||||
use ui::{ContextMenu, DropdownMenu, DropdownStyle, Indicator, prelude::*};
|
use ui::{ContextMenu, DropdownMenu, DropdownStyle, Indicator, prelude::*};
|
||||||
use util::truncate_and_trailoff;
|
use util::{maybe, truncate_and_trailoff};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
debugger_panel::DebugPanel,
|
debugger_panel::DebugPanel,
|
||||||
session::{DebugSession, running::RunningState},
|
session::{DebugSession, running::RunningState},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct SessionListEntry {
|
||||||
|
ancestors: Vec<Entity<DebugSession>>,
|
||||||
|
leaf: Entity<DebugSession>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SessionListEntry {
|
||||||
|
pub(crate) fn label_element(&self, depth: usize, cx: &mut App) -> AnyElement {
|
||||||
|
const MAX_LABEL_CHARS: usize = 150;
|
||||||
|
|
||||||
|
let mut label = String::new();
|
||||||
|
for ancestor in &self.ancestors {
|
||||||
|
label.push_str(&ancestor.update(cx, |ancestor, cx| {
|
||||||
|
ancestor.label(cx).unwrap_or("(child)".into())
|
||||||
|
}));
|
||||||
|
label.push_str(" » ");
|
||||||
|
}
|
||||||
|
label.push_str(
|
||||||
|
&self
|
||||||
|
.leaf
|
||||||
|
.update(cx, |leaf, cx| leaf.label(cx).unwrap_or("(child)".into())),
|
||||||
|
);
|
||||||
|
let label = truncate_and_trailoff(&label, MAX_LABEL_CHARS);
|
||||||
|
|
||||||
|
let is_terminated = self
|
||||||
|
.leaf
|
||||||
|
.read(cx)
|
||||||
|
.running_state
|
||||||
|
.read(cx)
|
||||||
|
.session()
|
||||||
|
.read(cx)
|
||||||
|
.is_terminated();
|
||||||
|
let icon = {
|
||||||
|
if is_terminated {
|
||||||
|
Some(Indicator::dot().color(Color::Error))
|
||||||
|
} else {
|
||||||
|
match self
|
||||||
|
.leaf
|
||||||
|
.read(cx)
|
||||||
|
.running_state
|
||||||
|
.read(cx)
|
||||||
|
.thread_status(cx)
|
||||||
|
.unwrap_or_default()
|
||||||
|
{
|
||||||
|
project::debugger::session::ThreadStatus::Stopped => {
|
||||||
|
Some(Indicator::dot().color(Color::Conflict))
|
||||||
|
}
|
||||||
|
_ => Some(Indicator::dot().color(Color::Success)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
h_flex()
|
||||||
|
.id("session-label")
|
||||||
|
.ml(depth * px(16.0))
|
||||||
|
.gap_2()
|
||||||
|
.when_some(icon, |this, indicator| this.child(indicator))
|
||||||
|
.justify_between()
|
||||||
|
.child(
|
||||||
|
Label::new(label)
|
||||||
|
.size(LabelSize::Small)
|
||||||
|
.when(is_terminated, |this| this.strikethrough()),
|
||||||
|
)
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl DebugPanel {
|
impl DebugPanel {
|
||||||
fn dropdown_label(label: impl Into<SharedString>) -> Label {
|
fn dropdown_label(label: impl Into<SharedString>) -> Label {
|
||||||
const MAX_LABEL_CHARS: usize = 50;
|
const MAX_LABEL_CHARS: usize = 50;
|
||||||
|
@ -25,145 +91,205 @@ impl DebugPanel {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Option<impl IntoElement> {
|
) -> Option<impl IntoElement> {
|
||||||
if let Some(running_state) = running_state {
|
let running_state = running_state?;
|
||||||
let sessions = self.sessions().clone();
|
|
||||||
let weak = cx.weak_entity();
|
|
||||||
let running_state = running_state.read(cx);
|
|
||||||
let label = if let Some(active_session) = active_session.clone() {
|
|
||||||
active_session.read(cx).session(cx).read(cx).label()
|
|
||||||
} else {
|
|
||||||
SharedString::new_static("Unknown Session")
|
|
||||||
};
|
|
||||||
|
|
||||||
let is_terminated = running_state.session().read(cx).is_terminated();
|
let mut session_entries = Vec::with_capacity(self.sessions_with_children.len() * 3);
|
||||||
let is_started = active_session
|
let mut sessions_with_children = self.sessions_with_children.iter().peekable();
|
||||||
.is_some_and(|session| session.read(cx).session(cx).read(cx).is_started());
|
|
||||||
|
|
||||||
let session_state_indicator = if is_terminated {
|
while let Some((root, children)) = sessions_with_children.next() {
|
||||||
Indicator::dot().color(Color::Error).into_any_element()
|
let root_entry = if let Ok([single_child]) = <&[_; 1]>::try_from(children.as_slice())
|
||||||
} else if !is_started {
|
&& let Some(single_child) = single_child.upgrade()
|
||||||
Icon::new(IconName::ArrowCircle)
|
&& single_child.read(cx).quirks.compact
|
||||||
.size(IconSize::Small)
|
{
|
||||||
.color(Color::Muted)
|
sessions_with_children.next();
|
||||||
.with_animation(
|
SessionListEntry {
|
||||||
"arrow-circle",
|
leaf: single_child.clone(),
|
||||||
Animation::new(Duration::from_secs(2)).repeat(),
|
ancestors: vec![root.clone()],
|
||||||
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
|
}
|
||||||
)
|
|
||||||
.into_any_element()
|
|
||||||
} else {
|
} else {
|
||||||
match running_state.thread_status(cx).unwrap_or_default() {
|
SessionListEntry {
|
||||||
ThreadStatus::Stopped => {
|
leaf: root.clone(),
|
||||||
Indicator::dot().color(Color::Conflict).into_any_element()
|
ancestors: Vec::new(),
|
||||||
}
|
|
||||||
_ => Indicator::dot().color(Color::Success).into_any_element(),
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
session_entries.push(root_entry);
|
||||||
|
|
||||||
let trigger = h_flex()
|
session_entries.extend(
|
||||||
.gap_2()
|
sessions_with_children
|
||||||
.child(session_state_indicator)
|
.by_ref()
|
||||||
.justify_between()
|
.take_while(|(session, _)| {
|
||||||
.child(
|
session
|
||||||
DebugPanel::dropdown_label(label)
|
.read(cx)
|
||||||
.when(is_terminated, |this| this.strikethrough()),
|
.session(cx)
|
||||||
)
|
.read(cx)
|
||||||
.into_any_element();
|
.parent_id(cx)
|
||||||
|
.is_some()
|
||||||
Some(
|
})
|
||||||
DropdownMenu::new_with_element(
|
.map(|(session, _)| SessionListEntry {
|
||||||
"debugger-session-list",
|
leaf: session.clone(),
|
||||||
trigger,
|
ancestors: vec![],
|
||||||
ContextMenu::build(window, cx, move |mut this, _, cx| {
|
|
||||||
let context_menu = cx.weak_entity();
|
|
||||||
let mut session_depths = HashMap::default();
|
|
||||||
for session in sessions.into_iter() {
|
|
||||||
let weak_session = session.downgrade();
|
|
||||||
let weak_session_id = weak_session.entity_id();
|
|
||||||
let session_id = session.read(cx).session_id(cx);
|
|
||||||
let parent_depth = session
|
|
||||||
.read(cx)
|
|
||||||
.session(cx)
|
|
||||||
.read(cx)
|
|
||||||
.parent_id(cx)
|
|
||||||
.and_then(|parent_id| session_depths.get(&parent_id).cloned());
|
|
||||||
let self_depth =
|
|
||||||
*session_depths.entry(session_id).or_insert_with(|| {
|
|
||||||
parent_depth.map(|depth| depth + 1).unwrap_or(0usize)
|
|
||||||
});
|
|
||||||
this = this.custom_entry(
|
|
||||||
{
|
|
||||||
let weak = weak.clone();
|
|
||||||
let context_menu = context_menu.clone();
|
|
||||||
move |_, cx| {
|
|
||||||
weak_session
|
|
||||||
.read_with(cx, |session, cx| {
|
|
||||||
let context_menu = context_menu.clone();
|
|
||||||
|
|
||||||
let id: SharedString =
|
|
||||||
format!("debug-session-{}", session_id.0)
|
|
||||||
.into();
|
|
||||||
|
|
||||||
h_flex()
|
|
||||||
.w_full()
|
|
||||||
.group(id.clone())
|
|
||||||
.justify_between()
|
|
||||||
.child(session.label_element(self_depth, cx))
|
|
||||||
.child(
|
|
||||||
IconButton::new(
|
|
||||||
"close-debug-session",
|
|
||||||
IconName::Close,
|
|
||||||
)
|
|
||||||
.visible_on_hover(id.clone())
|
|
||||||
.icon_size(IconSize::Small)
|
|
||||||
.on_click({
|
|
||||||
let weak = weak.clone();
|
|
||||||
move |_, window, cx| {
|
|
||||||
weak.update(cx, |panel, cx| {
|
|
||||||
panel.close_session(
|
|
||||||
weak_session_id,
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
context_menu
|
|
||||||
.update(cx, |this, cx| {
|
|
||||||
this.cancel(
|
|
||||||
&Default::default(),
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.into_any_element()
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|_| div().into_any_element())
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
let weak = weak.clone();
|
|
||||||
move |window, cx| {
|
|
||||||
weak.update(cx, |panel, cx| {
|
|
||||||
panel.activate_session(session.clone(), window, cx);
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
this
|
|
||||||
}),
|
}),
|
||||||
)
|
);
|
||||||
.style(DropdownStyle::Ghost)
|
|
||||||
.handle(self.session_picker_menu_handle.clone()),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let weak = cx.weak_entity();
|
||||||
|
let trigger_label = if let Some(active_session) = active_session.clone() {
|
||||||
|
active_session.update(cx, |active_session, cx| {
|
||||||
|
active_session.label(cx).unwrap_or("(child)".into())
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
SharedString::new_static("Unknown Session")
|
||||||
|
};
|
||||||
|
let running_state = running_state.read(cx);
|
||||||
|
|
||||||
|
let is_terminated = running_state.session().read(cx).is_terminated();
|
||||||
|
let is_started = active_session
|
||||||
|
.is_some_and(|session| session.read(cx).session(cx).read(cx).is_started());
|
||||||
|
|
||||||
|
let session_state_indicator = if is_terminated {
|
||||||
|
Indicator::dot().color(Color::Error).into_any_element()
|
||||||
|
} else if !is_started {
|
||||||
|
Icon::new(IconName::ArrowCircle)
|
||||||
|
.size(IconSize::Small)
|
||||||
|
.color(Color::Muted)
|
||||||
|
.with_animation(
|
||||||
|
"arrow-circle",
|
||||||
|
Animation::new(Duration::from_secs(2)).repeat(),
|
||||||
|
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
|
||||||
|
)
|
||||||
|
.into_any_element()
|
||||||
|
} else {
|
||||||
|
match running_state.thread_status(cx).unwrap_or_default() {
|
||||||
|
ThreadStatus::Stopped => Indicator::dot().color(Color::Conflict).into_any_element(),
|
||||||
|
_ => Indicator::dot().color(Color::Success).into_any_element(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let trigger = h_flex()
|
||||||
|
.gap_2()
|
||||||
|
.child(session_state_indicator)
|
||||||
|
.justify_between()
|
||||||
|
.child(
|
||||||
|
DebugPanel::dropdown_label(trigger_label)
|
||||||
|
.when(is_terminated, |this| this.strikethrough()),
|
||||||
|
)
|
||||||
|
.into_any_element();
|
||||||
|
|
||||||
|
let menu = DropdownMenu::new_with_element(
|
||||||
|
"debugger-session-list",
|
||||||
|
trigger,
|
||||||
|
ContextMenu::build(window, cx, move |mut this, _, cx| {
|
||||||
|
let context_menu = cx.weak_entity();
|
||||||
|
let mut session_depths = HashMap::default();
|
||||||
|
for session_entry in session_entries {
|
||||||
|
let session_id = session_entry.leaf.read(cx).session_id(cx);
|
||||||
|
let parent_depth = session_entry
|
||||||
|
.ancestors
|
||||||
|
.first()
|
||||||
|
.unwrap_or(&session_entry.leaf)
|
||||||
|
.read(cx)
|
||||||
|
.session(cx)
|
||||||
|
.read(cx)
|
||||||
|
.parent_id(cx)
|
||||||
|
.and_then(|parent_id| session_depths.get(&parent_id).cloned());
|
||||||
|
let self_depth = *session_depths
|
||||||
|
.entry(session_id)
|
||||||
|
.or_insert_with(|| parent_depth.map(|depth| depth + 1).unwrap_or(0usize));
|
||||||
|
this = this.custom_entry(
|
||||||
|
{
|
||||||
|
let weak = weak.clone();
|
||||||
|
let context_menu = context_menu.clone();
|
||||||
|
let ancestors: Rc<[_]> = session_entry
|
||||||
|
.ancestors
|
||||||
|
.iter()
|
||||||
|
.map(|session| session.downgrade())
|
||||||
|
.collect();
|
||||||
|
let leaf = session_entry.leaf.downgrade();
|
||||||
|
move |window, cx| {
|
||||||
|
Self::render_session_menu_entry(
|
||||||
|
weak.clone(),
|
||||||
|
context_menu.clone(),
|
||||||
|
ancestors.clone(),
|
||||||
|
leaf.clone(),
|
||||||
|
self_depth,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
let weak = weak.clone();
|
||||||
|
let leaf = session_entry.leaf.clone();
|
||||||
|
move |window, cx| {
|
||||||
|
weak.update(cx, |panel, cx| {
|
||||||
|
panel.activate_session(leaf.clone(), window, cx);
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.style(DropdownStyle::Ghost)
|
||||||
|
.handle(self.session_picker_menu_handle.clone());
|
||||||
|
|
||||||
|
Some(menu)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_session_menu_entry(
|
||||||
|
weak: WeakEntity<DebugPanel>,
|
||||||
|
context_menu: WeakEntity<ContextMenu>,
|
||||||
|
ancestors: Rc<[WeakEntity<DebugSession>]>,
|
||||||
|
leaf: WeakEntity<DebugSession>,
|
||||||
|
self_depth: usize,
|
||||||
|
_window: &mut Window,
|
||||||
|
cx: &mut App,
|
||||||
|
) -> AnyElement {
|
||||||
|
let Some(session_entry) = maybe!({
|
||||||
|
let ancestors = ancestors
|
||||||
|
.iter()
|
||||||
|
.map(|ancestor| ancestor.upgrade())
|
||||||
|
.collect::<Option<Vec<_>>>()?;
|
||||||
|
let leaf = leaf.upgrade()?;
|
||||||
|
Some(SessionListEntry { ancestors, leaf })
|
||||||
|
}) else {
|
||||||
|
return div().into_any_element();
|
||||||
|
};
|
||||||
|
|
||||||
|
let id: SharedString = format!(
|
||||||
|
"debug-session-{}",
|
||||||
|
session_entry.leaf.read(cx).session_id(cx).0
|
||||||
|
)
|
||||||
|
.into();
|
||||||
|
let session_entity_id = session_entry.leaf.entity_id();
|
||||||
|
|
||||||
|
h_flex()
|
||||||
|
.w_full()
|
||||||
|
.group(id.clone())
|
||||||
|
.justify_between()
|
||||||
|
.child(session_entry.label_element(self_depth, cx))
|
||||||
|
.child(
|
||||||
|
IconButton::new("close-debug-session", IconName::Close)
|
||||||
|
.visible_on_hover(id.clone())
|
||||||
|
.icon_size(IconSize::Small)
|
||||||
|
.on_click({
|
||||||
|
let weak = weak.clone();
|
||||||
|
move |_, window, cx| {
|
||||||
|
weak.update(cx, |panel, cx| {
|
||||||
|
panel.close_session(session_entity_id, window, cx);
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
context_menu
|
||||||
|
.update(cx, |this, cx| {
|
||||||
|
this.cancel(&Default::default(), window, cx);
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.into_any_element()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn render_thread_dropdown(
|
pub(crate) fn render_thread_dropdown(
|
||||||
|
|
|
@ -5,14 +5,13 @@ use dap::client::SessionId;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
App, Axis, Entity, EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity,
|
App, Axis, Entity, EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity,
|
||||||
};
|
};
|
||||||
use project::Project;
|
|
||||||
use project::debugger::session::Session;
|
use project::debugger::session::Session;
|
||||||
use project::worktree_store::WorktreeStore;
|
use project::worktree_store::WorktreeStore;
|
||||||
|
use project::{Project, debugger::session::SessionQuirks};
|
||||||
use rpc::proto;
|
use rpc::proto;
|
||||||
use running::RunningState;
|
use running::RunningState;
|
||||||
use std::{cell::OnceCell, sync::OnceLock};
|
use std::cell::OnceCell;
|
||||||
use ui::{Indicator, prelude::*};
|
use ui::prelude::*;
|
||||||
use util::truncate_and_trailoff;
|
|
||||||
use workspace::{
|
use workspace::{
|
||||||
CollaboratorId, FollowableItem, ViewId, Workspace,
|
CollaboratorId, FollowableItem, ViewId, Workspace,
|
||||||
item::{self, Item},
|
item::{self, Item},
|
||||||
|
@ -20,8 +19,8 @@ use workspace::{
|
||||||
|
|
||||||
pub struct DebugSession {
|
pub struct DebugSession {
|
||||||
remote_id: Option<workspace::ViewId>,
|
remote_id: Option<workspace::ViewId>,
|
||||||
running_state: Entity<RunningState>,
|
pub(crate) running_state: Entity<RunningState>,
|
||||||
label: OnceLock<SharedString>,
|
pub(crate) quirks: SessionQuirks,
|
||||||
stack_trace_view: OnceCell<Entity<StackTraceView>>,
|
stack_trace_view: OnceCell<Entity<StackTraceView>>,
|
||||||
_worktree_store: WeakEntity<WorktreeStore>,
|
_worktree_store: WeakEntity<WorktreeStore>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
|
@ -57,6 +56,7 @@ impl DebugSession {
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
let quirks = session.read(cx).quirks();
|
||||||
|
|
||||||
cx.new(|cx| Self {
|
cx.new(|cx| Self {
|
||||||
_subscriptions: [cx.subscribe(&running_state, |_, _, _, cx| {
|
_subscriptions: [cx.subscribe(&running_state, |_, _, _, cx| {
|
||||||
|
@ -64,7 +64,7 @@ impl DebugSession {
|
||||||
})],
|
})],
|
||||||
remote_id: None,
|
remote_id: None,
|
||||||
running_state,
|
running_state,
|
||||||
label: OnceLock::new(),
|
quirks,
|
||||||
stack_trace_view: OnceCell::new(),
|
stack_trace_view: OnceCell::new(),
|
||||||
_worktree_store: project.read(cx).worktree_store().downgrade(),
|
_worktree_store: project.read(cx).worktree_store().downgrade(),
|
||||||
workspace,
|
workspace,
|
||||||
|
@ -110,65 +110,29 @@ impl DebugSession {
|
||||||
.update(cx, |state, cx| state.shutdown(cx));
|
.update(cx, |state, cx| state.shutdown(cx));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn label(&self, cx: &App) -> SharedString {
|
pub(crate) fn label(&self, cx: &mut App) -> Option<SharedString> {
|
||||||
if let Some(label) = self.label.get() {
|
let session = self.running_state.read(cx).session().clone();
|
||||||
return label.clone();
|
session.update(cx, |session, cx| {
|
||||||
}
|
let session_label = session.label();
|
||||||
|
let quirks = session.quirks();
|
||||||
let session = self.running_state.read(cx).session();
|
let mut single_thread_name = || {
|
||||||
|
let threads = session.threads(cx);
|
||||||
self.label
|
match threads.as_slice() {
|
||||||
.get_or_init(|| session.read(cx).label())
|
[(thread, _)] => Some(SharedString::from(&thread.name)),
|
||||||
.to_owned()
|
_ => None,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if quirks.prefer_thread_name {
|
||||||
|
single_thread_name().or(session_label)
|
||||||
|
} else {
|
||||||
|
session_label.or_else(single_thread_name)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn running_state(&self) -> &Entity<RunningState> {
|
pub fn running_state(&self) -> &Entity<RunningState> {
|
||||||
&self.running_state
|
&self.running_state
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn label_element(&self, depth: usize, cx: &App) -> AnyElement {
|
|
||||||
const MAX_LABEL_CHARS: usize = 150;
|
|
||||||
|
|
||||||
let label = self.label(cx);
|
|
||||||
let label = truncate_and_trailoff(&label, MAX_LABEL_CHARS);
|
|
||||||
|
|
||||||
let is_terminated = self
|
|
||||||
.running_state
|
|
||||||
.read(cx)
|
|
||||||
.session()
|
|
||||||
.read(cx)
|
|
||||||
.is_terminated();
|
|
||||||
let icon = {
|
|
||||||
if is_terminated {
|
|
||||||
Some(Indicator::dot().color(Color::Error))
|
|
||||||
} else {
|
|
||||||
match self
|
|
||||||
.running_state
|
|
||||||
.read(cx)
|
|
||||||
.thread_status(cx)
|
|
||||||
.unwrap_or_default()
|
|
||||||
{
|
|
||||||
project::debugger::session::ThreadStatus::Stopped => {
|
|
||||||
Some(Indicator::dot().color(Color::Conflict))
|
|
||||||
}
|
|
||||||
_ => Some(Indicator::dot().color(Color::Success)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
h_flex()
|
|
||||||
.id("session-label")
|
|
||||||
.ml(depth * px(16.0))
|
|
||||||
.gap_2()
|
|
||||||
.when_some(icon, |this, indicator| this.child(indicator))
|
|
||||||
.justify_between()
|
|
||||||
.child(
|
|
||||||
Label::new(label)
|
|
||||||
.size(LabelSize::Small)
|
|
||||||
.when(is_terminated, |this| this.strikethrough()),
|
|
||||||
)
|
|
||||||
.into_any_element()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<DebugPanelItemEvent> for DebugSession {}
|
impl EventEmitter<DebugPanelItemEvent> for DebugSession {}
|
||||||
|
|
|
@ -427,7 +427,7 @@ async fn test_handle_start_debugging_request(
|
||||||
let sessions = workspace
|
let sessions = workspace
|
||||||
.update(cx, |workspace, _window, cx| {
|
.update(cx, |workspace, _window, cx| {
|
||||||
let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
|
let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
|
||||||
debug_panel.read(cx).sessions()
|
debug_panel.read(cx).sessions().collect::<Vec<_>>()
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(sessions.len(), 1);
|
assert_eq!(sessions.len(), 1);
|
||||||
|
@ -451,7 +451,7 @@ async fn test_handle_start_debugging_request(
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.session(cx);
|
.session(cx);
|
||||||
let current_sessions = debug_panel.read(cx).sessions();
|
let current_sessions = debug_panel.read(cx).sessions().collect::<Vec<_>>();
|
||||||
assert_eq!(active_session, current_sessions[1].read(cx).session(cx));
|
assert_eq!(active_session, current_sessions[1].read(cx).session(cx));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
active_session.read(cx).parent_session(),
|
active_session.read(cx).parent_session(),
|
||||||
|
@ -1796,7 +1796,7 @@ async fn test_debug_adapters_shutdown_on_app_quit(
|
||||||
let panel = workspace.panel::<DebugPanel>(cx).unwrap();
|
let panel = workspace.panel::<DebugPanel>(cx).unwrap();
|
||||||
panel.read_with(cx, |panel, _| {
|
panel.read_with(cx, |panel, _| {
|
||||||
assert!(
|
assert!(
|
||||||
!panel.sessions().is_empty(),
|
panel.sessions().next().is_some(),
|
||||||
"Debug session should be active"
|
"Debug session should be active"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,6 +6,7 @@ use super::{
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
InlayHint, InlayHintLabel, ProjectEnvironment, ResolveState,
|
InlayHint, InlayHintLabel, ProjectEnvironment, ResolveState,
|
||||||
|
debugger::session::SessionQuirks,
|
||||||
project_settings::ProjectSettings,
|
project_settings::ProjectSettings,
|
||||||
terminals::{SshCommand, wrap_for_ssh},
|
terminals::{SshCommand, wrap_for_ssh},
|
||||||
worktree_store::WorktreeStore,
|
worktree_store::WorktreeStore,
|
||||||
|
@ -385,10 +386,11 @@ impl DapStore {
|
||||||
|
|
||||||
pub fn new_session(
|
pub fn new_session(
|
||||||
&mut self,
|
&mut self,
|
||||||
label: SharedString,
|
label: Option<SharedString>,
|
||||||
adapter: DebugAdapterName,
|
adapter: DebugAdapterName,
|
||||||
task_context: TaskContext,
|
task_context: TaskContext,
|
||||||
parent_session: Option<Entity<Session>>,
|
parent_session: Option<Entity<Session>>,
|
||||||
|
quirks: SessionQuirks,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Entity<Session> {
|
) -> Entity<Session> {
|
||||||
let session_id = SessionId(util::post_inc(&mut self.next_session_id));
|
let session_id = SessionId(util::post_inc(&mut self.next_session_id));
|
||||||
|
@ -406,6 +408,7 @@ impl DapStore {
|
||||||
label,
|
label,
|
||||||
adapter,
|
adapter,
|
||||||
task_context,
|
task_context,
|
||||||
|
quirks,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -151,6 +151,12 @@ pub struct RunningMode {
|
||||||
messages_tx: UnboundedSender<Message>,
|
messages_tx: UnboundedSender<Message>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
|
||||||
|
pub struct SessionQuirks {
|
||||||
|
pub compact: bool,
|
||||||
|
pub prefer_thread_name: bool,
|
||||||
|
}
|
||||||
|
|
||||||
fn client_source(abs_path: &Path) -> dap::Source {
|
fn client_source(abs_path: &Path) -> dap::Source {
|
||||||
dap::Source {
|
dap::Source {
|
||||||
name: abs_path
|
name: abs_path
|
||||||
|
@ -656,7 +662,7 @@ pub struct OutputToken(pub usize);
|
||||||
pub struct Session {
|
pub struct Session {
|
||||||
pub mode: Mode,
|
pub mode: Mode,
|
||||||
id: SessionId,
|
id: SessionId,
|
||||||
label: SharedString,
|
label: Option<SharedString>,
|
||||||
adapter: DebugAdapterName,
|
adapter: DebugAdapterName,
|
||||||
pub(super) capabilities: Capabilities,
|
pub(super) capabilities: Capabilities,
|
||||||
child_session_ids: HashSet<SessionId>,
|
child_session_ids: HashSet<SessionId>,
|
||||||
|
@ -679,6 +685,7 @@ pub struct Session {
|
||||||
background_tasks: Vec<Task<()>>,
|
background_tasks: Vec<Task<()>>,
|
||||||
restart_task: Option<Task<()>>,
|
restart_task: Option<Task<()>>,
|
||||||
task_context: TaskContext,
|
task_context: TaskContext,
|
||||||
|
quirks: SessionQuirks,
|
||||||
}
|
}
|
||||||
|
|
||||||
trait CacheableCommand: Any + Send + Sync {
|
trait CacheableCommand: Any + Send + Sync {
|
||||||
|
@ -792,9 +799,10 @@ impl Session {
|
||||||
breakpoint_store: Entity<BreakpointStore>,
|
breakpoint_store: Entity<BreakpointStore>,
|
||||||
session_id: SessionId,
|
session_id: SessionId,
|
||||||
parent_session: Option<Entity<Session>>,
|
parent_session: Option<Entity<Session>>,
|
||||||
label: SharedString,
|
label: Option<SharedString>,
|
||||||
adapter: DebugAdapterName,
|
adapter: DebugAdapterName,
|
||||||
task_context: TaskContext,
|
task_context: TaskContext,
|
||||||
|
quirks: SessionQuirks,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Entity<Self> {
|
) -> Entity<Self> {
|
||||||
cx.new::<Self>(|cx| {
|
cx.new::<Self>(|cx| {
|
||||||
|
@ -848,6 +856,7 @@ impl Session {
|
||||||
label,
|
label,
|
||||||
adapter,
|
adapter,
|
||||||
task_context,
|
task_context,
|
||||||
|
quirks,
|
||||||
};
|
};
|
||||||
|
|
||||||
this
|
this
|
||||||
|
@ -1022,7 +1031,7 @@ impl Session {
|
||||||
self.adapter.clone()
|
self.adapter.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn label(&self) -> SharedString {
|
pub fn label(&self) -> Option<SharedString> {
|
||||||
self.label.clone()
|
self.label.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2481,4 +2490,8 @@ impl Session {
|
||||||
pub fn thread_state(&self, thread_id: ThreadId) -> Option<ThreadStatus> {
|
pub fn thread_state(&self, thread_id: ThreadId) -> Option<ThreadStatus> {
|
||||||
self.thread_states.thread_state(thread_id)
|
self.thread_states.thread_state(thread_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn quirks(&self) -> SessionQuirks {
|
||||||
|
self.quirks
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4301,6 +4301,7 @@ impl ProjectPanel {
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let components_len = components.len();
|
let components_len = components.len();
|
||||||
|
// TODO this can underflow
|
||||||
let active_index = components_len
|
let active_index = components_len
|
||||||
- 1
|
- 1
|
||||||
- folded_ancestors.current_ancestor_depth;
|
- folded_ancestors.current_ancestor_depth;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue