mod console;mod loaded_source_list;mod module_list;pub mod stack_frame_list;pub mod variable_list; use super::{DebugPanelItemEvent, ThreadItem};use console::Console;use dap::{client::SessionId, debugger_settings::DebuggerSettings, Capabilities, Thread};use gpui::{AppContext, Entity, EventEmitter, FocusHandle, Focusable, Subscription, WeakEntity};use loaded_source_list::LoadedSourceList;use module_list::ModuleList;use project::debugger::session::{Session, SessionEvent, ThreadId, ThreadStatus};use rpc::proto::ViewId;use settings::Settings;use stack_frame_list::StackFrameList;use ui::{ div, h_flex, v_flex, ActiveTheme, AnyElement, App, Button, ButtonCommon, Clickable, Context, ContextMenu, Disableable, Divider, DropdownMenu, FluentBuilder, IconButton, IconName, IconSize, Indicator, InteractiveElement, IntoElement, Label, ParentElement, Render, SharedString, StatefulInteractiveElement, Styled, Tooltip, Window,};use util::ResultExt;use variable_list::VariableList;use workspace::Workspace; pub struct RunningState { session: Entity<Session>, thread_id: Option<ThreadId>, console: Entity<console::Console>, focus_handle: FocusHandle, _remote_id: Option<ViewId>, show_console_indicator: bool, module_list: Entity<module_list::ModuleList>, active_thread_item: ThreadItem, workspace: WeakEntity<Workspace>, session_id: SessionId, variable_list: Entity<variable_list::VariableList>, _subscriptions: Vec<Subscription>, stack_frame_list: Entity<stack_frame_list::StackFrameList>, loaded_source_list: Entity<loaded_source_list::LoadedSourceList>,} impl Render for RunningState { fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { let threads = self.session.update(cx, |this, cx| this.threads(cx)); self.select_current_thread(&threads, cx); let thread_status = self .thread_id .map(|thread_id| self.session.read(cx).thread_status(thread_id)) .unwrap_or(ThreadStatus::Exited); let selected_thread_name = threads .iter() .find(|(thread, _)| self.thread_id.map(|id| id.0) == Some(thread.id)) .map(|(thread, _)| thread.name.clone()) .unwrap_or("Threads".to_owned()); self.variable_list.update(cx, |this, cx| { this.disabled(thread_status != ThreadStatus::Stopped, cx); }); let active_thread_item = &self.active_thread_item; let has_no_threads = threads.is_empty(); let capabilities = self.capabilities(cx); let state = cx.entity(); h_flex() .key_context("DebugPanelItem") .track_focus(&self.focus_handle(cx)) .size_full() .items_start() .child( v_flex() .size_full() .items_start() .child( h_flex() .w_full() .border_b_1() .border_color(cx.theme().colors().border_variant) .justify_between() .child( h_flex() .px_1() .py_0p5() .w_full() .gap_1() .map(|this| { if thread_status == ThreadStatus::Running { this.child( IconButton::new( "debug-pause", IconName::DebugPause, ) .icon_size(IconSize::XSmall) .on_click(cx.listener(|this, _, _window, cx| { this.pause_thread(cx); })) .tooltip(move |window, cx| { Tooltip::text("Pause program")(window, cx) }), ) } else { this.child( IconButton::new( "debug-continue", IconName::DebugContinue, ) .icon_size(IconSize::XSmall) .on_click(cx.listener(|this, _, _window, cx| { this.continue_thread(cx) })) .disabled(thread_status != ThreadStatus::Stopped) .tooltip(move |window, cx| { Tooltip::text("Continue program")(window, cx) }), ) } }) .child( IconButton::new("debug-restart", IconName::DebugRestart) .icon_size(IconSize::XSmall) .on_click(cx.listener(|this, _, _window, cx| { this.restart_session(cx); })) .disabled( !capabilities .supports_restart_request .unwrap_or_default(), ) .tooltip(move |window, cx| { Tooltip::text("Restart")(window, cx) }), ) .child( IconButton::new("debug-stop", IconName::DebugStop) .icon_size(IconSize::XSmall) .on_click(cx.listener(|this, _, _window, cx| { this.stop_thread(cx); })) .disabled( thread_status != ThreadStatus::Stopped && thread_status != ThreadStatus::Running, ) .tooltip({ let label = if capabilities .supports_terminate_threads_request .unwrap_or_default() { "Terminate Thread" } else { "Terminate all Threads" }; move |window, cx| Tooltip::text(label)(window, cx) }), ) .child( IconButton::new( "debug-disconnect", IconName::DebugDisconnect, ) .icon_size(IconSize::XSmall) .on_click(cx.listener(|this, _, _window, cx| { this.disconnect_client(cx); })) .disabled( thread_status == ThreadStatus::Exited || thread_status == ThreadStatus::Ended, ) .tooltip(Tooltip::text("Disconnect")), ) .child(Divider::vertical()) .when( capabilities.supports_step_back.unwrap_or(false), |this| { this.child( IconButton::new( "debug-step-back", IconName::DebugStepBack, ) .icon_size(IconSize::XSmall) .on_click(cx.listener(|this, _, _window, cx| { this.step_back(cx); })) .disabled(thread_status != ThreadStatus::Stopped) .tooltip(move |window, cx| { Tooltip::text("Step back")(window, cx) }), ) }, ) .child( IconButton::new("debug-step-over", IconName::DebugStepOver) .icon_size(IconSize::XSmall) .on_click(cx.listener(|this, _, _window, cx| { this.step_over(cx); })) .disabled(thread_status != ThreadStatus::Stopped) .tooltip(move |window, cx| { Tooltip::text("Step over")(window, cx) }), ) .child( IconButton::new("debug-step-in", IconName::DebugStepInto) .icon_size(IconSize::XSmall) .on_click(cx.listener(|this, _, _window, cx| { this.step_in(cx); })) .disabled(thread_status != ThreadStatus::Stopped) .tooltip(move |window, cx| { Tooltip::text("Step in")(window, cx) }), ) .child( IconButton::new("debug-step-out", IconName::DebugStepOut) .icon_size(IconSize::XSmall) .on_click(cx.listener(|this, _, _window, cx| { this.step_out(cx); })) .disabled(thread_status != ThreadStatus::Stopped) .tooltip(move |window, cx| { Tooltip::text("Step out")(window, cx) }), ) .child(Divider::vertical()) .child( IconButton::new( "debug-ignore-breakpoints", if self.session.read(cx).breakpoints_enabled() { IconName::DebugBreakpoint } else { IconName::DebugIgnoreBreakpoints }, ) .icon_size(IconSize::XSmall) .on_click(cx.listener(|this, _, _window, cx| { this.toggle_ignore_breakpoints(cx); })) .disabled( thread_status == ThreadStatus::Exited || thread_status == ThreadStatus::Ended, ) .tooltip( move |window, cx| { Tooltip::text("Ignore breakpoints")(window, cx) }, ), ), ) .child( h_flex() .px_1() .py_0p5() .gap_2() .w_3_4() .justify_end() .child(Label::new("Thread:")) .child( DropdownMenu::new( ("thread-list", self.session_id.0), selected_thread_name, ContextMenu::build( window, cx, move |mut this, _, _| { for (thread, _) in threads { let state = state.clone(); let thread_id = thread.id; this = this.entry( thread.name, None, move |_, cx| { state.update(cx, |state, cx| { state.select_thread( ThreadId(thread_id), cx, ); }); }, ); } this }, ), ) .disabled( has_no_threads || thread_status != ThreadStatus::Stopped, ), ), ), ) .child( h_flex() .size_full() .items_start() .p_1() .gap_4() .child(self.stack_frame_list.clone()), ), ) .child( v_flex() .border_l_1() .border_color(cx.theme().colors().border_variant) .size_full() .items_start() .child( h_flex() .border_b_1() .w_full() .border_color(cx.theme().colors().border_variant) .child(self.render_entry_button( &SharedString::from("Variables"), ThreadItem::Variables, cx, )) .when( capabilities.supports_modules_request.unwrap_or_default(), |this| { this.child(self.render_entry_button( &SharedString::from("Modules"), ThreadItem::Modules, cx, )) }, ) .when( capabilities .supports_loaded_sources_request .unwrap_or_default(), |this| { this.child(self.render_entry_button( &SharedString::from("Loaded Sources"), ThreadItem::LoadedSource, cx, )) }, ) .child(self.render_entry_button( &SharedString::from("Console"), ThreadItem::Console, cx, )), ) .when(*active_thread_item == ThreadItem::Variables, |this| { this.child(self.variable_list.clone()) }) .when(*active_thread_item == ThreadItem::Modules, |this| { this.size_full().child(self.module_list.clone()) }) .when(*active_thread_item == ThreadItem::LoadedSource, |this| { this.size_full().child(self.loaded_source_list.clone()) }) .when(*active_thread_item == ThreadItem::Console, |this| { this.child(self.console.clone()) }), ) }} impl RunningState { pub fn new( session: Entity<Session>, workspace: WeakEntity<Workspace>, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let focus_handle = cx.focus_handle(); let session_id = session.read(cx).session_id(); let weak_state = cx.weak_entity(); let stack_frame_list = cx.new(|cx| { StackFrameList::new(workspace.clone(), session.clone(), weak_state, window, cx) }); let variable_list = cx.new(|cx| VariableList::new(session.clone(), stack_frame_list.clone(), window, cx)); let module_list = cx.new(|cx| ModuleList::new(session.clone(), workspace.clone(), cx)); let loaded_source_list = cx.new(|cx| LoadedSourceList::new(session.clone(), cx)); let console = cx.new(|cx| { Console::new( session.clone(), stack_frame_list.clone(), variable_list.clone(), window, cx, ) }); let _subscriptions = vec![ cx.observe(&module_list, |_, _, cx| cx.notify()), cx.subscribe_in(&session, window, |this, _, event, window, cx| { match event { SessionEvent::Stopped(thread_id) => { this.workspace .update(cx, |workspace, cx| { workspace.open_panel::<crate::DebugPanel>(window, cx); }) .log_err(); if let Some(thread_id) = thread_id { this.select_thread(*thread_id, cx); } } SessionEvent::Threads => { let threads = this.session.update(cx, |this, cx| this.threads(cx)); this.select_current_thread(&threads, cx); } _ => {} } cx.notify() }), ]; Self { session, console, workspace, module_list, focus_handle, variable_list, _subscriptions, thread_id: None, _remote_id: None, stack_frame_list, loaded_source_list, session_id, show_console_indicator: false, active_thread_item: ThreadItem::Variables, } } pub(crate) fn go_to_selected_stack_frame(&self, window: &Window, cx: &mut Context<Self>) { if self.thread_id.is_some() { self.stack_frame_list .update(cx, |list, cx| list.go_to_selected_stack_frame(window, cx)); } } pub fn session(&self) -> &Entity<Session> { &self.session } pub fn session_id(&self) -> SessionId { self.session_id } #[cfg(any(test, feature = "test-support"))] pub fn set_thread_item(&mut self, thread_item: ThreadItem, cx: &mut Context<Self>) { self.active_thread_item = thread_item; cx.notify() } #[cfg(any(test, feature = "test-support"))] pub fn stack_frame_list(&self) -> &Entity<StackFrameList> { &self.stack_frame_list } #[cfg(any(test, feature = "test-support"))] pub fn console(&self) -> &Entity<Console> { &self.console } #[cfg(any(test, feature = "test-support"))] pub fn module_list(&self) -> &Entity<ModuleList> { &self.module_list } #[cfg(any(test, feature = "test-support"))] pub fn variable_list(&self) -> &Entity<VariableList> { &self.variable_list } #[cfg(any(test, feature = "test-support"))] pub fn are_breakpoints_ignored(&self, cx: &App) -> bool { self.session.read(cx).ignore_breakpoints() } pub fn capabilities(&self, cx: &App) -> Capabilities { self.session().read(cx).capabilities().clone() } pub fn select_current_thread( &mut self, threads: &Vec<(Thread, ThreadStatus)>, cx: &mut Context<Self>, ) { let selected_thread = self .thread_id .and_then(|thread_id| threads.iter().find(|(thread, _)| thread.id == thread_id.0)) .or_else(|| threads.first()); let Some((selected_thread, _)) = selected_thread else { return; };Lines 1–500 of 697. Load more
mod console; mod loaded_source_list; mod module_list; pub mod stack_frame_list; pub mod variable_list; use super::{DebugPanelItemEvent, ThreadItem}; use console::Console; use dap::{client::SessionId, debugger_settings::DebuggerSettings, Capabilities, Thread}; use gpui::{AppContext, Entity, EventEmitter, FocusHandle, Focusable, Subscription, WeakEntity}; use loaded_source_list::LoadedSourceList; use module_list::ModuleList; use project::debugger::session::{Session, SessionEvent, ThreadId, ThreadStatus}; use rpc::proto::ViewId; use settings::Settings; use stack_frame_list::StackFrameList; use ui::{ div, h_flex, v_flex, ActiveTheme, AnyElement, App, Button, ButtonCommon, Clickable, Context, ContextMenu, Disableable, Divider, DropdownMenu, FluentBuilder, IconButton, IconName, IconSize, Indicator, InteractiveElement, IntoElement, Label, ParentElement, Render, SharedString, StatefulInteractiveElement, Styled, Tooltip, Window, }; use util::ResultExt; use variable_list::VariableList; use workspace::Workspace; pub struct RunningState { session: Entity<Session>, thread_id: Option<ThreadId>, console: Entity<console::Console>, focus_handle: FocusHandle, _remote_id: Option<ViewId>, show_console_indicator: bool, module_list: Entity<module_list::ModuleList>, active_thread_item: ThreadItem, workspace: WeakEntity<Workspace>, session_id: SessionId, variable_list: Entity<variable_list::VariableList>, _subscriptions: Vec<Subscription>, stack_frame_list: Entity<stack_frame_list::StackFrameList>, loaded_source_list: Entity<loaded_source_list::LoadedSourceList>, } impl Render for RunningState { fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { let threads = self.session.update(cx, |this, cx| this.threads(cx)); self.select_current_thread(&threads, cx); let thread_status = self .thread_id .map(|thread_id| self.session.read(cx).thread_status(thread_id)) .unwrap_or(ThreadStatus::Exited); let selected_thread_name = threads .iter() .find(|(thread, _)| self.thread_id.map(|id| id.0) == Some(thread.id)) .map(|(thread, _)| thread.name.clone()) .unwrap_or("Threads".to_owned()); self.variable_list.update(cx, |this, cx| { this.disabled(thread_status != ThreadStatus::Stopped, cx); }); let active_thread_item = &self.active_thread_item; let has_no_threads = threads.is_empty(); let capabilities = self.capabilities(cx); let state = cx.entity(); h_flex() .key_context("DebugPanelItem") .track_focus(&self.focus_handle(cx)) .size_full() .items_start() .child( v_flex() .size_full() .items_start() .child( h_flex() .w_full() .border_b_1() .border_color(cx.theme().colors().border_variant) .justify_between() .child( h_flex() .px_1() .py_0p5() .w_full() .gap_1() .map(|this| { if thread_status == ThreadStatus::Running { this.child( IconButton::new( "debug-pause", IconName::DebugPause, ) .icon_size(IconSize::XSmall) .on_click(cx.listener(|this, _, _window, cx| { this.pause_thread(cx); })) .tooltip(move |window, cx| { Tooltip::text("Pause program")(window, cx) }), ) } else { this.child( IconButton::new( "debug-continue", IconName::DebugContinue, ) .icon_size(IconSize::XSmall) .on_click(cx.listener(|this, _, _window, cx| { this.continue_thread(cx) })) .disabled(thread_status != ThreadStatus::Stopped) .tooltip(move |window, cx| { Tooltip::text("Continue program")(window, cx) }), ) } }) .child( IconButton::new("debug-restart", IconName::DebugRestart) .icon_size(IconSize::XSmall) .on_click(cx.listener(|this, _, _window, cx| { this.restart_session(cx); })) .disabled( !capabilities .supports_restart_request .unwrap_or_default(), ) .tooltip(move |window, cx| { Tooltip::text("Restart")(window, cx) }), ) .child( IconButton::new("debug-stop", IconName::DebugStop) .icon_size(IconSize::XSmall) .on_click(cx.listener(|this, _, _window, cx| { this.stop_thread(cx); })) .disabled( thread_status != ThreadStatus::Stopped && thread_status != ThreadStatus::Running, ) .tooltip({ let label = if capabilities .supports_terminate_threads_request .unwrap_or_default() { "Terminate Thread" } else { "Terminate all Threads" }; move |window, cx| Tooltip::text(label)(window, cx) }), ) .child( IconButton::new( "debug-disconnect", IconName::DebugDisconnect, ) .icon_size(IconSize::XSmall) .on_click(cx.listener(|this, _, _window, cx| { this.disconnect_client(cx); })) .disabled( thread_status == ThreadStatus::Exited || thread_status == ThreadStatus::Ended, ) .tooltip(Tooltip::text("Disconnect")), ) .child(Divider::vertical()) .when( capabilities.supports_step_back.unwrap_or(false), |this| { this.child( IconButton::new( "debug-step-back", IconName::DebugStepBack, ) .icon_size(IconSize::XSmall) .on_click(cx.listener(|this, _, _window, cx| { this.step_back(cx); })) .disabled(thread_status != ThreadStatus::Stopped) .tooltip(move |window, cx| { Tooltip::text("Step back")(window, cx) }), ) }, ) .child( IconButton::new("debug-step-over", IconName::DebugStepOver) .icon_size(IconSize::XSmall) .on_click(cx.listener(|this, _, _window, cx| { this.step_over(cx); })) .disabled(thread_status != ThreadStatus::Stopped) .tooltip(move |window, cx| { Tooltip::text("Step over")(window, cx) }), ) .child( IconButton::new("debug-step-in", IconName::DebugStepInto) .icon_size(IconSize::XSmall) .on_click(cx.listener(|this, _, _window, cx| { this.step_in(cx); })) .disabled(thread_status != ThreadStatus::Stopped) .tooltip(move |window, cx| { Tooltip::text("Step in")(window, cx) }), ) .child( IconButton::new("debug-step-out", IconName::DebugStepOut) .icon_size(IconSize::XSmall) .on_click(cx.listener(|this, _, _window, cx| { this.step_out(cx); })) .disabled(thread_status != ThreadStatus::Stopped) .tooltip(move |window, cx| { Tooltip::text("Step out")(window, cx) }), ) .child(Divider::vertical()) .child( IconButton::new( "debug-ignore-breakpoints", if self.session.read(cx).breakpoints_enabled() { IconName::DebugBreakpoint } else { IconName::DebugIgnoreBreakpoints }, ) .icon_size(IconSize::XSmall) .on_click(cx.listener(|this, _, _window, cx| { this.toggle_ignore_breakpoints(cx); })) .disabled( thread_status == ThreadStatus::Exited || thread_status == ThreadStatus::Ended, ) .tooltip( move |window, cx| { Tooltip::text("Ignore breakpoints")(window, cx) }, ), ), ) .child( h_flex() .px_1() .py_0p5() .gap_2() .w_3_4() .justify_end() .child(Label::new("Thread:")) .child( DropdownMenu::new( ("thread-list", self.session_id.0), selected_thread_name, ContextMenu::build( window, cx, move |mut this, _, _| { for (thread, _) in threads { let state = state.clone(); let thread_id = thread.id; this = this.entry( thread.name, None, move |_, cx| { state.update(cx, |state, cx| { state.select_thread( ThreadId(thread_id), cx, ); }); }, ); } this }, ), ) .disabled( has_no_threads || thread_status != ThreadStatus::Stopped, ), ), ), ) .child( h_flex() .size_full() .items_start() .p_1() .gap_4() .child(self.stack_frame_list.clone()), ), ) .child( v_flex() .border_l_1() .border_color(cx.theme().colors().border_variant) .size_full() .items_start() .child( h_flex() .border_b_1() .w_full() .border_color(cx.theme().colors().border_variant) .child(self.render_entry_button( &SharedString::from("Variables"), ThreadItem::Variables, cx, )) .when( capabilities.supports_modules_request.unwrap_or_default(), |this| { this.child(self.render_entry_button( &SharedString::from("Modules"), ThreadItem::Modules, cx, )) }, ) .when( capabilities .supports_loaded_sources_request .unwrap_or_default(), |this| { this.child(self.render_entry_button( &SharedString::from("Loaded Sources"), ThreadItem::LoadedSource, cx, )) }, ) .child(self.render_entry_button( &SharedString::from("Console"), ThreadItem::Console, cx, )), ) .when(*active_thread_item == ThreadItem::Variables, |this| { this.child(self.variable_list.clone()) }) .when(*active_thread_item == ThreadItem::Modules, |this| { this.size_full().child(self.module_list.clone()) }) .when(*active_thread_item == ThreadItem::LoadedSource, |this| { this.size_full().child(self.loaded_source_list.clone()) }) .when(*active_thread_item == ThreadItem::Console, |this| { this.child(self.console.clone()) }), ) } } impl RunningState { pub fn new( session: Entity<Session>, workspace: WeakEntity<Workspace>, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let focus_handle = cx.focus_handle(); let session_id = session.read(cx).session_id(); let weak_state = cx.weak_entity(); let stack_frame_list = cx.new(|cx| { StackFrameList::new(workspace.clone(), session.clone(), weak_state, window, cx) }); let variable_list = cx.new(|cx| VariableList::new(session.clone(), stack_frame_list.clone(), window, cx)); let module_list = cx.new(|cx| ModuleList::new(session.clone(), workspace.clone(), cx)); let loaded_source_list = cx.new(|cx| LoadedSourceList::new(session.clone(), cx)); let console = cx.new(|cx| { Console::new( session.clone(), stack_frame_list.clone(), variable_list.clone(), window, cx, ) }); let _subscriptions = vec![ cx.observe(&module_list, |_, _, cx| cx.notify()), cx.subscribe_in(&session, window, |this, _, event, window, cx| { match event { SessionEvent::Stopped(thread_id) => { this.workspace .update(cx, |workspace, cx| { workspace.open_panel::<crate::DebugPanel>(window, cx); }) .log_err(); if let Some(thread_id) = thread_id { this.select_thread(*thread_id, cx); } } SessionEvent::Threads => { let threads = this.session.update(cx, |this, cx| this.threads(cx)); this.select_current_thread(&threads, cx); } _ => {} } cx.notify() }), ]; Self { session, console, workspace, module_list, focus_handle, variable_list, _subscriptions, thread_id: None, _remote_id: None, stack_frame_list, loaded_source_list, session_id, show_console_indicator: false, active_thread_item: ThreadItem::Variables, } } pub(crate) fn go_to_selected_stack_frame(&self, window: &Window, cx: &mut Context<Self>) { if self.thread_id.is_some() { self.stack_frame_list .update(cx, |list, cx| list.go_to_selected_stack_frame(window, cx)); } } pub fn session(&self) -> &Entity<Session> { &self.session } pub fn session_id(&self) -> SessionId { self.session_id } #[cfg(any(test, feature = "test-support"))] pub fn set_thread_item(&mut self, thread_item: ThreadItem, cx: &mut Context<Self>) { self.active_thread_item = thread_item; cx.notify() } #[cfg(any(test, feature = "test-support"))] pub fn stack_frame_list(&self) -> &Entity<StackFrameList> { &self.stack_frame_list } #[cfg(any(test, feature = "test-support"))] pub fn console(&self) -> &Entity<Console> { &self.console } #[cfg(any(test, feature = "test-support"))] pub fn module_list(&self) -> &Entity<ModuleList> { &self.module_list } #[cfg(any(test, feature = "test-support"))] pub fn variable_list(&self) -> &Entity<VariableList> { &self.variable_list } #[cfg(any(test, feature = "test-support"))] pub fn are_breakpoints_ignored(&self, cx: &App) -> bool { self.session.read(cx).ignore_breakpoints() } pub fn capabilities(&self, cx: &App) -> Capabilities { self.session().read(cx).capabilities().clone() } pub fn select_current_thread( &mut self, threads: &Vec<(Thread, ThreadStatus)>, cx: &mut Context<Self>, ) { let selected_thread = self .thread_id .and_then(|thread_id| threads.iter().find(|(thread, _)| thread.id == thread_id.0)) .or_else(|| threads.first()); let Some((selected_thread, _)) = selected_thread else { return; };