use dap::{ adapters::DebugAdapterName, client::SessionId, debugger_settings::DebuggerSettings, transport::{IoKind, LogKind},};use editor::{Editor, EditorEvent};use futures::{ StreamExt, channel::mpsc::{UnboundedSender, unbounded},};use gpui::{ App, AppContext, Context, Empty, Entity, EventEmitter, FocusHandle, Focusable, IntoElement, ParentElement, Render, SharedString, Styled, Subscription, WeakEntity, Window, actions, div,};use project::{ Project, debugger::{dap_store, session::Session}, search::SearchQuery,};use settings::Settings as _;use std::{ borrow::Cow, collections::{BTreeMap, HashMap, VecDeque}, sync::Arc,};use util::maybe;use workspace::{ ToolbarItemEvent, ToolbarItemView, Workspace, item::Item, searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle}, ui::{Button, Clickable, ContextMenu, Label, LabelCommon, PopoverMenu, h_flex},}; #[derive(Debug, Copy, Clone, PartialEq, Eq)]enum View { AdapterLogs, RpcMessages, InitializationSequence,} struct DapLogView { editor: Entity<Editor>, focus_handle: FocusHandle, log_store: Entity<LogStore>, editor_subscriptions: Vec<Subscription>, current_view: Option<(SessionId, View)>, project: Entity<Project>, _subscriptions: Vec<Subscription>,} struct LogStoreEntryIdentifier<'a> { session_id: SessionId, project: Cow<'a, WeakEntity<Project>>,}impl LogStoreEntryIdentifier<'_> { fn to_owned(&self) -> LogStoreEntryIdentifier<'static> { LogStoreEntryIdentifier { session_id: self.session_id, project: Cow::Owned(self.project.as_ref().clone()), } }} struct LogStoreMessage { id: LogStoreEntryIdentifier<'static>, kind: IoKind, command: Option<SharedString>, message: SharedString,} pub struct LogStore { projects: HashMap<WeakEntity<Project>, ProjectState>, rpc_tx: UnboundedSender<LogStoreMessage>, adapter_log_tx: UnboundedSender<LogStoreMessage>,} struct ProjectState { debug_sessions: BTreeMap<SessionId, DebugAdapterState>, _subscriptions: [gpui::Subscription; 2],} struct DebugAdapterState { id: SessionId, log_messages: VecDeque<SharedString>, rpc_messages: RpcMessages, session_label: SharedString, adapter_name: DebugAdapterName, has_adapter_logs: bool, is_terminated: bool,} struct RpcMessages { messages: VecDeque<SharedString>, last_message_kind: Option<MessageKind>, initialization_sequence: Vec<SharedString>, last_init_message_kind: Option<MessageKind>,} impl RpcMessages { const MESSAGE_QUEUE_LIMIT: usize = 255; fn new() -> Self { Self { last_message_kind: None, last_init_message_kind: None, messages: VecDeque::with_capacity(Self::MESSAGE_QUEUE_LIMIT), initialization_sequence: Vec::new(), } }} const SEND: &str = "// Send";const RECEIVE: &str = "// Receive"; #[derive(Clone, Copy, PartialEq, Eq)]enum MessageKind { Send, Receive,} impl MessageKind { fn label(&self) -> &'static str { match self { Self::Send => SEND, Self::Receive => RECEIVE, } }} impl DebugAdapterState { fn new( id: SessionId, adapter_name: DebugAdapterName, session_label: SharedString, has_adapter_logs: bool, ) -> Self { Self { id, log_messages: VecDeque::new(), rpc_messages: RpcMessages::new(), adapter_name, session_label, has_adapter_logs, is_terminated: false, } }} impl LogStore { pub fn new(cx: &Context<Self>) -> Self { let (rpc_tx, mut rpc_rx) = unbounded::<LogStoreMessage>(); cx.spawn(async move |this, cx| { while let Some(message) = rpc_rx.next().await { if let Some(this) = this.upgrade() { this.update(cx, |this, cx| { this.add_debug_adapter_message(message, cx); })?; } smol::future::yield_now().await; } anyhow::Ok(()) }) .detach_and_log_err(cx); let (adapter_log_tx, mut adapter_log_rx) = unbounded::<LogStoreMessage>(); cx.spawn(async move |this, cx| { while let Some(message) = adapter_log_rx.next().await { if let Some(this) = this.upgrade() { this.update(cx, |this, cx| { this.add_debug_adapter_log(message, cx); })?; } smol::future::yield_now().await; } anyhow::Ok(()) }) .detach_and_log_err(cx); Self { rpc_tx, adapter_log_tx, projects: HashMap::new(), } } pub fn add_project(&mut self, project: &Entity<Project>, cx: &mut Context<Self>) { self.projects.insert( project.downgrade(), ProjectState { _subscriptions: [ cx.observe_release(project, { let weak_project = project.downgrade(); move |this, _, _| { this.projects.remove(&weak_project); } }), cx.subscribe(&project.read(cx).dap_store(), { let weak_project = project.downgrade(); move |this, dap_store, event, cx| match event { dap_store::DapStoreEvent::DebugClientStarted(session_id) => { let session = dap_store.read(cx).session_by_id(session_id); if let Some(session) = session { this.add_debug_session( LogStoreEntryIdentifier { project: Cow::Owned(weak_project.clone()), session_id: *session_id, }, session, cx, ); } } dap_store::DapStoreEvent::DebugClientShutdown(session_id) => { let id = LogStoreEntryIdentifier { project: Cow::Borrowed(&weak_project), session_id: *session_id, }; if let Some(state) = this.get_debug_adapter_state(&id) { state.is_terminated = true; } this.clean_sessions(cx); } _ => {} } }), ], debug_sessions: Default::default(), }, ); } fn get_debug_adapter_state( &mut self, id: &LogStoreEntryIdentifier<'_>, ) -> Option<&mut DebugAdapterState> { self.projects .get_mut(&id.project) .and_then(|state| state.debug_sessions.get_mut(&id.session_id)) } fn add_debug_adapter_message( &mut self, LogStoreMessage { id, kind: io_kind, command, message, }: LogStoreMessage, cx: &mut Context<Self>, ) { let Some(debug_client_state) = self.get_debug_adapter_state(&id) else { return; }; let is_init_seq = command.as_ref().is_some_and(|command| { matches!( command.as_ref(), "attach" | "launch" | "initialize" | "configurationDone" ) }); let kind = match io_kind { IoKind::StdOut | IoKind::StdErr => MessageKind::Receive, IoKind::StdIn => MessageKind::Send, }; let rpc_messages = &mut debug_client_state.rpc_messages; // Push a separator if the kind has changed if rpc_messages.last_message_kind != Some(kind) { Self::get_debug_adapter_entry( &mut rpc_messages.messages, id.to_owned(), kind.label().into(), LogKind::Rpc, cx, ); rpc_messages.last_message_kind = Some(kind); } let entry = Self::get_debug_adapter_entry( &mut rpc_messages.messages, id.to_owned(), message, LogKind::Rpc, cx, ); if is_init_seq { if rpc_messages.last_init_message_kind != Some(kind) { rpc_messages .initialization_sequence .push(SharedString::from(kind.label())); rpc_messages.last_init_message_kind = Some(kind); } rpc_messages.initialization_sequence.push(entry); } cx.notify(); } fn add_debug_adapter_log( &mut self, LogStoreMessage { id, kind: io_kind, message, .. }: LogStoreMessage, cx: &mut Context<Self>, ) { let Some(debug_adapter_state) = self.get_debug_adapter_state(&id) else { return; }; let message = match io_kind { IoKind::StdErr => format!("stderr: {message}").into(), _ => message, }; Self::get_debug_adapter_entry( &mut debug_adapter_state.log_messages, id.to_owned(), message, LogKind::Adapter, cx, ); cx.notify(); } fn get_debug_adapter_entry( log_lines: &mut VecDeque<SharedString>, id: LogStoreEntryIdentifier<'static>, message: SharedString, kind: LogKind, cx: &mut Context<Self>, ) -> SharedString { if let Some(excess) = log_lines .len() .checked_sub(RpcMessages::MESSAGE_QUEUE_LIMIT) && excess > 0 { log_lines.drain(..excess); } let format_messages = DebuggerSettings::get_global(cx).format_dap_log_messages; let entry = if format_messages { maybe!({ serde_json::to_string_pretty::<serde_json::Value>( &serde_json::from_str(&message).ok()?, ) .ok() }) .map(SharedString::from) .unwrap_or(message) } else { message }; log_lines.push_back(entry.clone()); cx.emit(Event::NewLogEntry { id, entry: entry.clone(), kind, }); entry } fn add_debug_session( &mut self, id: LogStoreEntryIdentifier<'static>, session: Entity<Session>, cx: &mut Context<Self>, ) { maybe!({ let project_entry = self.projects.get_mut(&id.project)?; let std::collections::btree_map::Entry::Vacant(state) = project_entry.debug_sessions.entry(id.session_id) else { return None; }; let (adapter_name, session_label, has_adapter_logs) = session.read_with(cx, |session, _| { ( session.adapter(), session.label(), session .adapter_client() .is_some_and(|client| client.has_adapter_logs()), ) }); state.insert(DebugAdapterState::new( id.session_id, adapter_name, session_label .unwrap_or_else(|| format!("Session {} (child)", id.session_id.0).into()), has_adapter_logs, )); self.clean_sessions(cx); let io_tx = self.rpc_tx.clone(); let client = session.read(cx).adapter_client()?; let project = id.project.clone(); let session_id = id.session_id; client.add_log_handler( move |kind, command, message| { io_tx .unbounded_send(LogStoreMessage { id: LogStoreEntryIdentifier { session_id, project: project.clone(), }, kind, command: command.map(|command| command.to_owned().into()), message: message.to_owned().into(), }) .ok(); }, LogKind::Rpc, ); let log_io_tx = self.adapter_log_tx.clone(); let project = id.project; client.add_log_handler( move |kind, command, message| { log_io_tx .unbounded_send(LogStoreMessage { id: LogStoreEntryIdentifier { session_id, project: project.clone(), }, kind, command: command.map(|command| command.to_owned().into()), message: message.to_owned().into(), }) .ok(); }, LogKind::Adapter, ); Some(()) }); } fn clean_sessions(&mut self, cx: &mut Context<Self>) { self.projects.values_mut().for_each(|project| { let mut allowed_terminated_sessions = 10u32; project.debug_sessions.retain(|_, session| { if !session.is_terminated { return true; } allowed_terminated_sessions = allowed_terminated_sessions.saturating_sub(1); allowed_terminated_sessions > 0 }); }); cx.notify(); } fn log_messages_for_session( &mut self, id: &LogStoreEntryIdentifier<'_>, ) -> Option<&mut VecDeque<SharedString>> { self.get_debug_adapter_state(id) .map(|state| &mut state.log_messages) } fn rpc_messages_for_session( &mut self, id: &LogStoreEntryIdentifier<'_>, ) -> Option<&mut VecDeque<SharedString>> { self.get_debug_adapter_state(id) .map(|state| &mut state.rpc_messages.messages) } fn initialization_sequence_for_session( &mut self, id: &LogStoreEntryIdentifier<'_>, ) -> Option<&Vec<SharedString>> { self.get_debug_adapter_state(id) .map(|state| &state.rpc_messages.initialization_sequence) }} pub struct DapLogToolbarItemView { log_view: Option<Entity<DapLogView>>,} impl DapLogToolbarItemView { pub fn new() -> Self { Self { log_view: None } }Lines 1–500 of 1146. Load more
use dap::{ adapters::DebugAdapterName, client::SessionId, debugger_settings::DebuggerSettings, transport::{IoKind, LogKind}, }; use editor::{Editor, EditorEvent}; use futures::{ StreamExt, channel::mpsc::{UnboundedSender, unbounded}, }; use gpui::{ App, AppContext, Context, Empty, Entity, EventEmitter, FocusHandle, Focusable, IntoElement, ParentElement, Render, SharedString, Styled, Subscription, WeakEntity, Window, actions, div, }; use project::{ Project, debugger::{dap_store, session::Session}, search::SearchQuery, }; use settings::Settings as _; use std::{ borrow::Cow, collections::{BTreeMap, HashMap, VecDeque}, sync::Arc, }; use util::maybe; use workspace::{ ToolbarItemEvent, ToolbarItemView, Workspace, item::Item, searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle}, ui::{Button, Clickable, ContextMenu, Label, LabelCommon, PopoverMenu, h_flex}, }; #[derive(Debug, Copy, Clone, PartialEq, Eq)] enum View { AdapterLogs, RpcMessages, InitializationSequence, } struct DapLogView { editor: Entity<Editor>, focus_handle: FocusHandle, log_store: Entity<LogStore>, editor_subscriptions: Vec<Subscription>, current_view: Option<(SessionId, View)>, project: Entity<Project>, _subscriptions: Vec<Subscription>, } struct LogStoreEntryIdentifier<'a> { session_id: SessionId, project: Cow<'a, WeakEntity<Project>>, } impl LogStoreEntryIdentifier<'_> { fn to_owned(&self) -> LogStoreEntryIdentifier<'static> { LogStoreEntryIdentifier { session_id: self.session_id, project: Cow::Owned(self.project.as_ref().clone()), } } } struct LogStoreMessage { id: LogStoreEntryIdentifier<'static>, kind: IoKind, command: Option<SharedString>, message: SharedString, } pub struct LogStore { projects: HashMap<WeakEntity<Project>, ProjectState>, rpc_tx: UnboundedSender<LogStoreMessage>, adapter_log_tx: UnboundedSender<LogStoreMessage>, } struct ProjectState { debug_sessions: BTreeMap<SessionId, DebugAdapterState>, _subscriptions: [gpui::Subscription; 2], } struct DebugAdapterState { id: SessionId, log_messages: VecDeque<SharedString>, rpc_messages: RpcMessages, session_label: SharedString, adapter_name: DebugAdapterName, has_adapter_logs: bool, is_terminated: bool, } struct RpcMessages { messages: VecDeque<SharedString>, last_message_kind: Option<MessageKind>, initialization_sequence: Vec<SharedString>, last_init_message_kind: Option<MessageKind>, } impl RpcMessages { const MESSAGE_QUEUE_LIMIT: usize = 255; fn new() -> Self { Self { last_message_kind: None, last_init_message_kind: None, messages: VecDeque::with_capacity(Self::MESSAGE_QUEUE_LIMIT), initialization_sequence: Vec::new(), } } } const SEND: &str = "// Send"; const RECEIVE: &str = "// Receive"; #[derive(Clone, Copy, PartialEq, Eq)] enum MessageKind { Send, Receive, } impl MessageKind { fn label(&self) -> &'static str { match self { Self::Send => SEND, Self::Receive => RECEIVE, } } } impl DebugAdapterState { fn new( id: SessionId, adapter_name: DebugAdapterName, session_label: SharedString, has_adapter_logs: bool, ) -> Self { Self { id, log_messages: VecDeque::new(), rpc_messages: RpcMessages::new(), adapter_name, session_label, has_adapter_logs, is_terminated: false, } } } impl LogStore { pub fn new(cx: &Context<Self>) -> Self { let (rpc_tx, mut rpc_rx) = unbounded::<LogStoreMessage>(); cx.spawn(async move |this, cx| { while let Some(message) = rpc_rx.next().await { if let Some(this) = this.upgrade() { this.update(cx, |this, cx| { this.add_debug_adapter_message(message, cx); })?; } smol::future::yield_now().await; } anyhow::Ok(()) }) .detach_and_log_err(cx); let (adapter_log_tx, mut adapter_log_rx) = unbounded::<LogStoreMessage>(); cx.spawn(async move |this, cx| { while let Some(message) = adapter_log_rx.next().await { if let Some(this) = this.upgrade() { this.update(cx, |this, cx| { this.add_debug_adapter_log(message, cx); })?; } smol::future::yield_now().await; } anyhow::Ok(()) }) .detach_and_log_err(cx); Self { rpc_tx, adapter_log_tx, projects: HashMap::new(), } } pub fn add_project(&mut self, project: &Entity<Project>, cx: &mut Context<Self>) { self.projects.insert( project.downgrade(), ProjectState { _subscriptions: [ cx.observe_release(project, { let weak_project = project.downgrade(); move |this, _, _| { this.projects.remove(&weak_project); } }), cx.subscribe(&project.read(cx).dap_store(), { let weak_project = project.downgrade(); move |this, dap_store, event, cx| match event { dap_store::DapStoreEvent::DebugClientStarted(session_id) => { let session = dap_store.read(cx).session_by_id(session_id); if let Some(session) = session { this.add_debug_session( LogStoreEntryIdentifier { project: Cow::Owned(weak_project.clone()), session_id: *session_id, }, session, cx, ); } } dap_store::DapStoreEvent::DebugClientShutdown(session_id) => { let id = LogStoreEntryIdentifier { project: Cow::Borrowed(&weak_project), session_id: *session_id, }; if let Some(state) = this.get_debug_adapter_state(&id) { state.is_terminated = true; } this.clean_sessions(cx); } _ => {} } }), ], debug_sessions: Default::default(), }, ); } fn get_debug_adapter_state( &mut self, id: &LogStoreEntryIdentifier<'_>, ) -> Option<&mut DebugAdapterState> { self.projects .get_mut(&id.project) .and_then(|state| state.debug_sessions.get_mut(&id.session_id)) } fn add_debug_adapter_message( &mut self, LogStoreMessage { id, kind: io_kind, command, message, }: LogStoreMessage, cx: &mut Context<Self>, ) { let Some(debug_client_state) = self.get_debug_adapter_state(&id) else { return; }; let is_init_seq = command.as_ref().is_some_and(|command| { matches!( command.as_ref(), "attach" | "launch" | "initialize" | "configurationDone" ) }); let kind = match io_kind { IoKind::StdOut | IoKind::StdErr => MessageKind::Receive, IoKind::StdIn => MessageKind::Send, }; let rpc_messages = &mut debug_client_state.rpc_messages; // Push a separator if the kind has changed if rpc_messages.last_message_kind != Some(kind) { Self::get_debug_adapter_entry( &mut rpc_messages.messages, id.to_owned(), kind.label().into(), LogKind::Rpc, cx, ); rpc_messages.last_message_kind = Some(kind); } let entry = Self::get_debug_adapter_entry( &mut rpc_messages.messages, id.to_owned(), message, LogKind::Rpc, cx, ); if is_init_seq { if rpc_messages.last_init_message_kind != Some(kind) { rpc_messages .initialization_sequence .push(SharedString::from(kind.label())); rpc_messages.last_init_message_kind = Some(kind); } rpc_messages.initialization_sequence.push(entry); } cx.notify(); } fn add_debug_adapter_log( &mut self, LogStoreMessage { id, kind: io_kind, message, .. }: LogStoreMessage, cx: &mut Context<Self>, ) { let Some(debug_adapter_state) = self.get_debug_adapter_state(&id) else { return; }; let message = match io_kind { IoKind::StdErr => format!("stderr: {message}").into(), _ => message, }; Self::get_debug_adapter_entry( &mut debug_adapter_state.log_messages, id.to_owned(), message, LogKind::Adapter, cx, ); cx.notify(); } fn get_debug_adapter_entry( log_lines: &mut VecDeque<SharedString>, id: LogStoreEntryIdentifier<'static>, message: SharedString, kind: LogKind, cx: &mut Context<Self>, ) -> SharedString { if let Some(excess) = log_lines .len() .checked_sub(RpcMessages::MESSAGE_QUEUE_LIMIT) && excess > 0 { log_lines.drain(..excess); } let format_messages = DebuggerSettings::get_global(cx).format_dap_log_messages; let entry = if format_messages { maybe!({ serde_json::to_string_pretty::<serde_json::Value>( &serde_json::from_str(&message).ok()?, ) .ok() }) .map(SharedString::from) .unwrap_or(message) } else { message }; log_lines.push_back(entry.clone()); cx.emit(Event::NewLogEntry { id, entry: entry.clone(), kind, }); entry } fn add_debug_session( &mut self, id: LogStoreEntryIdentifier<'static>, session: Entity<Session>, cx: &mut Context<Self>, ) { maybe!({ let project_entry = self.projects.get_mut(&id.project)?; let std::collections::btree_map::Entry::Vacant(state) = project_entry.debug_sessions.entry(id.session_id) else { return None; }; let (adapter_name, session_label, has_adapter_logs) = session.read_with(cx, |session, _| { ( session.adapter(), session.label(), session .adapter_client() .is_some_and(|client| client.has_adapter_logs()), ) }); state.insert(DebugAdapterState::new( id.session_id, adapter_name, session_label .unwrap_or_else(|| format!("Session {} (child)", id.session_id.0).into()), has_adapter_logs, )); self.clean_sessions(cx); let io_tx = self.rpc_tx.clone(); let client = session.read(cx).adapter_client()?; let project = id.project.clone(); let session_id = id.session_id; client.add_log_handler( move |kind, command, message| { io_tx .unbounded_send(LogStoreMessage { id: LogStoreEntryIdentifier { session_id, project: project.clone(), }, kind, command: command.map(|command| command.to_owned().into()), message: message.to_owned().into(), }) .ok(); }, LogKind::Rpc, ); let log_io_tx = self.adapter_log_tx.clone(); let project = id.project; client.add_log_handler( move |kind, command, message| { log_io_tx .unbounded_send(LogStoreMessage { id: LogStoreEntryIdentifier { session_id, project: project.clone(), }, kind, command: command.map(|command| command.to_owned().into()), message: message.to_owned().into(), }) .ok(); }, LogKind::Adapter, ); Some(()) }); } fn clean_sessions(&mut self, cx: &mut Context<Self>) { self.projects.values_mut().for_each(|project| { let mut allowed_terminated_sessions = 10u32; project.debug_sessions.retain(|_, session| { if !session.is_terminated { return true; } allowed_terminated_sessions = allowed_terminated_sessions.saturating_sub(1); allowed_terminated_sessions > 0 }); }); cx.notify(); } fn log_messages_for_session( &mut self, id: &LogStoreEntryIdentifier<'_>, ) -> Option<&mut VecDeque<SharedString>> { self.get_debug_adapter_state(id) .map(|state| &mut state.log_messages) } fn rpc_messages_for_session( &mut self, id: &LogStoreEntryIdentifier<'_>, ) -> Option<&mut VecDeque<SharedString>> { self.get_debug_adapter_state(id) .map(|state| &mut state.rpc_messages.messages) } fn initialization_sequence_for_session( &mut self, id: &LogStoreEntryIdentifier<'_>, ) -> Option<&Vec<SharedString>> { self.get_debug_adapter_state(id) .map(|state| &state.rpc_messages.initialization_sequence) } } pub struct DapLogToolbarItemView { log_view: Option<Entity<DapLogView>>, } impl DapLogToolbarItemView { pub fn new() -> Self { Self { log_view: None } }