diff --git a/crates/debugger_ui/src/debugger_panel.rs b/crates/debugger_ui/src/debugger_panel.rs index 082b68fb14100f778309fd5f0b9b2c3a6c334e39..dea94023210641ae30276560c12cf5ac77603b2b 100644 --- a/crates/debugger_ui/src/debugger_panel.rs +++ b/crates/debugger_ui/src/debugger_panel.rs @@ -2,8 +2,9 @@ use crate::persistence::DebuggerPaneItem; use crate::session::DebugSession; use crate::{ ClearAllBreakpoints, Continue, Detach, FocusBreakpointList, FocusConsole, FocusFrames, - FocusLoadedSources, FocusModules, FocusTerminal, FocusVariables, Pause, Restart, StepBack, - StepInto, StepOut, StepOver, Stop, ToggleIgnoreBreakpoints, persistence, + FocusLoadedSources, FocusModules, FocusTerminal, FocusVariables, Pause, Restart, + ShowStackTrace, StepBack, StepInto, StepOut, StepOver, Stop, ToggleIgnoreBreakpoints, + persistence, }; use anyhow::{Result, anyhow}; use command_palette_hooks::CommandPaletteFilter; @@ -67,11 +68,7 @@ pub struct DebugPanel { } impl DebugPanel { - pub fn new( - workspace: &Workspace, - _window: &mut Window, - cx: &mut Context, - ) -> Entity { + pub fn new(workspace: &Workspace, cx: &mut Context) -> Entity { cx.new(|cx| { let project = workspace.project().clone(); @@ -119,6 +116,7 @@ impl DebugPanel { TypeId::of::(), TypeId::of::(), TypeId::of::(), + TypeId::of::(), TypeId::of::(), TypeId::of::(), ]; @@ -170,8 +168,8 @@ impl DebugPanel { cx: &mut AsyncWindowContext, ) -> Task>> { cx.spawn(async move |cx| { - workspace.update_in(cx, |workspace, window, cx| { - let debug_panel = DebugPanel::new(workspace, window, cx); + workspace.update(cx, |workspace, cx| { + let debug_panel = DebugPanel::new(workspace, cx); workspace.register_action(|workspace, _: &ClearAllBreakpoints, _, cx| { workspace.project().read(cx).breakpoint_store().update( @@ -421,6 +419,7 @@ impl DebugPanel { pub fn active_session(&self) -> Option> { self.active_session.clone() } + fn close_session(&mut self, entity_id: EntityId, window: &mut Window, cx: &mut Context) { let Some(session) = self .sessions @@ -999,7 +998,7 @@ impl DebugPanel { this.go_to_selected_stack_frame(window, cx); }); }); - self.active_session = Some(session_item); + self.active_session = Some(session_item.clone()); cx.notify(); } diff --git a/crates/debugger_ui/src/debugger_ui.rs b/crates/debugger_ui/src/debugger_ui.rs index 6306060c5891e4e992b00c9debcacc389609cf00..62778ade91de83ddc97c2e829028ce3a0f9c30e2 100644 --- a/crates/debugger_ui/src/debugger_ui.rs +++ b/crates/debugger_ui/src/debugger_ui.rs @@ -7,14 +7,16 @@ use new_session_modal::NewSessionModal; use project::debugger::{self, breakpoint_store::SourceBreakpoint}; use session::DebugSession; use settings::Settings; +use stack_trace_view::StackTraceView; use util::maybe; -use workspace::{ShutdownDebugAdapters, Workspace}; +use workspace::{ItemHandle, ShutdownDebugAdapters, Workspace}; pub mod attach_modal; pub mod debugger_panel; mod new_session_modal; mod persistence; pub(crate) mod session; +mod stack_trace_view; #[cfg(any(test, feature = "test-support"))] pub mod tests; @@ -41,6 +43,7 @@ actions!( FocusModules, FocusLoadedSources, FocusTerminal, + ShowStackTrace, ] ); @@ -146,6 +149,38 @@ pub fn init(cx: &mut App) { }) }, ) + .register_action( + |workspace: &mut Workspace, _: &ShowStackTrace, window, cx| { + let Some(debug_panel) = workspace.panel::(cx) else { + return; + }; + + if let Some(existing) = workspace.item_of_type::(cx) { + let is_active = workspace + .active_item(cx) + .is_some_and(|item| item.item_id() == existing.item_id()); + workspace.activate_item(&existing, true, !is_active, window, cx); + } else { + let Some(active_session) = debug_panel.read(cx).active_session() else { + return; + }; + + let project = workspace.project(); + + let stack_trace_view = active_session.update(cx, |session, cx| { + session.stack_trace_view(project, window, cx).clone() + }); + + workspace.add_item_to_active_pane( + Box::new(stack_trace_view), + None, + true, + window, + cx, + ); + } + }, + ) .register_action(|workspace: &mut Workspace, _: &Start, window, cx| { NewSessionModal::show(workspace, window, cx); }); diff --git a/crates/debugger_ui/src/session.rs b/crates/debugger_ui/src/session.rs index bc1cb75cddcbb146d495040e17792a572c315e64..ec341f2a401e02ac43587e1966fcd266dab1bb6b 100644 --- a/crates/debugger_ui/src/session.rs +++ b/crates/debugger_ui/src/session.rs @@ -1,6 +1,6 @@ pub mod running; -use std::sync::OnceLock; +use std::{cell::OnceCell, sync::OnceLock}; use dap::client::SessionId; use gpui::{ @@ -17,15 +17,16 @@ use workspace::{ item::{self, Item}, }; -use crate::{debugger_panel::DebugPanel, persistence::SerializedLayout}; +use crate::{StackTraceView, debugger_panel::DebugPanel, persistence::SerializedLayout}; pub struct DebugSession { remote_id: Option, running_state: Entity, label: OnceLock, + stack_trace_view: OnceCell>, _debug_panel: WeakEntity, _worktree_store: WeakEntity, - _workspace: WeakEntity, + workspace: WeakEntity, _subscriptions: [Subscription; 1], } @@ -66,8 +67,9 @@ impl DebugSession { running_state, label: OnceLock::new(), _debug_panel, + stack_trace_view: OnceCell::new(), _worktree_store: project.read(cx).worktree_store().downgrade(), - _workspace: workspace, + workspace, }) } @@ -75,6 +77,32 @@ impl DebugSession { self.running_state.read(cx).session_id() } + pub(crate) fn stack_trace_view( + &mut self, + project: &Entity, + window: &mut Window, + cx: &mut Context, + ) -> &Entity { + let workspace = self.workspace.clone(); + let running_state = self.running_state.clone(); + + self.stack_trace_view.get_or_init(|| { + let stackframe_list = running_state.read(cx).stack_frame_list().clone(); + + let stack_frame_view = cx.new(|cx| { + StackTraceView::new( + workspace.clone(), + project.clone(), + stackframe_list, + window, + cx, + ) + }); + + stack_frame_view + }) + } + pub fn session(&self, cx: &App) -> Entity { self.running_state.read(cx).session().clone() } diff --git a/crates/debugger_ui/src/session/running.rs b/crates/debugger_ui/src/session/running.rs index 17bdc42d12fa56d1a877111393bcd68a6918a43d..1add2373565f1877f628b25f70d8bbb829bbaf76 100644 --- a/crates/debugger_ui/src/session/running.rs +++ b/crates/debugger_ui/src/session/running.rs @@ -1235,8 +1235,7 @@ impl RunningState { self.stack_frame_list.read(cx).selected_stack_frame_id() } - #[cfg(test)] - pub fn stack_frame_list(&self) -> &Entity { + pub(crate) fn stack_frame_list(&self) -> &Entity { &self.stack_frame_list } diff --git a/crates/debugger_ui/src/session/running/console.rs b/crates/debugger_ui/src/session/running/console.rs index a4bb75f4784c7ff85d9688afc28d68a943ff2f6d..0a02ac331b46001feec2f12c8b115214cf8f6e8d 100644 --- a/crates/debugger_ui/src/session/running/console.rs +++ b/crates/debugger_ui/src/session/running/console.rs @@ -62,7 +62,6 @@ impl Console { editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx); editor }); - let focus_handle = cx.focus_handle(); let this = cx.weak_entity(); let query_bar = cx.new(|cx| { @@ -77,6 +76,8 @@ impl Console { editor }); + let focus_handle = query_bar.focus_handle(cx); + let _subscriptions = vec![cx.subscribe(&stack_frame_list, Self::handle_stack_frame_list_events)]; @@ -110,6 +111,7 @@ impl Console { ) { match event { StackFrameListEvent::SelectedStackFrameChanged(_) => cx.notify(), + StackFrameListEvent::BuiltEntries => {} } } diff --git a/crates/debugger_ui/src/session/running/stack_frame_list.rs b/crates/debugger_ui/src/session/running/stack_frame_list.rs index 113c205a1a6e1afc6cb6842345dcfb413ca66e22..540425d7ea490534f81f01f74b40d53dac116ed4 100644 --- a/crates/debugger_ui/src/session/running/stack_frame_list.rs +++ b/crates/debugger_ui/src/session/running/stack_frame_list.rs @@ -15,13 +15,16 @@ use project::debugger::session::{Session, SessionEvent, StackFrame}; use project::{ProjectItem, ProjectPath}; use ui::{Scrollbar, ScrollbarState, Tooltip, prelude::*}; use util::ResultExt; -use workspace::Workspace; +use workspace::{ItemHandle, Workspace}; + +use crate::StackTraceView; use super::RunningState; #[derive(Debug)] pub enum StackFrameListEvent { SelectedStackFrameChanged(StackFrameId), + BuiltEntries, } pub struct StackFrameList { @@ -101,13 +104,18 @@ impl StackFrameList { &self.entries } - #[cfg(test)] - pub(crate) fn flatten_entries(&self) -> Vec { + pub(crate) fn flatten_entries(&self, show_collapsed: bool) -> Vec { self.entries .iter() .flat_map(|frame| match frame { StackFrameEntry::Normal(frame) => vec![frame.clone()], - StackFrameEntry::Collapsed(frames) => frames.clone(), + StackFrameEntry::Collapsed(frames) => { + if show_collapsed { + frames.clone() + } else { + vec![] + } + } }) .collect::>() } @@ -136,6 +144,25 @@ impl StackFrameList { self.selected_stack_frame_id } + pub(crate) fn select_stack_frame_id( + &mut self, + id: StackFrameId, + window: &Window, + cx: &mut Context, + ) { + if !self.entries.iter().any(|entry| match entry { + StackFrameEntry::Normal(entry) => entry.id == id, + StackFrameEntry::Collapsed(stack_frames) => { + stack_frames.iter().any(|frame| frame.id == id) + } + }) { + return; + } + + self.selected_stack_frame_id = Some(id); + self.go_to_selected_stack_frame(window, cx); + } + pub(super) fn schedule_refresh( &mut self, select_first: bool, @@ -206,6 +233,7 @@ impl StackFrameList { .detach_and_log_err(cx); } + cx.emit(StackFrameListEvent::BuiltEntries); cx.notify(); } @@ -255,7 +283,7 @@ impl StackFrameList { let row = (stack_frame.line.saturating_sub(1)) as u32; - let Some(abs_path) = self.abs_path_from_stack_frame(&stack_frame) else { + let Some(abs_path) = Self::abs_path_from_stack_frame(&stack_frame) else { return Task::ready(Err(anyhow!("Project path not found"))); }; @@ -294,12 +322,22 @@ impl StackFrameList { let project_path = buffer.read(cx).project_path(cx).ok_or_else(|| { anyhow!("Could not select a stack frame for unnamed buffer") })?; + + let open_preview = !workspace + .item_of_type::(cx) + .map(|viewer| { + workspace + .active_item(cx) + .is_some_and(|item| item.item_id() == viewer.item_id()) + }) + .unwrap_or_default(); + anyhow::Ok(workspace.open_path_preview( project_path, None, - false, true, true, + open_preview, window, cx, )) @@ -332,7 +370,7 @@ impl StackFrameList { }) } - fn abs_path_from_stack_frame(&self, stack_frame: &dap::StackFrame) -> Option> { + pub(crate) fn abs_path_from_stack_frame(stack_frame: &dap::StackFrame) -> Option> { stack_frame.source.as_ref().and_then(|s| { s.path .as_deref() diff --git a/crates/debugger_ui/src/session/running/variable_list.rs b/crates/debugger_ui/src/session/running/variable_list.rs index 7c88b7017dfae51f2ec3580ca47c1033a674d16a..d87d8c9b7376971b1f4ddf6a4e163fe030dd40e3 100644 --- a/crates/debugger_ui/src/session/running/variable_list.rs +++ b/crates/debugger_ui/src/session/running/variable_list.rs @@ -302,6 +302,7 @@ impl VariableList { self.selected_stack_frame_id = Some(*stack_frame_id); cx.notify(); } + StackFrameListEvent::BuiltEntries => {} } } diff --git a/crates/debugger_ui/src/stack_trace_view.rs b/crates/debugger_ui/src/stack_trace_view.rs new file mode 100644 index 0000000000000000000000000000000000000000..f73b15079d3944732f108b7b8cf1d380d555bc1c --- /dev/null +++ b/crates/debugger_ui/src/stack_trace_view.rs @@ -0,0 +1,453 @@ +use std::any::{Any, TypeId}; + +use collections::HashMap; +use dap::StackFrameId; +use editor::{ + Anchor, Bias, DebugStackFrameLine, Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer, + RowHighlightOptions, ToPoint, scroll::Autoscroll, +}; +use gpui::{ + AnyView, App, AppContext, Entity, EventEmitter, Focusable, IntoElement, Render, SharedString, + Subscription, Task, WeakEntity, Window, +}; +use language::{BufferSnapshot, Capability, Point, Selection, SelectionGoal, TreeSitterOptions}; +use project::{Project, ProjectPath}; +use ui::{ActiveTheme as _, Context, ParentElement as _, Styled as _, div}; +use util::ResultExt as _; +use workspace::{ + Item, ItemHandle as _, ItemNavHistory, ToolbarItemLocation, Workspace, + item::{BreadcrumbText, ItemEvent}, + searchable::SearchableItemHandle, +}; + +use crate::session::running::stack_frame_list::{StackFrameList, StackFrameListEvent}; +use anyhow::Result; + +pub(crate) struct StackTraceView { + editor: Entity, + multibuffer: Entity, + workspace: WeakEntity, + project: Entity, + stack_frame_list: Entity, + selected_stack_frame_id: Option, + highlights: Vec<(StackFrameId, Anchor)>, + excerpt_for_frames: collections::HashMap, + refresh_task: Option>>, + _subscription: Option, +} + +impl StackTraceView { + pub(crate) fn new( + workspace: WeakEntity, + project: Entity, + stack_frame_list: Entity, + window: &mut Window, + cx: &mut Context, + ) -> Self { + let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite)); + let editor = cx.new(|cx| { + let mut editor = + Editor::for_multibuffer(multibuffer.clone(), Some(project.clone()), window, cx); + editor.set_vertical_scroll_margin(5, cx); + editor + }); + + cx.subscribe_in(&editor, window, |this, editor, event, window, cx| { + if let EditorEvent::SelectionsChanged { local: true } = event { + let excerpt_id = editor.update(cx, |editor, cx| { + let position: Point = editor.selections.newest(cx).head(); + + editor + .snapshot(window, cx) + .buffer_snapshot + .excerpt_containing(position..position) + .map(|excerpt| excerpt.id()) + }); + + if let Some(stack_frame_id) = excerpt_id + .and_then(|id| this.excerpt_for_frames.get(&id)) + .filter(|id| Some(**id) != this.selected_stack_frame_id) + { + this.stack_frame_list.update(cx, |list, cx| { + list.select_stack_frame_id(*stack_frame_id, window, cx); + }); + } + } + }) + .detach(); + + cx.subscribe_in( + &stack_frame_list, + window, + |this, stack_frame_list, event, window, cx| match event { + StackFrameListEvent::BuiltEntries => { + this.selected_stack_frame_id = + stack_frame_list.read(cx).selected_stack_frame_id(); + this.update_excerpts(window, cx); + } + StackFrameListEvent::SelectedStackFrameChanged(selected_frame_id) => { + this.selected_stack_frame_id = Some(*selected_frame_id); + this.update_highlights(window, cx); + + if let Some(frame_anchor) = this + .highlights + .iter() + .find(|(frame_id, _)| frame_id == selected_frame_id) + .map(|highlight| highlight.1) + { + this.editor.update(cx, |editor, cx| { + if frame_anchor.excerpt_id + != editor.selections.newest_anchor().head().excerpt_id + { + let auto_scroll = + Some(Autoscroll::center().for_anchor(frame_anchor)); + + editor.change_selections(auto_scroll, window, cx, |selections| { + let selection_id = selections.new_selection_id(); + + let selection = Selection { + id: selection_id, + start: frame_anchor, + end: frame_anchor, + goal: SelectionGoal::None, + reversed: false, + }; + + selections.select_anchors(vec![selection]); + }) + } + }); + } + } + }, + ) + .detach(); + + let mut this = Self { + editor, + multibuffer, + workspace, + project, + excerpt_for_frames: HashMap::default(), + highlights: Vec::default(), + stack_frame_list, + selected_stack_frame_id: None, + refresh_task: None, + _subscription: None, + }; + + this.update_excerpts(window, cx); + this + } + + fn update_excerpts(&mut self, window: &mut Window, cx: &mut Context) { + self.refresh_task.take(); + self.editor.update(cx, |editor, cx| { + editor.clear_highlights::(cx) + }); + + let stack_frames = self + .stack_frame_list + .update(cx, |list, _| list.flatten_entries(false)); + + let frames_to_open: Vec<_> = stack_frames + .into_iter() + .filter_map(|frame| { + Some(( + frame.id, + frame.line as u32 - 1, + StackFrameList::abs_path_from_stack_frame(&frame)?, + )) + }) + .collect(); + + self.multibuffer + .update(cx, |multi_buffer, cx| multi_buffer.clear(cx)); + + let task = cx.spawn_in(window, async move |this, cx| { + let mut to_highlights = Vec::default(); + + for (stack_frame_id, line, abs_path) in frames_to_open { + let (worktree, relative_path) = this + .update(cx, |this, cx| { + this.workspace.update(cx, |workspace, cx| { + workspace.project().update(cx, |this, cx| { + this.find_or_create_worktree(&abs_path, false, cx) + }) + }) + })?? + .await?; + + let project_path = ProjectPath { + worktree_id: worktree.read_with(cx, |tree, _| tree.id())?, + path: relative_path.into(), + }; + + if let Some(buffer) = this + .read_with(cx, |this, _| this.project.clone())? + .update(cx, |project, cx| project.open_buffer(project_path, cx))? + .await + .log_err() + { + this.update(cx, |this, cx| { + this.multibuffer.update(cx, |multi_buffer, cx| { + let line_point = Point::new(line, 0); + let start_context = Self::heuristic_syntactic_expand( + &buffer.read(cx).snapshot(), + line_point, + ); + + // Users will want to see what happened before an active debug line in most cases + let range = ExcerptRange { + context: start_context..Point::new(line.saturating_add(1), 0), + primary: line_point..line_point, + }; + multi_buffer.push_excerpts(buffer.clone(), vec![range], cx); + + let line_anchor = + multi_buffer.buffer_point_to_anchor(&buffer, line_point, cx); + + if let Some(line_anchor) = line_anchor { + this.excerpt_for_frames + .insert(line_anchor.excerpt_id, stack_frame_id); + to_highlights.push((stack_frame_id, line_anchor)); + } + }); + }) + .ok(); + } + } + + this.update_in(cx, |this, window, cx| { + this.highlights = to_highlights; + this.update_highlights(window, cx); + }) + .ok(); + + anyhow::Ok(()) + }); + + self.refresh_task = Some(task); + } + + fn update_highlights(&mut self, window: &mut Window, cx: &mut Context) { + self.editor.update(cx, |editor, _| { + editor.clear_row_highlights::() + }); + + let stack_frames = self + .stack_frame_list + .update(cx, |session, _| session.flatten_entries(false)); + + let active_idx = self + .selected_stack_frame_id + .and_then(|id| { + stack_frames + .iter() + .enumerate() + .find_map(|(idx, frame)| if frame.id == id { Some(idx) } else { None }) + }) + .unwrap_or(0); + + self.editor.update(cx, |editor, cx| { + let snapshot = editor.snapshot(window, cx).display_snapshot; + let first_color = cx.theme().colors().editor_debugger_active_line_background; + + let color = first_color.opacity(0.5); + + let mut is_first = true; + + for (_, highlight) in self.highlights.iter().skip(active_idx) { + let position = highlight.to_point(&snapshot.buffer_snapshot); + let color = if is_first { + is_first = false; + first_color + } else { + color + }; + + let start = snapshot + .buffer_snapshot + .clip_point(Point::new(position.row, 0), Bias::Left); + let end = start + Point::new(1, 0); + let start = snapshot.buffer_snapshot.anchor_before(start); + let end = snapshot.buffer_snapshot.anchor_before(end); + editor.highlight_rows::( + start..end, + color, + RowHighlightOptions::default(), + cx, + ); + } + }) + } + + fn heuristic_syntactic_expand(snapshot: &BufferSnapshot, selected_point: Point) -> Point { + let mut text_objects = snapshot.text_object_ranges( + selected_point..selected_point, + TreeSitterOptions::max_start_depth(4), + ); + + let mut start_position = text_objects + .find(|(_, obj)| matches!(obj, language::TextObject::AroundFunction)) + .map(|(range, _)| snapshot.offset_to_point(range.start)) + .map(|point| Point::new(point.row.max(selected_point.row.saturating_sub(8)), 0)) + .unwrap_or(selected_point); + + if start_position.row == selected_point.row { + start_position.row = start_position.row.saturating_sub(1); + } + + start_position + } +} + +impl Render for StackTraceView { + fn render(&mut self, _: &mut Window, _: &mut Context) -> impl IntoElement { + div().size_full().child(self.editor.clone()) + } +} + +impl EventEmitter for StackTraceView {} +impl Focusable for StackTraceView { + fn focus_handle(&self, cx: &App) -> gpui::FocusHandle { + self.editor.focus_handle(cx) + } +} + +impl Item for StackTraceView { + type Event = EditorEvent; + + fn to_item_events(event: &EditorEvent, f: impl FnMut(ItemEvent)) { + Editor::to_item_events(event, f) + } + + fn deactivated(&mut self, window: &mut Window, cx: &mut Context) { + self.editor + .update(cx, |editor, cx| editor.deactivated(window, cx)); + } + + fn navigate( + &mut self, + data: Box, + window: &mut Window, + cx: &mut Context, + ) -> bool { + self.editor + .update(cx, |editor, cx| editor.navigate(data, window, cx)) + } + + fn tab_tooltip_text(&self, _: &App) -> Option { + Some("Stack Frame Viewer".into()) + } + + fn tab_content_text(&self, _detail: usize, _: &App) -> SharedString { + "Stack Frames".into() + } + + fn for_each_project_item( + &self, + cx: &App, + f: &mut dyn FnMut(gpui::EntityId, &dyn project::ProjectItem), + ) { + self.editor.for_each_project_item(cx, f) + } + + fn is_singleton(&self, _: &App) -> bool { + false + } + + fn set_nav_history( + &mut self, + nav_history: ItemNavHistory, + _: &mut Window, + cx: &mut Context, + ) { + self.editor.update(cx, |editor, _| { + editor.set_nav_history(Some(nav_history)); + }); + } + + fn is_dirty(&self, cx: &App) -> bool { + self.multibuffer.read(cx).is_dirty(cx) + } + + fn has_deleted_file(&self, cx: &App) -> bool { + self.multibuffer.read(cx).has_deleted_file(cx) + } + + fn has_conflict(&self, cx: &App) -> bool { + self.multibuffer.read(cx).has_conflict(cx) + } + + fn can_save(&self, _: &App) -> bool { + true + } + + fn save( + &mut self, + format: bool, + project: Entity, + window: &mut Window, + cx: &mut Context, + ) -> Task> { + self.editor.save(format, project, window, cx) + } + + fn save_as( + &mut self, + _: Entity, + _: ProjectPath, + _window: &mut Window, + _: &mut Context, + ) -> Task> { + unreachable!() + } + + fn reload( + &mut self, + project: Entity, + window: &mut Window, + cx: &mut Context, + ) -> Task> { + self.editor.reload(project, window, cx) + } + + fn act_as_type<'a>( + &'a self, + type_id: TypeId, + self_handle: &'a Entity, + _: &'a App, + ) -> Option { + if type_id == TypeId::of::() { + Some(self_handle.to_any()) + } else if type_id == TypeId::of::() { + Some(self.editor.to_any()) + } else { + None + } + } + + fn as_searchable(&self, _: &Entity) -> Option> { + Some(Box::new(self.editor.clone())) + } + + fn breadcrumb_location(&self, _: &App) -> ToolbarItemLocation { + ToolbarItemLocation::PrimaryLeft + } + + fn breadcrumbs(&self, theme: &theme::Theme, cx: &App) -> Option> { + self.editor.breadcrumbs(theme, cx) + } + + fn added_to_workspace( + &mut self, + workspace: &mut Workspace, + window: &mut Window, + cx: &mut Context, + ) { + self.editor.update(cx, |editor, cx| { + editor.added_to_workspace(workspace, window, cx) + }); + } +} diff --git a/crates/debugger_ui/src/tests/variable_list.rs b/crates/debugger_ui/src/tests/variable_list.rs index cfb66c2e0f4a53dc37d5011103779f810c19fd22..79599160feb1712e59b90303855ce63244f9fa72 100644 --- a/crates/debugger_ui/src/tests/variable_list.rs +++ b/crates/debugger_ui/src/tests/variable_list.rs @@ -190,7 +190,7 @@ async fn test_basic_fetch_initial_scope_and_variables( running_state.update(cx, |running_state, cx| { let (stack_frame_list, stack_frame_id) = running_state.stack_frame_list().update(cx, |list, _| { - (list.flatten_entries(), list.selected_stack_frame_id()) + (list.flatten_entries(true), list.selected_stack_frame_id()) }); assert_eq!(stack_frames, stack_frame_list); @@ -431,7 +431,7 @@ async fn test_fetch_variables_for_multiple_scopes( running_state.update(cx, |running_state, cx| { let (stack_frame_list, stack_frame_id) = running_state.stack_frame_list().update(cx, |list, _| { - (list.flatten_entries(), list.selected_stack_frame_id()) + (list.flatten_entries(true), list.selected_stack_frame_id()) }); assert_eq!(Some(1), stack_frame_id); @@ -1452,7 +1452,7 @@ async fn test_variable_list_only_sends_requests_when_rendering( running_state.update(cx, |running_state, cx| { let (stack_frame_list, stack_frame_id) = running_state.stack_frame_list().update(cx, |list, _| { - (list.flatten_entries(), list.selected_stack_frame_id()) + (list.flatten_entries(true), list.selected_stack_frame_id()) }); assert_eq!(Some(1), stack_frame_id); @@ -1734,7 +1734,7 @@ async fn test_it_fetches_scopes_variables_when_you_select_a_stack_frame( running_state.update(cx, |running_state, cx| { let (stack_frame_list, stack_frame_id) = running_state.stack_frame_list().update(cx, |list, _| { - (list.flatten_entries(), list.selected_stack_frame_id()) + (list.flatten_entries(true), list.selected_stack_frame_id()) }); let variable_list = running_state.variable_list().read(cx); @@ -1789,7 +1789,7 @@ async fn test_it_fetches_scopes_variables_when_you_select_a_stack_frame( running_state.update(cx, |running_state, cx| { let (stack_frame_list, stack_frame_id) = running_state.stack_frame_list().update(cx, |list, _| { - (list.flatten_entries(), list.selected_stack_frame_id()) + (list.flatten_entries(true), list.selected_stack_frame_id()) }); let variable_list = running_state.variable_list().read(cx); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 6edc5970e06e460b1637bbb689f262fa506d055d..78082540092375b5b39165243e7151a8479832fd 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -289,6 +289,7 @@ impl InlayId { } pub enum ActiveDebugLine {} +pub enum DebugStackFrameLine {} enum DocumentHighlightRead {} enum DocumentHighlightWrite {} enum InputComposition {} @@ -13880,7 +13881,10 @@ impl Editor { Default::default(), cx, ); - self.request_autoscroll(Autoscroll::center().for_anchor(start), cx); + + if self.buffer.read(cx).is_singleton() { + self.request_autoscroll(Autoscroll::center().for_anchor(start), cx); + } } pub fn go_to_definition( @@ -16886,6 +16890,7 @@ impl Editor { handled = true; self.clear_row_highlights::(); + self.go_to_line::( multibuffer_anchor, Some(cx.theme().colors().editor_debugger_active_line_background), @@ -17900,9 +17905,7 @@ impl Editor { let Some(project) = self.project.clone() else { return; }; - let Some(buffer) = self.buffer.read(cx).as_singleton() else { - return; - }; + if !self.inline_value_cache.enabled { let inlays = std::mem::take(&mut self.inline_value_cache.inlays); self.splice_inlays(&inlays, Vec::new(), cx); @@ -17920,15 +17923,24 @@ impl Editor { .ok()?; let inline_values = editor - .update(cx, |_, cx| { + .update(cx, |editor, cx| { let Some(current_execution_position) = current_execution_position else { return Some(Task::ready(Ok(Vec::new()))); }; - // todo(debugger) when introducing multi buffer inline values check execution position's buffer id to make sure the text - // anchor is in the same buffer + let buffer = editor.buffer.read_with(cx, |buffer, cx| { + let snapshot = buffer.snapshot(cx); + + let excerpt = snapshot.excerpt_containing( + current_execution_position..current_execution_position, + )?; + + editor.buffer.read(cx).buffer(excerpt.buffer_id()) + })?; + let range = buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor; + project.inline_values(buffer, range, cx) }) .ok()