Detailed changes
@@ -448,7 +448,7 @@ impl ActivityIndicator {
.into_any_element(),
),
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,
});
}
@@ -378,6 +378,14 @@ pub trait DebugAdapter: 'static + Send + Sync {
fn label_for_child_session(&self, _args: &StartDebuggingRequestArguments) -> Option<String> {
None
}
+
+ fn compact_child_session(&self) -> bool {
+ false
+ }
+
+ fn prefer_thread_name(&self) -> bool {
+ false
+ }
}
#[cfg(any(test, feature = "test-support"))]
@@ -534,6 +534,14 @@ impl DebugAdapter for JsDebugAdapter {
.filter(|name| !name.is_empty())?;
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) {
@@ -399,7 +399,8 @@ impl LogStore {
state.insert(DebugAdapterState::new(
id.session_id,
adapter_name,
- session_label,
+ session_label
+ .unwrap_or_else(|| format!("Session {} (child)", id.session_id.0).into()),
has_adapter_logs,
));
@@ -9,6 +9,7 @@ use crate::{
ToggleExpandItem, ToggleSessionPicker, ToggleThreadPicker, persistence, spawn_task_or_modal,
};
use anyhow::{Context as _, Result, anyhow};
+use collections::IndexMap;
use dap::adapters::DebugAdapterName;
use dap::debugger_settings::DebugPanelDockPosition;
use dap::{
@@ -26,7 +27,7 @@ use text::ToPoint as _;
use itertools::Itertools as _;
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::{Project, debugger::session::ThreadStatus};
use rpc::proto::{self};
@@ -63,13 +64,14 @@ pub enum DebugPanelEvent {
pub struct DebugPanel {
size: Pixels,
- sessions: Vec<Entity<DebugSession>>,
active_session: Option<Entity<DebugSession>>,
project: Entity<Project>,
workspace: WeakEntity<Workspace>,
focus_handle: FocusHandle,
context_menu: Option<(Entity<ContextMenu>, Point<Pixels>, Subscription)>,
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) session_picker_menu_handle: PopoverMenuHandle<ContextMenu>,
fs: Arc<dyn Fs>,
@@ -100,7 +102,7 @@ impl DebugPanel {
Self {
size: px(300.),
- sessions: vec![],
+ sessions_with_children: Default::default(),
active_session: None,
focus_handle,
breakpoint_list: BreakpointList::new(
@@ -138,8 +140,9 @@ impl DebugPanel {
});
}
- pub(crate) fn sessions(&self) -> Vec<Entity<DebugSession>> {
- self.sessions.clone()
+ #[cfg(test)]
+ pub(crate) fn sessions(&self) -> impl Iterator<Item = Entity<DebugSession>> {
+ self.sessions_with_children.keys().cloned()
}
pub fn active_session(&self) -> Option<Entity<DebugSession>> {
@@ -185,12 +188,20 @@ impl DebugPanel {
cx: &mut Context<Self>,
) {
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| {
dap_store.new_session(
- scenario.label.clone(),
+ Some(scenario.label.clone()),
DebugAdapterName(scenario.adapter.clone()),
task_context.clone(),
None,
+ quirks,
cx,
)
});
@@ -363,14 +374,15 @@ impl DebugPanel {
};
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 binary = curr_session.read(cx).binary().cloned().unwrap();
let task_context = curr_session.read(cx).task_context().clone();
let curr_session_id = curr_session.read(cx).session_id();
- self.sessions
- .retain(|session| session.read(cx).session_id(cx) != curr_session_id);
+ self.sessions_with_children
+ .retain(|session, _| session.read(cx).session_id(cx) != curr_session_id);
let task = dap_store_handle.update(cx, |dap_store, cx| {
dap_store.shutdown_session(curr_session_id, cx)
});
@@ -379,7 +391,7 @@ impl DebugPanel {
task.await.log_err();
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| {
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 label = self.label_for_child_session(&parent_session, request, cx);
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 {
log::error!("Attempted to start a child-session without a binary");
return;
@@ -438,6 +451,7 @@ impl DebugPanel {
adapter,
task_context,
Some(parent_session.clone()),
+ quirks,
cx,
);
@@ -463,8 +477,8 @@ impl DebugPanel {
cx: &mut Context<Self>,
) {
let Some(session) = self
- .sessions
- .iter()
+ .sessions_with_children
+ .keys()
.find(|other| entity_id == other.entity_id())
.cloned()
else {
@@ -498,15 +512,14 @@ impl DebugPanel {
}
session.update(cx, |session, cx| session.shutdown(cx)).ok();
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
.active_session
.as_ref()
.map(|session| session.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()
@@ -976,8 +989,8 @@ impl DebugPanel {
cx: &mut Context<Self>,
) {
if let Some(session) = self
- .sessions
- .iter()
+ .sessions_with_children
+ .keys()
.find(|session| session.read(cx).session_id(cx) == session_id)
{
self.activate_session(session.clone(), window, cx);
@@ -990,7 +1003,7 @@ impl DebugPanel {
window: &mut Window,
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.update(cx, |this, cx| {
this.running_state().update(cx, |this, cx| {
@@ -1261,18 +1274,27 @@ impl DebugPanel {
parent_session: &Entity<Session>,
request: &StartDebuggingRequestArguments,
cx: &mut Context<'_, Self>,
- ) -> SharedString {
+ ) -> Option<SharedString> {
let adapter = parent_session.read(cx).adapter();
if let Some(adapter) = DapRegistry::global(cx).adapter(&adapter) {
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();
- if !label.ends_with("(child)") {
- label = format!("{label} (child)").into();
+ None
+ }
+
+ 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 debug_session = this.update_in(cx, |this, window, cx| {
let parent_session = this
- .sessions
- .iter()
+ .sessions_with_children
+ .keys()
.find(|p| Some(p.read(cx).session_id(cx)) == session.read(cx).parent_id(cx))
.cloned();
- this.sessions.retain(|session| {
+ this.retain_sessions(|session| {
!session
.read(cx)
.running_state()
@@ -1337,13 +1359,23 @@ async fn register_session_inner(
)
.detach();
let insert_position = this
- .sessions
- .iter()
+ .sessions_with_children
+ .keys()
.position(|session| Some(session) == parent_session.as_ref())
.map(|position| position + 1)
- .unwrap_or(this.sessions.len());
+ .unwrap_or(this.sessions_with_children.len());
// 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
})?;
@@ -1383,7 +1415,7 @@ impl Panel for DebugPanel {
cx: &mut Context<Self>,
) {
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| {
item.running_state()
.update(cx, |state, _| state.invert_axies())
@@ -1,16 +1,82 @@
-use std::time::Duration;
+use std::{rc::Rc, time::Duration};
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 ui::{ContextMenu, DropdownMenu, DropdownStyle, Indicator, prelude::*};
-use util::truncate_and_trailoff;
+use util::{maybe, truncate_and_trailoff};
use crate::{
debugger_panel::DebugPanel,
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 {
fn dropdown_label(label: impl Into<SharedString>) -> Label {
const MAX_LABEL_CHARS: usize = 50;
@@ -25,145 +91,205 @@ impl DebugPanel {
window: &mut Window,
cx: &mut Context<Self>,
) -> Option<impl IntoElement> {
- if let Some(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 running_state = running_state?;
+
+ let mut session_entries = Vec::with_capacity(self.sessions_with_children.len() * 3);
+ let mut sessions_with_children = self.sessions_with_children.iter().peekable();
- 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()
+ while let Some((root, children)) = sessions_with_children.next() {
+ let root_entry = if let Ok([single_child]) = <&[_; 1]>::try_from(children.as_slice())
+ && let Some(single_child) = single_child.upgrade()
+ && single_child.read(cx).quirks.compact
+ {
+ sessions_with_children.next();
+ SessionListEntry {
+ leaf: single_child.clone(),
+ ancestors: vec![root.clone()],
+ }
} 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(),
+ SessionListEntry {
+ leaf: root.clone(),
+ ancestors: Vec::new(),
}
};
+ session_entries.push(root_entry);
+
+ session_entries.extend(
+ sessions_with_children
+ .by_ref()
+ .take_while(|(session, _)| {
+ session
+ .read(cx)
+ .session(cx)
+ .read(cx)
+ .parent_id(cx)
+ .is_some()
+ })
+ .map(|(session, _)| SessionListEntry {
+ leaf: session.clone(),
+ ancestors: vec![],
+ }),
+ );
+ }
- let trigger = h_flex()
- .gap_2()
- .child(session_state_indicator)
- .justify_between()
- .child(
- DebugPanel::dropdown_label(label)
- .when(is_terminated, |this| this.strikethrough()),
+ 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();
+ .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(),
+ }
+ };
- Some(
- 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 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();
- }
- },
- );
+ 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();
}
- this
}),
- )
- .style(DropdownStyle::Ghost)
- .handle(self.session_picker_menu_handle.clone()),
)
- } else {
- None
- }
+ .into_any_element()
}
pub(crate) fn render_thread_dropdown(
@@ -5,14 +5,13 @@ use dap::client::SessionId;
use gpui::{
App, Axis, Entity, EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity,
};
-use project::Project;
use project::debugger::session::Session;
use project::worktree_store::WorktreeStore;
+use project::{Project, debugger::session::SessionQuirks};
use rpc::proto;
use running::RunningState;
-use std::{cell::OnceCell, sync::OnceLock};
-use ui::{Indicator, prelude::*};
-use util::truncate_and_trailoff;
+use std::cell::OnceCell;
+use ui::prelude::*;
use workspace::{
CollaboratorId, FollowableItem, ViewId, Workspace,
item::{self, Item},
@@ -20,8 +19,8 @@ use workspace::{
pub struct DebugSession {
remote_id: Option<workspace::ViewId>,
- running_state: Entity<RunningState>,
- label: OnceLock<SharedString>,
+ pub(crate) running_state: Entity<RunningState>,
+ pub(crate) quirks: SessionQuirks,
stack_trace_view: OnceCell<Entity<StackTraceView>>,
_worktree_store: WeakEntity<WorktreeStore>,
workspace: WeakEntity<Workspace>,
@@ -57,6 +56,7 @@ impl DebugSession {
cx,
)
});
+ let quirks = session.read(cx).quirks();
cx.new(|cx| Self {
_subscriptions: [cx.subscribe(&running_state, |_, _, _, cx| {
@@ -64,7 +64,7 @@ impl DebugSession {
})],
remote_id: None,
running_state,
- label: OnceLock::new(),
+ quirks,
stack_trace_view: OnceCell::new(),
_worktree_store: project.read(cx).worktree_store().downgrade(),
workspace,
@@ -110,65 +110,29 @@ impl DebugSession {
.update(cx, |state, cx| state.shutdown(cx));
}
- pub(crate) fn label(&self, cx: &App) -> SharedString {
- if let Some(label) = self.label.get() {
- return label.clone();
- }
-
- let session = self.running_state.read(cx).session();
-
- self.label
- .get_or_init(|| session.read(cx).label())
- .to_owned()
+ pub(crate) fn label(&self, cx: &mut App) -> Option<SharedString> {
+ let session = self.running_state.read(cx).session().clone();
+ session.update(cx, |session, cx| {
+ let session_label = session.label();
+ let quirks = session.quirks();
+ let mut single_thread_name = || {
+ let threads = session.threads(cx);
+ match threads.as_slice() {
+ [(thread, _)] => Some(SharedString::from(&thread.name)),
+ _ => 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> {
&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 {}
@@ -427,7 +427,7 @@ async fn test_handle_start_debugging_request(
let sessions = workspace
.update(cx, |workspace, _window, cx| {
let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
- debug_panel.read(cx).sessions()
+ debug_panel.read(cx).sessions().collect::<Vec<_>>()
})
.unwrap();
assert_eq!(sessions.len(), 1);
@@ -451,7 +451,7 @@ async fn test_handle_start_debugging_request(
.unwrap()
.read(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.read(cx).parent_session(),
@@ -1796,7 +1796,7 @@ async fn test_debug_adapters_shutdown_on_app_quit(
let panel = workspace.panel::<DebugPanel>(cx).unwrap();
panel.read_with(cx, |panel, _| {
assert!(
- !panel.sessions().is_empty(),
+ panel.sessions().next().is_some(),
"Debug session should be active"
);
});
@@ -6,6 +6,7 @@ use super::{
};
use crate::{
InlayHint, InlayHintLabel, ProjectEnvironment, ResolveState,
+ debugger::session::SessionQuirks,
project_settings::ProjectSettings,
terminals::{SshCommand, wrap_for_ssh},
worktree_store::WorktreeStore,
@@ -385,10 +386,11 @@ impl DapStore {
pub fn new_session(
&mut self,
- label: SharedString,
+ label: Option<SharedString>,
adapter: DebugAdapterName,
task_context: TaskContext,
parent_session: Option<Entity<Session>>,
+ quirks: SessionQuirks,
cx: &mut Context<Self>,
) -> Entity<Session> {
let session_id = SessionId(util::post_inc(&mut self.next_session_id));
@@ -406,6 +408,7 @@ impl DapStore {
label,
adapter,
task_context,
+ quirks,
cx,
);
@@ -151,6 +151,12 @@ pub struct RunningMode {
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 {
dap::Source {
name: abs_path
@@ -656,7 +662,7 @@ pub struct OutputToken(pub usize);
pub struct Session {
pub mode: Mode,
id: SessionId,
- label: SharedString,
+ label: Option<SharedString>,
adapter: DebugAdapterName,
pub(super) capabilities: Capabilities,
child_session_ids: HashSet<SessionId>,
@@ -679,6 +685,7 @@ pub struct Session {
background_tasks: Vec<Task<()>>,
restart_task: Option<Task<()>>,
task_context: TaskContext,
+ quirks: SessionQuirks,
}
trait CacheableCommand: Any + Send + Sync {
@@ -792,9 +799,10 @@ impl Session {
breakpoint_store: Entity<BreakpointStore>,
session_id: SessionId,
parent_session: Option<Entity<Session>>,
- label: SharedString,
+ label: Option<SharedString>,
adapter: DebugAdapterName,
task_context: TaskContext,
+ quirks: SessionQuirks,
cx: &mut App,
) -> Entity<Self> {
cx.new::<Self>(|cx| {
@@ -848,6 +856,7 @@ impl Session {
label,
adapter,
task_context,
+ quirks,
};
this
@@ -1022,7 +1031,7 @@ impl Session {
self.adapter.clone()
}
- pub fn label(&self) -> SharedString {
+ pub fn label(&self) -> Option<SharedString> {
self.label.clone()
}
@@ -2481,4 +2490,8 @@ impl Session {
pub fn thread_state(&self, thread_id: ThreadId) -> Option<ThreadStatus> {
self.thread_states.thread_state(thread_id)
}
+
+ pub fn quirks(&self) -> SessionQuirks {
+ self.quirks
+ }
}
@@ -4301,6 +4301,7 @@ impl ProjectPanel {
.collect::<Vec<_>>();
let components_len = components.len();
+ // TODO this can underflow
let active_index = components_len
- 1
- folded_ancestors.current_ancestor_depth;