1160 lines · 37.4 KB · Rust
RawDownload
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, TaskExt, 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, SearchToken, 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 1160.