lsp_log.rs

   1use collections::{HashMap, VecDeque};
   2use copilot::Copilot;
   3use editor::{Editor, EditorEvent, actions::MoveToEnd, scroll::Autoscroll};
   4use futures::{StreamExt, channel::mpsc};
   5use gpui::{
   6    AnyView, App, Context, Corner, Entity, EventEmitter, FocusHandle, Focusable, Global,
   7    IntoElement, ParentElement, Render, Styled, Subscription, WeakEntity, Window, actions, div,
   8};
   9use itertools::Itertools;
  10use language::{LanguageServerId, language_settings::SoftWrap};
  11use lsp::{
  12    IoKind, LanguageServer, LanguageServerBinary, LanguageServerName, LanguageServerSelector,
  13    MessageType, SetTraceParams, TraceValue, notification::SetTrace,
  14};
  15use project::{
  16    LspStore, Project, ProjectItem, WorktreeId, lsp_store::LanguageServerLogType,
  17    search::SearchQuery,
  18};
  19use std::{any::TypeId, borrow::Cow, sync::Arc};
  20use ui::{Button, Checkbox, ContextMenu, Label, PopoverMenu, ToggleState, prelude::*};
  21use util::ResultExt as _;
  22use workspace::{
  23    SplitDirection, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, WorkspaceId,
  24    item::{Item, ItemHandle},
  25    searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},
  26};
  27
  28use crate::get_or_create_tool;
  29
  30const SEND_LINE: &str = "\n// Send:";
  31const RECEIVE_LINE: &str = "\n// Receive:";
  32const MAX_STORED_LOG_ENTRIES: usize = 2000;
  33
  34pub struct LogStore {
  35    store_logs: bool,
  36    projects: HashMap<WeakEntity<Project>, ProjectState>,
  37    language_servers: HashMap<LanguageServerId, LanguageServerState>,
  38    copilot_log_subscription: Option<lsp::Subscription>,
  39    _copilot_subscription: Option<gpui::Subscription>,
  40    io_tx: mpsc::UnboundedSender<(LanguageServerId, IoKind, String)>,
  41}
  42
  43struct ProjectState {
  44    _subscriptions: [gpui::Subscription; 2],
  45}
  46
  47trait Message: AsRef<str> {
  48    type Level: Copy + std::fmt::Debug;
  49    fn should_include(&self, _: Self::Level) -> bool {
  50        true
  51    }
  52}
  53
  54#[derive(Debug)]
  55pub(super) struct LogMessage {
  56    message: String,
  57    typ: MessageType,
  58}
  59
  60impl AsRef<str> for LogMessage {
  61    fn as_ref(&self) -> &str {
  62        &self.message
  63    }
  64}
  65
  66impl Message for LogMessage {
  67    type Level = MessageType;
  68
  69    fn should_include(&self, level: Self::Level) -> bool {
  70        match (self.typ, level) {
  71            (MessageType::ERROR, _) => true,
  72            (_, MessageType::ERROR) => false,
  73            (MessageType::WARNING, _) => true,
  74            (_, MessageType::WARNING) => false,
  75            (MessageType::INFO, _) => true,
  76            (_, MessageType::INFO) => false,
  77            _ => true,
  78        }
  79    }
  80}
  81
  82#[derive(Debug)]
  83pub(super) struct TraceMessage {
  84    message: String,
  85    is_verbose: bool,
  86}
  87
  88impl AsRef<str> for TraceMessage {
  89    fn as_ref(&self) -> &str {
  90        &self.message
  91    }
  92}
  93
  94impl Message for TraceMessage {
  95    type Level = TraceValue;
  96
  97    fn should_include(&self, level: Self::Level) -> bool {
  98        match level {
  99            TraceValue::Off => false,
 100            TraceValue::Messages => !self.is_verbose,
 101            TraceValue::Verbose => true,
 102        }
 103    }
 104}
 105
 106#[derive(Debug)]
 107struct RpcMessage {
 108    message: String,
 109}
 110
 111impl AsRef<str> for RpcMessage {
 112    fn as_ref(&self) -> &str {
 113        &self.message
 114    }
 115}
 116
 117impl Message for RpcMessage {
 118    type Level = ();
 119}
 120
 121pub struct LanguageServerState {
 122    name: Option<LanguageServerName>,
 123    worktree_id: Option<WorktreeId>,
 124    kind: LanguageServerKind,
 125    log_messages: VecDeque<LogMessage>,
 126    trace_messages: VecDeque<TraceMessage>,
 127    rpc_state: Option<LanguageServerRpcState>,
 128    trace_level: TraceValue,
 129    log_level: MessageType,
 130    io_logs_subscription: Option<lsp::Subscription>,
 131}
 132
 133impl std::fmt::Debug for LanguageServerState {
 134    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 135        f.debug_struct("LanguageServerState")
 136            .field("name", &self.name)
 137            .field("worktree_id", &self.worktree_id)
 138            .field("kind", &self.kind)
 139            .field("log_messages", &self.log_messages)
 140            .field("trace_messages", &self.trace_messages)
 141            .field("rpc_state", &self.rpc_state)
 142            .field("trace_level", &self.trace_level)
 143            .field("log_level", &self.log_level)
 144            .finish_non_exhaustive()
 145    }
 146}
 147
 148#[derive(PartialEq, Clone)]
 149pub enum LanguageServerKind {
 150    Local { project: WeakEntity<Project> },
 151    Remote { project: WeakEntity<Project> },
 152    LocalSsh { lsp_store: WeakEntity<LspStore> },
 153    Global,
 154}
 155
 156impl std::fmt::Debug for LanguageServerKind {
 157    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 158        match self {
 159            LanguageServerKind::Local { .. } => write!(f, "LanguageServerKind::Local"),
 160            LanguageServerKind::Remote { .. } => write!(f, "LanguageServerKind::Remote"),
 161            LanguageServerKind::LocalSsh { .. } => write!(f, "LanguageServerKind::LocalSsh"),
 162            LanguageServerKind::Global => write!(f, "LanguageServerKind::Global"),
 163        }
 164    }
 165}
 166
 167impl LanguageServerKind {
 168    fn project(&self) -> Option<&WeakEntity<Project>> {
 169        match self {
 170            Self::Local { project } => Some(project),
 171            Self::Remote { project } => Some(project),
 172            Self::LocalSsh { .. } => None,
 173            Self::Global { .. } => None,
 174        }
 175    }
 176}
 177
 178#[derive(Debug)]
 179pub struct LanguageServerRpcState {
 180    rpc_messages: VecDeque<RpcMessage>,
 181    last_message_kind: Option<MessageKind>,
 182}
 183
 184pub struct LspLogView {
 185    pub(crate) editor: Entity<Editor>,
 186    editor_subscriptions: Vec<Subscription>,
 187    log_store: Entity<LogStore>,
 188    current_server_id: Option<LanguageServerId>,
 189    active_entry_kind: LogKind,
 190    project: Entity<Project>,
 191    focus_handle: FocusHandle,
 192    _log_store_subscriptions: Vec<Subscription>,
 193}
 194
 195pub struct LspLogToolbarItemView {
 196    log_view: Option<Entity<LspLogView>>,
 197    _log_view_subscription: Option<Subscription>,
 198}
 199
 200#[derive(Debug, Copy, Clone, PartialEq, Eq)]
 201enum MessageKind {
 202    Send,
 203    Receive,
 204}
 205
 206#[derive(Clone, Copy, Debug, Default, PartialEq)]
 207pub enum LogKind {
 208    Rpc,
 209    Trace,
 210    #[default]
 211    Logs,
 212    ServerInfo,
 213}
 214
 215impl LogKind {
 216    fn from_server_log_type(log_type: &LanguageServerLogType) -> Self {
 217        match log_type {
 218            LanguageServerLogType::Log(_) => Self::Logs,
 219            LanguageServerLogType::Trace { .. } => Self::Trace,
 220            LanguageServerLogType::Rpc { .. } => Self::Rpc,
 221        }
 222    }
 223    fn label(&self) -> &'static str {
 224        match self {
 225            LogKind::Rpc => RPC_MESSAGES,
 226            LogKind::Trace => SERVER_TRACE,
 227            LogKind::Logs => SERVER_LOGS,
 228            LogKind::ServerInfo => SERVER_INFO,
 229        }
 230    }
 231}
 232
 233#[derive(Clone, Debug, PartialEq)]
 234pub(crate) struct LogMenuItem {
 235    pub server_id: LanguageServerId,
 236    pub server_name: LanguageServerName,
 237    pub worktree_root_name: String,
 238    pub rpc_trace_enabled: bool,
 239    pub selected_entry: LogKind,
 240    pub trace_level: lsp::TraceValue,
 241    pub server_kind: LanguageServerKind,
 242}
 243
 244actions!(
 245    dev,
 246    [
 247        /// Opens the language server protocol logs viewer.
 248        OpenLanguageServerLogs
 249    ]
 250);
 251
 252pub struct GlobalLogStore(pub WeakEntity<LogStore>);
 253
 254impl Global for GlobalLogStore {}
 255
 256pub fn init(store_logs: bool, cx: &mut App) {
 257    let log_store = cx.new(|cx| LogStore::new(store_logs, cx));
 258    cx.set_global(GlobalLogStore(log_store.downgrade()));
 259
 260    cx.observe_new(move |workspace: &mut Workspace, _, cx| {
 261        log_store.update(cx, |store, cx| {
 262            store.add_project(workspace.project(), cx);
 263        });
 264
 265        let log_store = log_store.clone();
 266        workspace.register_action(move |workspace, _: &OpenLanguageServerLogs, window, cx| {
 267            let log_store = log_store.clone();
 268            let project = workspace.project().clone();
 269            get_or_create_tool(
 270                workspace,
 271                SplitDirection::Right,
 272                window,
 273                cx,
 274                move |window, cx| LspLogView::new(project, log_store, window, cx),
 275            );
 276        });
 277    })
 278    .detach();
 279}
 280
 281impl LogStore {
 282    pub fn new(store_logs: bool, cx: &mut Context<Self>) -> Self {
 283        let (io_tx, mut io_rx) = mpsc::unbounded();
 284
 285        let copilot_subscription = Copilot::global(cx).map(|copilot| {
 286            let copilot = &copilot;
 287            cx.subscribe(copilot, |log_store, copilot, edit_prediction_event, cx| {
 288                if let copilot::Event::CopilotLanguageServerStarted = edit_prediction_event
 289                    && let Some(server) = copilot.read(cx).language_server()
 290                {
 291                    let server_id = server.server_id();
 292                    let weak_lsp_store = cx.weak_entity();
 293                    log_store.copilot_log_subscription =
 294                        Some(server.on_notification::<copilot::request::LogMessage, _>(
 295                            move |params, cx| {
 296                                weak_lsp_store
 297                                    .update(cx, |lsp_store, cx| {
 298                                        lsp_store.add_language_server_log(
 299                                            server_id,
 300                                            MessageType::LOG,
 301                                            &params.message,
 302                                            cx,
 303                                        );
 304                                    })
 305                                    .ok();
 306                            },
 307                        ));
 308
 309                    let name = LanguageServerName::new_static("copilot");
 310                    log_store.add_language_server(
 311                        LanguageServerKind::Global,
 312                        server.server_id(),
 313                        Some(name),
 314                        None,
 315                        Some(server.clone()),
 316                        cx,
 317                    );
 318                }
 319            })
 320        });
 321
 322        let log_store = Self {
 323            copilot_log_subscription: None,
 324            _copilot_subscription: copilot_subscription,
 325            projects: HashMap::default(),
 326            language_servers: HashMap::default(),
 327            store_logs,
 328            io_tx,
 329        };
 330
 331        cx.spawn(async move |log_store, cx| {
 332            while let Some((server_id, io_kind, message)) = io_rx.next().await {
 333                if let Some(log_store) = log_store.upgrade() {
 334                    log_store.update(cx, |log_store, cx| {
 335                        log_store.on_io(server_id, io_kind, &message, cx);
 336                    })?;
 337                }
 338            }
 339            anyhow::Ok(())
 340        })
 341        .detach_and_log_err(cx);
 342        log_store
 343    }
 344
 345    pub fn add_project(&mut self, project: &Entity<Project>, cx: &mut Context<Self>) {
 346        let weak_project = project.downgrade();
 347        self.projects.insert(
 348            project.downgrade(),
 349            ProjectState {
 350                _subscriptions: [
 351                    cx.observe_release(project, move |this, _, _| {
 352                        this.projects.remove(&weak_project);
 353                        this.language_servers
 354                            .retain(|_, state| state.kind.project() != Some(&weak_project));
 355                    }),
 356                    cx.subscribe(project, move |log_store, project, event, cx| {
 357                        let server_kind = if project.read(cx).is_local() {
 358                            LanguageServerKind::Local {
 359                                project: project.downgrade(),
 360                            }
 361                        } else {
 362                            LanguageServerKind::Remote {
 363                                project: project.downgrade(),
 364                            }
 365                        };
 366                        match event {
 367                            project::Event::LanguageServerAdded(id, name, worktree_id) => {
 368                                log_store.add_language_server(
 369                                    server_kind,
 370                                    *id,
 371                                    Some(name.clone()),
 372                                    *worktree_id,
 373                                    project
 374                                        .read(cx)
 375                                        .lsp_store()
 376                                        .read(cx)
 377                                        .language_server_for_id(*id),
 378                                    cx,
 379                                );
 380                            }
 381                            project::Event::LanguageServerBufferRegistered {
 382                                server_id,
 383                                buffer_id,
 384                                name,
 385                                ..
 386                            } if project.read(cx).is_via_collab() => {
 387                                let worktree_id = project
 388                                    .read(cx)
 389                                    .buffer_for_id(*buffer_id, cx)
 390                                    .and_then(|buffer| {
 391                                        Some(buffer.read(cx).project_path(cx)?.worktree_id)
 392                                    });
 393                                let name = name.clone().or_else(|| {
 394                                    project
 395                                        .read(cx)
 396                                        .lsp_store()
 397                                        .read(cx)
 398                                        .language_server_statuses
 399                                        .get(server_id)
 400                                        .map(|status| status.name.clone())
 401                                });
 402                                log_store.add_language_server(
 403                                    server_kind,
 404                                    *server_id,
 405                                    name,
 406                                    worktree_id,
 407                                    None,
 408                                    cx,
 409                                );
 410                            }
 411                            project::Event::LanguageServerRemoved(id) => {
 412                                log_store.remove_language_server(*id, cx);
 413                            }
 414                            project::Event::LanguageServerLog(id, typ, message) => {
 415                                log_store.add_language_server(
 416                                    server_kind,
 417                                    *id,
 418                                    None,
 419                                    None,
 420                                    None,
 421                                    cx,
 422                                );
 423                                match typ {
 424                                    project::LanguageServerLogType::Log(typ) => {
 425                                        log_store.add_language_server_log(*id, *typ, message, cx);
 426                                    }
 427                                    project::LanguageServerLogType::Trace { verbose_info } => {
 428                                        log_store.add_language_server_trace(
 429                                            *id,
 430                                            message,
 431                                            verbose_info.clone(),
 432                                            cx,
 433                                        );
 434                                    }
 435                                    project::LanguageServerLogType::Rpc { received } => {
 436                                        let kind = if *received {
 437                                            MessageKind::Receive
 438                                        } else {
 439                                            MessageKind::Send
 440                                        };
 441                                        log_store.add_language_server_rpc(*id, kind, message, cx);
 442                                    }
 443                                }
 444                            }
 445                            project::Event::ToggleLspLogs { server_id, enabled } => {
 446                                // we do not support any other log toggling yet
 447                                if *enabled {
 448                                    log_store.enable_rpc_trace_for_language_server(*server_id);
 449                                } else {
 450                                    log_store.disable_rpc_trace_for_language_server(*server_id);
 451                                }
 452                            }
 453                            _ => {}
 454                        }
 455                    }),
 456                ],
 457            },
 458        );
 459    }
 460
 461    pub(super) fn get_language_server_state(
 462        &mut self,
 463        id: LanguageServerId,
 464    ) -> Option<&mut LanguageServerState> {
 465        self.language_servers.get_mut(&id)
 466    }
 467
 468    pub fn add_language_server(
 469        &mut self,
 470        kind: LanguageServerKind,
 471        server_id: LanguageServerId,
 472        name: Option<LanguageServerName>,
 473        worktree_id: Option<WorktreeId>,
 474        server: Option<Arc<LanguageServer>>,
 475        cx: &mut Context<Self>,
 476    ) -> Option<&mut LanguageServerState> {
 477        let server_state = self.language_servers.entry(server_id).or_insert_with(|| {
 478            cx.notify();
 479            LanguageServerState {
 480                name: None,
 481                worktree_id: None,
 482                kind,
 483                rpc_state: None,
 484                log_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES),
 485                trace_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES),
 486                trace_level: TraceValue::Off,
 487                log_level: MessageType::LOG,
 488                io_logs_subscription: None,
 489            }
 490        });
 491
 492        if let Some(name) = name {
 493            server_state.name = Some(name);
 494        }
 495        if let Some(worktree_id) = worktree_id {
 496            server_state.worktree_id = Some(worktree_id);
 497        }
 498
 499        if let Some(server) = server.filter(|_| server_state.io_logs_subscription.is_none()) {
 500            let io_tx = self.io_tx.clone();
 501            let server_id = server.server_id();
 502            server_state.io_logs_subscription = Some(server.on_io(move |io_kind, message| {
 503                io_tx
 504                    .unbounded_send((server_id, io_kind, message.to_string()))
 505                    .ok();
 506            }));
 507        }
 508
 509        Some(server_state)
 510    }
 511
 512    fn add_language_server_log(
 513        &mut self,
 514        id: LanguageServerId,
 515        typ: MessageType,
 516        message: &str,
 517        cx: &mut Context<Self>,
 518    ) -> Option<()> {
 519        let store_logs = self.store_logs;
 520        let language_server_state = self.get_language_server_state(id)?;
 521
 522        let log_lines = &mut language_server_state.log_messages;
 523        let message = message.trim_end().to_string();
 524        if !store_logs {
 525            // Send all messages regardless of the visibility in case of not storing, to notify the receiver anyway
 526            self.emit_event(
 527                Event::NewServerLogEntry {
 528                    id,
 529                    kind: LanguageServerLogType::Log(typ),
 530                    text: message,
 531                },
 532                cx,
 533            );
 534        } else if let Some(new_message) = Self::push_new_message(
 535            log_lines,
 536            LogMessage { message, typ },
 537            language_server_state.log_level,
 538        ) {
 539            self.emit_event(
 540                Event::NewServerLogEntry {
 541                    id,
 542                    kind: LanguageServerLogType::Log(typ),
 543                    text: new_message,
 544                },
 545                cx,
 546            );
 547        }
 548        Some(())
 549    }
 550
 551    fn add_language_server_trace(
 552        &mut self,
 553        id: LanguageServerId,
 554        message: &str,
 555        verbose_info: Option<String>,
 556        cx: &mut Context<Self>,
 557    ) -> Option<()> {
 558        let store_logs = self.store_logs;
 559        let language_server_state = self.get_language_server_state(id)?;
 560
 561        let log_lines = &mut language_server_state.trace_messages;
 562        if !store_logs {
 563            // Send all messages regardless of the visibility in case of not storing, to notify the receiver anyway
 564            self.emit_event(
 565                Event::NewServerLogEntry {
 566                    id,
 567                    kind: LanguageServerLogType::Trace { verbose_info },
 568                    text: message.trim().to_string(),
 569                },
 570                cx,
 571            );
 572        } else if let Some(new_message) = Self::push_new_message(
 573            log_lines,
 574            TraceMessage {
 575                message: message.trim().to_string(),
 576                is_verbose: false,
 577            },
 578            TraceValue::Messages,
 579        ) {
 580            if let Some(verbose_message) = verbose_info.as_ref() {
 581                Self::push_new_message(
 582                    log_lines,
 583                    TraceMessage {
 584                        message: verbose_message.clone(),
 585                        is_verbose: true,
 586                    },
 587                    TraceValue::Verbose,
 588                );
 589            }
 590            self.emit_event(
 591                Event::NewServerLogEntry {
 592                    id,
 593                    kind: LanguageServerLogType::Trace { verbose_info },
 594                    text: new_message,
 595                },
 596                cx,
 597            );
 598        }
 599        Some(())
 600    }
 601
 602    fn push_new_message<T: Message>(
 603        log_lines: &mut VecDeque<T>,
 604        message: T,
 605        current_severity: <T as Message>::Level,
 606    ) -> Option<String> {
 607        while log_lines.len() + 1 >= MAX_STORED_LOG_ENTRIES {
 608            log_lines.pop_front();
 609        }
 610        let visible = message.should_include(current_severity);
 611
 612        let visible_message = visible.then(|| message.as_ref().to_string());
 613        log_lines.push_back(message);
 614        visible_message
 615    }
 616
 617    fn add_language_server_rpc(
 618        &mut self,
 619        language_server_id: LanguageServerId,
 620        kind: MessageKind,
 621        message: &str,
 622        cx: &mut Context<'_, Self>,
 623    ) {
 624        let store_logs = self.store_logs;
 625        let Some(state) = self
 626            .get_language_server_state(language_server_id)
 627            .and_then(|state| state.rpc_state.as_mut())
 628        else {
 629            return;
 630        };
 631
 632        let received = kind == MessageKind::Receive;
 633        let rpc_log_lines = &mut state.rpc_messages;
 634        if state.last_message_kind != Some(kind) {
 635            while rpc_log_lines.len() + 1 >= MAX_STORED_LOG_ENTRIES {
 636                rpc_log_lines.pop_front();
 637            }
 638            let line_before_message = match kind {
 639                MessageKind::Send => SEND_LINE,
 640                MessageKind::Receive => RECEIVE_LINE,
 641            };
 642            if store_logs {
 643                rpc_log_lines.push_back(RpcMessage {
 644                    message: line_before_message.to_string(),
 645                });
 646            }
 647            // Do not send a synthetic message over the wire, it will be derived from the actual RPC message
 648            cx.emit(Event::NewServerLogEntry {
 649                id: language_server_id,
 650                kind: LanguageServerLogType::Rpc { received },
 651                text: line_before_message.to_string(),
 652            });
 653        }
 654
 655        while rpc_log_lines.len() + 1 >= MAX_STORED_LOG_ENTRIES {
 656            rpc_log_lines.pop_front();
 657        }
 658
 659        if store_logs {
 660            rpc_log_lines.push_back(RpcMessage {
 661                message: message.trim().to_owned(),
 662            });
 663        }
 664
 665        self.emit_event(
 666            Event::NewServerLogEntry {
 667                id: language_server_id,
 668                kind: LanguageServerLogType::Rpc { received },
 669                text: message.to_owned(),
 670            },
 671            cx,
 672        );
 673    }
 674
 675    pub fn remove_language_server(&mut self, id: LanguageServerId, cx: &mut Context<Self>) {
 676        self.language_servers.remove(&id);
 677        cx.notify();
 678    }
 679
 680    pub(super) fn server_logs(&self, server_id: LanguageServerId) -> Option<&VecDeque<LogMessage>> {
 681        Some(&self.language_servers.get(&server_id)?.log_messages)
 682    }
 683
 684    pub(super) fn server_trace(
 685        &self,
 686        server_id: LanguageServerId,
 687    ) -> Option<&VecDeque<TraceMessage>> {
 688        Some(&self.language_servers.get(&server_id)?.trace_messages)
 689    }
 690
 691    fn server_ids_for_project<'a>(
 692        &'a self,
 693        lookup_project: &'a WeakEntity<Project>,
 694    ) -> impl Iterator<Item = LanguageServerId> + 'a {
 695        self.language_servers
 696            .iter()
 697            .filter_map(move |(id, state)| match &state.kind {
 698                LanguageServerKind::Local { project } | LanguageServerKind::Remote { project } => {
 699                    if project == lookup_project {
 700                        Some(*id)
 701                    } else {
 702                        None
 703                    }
 704                }
 705                LanguageServerKind::Global | LanguageServerKind::LocalSsh { .. } => Some(*id),
 706            })
 707    }
 708
 709    pub fn enable_rpc_trace_for_language_server(
 710        &mut self,
 711        server_id: LanguageServerId,
 712    ) -> Option<&mut LanguageServerRpcState> {
 713        let rpc_state = self
 714            .language_servers
 715            .get_mut(&server_id)?
 716            .rpc_state
 717            .get_or_insert_with(|| LanguageServerRpcState {
 718                rpc_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES),
 719                last_message_kind: None,
 720            });
 721        Some(rpc_state)
 722    }
 723
 724    pub fn disable_rpc_trace_for_language_server(
 725        &mut self,
 726        server_id: LanguageServerId,
 727    ) -> Option<()> {
 728        self.language_servers.get_mut(&server_id)?.rpc_state.take();
 729        Some(())
 730    }
 731
 732    pub fn has_server_logs(&self, server: &LanguageServerSelector) -> bool {
 733        match server {
 734            LanguageServerSelector::Id(id) => self.language_servers.contains_key(id),
 735            LanguageServerSelector::Name(name) => self
 736                .language_servers
 737                .iter()
 738                .any(|(_, state)| state.name.as_ref() == Some(name)),
 739        }
 740    }
 741
 742    pub fn open_server_log(
 743        &mut self,
 744        workspace: WeakEntity<Workspace>,
 745        server: LanguageServerSelector,
 746        window: &mut Window,
 747        cx: &mut Context<Self>,
 748    ) {
 749        cx.spawn_in(window, async move |log_store, cx| {
 750            let Some(log_store) = log_store.upgrade() else {
 751                return;
 752            };
 753            workspace
 754                .update_in(cx, |workspace, window, cx| {
 755                    let project = workspace.project().clone();
 756                    let tool_log_store = log_store.clone();
 757                    let log_view = get_or_create_tool(
 758                        workspace,
 759                        SplitDirection::Right,
 760                        window,
 761                        cx,
 762                        move |window, cx| LspLogView::new(project, tool_log_store, window, cx),
 763                    );
 764                    log_view.update(cx, |log_view, cx| {
 765                        let server_id = match server {
 766                            LanguageServerSelector::Id(id) => Some(id),
 767                            LanguageServerSelector::Name(name) => {
 768                                log_store.read(cx).language_servers.iter().find_map(
 769                                    |(id, state)| {
 770                                        if state.name.as_ref() == Some(&name) {
 771                                            Some(*id)
 772                                        } else {
 773                                            None
 774                                        }
 775                                    },
 776                                )
 777                            }
 778                        };
 779                        if let Some(server_id) = server_id {
 780                            log_view.show_logs_for_server(server_id, window, cx);
 781                        }
 782                    });
 783                })
 784                .ok();
 785        })
 786        .detach();
 787    }
 788
 789    pub fn open_server_trace(
 790        &mut self,
 791        workspace: WeakEntity<Workspace>,
 792        server: LanguageServerSelector,
 793        window: &mut Window,
 794        cx: &mut Context<Self>,
 795    ) {
 796        cx.spawn_in(window, async move |log_store, cx| {
 797            let Some(log_store) = log_store.upgrade() else {
 798                return;
 799            };
 800            workspace
 801                .update_in(cx, |workspace, window, cx| {
 802                    let project = workspace.project().clone();
 803                    let tool_log_store = log_store.clone();
 804                    let log_view = get_or_create_tool(
 805                        workspace,
 806                        SplitDirection::Right,
 807                        window,
 808                        cx,
 809                        move |window, cx| LspLogView::new(project, tool_log_store, window, cx),
 810                    );
 811                    log_view.update(cx, |log_view, cx| {
 812                        let server_id = match server {
 813                            LanguageServerSelector::Id(id) => Some(id),
 814                            LanguageServerSelector::Name(name) => {
 815                                log_store.read(cx).language_servers.iter().find_map(
 816                                    |(id, state)| {
 817                                        if state.name.as_ref() == Some(&name) {
 818                                            Some(*id)
 819                                        } else {
 820                                            None
 821                                        }
 822                                    },
 823                                )
 824                            }
 825                        };
 826                        if let Some(server_id) = server_id {
 827                            log_view.show_rpc_trace_for_server(server_id, window, cx);
 828                        }
 829                    });
 830                })
 831                .ok();
 832        })
 833        .detach();
 834    }
 835
 836    fn on_io(
 837        &mut self,
 838        language_server_id: LanguageServerId,
 839        io_kind: IoKind,
 840        message: &str,
 841        cx: &mut Context<Self>,
 842    ) -> Option<()> {
 843        let is_received = match io_kind {
 844            IoKind::StdOut => true,
 845            IoKind::StdIn => false,
 846            IoKind::StdErr => {
 847                self.add_language_server_log(language_server_id, MessageType::LOG, message, cx);
 848                return Some(());
 849            }
 850        };
 851
 852        let kind = if is_received {
 853            MessageKind::Receive
 854        } else {
 855            MessageKind::Send
 856        };
 857
 858        self.add_language_server_rpc(language_server_id, kind, message, cx);
 859        cx.notify();
 860        Some(())
 861    }
 862
 863    fn emit_event(&mut self, e: Event, cx: &mut Context<Self>) {
 864        match &e {
 865            Event::NewServerLogEntry { id, kind, text } => {
 866                if let Some(state) = self.get_language_server_state(*id) {
 867                    let downstream_client = match &state.kind {
 868                        LanguageServerKind::Remote { project }
 869                        | LanguageServerKind::Local { project } => project
 870                            .upgrade()
 871                            .map(|project| project.read(cx).lsp_store()),
 872                        LanguageServerKind::LocalSsh { lsp_store } => lsp_store.upgrade(),
 873                        LanguageServerKind::Global => None,
 874                    }
 875                    .and_then(|lsp_store| lsp_store.read(cx).downstream_client());
 876                    if let Some((client, project_id)) = downstream_client {
 877                        client
 878                            .send(proto::LanguageServerLog {
 879                                project_id,
 880                                language_server_id: id.to_proto(),
 881                                message: text.clone(),
 882                                log_type: Some(kind.to_proto()),
 883                            })
 884                            .ok();
 885                    }
 886                }
 887            }
 888        }
 889
 890        cx.emit(e);
 891    }
 892}
 893
 894impl LspLogView {
 895    pub fn new(
 896        project: Entity<Project>,
 897        log_store: Entity<LogStore>,
 898        window: &mut Window,
 899        cx: &mut Context<Self>,
 900    ) -> Self {
 901        let server_id = log_store
 902            .read(cx)
 903            .language_servers
 904            .iter()
 905            .find(|(_, server)| server.kind.project() == Some(&project.downgrade()))
 906            .map(|(id, _)| *id);
 907
 908        let weak_project = project.downgrade();
 909        let model_changes_subscription =
 910            cx.observe_in(&log_store, window, move |this, store, window, cx| {
 911                let first_server_id_for_project =
 912                    store.read(cx).server_ids_for_project(&weak_project).next();
 913                if let Some(current_lsp) = this.current_server_id {
 914                    if !store.read(cx).language_servers.contains_key(&current_lsp)
 915                        && let Some(server_id) = first_server_id_for_project
 916                    {
 917                        match this.active_entry_kind {
 918                            LogKind::Rpc => this.show_rpc_trace_for_server(server_id, window, cx),
 919                            LogKind::Trace => this.show_trace_for_server(server_id, window, cx),
 920                            LogKind::Logs => this.show_logs_for_server(server_id, window, cx),
 921                            LogKind::ServerInfo => this.show_server_info(server_id, window, cx),
 922                        }
 923                    }
 924                } else if let Some(server_id) = first_server_id_for_project {
 925                    match this.active_entry_kind {
 926                        LogKind::Rpc => this.show_rpc_trace_for_server(server_id, window, cx),
 927                        LogKind::Trace => this.show_trace_for_server(server_id, window, cx),
 928                        LogKind::Logs => this.show_logs_for_server(server_id, window, cx),
 929                        LogKind::ServerInfo => this.show_server_info(server_id, window, cx),
 930                    }
 931                }
 932
 933                cx.notify();
 934            });
 935
 936        let events_subscriptions = cx.subscribe_in(
 937            &log_store,
 938            window,
 939            move |log_view, _, e, window, cx| match e {
 940                Event::NewServerLogEntry { id, kind, text } => {
 941                    if log_view.current_server_id == Some(*id)
 942                        && LogKind::from_server_log_type(kind) == log_view.active_entry_kind
 943                    {
 944                        log_view.editor.update(cx, |editor, cx| {
 945                            editor.set_read_only(false);
 946                            let last_offset = editor.buffer().read(cx).len(cx);
 947                            let newest_cursor_is_at_end =
 948                                editor.selections.newest::<usize>(cx).start >= last_offset;
 949                            editor.edit(
 950                                vec![
 951                                    (last_offset..last_offset, text.as_str()),
 952                                    (last_offset..last_offset, "\n"),
 953                                ],
 954                                cx,
 955                            );
 956                            if text.len() > 1024
 957                                && let Some((fold_offset, _)) =
 958                                    text.char_indices().dropping(1024).next()
 959                                && fold_offset < text.len()
 960                            {
 961                                editor.fold_ranges(
 962                                    vec![last_offset + fold_offset..last_offset + text.len()],
 963                                    false,
 964                                    window,
 965                                    cx,
 966                                );
 967                            }
 968
 969                            if newest_cursor_is_at_end {
 970                                editor.request_autoscroll(Autoscroll::bottom(), cx);
 971                            }
 972                            editor.set_read_only(true);
 973                        });
 974                    }
 975                }
 976            },
 977        );
 978        let (editor, editor_subscriptions) = Self::editor_for_logs(String::new(), window, cx);
 979
 980        let focus_handle = cx.focus_handle();
 981        let focus_subscription = cx.on_focus(&focus_handle, window, |log_view, window, cx| {
 982            window.focus(&log_view.editor.focus_handle(cx));
 983        });
 984
 985        let mut lsp_log_view = Self {
 986            focus_handle,
 987            editor,
 988            editor_subscriptions,
 989            project,
 990            log_store,
 991            current_server_id: None,
 992            active_entry_kind: LogKind::Logs,
 993            _log_store_subscriptions: vec![
 994                model_changes_subscription,
 995                events_subscriptions,
 996                focus_subscription,
 997            ],
 998        };
 999        if let Some(server_id) = server_id {
1000            lsp_log_view.show_logs_for_server(server_id, window, cx);
1001        }
1002        lsp_log_view
1003    }
1004
1005    fn editor_for_logs(
1006        log_contents: String,
1007        window: &mut Window,
1008        cx: &mut Context<Self>,
1009    ) -> (Entity<Editor>, Vec<Subscription>) {
1010        let editor = initialize_new_editor(log_contents, true, window, cx);
1011        let editor_subscription = cx.subscribe(
1012            &editor,
1013            |_, _, event: &EditorEvent, cx: &mut Context<LspLogView>| cx.emit(event.clone()),
1014        );
1015        let search_subscription = cx.subscribe(
1016            &editor,
1017            |_, _, event: &SearchEvent, cx: &mut Context<LspLogView>| cx.emit(event.clone()),
1018        );
1019        (editor, vec![editor_subscription, search_subscription])
1020    }
1021
1022    fn editor_for_server_info(
1023        info: ServerInfo,
1024        window: &mut Window,
1025        cx: &mut Context<Self>,
1026    ) -> (Entity<Editor>, Vec<Subscription>) {
1027        let server_info = format!(
1028            "* Server: {NAME} (id {ID})
1029
1030* Binary: {BINARY:#?}
1031
1032* Registered workspace folders:
1033{WORKSPACE_FOLDERS}
1034
1035* Capabilities: {CAPABILITIES}
1036
1037* Configuration: {CONFIGURATION}",
1038            NAME = info.name,
1039            ID = info.id,
1040            BINARY = info.binary.as_ref().map_or_else(
1041                || "Unknown".to_string(),
1042                |bin| bin.path.as_path().to_string_lossy().to_string()
1043            ),
1044            WORKSPACE_FOLDERS = info.workspace_folders.join(", "),
1045            CAPABILITIES = serde_json::to_string_pretty(&info.capabilities)
1046                .unwrap_or_else(|e| format!("Failed to serialize capabilities: {e}")),
1047            CONFIGURATION = info
1048                .configuration
1049                .map(|configuration| serde_json::to_string_pretty(&configuration))
1050                .transpose()
1051                .unwrap_or_else(|e| Some(format!("Failed to serialize configuration: {e}")))
1052                .unwrap_or_else(|| "Unknown".to_string()),
1053        );
1054        let editor = initialize_new_editor(server_info, false, window, cx);
1055        let editor_subscription = cx.subscribe(
1056            &editor,
1057            |_, _, event: &EditorEvent, cx: &mut Context<LspLogView>| cx.emit(event.clone()),
1058        );
1059        let search_subscription = cx.subscribe(
1060            &editor,
1061            |_, _, event: &SearchEvent, cx: &mut Context<LspLogView>| cx.emit(event.clone()),
1062        );
1063        (editor, vec![editor_subscription, search_subscription])
1064    }
1065
1066    pub(crate) fn menu_items<'a>(&'a self, cx: &'a App) -> Option<Vec<LogMenuItem>> {
1067        let log_store = self.log_store.read(cx);
1068
1069        let unknown_server = LanguageServerName::new_static("unknown server");
1070
1071        let mut rows = log_store
1072            .language_servers
1073            .iter()
1074            .map(|(server_id, state)| match &state.kind {
1075                LanguageServerKind::Local { .. }
1076                | LanguageServerKind::Remote { .. }
1077                | LanguageServerKind::LocalSsh { .. } => {
1078                    let worktree_root_name = state
1079                        .worktree_id
1080                        .and_then(|id| self.project.read(cx).worktree_for_id(id, cx))
1081                        .map(|worktree| worktree.read(cx).root_name().to_string())
1082                        .unwrap_or_else(|| "Unknown worktree".to_string());
1083
1084                    LogMenuItem {
1085                        server_id: *server_id,
1086                        server_name: state.name.clone().unwrap_or(unknown_server.clone()),
1087                        server_kind: state.kind.clone(),
1088                        worktree_root_name,
1089                        rpc_trace_enabled: state.rpc_state.is_some(),
1090                        selected_entry: self.active_entry_kind,
1091                        trace_level: lsp::TraceValue::Off,
1092                    }
1093                }
1094
1095                LanguageServerKind::Global => LogMenuItem {
1096                    server_id: *server_id,
1097                    server_name: state.name.clone().unwrap_or(unknown_server.clone()),
1098                    server_kind: state.kind.clone(),
1099                    worktree_root_name: "supplementary".to_string(),
1100                    rpc_trace_enabled: state.rpc_state.is_some(),
1101                    selected_entry: self.active_entry_kind,
1102                    trace_level: lsp::TraceValue::Off,
1103                },
1104            })
1105            .chain(
1106                self.project
1107                    .read(cx)
1108                    .supplementary_language_servers(cx)
1109                    .filter_map(|(server_id, name)| {
1110                        let state = log_store.language_servers.get(&server_id)?;
1111                        Some(LogMenuItem {
1112                            server_id,
1113                            server_name: name,
1114                            server_kind: state.kind.clone(),
1115                            worktree_root_name: "supplementary".to_string(),
1116                            rpc_trace_enabled: state.rpc_state.is_some(),
1117                            selected_entry: self.active_entry_kind,
1118                            trace_level: lsp::TraceValue::Off,
1119                        })
1120                    }),
1121            )
1122            .collect::<Vec<_>>();
1123        rows.sort_by_key(|row| row.server_id);
1124        rows.dedup_by_key(|row| row.server_id);
1125        Some(rows)
1126    }
1127
1128    fn show_logs_for_server(
1129        &mut self,
1130        server_id: LanguageServerId,
1131        window: &mut Window,
1132        cx: &mut Context<Self>,
1133    ) {
1134        let typ = self
1135            .log_store
1136            .read(cx)
1137            .language_servers
1138            .get(&server_id)
1139            .map(|v| v.log_level)
1140            .unwrap_or(MessageType::LOG);
1141        let log_contents = self
1142            .log_store
1143            .read(cx)
1144            .server_logs(server_id)
1145            .map(|v| log_contents(v, typ));
1146        if let Some(log_contents) = log_contents {
1147            self.current_server_id = Some(server_id);
1148            self.active_entry_kind = LogKind::Logs;
1149            let (editor, editor_subscriptions) = Self::editor_for_logs(log_contents, window, cx);
1150            self.editor = editor;
1151            self.editor_subscriptions = editor_subscriptions;
1152            cx.notify();
1153        }
1154        self.editor.read(cx).focus_handle(cx).focus(window);
1155    }
1156
1157    fn update_log_level(
1158        &self,
1159        server_id: LanguageServerId,
1160        level: MessageType,
1161        window: &mut Window,
1162        cx: &mut Context<Self>,
1163    ) {
1164        let log_contents = self.log_store.update(cx, |this, _| {
1165            if let Some(state) = this.get_language_server_state(server_id) {
1166                state.log_level = level;
1167            }
1168
1169            this.server_logs(server_id).map(|v| log_contents(v, level))
1170        });
1171
1172        if let Some(log_contents) = log_contents {
1173            self.editor.update(cx, |editor, cx| {
1174                editor.set_text(log_contents, window, cx);
1175                editor.move_to_end(&MoveToEnd, window, cx);
1176            });
1177            cx.notify();
1178        }
1179
1180        self.editor.read(cx).focus_handle(cx).focus(window);
1181    }
1182
1183    fn show_trace_for_server(
1184        &mut self,
1185        server_id: LanguageServerId,
1186        window: &mut Window,
1187        cx: &mut Context<Self>,
1188    ) {
1189        let trace_level = self
1190            .log_store
1191            .update(cx, |this, _| {
1192                Some(this.get_language_server_state(server_id)?.trace_level)
1193            })
1194            .unwrap_or(TraceValue::Messages);
1195        let log_contents = self
1196            .log_store
1197            .read(cx)
1198            .server_trace(server_id)
1199            .map(|v| log_contents(v, trace_level));
1200        if let Some(log_contents) = log_contents {
1201            self.current_server_id = Some(server_id);
1202            self.active_entry_kind = LogKind::Trace;
1203            let (editor, editor_subscriptions) = Self::editor_for_logs(log_contents, window, cx);
1204            self.editor = editor;
1205            self.editor_subscriptions = editor_subscriptions;
1206            cx.notify();
1207        }
1208        self.editor.read(cx).focus_handle(cx).focus(window);
1209    }
1210
1211    fn show_rpc_trace_for_server(
1212        &mut self,
1213        server_id: LanguageServerId,
1214        window: &mut Window,
1215        cx: &mut Context<Self>,
1216    ) {
1217        self.toggle_rpc_trace_for_server(server_id, true, window, cx);
1218        let rpc_log = self.log_store.update(cx, |log_store, _| {
1219            log_store
1220                .enable_rpc_trace_for_language_server(server_id)
1221                .map(|state| log_contents(&state.rpc_messages, ()))
1222        });
1223        if let Some(rpc_log) = rpc_log {
1224            self.current_server_id = Some(server_id);
1225            self.active_entry_kind = LogKind::Rpc;
1226            let (editor, editor_subscriptions) = Self::editor_for_logs(rpc_log, window, cx);
1227            let language = self.project.read(cx).languages().language_for_name("JSON");
1228            editor
1229                .read(cx)
1230                .buffer()
1231                .read(cx)
1232                .as_singleton()
1233                .expect("log buffer should be a singleton")
1234                .update(cx, |_, cx| {
1235                    cx.spawn({
1236                        let buffer = cx.entity();
1237                        async move |_, cx| {
1238                            let language = language.await.ok();
1239                            buffer.update(cx, |buffer, cx| {
1240                                buffer.set_language(language, cx);
1241                            })
1242                        }
1243                    })
1244                    .detach_and_log_err(cx);
1245                });
1246
1247            self.editor = editor;
1248            self.editor_subscriptions = editor_subscriptions;
1249            cx.notify();
1250        }
1251
1252        self.editor.read(cx).focus_handle(cx).focus(window);
1253    }
1254
1255    fn toggle_rpc_trace_for_server(
1256        &mut self,
1257        server_id: LanguageServerId,
1258        enabled: bool,
1259        window: &mut Window,
1260        cx: &mut Context<Self>,
1261    ) {
1262        self.log_store.update(cx, |log_store, cx| {
1263            if enabled {
1264                log_store.enable_rpc_trace_for_language_server(server_id);
1265            } else {
1266                log_store.disable_rpc_trace_for_language_server(server_id);
1267            }
1268
1269            if let Some(server_state) = log_store.language_servers.get(&server_id) {
1270                if let LanguageServerKind::Remote { project } = &server_state.kind {
1271                    project
1272                        .update(cx, |project, cx| {
1273                            if let Some((client, project_id)) =
1274                                project.lsp_store().read(cx).upstream_client()
1275                            {
1276                                client
1277                                    .send(proto::ToggleLspLogs {
1278                                        project_id,
1279                                        log_type: proto::toggle_lsp_logs::LogType::Rpc as i32,
1280                                        server_id: server_id.to_proto(),
1281                                        enabled,
1282                                    })
1283                                    .log_err();
1284                            }
1285                        })
1286                        .ok();
1287                }
1288            };
1289        });
1290        if !enabled && Some(server_id) == self.current_server_id {
1291            self.show_logs_for_server(server_id, window, cx);
1292            cx.notify();
1293        }
1294    }
1295
1296    fn update_trace_level(
1297        &self,
1298        server_id: LanguageServerId,
1299        level: TraceValue,
1300        cx: &mut Context<Self>,
1301    ) {
1302        if let Some(server) = self
1303            .project
1304            .read(cx)
1305            .lsp_store()
1306            .read(cx)
1307            .language_server_for_id(server_id)
1308        {
1309            self.log_store.update(cx, |this, _| {
1310                if let Some(state) = this.get_language_server_state(server_id) {
1311                    state.trace_level = level;
1312                }
1313            });
1314
1315            server
1316                .notify::<SetTrace>(&SetTraceParams { value: level })
1317                .ok();
1318        }
1319    }
1320
1321    fn show_server_info(
1322        &mut self,
1323        server_id: LanguageServerId,
1324        window: &mut Window,
1325        cx: &mut Context<Self>,
1326    ) {
1327        let Some(server_info) = self
1328            .project
1329            .read(cx)
1330            .lsp_store()
1331            .update(cx, |lsp_store, _| {
1332                lsp_store
1333                    .language_server_for_id(server_id)
1334                    .as_ref()
1335                    .map(|language_server| ServerInfo::new(language_server))
1336                    .or_else(move || {
1337                        let capabilities =
1338                            lsp_store.lsp_server_capabilities.get(&server_id)?.clone();
1339                        let name = lsp_store
1340                            .language_server_statuses
1341                            .get(&server_id)
1342                            .map(|status| status.name.clone())?;
1343                        Some(ServerInfo {
1344                            id: server_id,
1345                            capabilities,
1346                            binary: None,
1347                            name,
1348                            workspace_folders: Vec::new(),
1349                            configuration: None,
1350                        })
1351                    })
1352            })
1353        else {
1354            return;
1355        };
1356        self.current_server_id = Some(server_id);
1357        self.active_entry_kind = LogKind::ServerInfo;
1358        let (editor, editor_subscriptions) = Self::editor_for_server_info(server_info, window, cx);
1359        self.editor = editor;
1360        self.editor_subscriptions = editor_subscriptions;
1361        cx.notify();
1362        self.editor.read(cx).focus_handle(cx).focus(window);
1363    }
1364}
1365
1366fn log_contents<T: Message>(lines: &VecDeque<T>, level: <T as Message>::Level) -> String {
1367    lines
1368        .iter()
1369        .filter(|message| message.should_include(level))
1370        .flat_map(|message| [message.as_ref(), "\n"])
1371        .collect()
1372}
1373
1374impl Render for LspLogView {
1375    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1376        self.editor.update(cx, |editor, cx| {
1377            editor.render(window, cx).into_any_element()
1378        })
1379    }
1380}
1381
1382impl Focusable for LspLogView {
1383    fn focus_handle(&self, _: &App) -> FocusHandle {
1384        self.focus_handle.clone()
1385    }
1386}
1387
1388impl Item for LspLogView {
1389    type Event = EditorEvent;
1390
1391    fn to_item_events(event: &Self::Event, f: impl FnMut(workspace::item::ItemEvent)) {
1392        Editor::to_item_events(event, f)
1393    }
1394
1395    fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
1396        "LSP Logs".into()
1397    }
1398
1399    fn telemetry_event_text(&self) -> Option<&'static str> {
1400        None
1401    }
1402
1403    fn as_searchable(&self, handle: &Entity<Self>) -> Option<Box<dyn SearchableItemHandle>> {
1404        Some(Box::new(handle.clone()))
1405    }
1406
1407    fn act_as_type<'a>(
1408        &'a self,
1409        type_id: TypeId,
1410        self_handle: &'a Entity<Self>,
1411        _: &'a App,
1412    ) -> Option<AnyView> {
1413        if type_id == TypeId::of::<Self>() {
1414            Some(self_handle.to_any())
1415        } else if type_id == TypeId::of::<Editor>() {
1416            Some(self.editor.to_any())
1417        } else {
1418            None
1419        }
1420    }
1421
1422    fn clone_on_split(
1423        &self,
1424        _workspace_id: Option<WorkspaceId>,
1425        window: &mut Window,
1426        cx: &mut Context<Self>,
1427    ) -> Option<Entity<Self>>
1428    where
1429        Self: Sized,
1430    {
1431        Some(cx.new(|cx| {
1432            let mut new_view = Self::new(self.project.clone(), self.log_store.clone(), window, cx);
1433            if let Some(server_id) = self.current_server_id {
1434                match self.active_entry_kind {
1435                    LogKind::Rpc => new_view.show_rpc_trace_for_server(server_id, window, cx),
1436                    LogKind::Trace => new_view.show_trace_for_server(server_id, window, cx),
1437                    LogKind::Logs => new_view.show_logs_for_server(server_id, window, cx),
1438                    LogKind::ServerInfo => new_view.show_server_info(server_id, window, cx),
1439                }
1440            }
1441            new_view
1442        }))
1443    }
1444}
1445
1446impl SearchableItem for LspLogView {
1447    type Match = <Editor as SearchableItem>::Match;
1448
1449    fn clear_matches(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1450        self.editor.update(cx, |e, cx| e.clear_matches(window, cx))
1451    }
1452
1453    fn update_matches(
1454        &mut self,
1455        matches: &[Self::Match],
1456        window: &mut Window,
1457        cx: &mut Context<Self>,
1458    ) {
1459        self.editor
1460            .update(cx, |e, cx| e.update_matches(matches, window, cx))
1461    }
1462
1463    fn query_suggestion(&mut self, window: &mut Window, cx: &mut Context<Self>) -> String {
1464        self.editor
1465            .update(cx, |e, cx| e.query_suggestion(window, cx))
1466    }
1467
1468    fn activate_match(
1469        &mut self,
1470        index: usize,
1471        matches: &[Self::Match],
1472        window: &mut Window,
1473        cx: &mut Context<Self>,
1474    ) {
1475        self.editor
1476            .update(cx, |e, cx| e.activate_match(index, matches, window, cx))
1477    }
1478
1479    fn select_matches(
1480        &mut self,
1481        matches: &[Self::Match],
1482        window: &mut Window,
1483        cx: &mut Context<Self>,
1484    ) {
1485        self.editor
1486            .update(cx, |e, cx| e.select_matches(matches, window, cx))
1487    }
1488
1489    fn find_matches(
1490        &mut self,
1491        query: Arc<project::search::SearchQuery>,
1492        window: &mut Window,
1493        cx: &mut Context<Self>,
1494    ) -> gpui::Task<Vec<Self::Match>> {
1495        self.editor
1496            .update(cx, |e, cx| e.find_matches(query, window, cx))
1497    }
1498
1499    fn replace(
1500        &mut self,
1501        _: &Self::Match,
1502        _: &SearchQuery,
1503        _window: &mut Window,
1504        _: &mut Context<Self>,
1505    ) {
1506        // Since LSP Log is read-only, it doesn't make sense to support replace operation.
1507    }
1508    fn supported_options(&self) -> workspace::searchable::SearchOptions {
1509        workspace::searchable::SearchOptions {
1510            case: true,
1511            word: true,
1512            regex: true,
1513            find_in_results: false,
1514            // LSP log is read-only.
1515            replacement: false,
1516            selection: false,
1517        }
1518    }
1519    fn active_match_index(
1520        &mut self,
1521        direction: Direction,
1522        matches: &[Self::Match],
1523        window: &mut Window,
1524        cx: &mut Context<Self>,
1525    ) -> Option<usize> {
1526        self.editor.update(cx, |e, cx| {
1527            e.active_match_index(direction, matches, window, cx)
1528        })
1529    }
1530}
1531
1532impl EventEmitter<ToolbarItemEvent> for LspLogToolbarItemView {}
1533
1534impl ToolbarItemView for LspLogToolbarItemView {
1535    fn set_active_pane_item(
1536        &mut self,
1537        active_pane_item: Option<&dyn ItemHandle>,
1538        _: &mut Window,
1539        cx: &mut Context<Self>,
1540    ) -> workspace::ToolbarItemLocation {
1541        if let Some(item) = active_pane_item
1542            && let Some(log_view) = item.downcast::<LspLogView>()
1543        {
1544            self.log_view = Some(log_view.clone());
1545            self._log_view_subscription = Some(cx.observe(&log_view, |_, _, cx| {
1546                cx.notify();
1547            }));
1548            return ToolbarItemLocation::PrimaryLeft;
1549        }
1550        self.log_view = None;
1551        self._log_view_subscription = None;
1552        ToolbarItemLocation::Hidden
1553    }
1554}
1555
1556impl Render for LspLogToolbarItemView {
1557    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1558        let Some(log_view) = self.log_view.clone() else {
1559            return div();
1560        };
1561
1562        let (menu_rows, current_server_id) = log_view.update(cx, |log_view, cx| {
1563            let menu_rows = log_view.menu_items(cx).unwrap_or_default();
1564            let current_server_id = log_view.current_server_id;
1565            (menu_rows, current_server_id)
1566        });
1567
1568        let current_server = current_server_id.and_then(|current_server_id| {
1569            if let Ok(ix) = menu_rows.binary_search_by_key(&current_server_id, |e| e.server_id) {
1570                Some(menu_rows[ix].clone())
1571            } else {
1572                None
1573            }
1574        });
1575
1576        let available_language_servers: Vec<_> = menu_rows
1577            .into_iter()
1578            .map(|row| {
1579                (
1580                    row.server_id,
1581                    row.server_name,
1582                    row.worktree_root_name,
1583                    row.selected_entry,
1584                )
1585            })
1586            .collect();
1587
1588        let log_toolbar_view = cx.entity();
1589
1590        let lsp_menu = PopoverMenu::new("LspLogView")
1591            .anchor(Corner::TopLeft)
1592            .trigger(
1593                Button::new(
1594                    "language_server_menu_header",
1595                    current_server
1596                        .as_ref()
1597                        .map(|row| {
1598                            Cow::Owned(format!(
1599                                "{} ({})",
1600                                row.server_name.0, row.worktree_root_name,
1601                            ))
1602                        })
1603                        .unwrap_or_else(|| "No server selected".into()),
1604                )
1605                .icon(IconName::ChevronDown)
1606                .icon_size(IconSize::Small)
1607                .icon_color(Color::Muted),
1608            )
1609            .menu({
1610                let log_view = log_view.clone();
1611                move |window, cx| {
1612                    let log_view = log_view.clone();
1613                    ContextMenu::build(window, cx, |mut menu, window, _| {
1614                        for (server_id, name, worktree_root, active_entry_kind) in
1615                            available_language_servers.iter()
1616                        {
1617                            let label = format!("{} ({})", name, worktree_root);
1618                            let server_id = *server_id;
1619                            let active_entry_kind = *active_entry_kind;
1620                            menu = menu.entry(
1621                                label,
1622                                None,
1623                                window.handler_for(&log_view, move |view, window, cx| {
1624                                    view.current_server_id = Some(server_id);
1625                                    view.active_entry_kind = active_entry_kind;
1626                                    match view.active_entry_kind {
1627                                        LogKind::Rpc => {
1628                                            view.toggle_rpc_trace_for_server(
1629                                                server_id, true, window, cx,
1630                                            );
1631                                            view.show_rpc_trace_for_server(server_id, window, cx);
1632                                        }
1633                                        LogKind::Trace => {
1634                                            view.show_trace_for_server(server_id, window, cx)
1635                                        }
1636                                        LogKind::Logs => {
1637                                            view.show_logs_for_server(server_id, window, cx)
1638                                        }
1639                                        LogKind::ServerInfo => {
1640                                            view.show_server_info(server_id, window, cx)
1641                                        }
1642                                    }
1643                                    cx.notify();
1644                                }),
1645                            );
1646                        }
1647                        menu
1648                    })
1649                    .into()
1650                }
1651            });
1652
1653        let view_selector = current_server.map(|server| {
1654            let server_id = server.server_id;
1655            let rpc_trace_enabled = server.rpc_trace_enabled;
1656            let log_view = log_view.clone();
1657            PopoverMenu::new("LspViewSelector")
1658                .anchor(Corner::TopLeft)
1659                .trigger(
1660                    Button::new("language_server_menu_header", server.selected_entry.label())
1661                        .icon(IconName::ChevronDown)
1662                        .icon_size(IconSize::Small)
1663                        .icon_color(Color::Muted),
1664                )
1665                .menu(move |window, cx| {
1666                    let log_toolbar_view = log_toolbar_view.clone();
1667                    let log_view = log_view.clone();
1668                    Some(ContextMenu::build(window, cx, move |this, window, _| {
1669                        this.entry(
1670                            SERVER_LOGS,
1671                            None,
1672                            window.handler_for(&log_view, move |view, window, cx| {
1673                                view.show_logs_for_server(server_id, window, cx);
1674                            }),
1675                        )
1676                        .entry(
1677                            SERVER_TRACE,
1678                            None,
1679                            window.handler_for(&log_view, move |view, window, cx| {
1680                                view.show_trace_for_server(server_id, window, cx);
1681                            }),
1682                        )
1683                        .custom_entry(
1684                            {
1685                                let log_toolbar_view = log_toolbar_view.clone();
1686                                move |window, _| {
1687                                    h_flex()
1688                                        .w_full()
1689                                        .justify_between()
1690                                        .child(Label::new(RPC_MESSAGES))
1691                                        .child(
1692                                            div().child(
1693                                                Checkbox::new(
1694                                                    "LspLogEnableRpcTrace",
1695                                                    if rpc_trace_enabled {
1696                                                        ToggleState::Selected
1697                                                    } else {
1698                                                        ToggleState::Unselected
1699                                                    },
1700                                                )
1701                                                .on_click(window.listener_for(
1702                                                    &log_toolbar_view,
1703                                                    move |view, selection, window, cx| {
1704                                                        let enabled = matches!(
1705                                                            selection,
1706                                                            ToggleState::Selected
1707                                                        );
1708                                                        view.toggle_rpc_logging_for_server(
1709                                                            server_id, enabled, window, cx,
1710                                                        );
1711                                                        cx.stop_propagation();
1712                                                    },
1713                                                )),
1714                                            ),
1715                                        )
1716                                        .into_any_element()
1717                                }
1718                            },
1719                            window.handler_for(&log_view, move |view, window, cx| {
1720                                view.show_rpc_trace_for_server(server_id, window, cx);
1721                            }),
1722                        )
1723                        .entry(
1724                            SERVER_INFO,
1725                            None,
1726                            window.handler_for(&log_view, move |view, window, cx| {
1727                                view.show_server_info(server_id, window, cx);
1728                            }),
1729                        )
1730                    }))
1731                })
1732        });
1733
1734        h_flex()
1735            .size_full()
1736            .gap_1()
1737            .justify_between()
1738            .child(
1739                h_flex()
1740                    .gap_0p5()
1741                    .child(lsp_menu)
1742                    .children(view_selector)
1743                    .child(
1744                        log_view.update(cx, |this, _cx| match this.active_entry_kind {
1745                            LogKind::Trace => {
1746                                let log_view = log_view.clone();
1747                                div().child(
1748                                    PopoverMenu::new("lsp-trace-level-menu")
1749                                        .anchor(Corner::TopLeft)
1750                                        .trigger(
1751                                            Button::new(
1752                                                "language_server_trace_level_selector",
1753                                                "Trace level",
1754                                            )
1755                                            .icon(IconName::ChevronDown)
1756                                            .icon_size(IconSize::Small)
1757                                            .icon_color(Color::Muted),
1758                                        )
1759                                        .menu({
1760                                            let log_view = log_view;
1761
1762                                            move |window, cx| {
1763                                                let id = log_view.read(cx).current_server_id?;
1764
1765                                                let trace_level =
1766                                                    log_view.update(cx, |this, cx| {
1767                                                        this.log_store.update(cx, |this, _| {
1768                                                            Some(
1769                                                                this.get_language_server_state(id)?
1770                                                                    .trace_level,
1771                                                            )
1772                                                        })
1773                                                    })?;
1774
1775                                                ContextMenu::build(
1776                                                    window,
1777                                                    cx,
1778                                                    |mut menu, window, cx| {
1779                                                        let log_view = log_view.clone();
1780
1781                                                        for (option, label) in [
1782                                                            (TraceValue::Off, "Off"),
1783                                                            (TraceValue::Messages, "Messages"),
1784                                                            (TraceValue::Verbose, "Verbose"),
1785                                                        ] {
1786                                                            menu = menu.entry(label, None, {
1787                                                                let log_view = log_view.clone();
1788                                                                move |_, cx| {
1789                                                                    log_view.update(cx, |this, cx| {
1790                                                                    if let Some(id) =
1791                                                                        this.current_server_id
1792                                                                    {
1793                                                                        this.update_trace_level(
1794                                                                            id, option, cx,
1795                                                                        );
1796                                                                    }
1797                                                                });
1798                                                                }
1799                                                            });
1800                                                            if option == trace_level {
1801                                                                menu.select_last(window, cx);
1802                                                            }
1803                                                        }
1804
1805                                                        menu
1806                                                    },
1807                                                )
1808                                                .into()
1809                                            }
1810                                        }),
1811                                )
1812                            }
1813                            LogKind::Logs => {
1814                                let log_view = log_view.clone();
1815                                div().child(
1816                                    PopoverMenu::new("lsp-log-level-menu")
1817                                        .anchor(Corner::TopLeft)
1818                                        .trigger(
1819                                            Button::new(
1820                                                "language_server_log_level_selector",
1821                                                "Log level",
1822                                            )
1823                                            .icon(IconName::ChevronDown)
1824                                            .icon_size(IconSize::Small)
1825                                            .icon_color(Color::Muted),
1826                                        )
1827                                        .menu({
1828                                            let log_view = log_view;
1829
1830                                            move |window, cx| {
1831                                                let id = log_view.read(cx).current_server_id?;
1832
1833                                                let log_level =
1834                                                    log_view.update(cx, |this, cx| {
1835                                                        this.log_store.update(cx, |this, _| {
1836                                                            Some(
1837                                                                this.get_language_server_state(id)?
1838                                                                    .log_level,
1839                                                            )
1840                                                        })
1841                                                    })?;
1842
1843                                                ContextMenu::build(
1844                                                    window,
1845                                                    cx,
1846                                                    |mut menu, window, cx| {
1847                                                        let log_view = log_view.clone();
1848
1849                                                        for (option, label) in [
1850                                                            (MessageType::LOG, "Log"),
1851                                                            (MessageType::INFO, "Info"),
1852                                                            (MessageType::WARNING, "Warning"),
1853                                                            (MessageType::ERROR, "Error"),
1854                                                        ] {
1855                                                            menu = menu.entry(label, None, {
1856                                                                let log_view = log_view.clone();
1857                                                                move |window, cx| {
1858                                                                    log_view.update(cx, |this, cx| {
1859                                                                    if let Some(id) =
1860                                                                        this.current_server_id
1861                                                                    {
1862                                                                        this.update_log_level(
1863                                                                            id, option, window, cx,
1864                                                                        );
1865                                                                    }
1866                                                                });
1867                                                                }
1868                                                            });
1869                                                            if option == log_level {
1870                                                                menu.select_last(window, cx);
1871                                                            }
1872                                                        }
1873
1874                                                        menu
1875                                                    },
1876                                                )
1877                                                .into()
1878                                            }
1879                                        }),
1880                                )
1881                            }
1882                            _ => div(),
1883                        }),
1884                    ),
1885            )
1886            .child(
1887                Button::new("clear_log_button", "Clear").on_click(cx.listener(
1888                    |this, _, window, cx| {
1889                        if let Some(log_view) = this.log_view.as_ref() {
1890                            log_view.update(cx, |log_view, cx| {
1891                                log_view.editor.update(cx, |editor, cx| {
1892                                    editor.set_read_only(false);
1893                                    editor.clear(window, cx);
1894                                    editor.set_read_only(true);
1895                                });
1896                            })
1897                        }
1898                    },
1899                )),
1900            )
1901    }
1902}
1903
1904fn initialize_new_editor(
1905    content: String,
1906    move_to_end: bool,
1907    window: &mut Window,
1908    cx: &mut App,
1909) -> Entity<Editor> {
1910    cx.new(|cx| {
1911        let mut editor = Editor::multi_line(window, cx);
1912        editor.hide_minimap_by_default(window, cx);
1913        editor.set_text(content, window, cx);
1914        editor.set_show_git_diff_gutter(false, cx);
1915        editor.set_show_runnables(false, cx);
1916        editor.set_show_breakpoints(false, cx);
1917        editor.set_read_only(true);
1918        editor.set_show_edit_predictions(Some(false), window, cx);
1919        editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
1920        if move_to_end {
1921            editor.move_to_end(&MoveToEnd, window, cx);
1922        }
1923        editor
1924    })
1925}
1926
1927const RPC_MESSAGES: &str = "RPC Messages";
1928const SERVER_LOGS: &str = "Server Logs";
1929const SERVER_TRACE: &str = "Server Trace";
1930const SERVER_INFO: &str = "Server Info";
1931
1932impl LspLogToolbarItemView {
1933    pub fn new() -> Self {
1934        Self {
1935            log_view: None,
1936            _log_view_subscription: None,
1937        }
1938    }
1939
1940    fn toggle_rpc_logging_for_server(
1941        &mut self,
1942        id: LanguageServerId,
1943        enabled: bool,
1944        window: &mut Window,
1945        cx: &mut Context<Self>,
1946    ) {
1947        if let Some(log_view) = &self.log_view {
1948            log_view.update(cx, |log_view, cx| {
1949                log_view.toggle_rpc_trace_for_server(id, enabled, window, cx);
1950                if !enabled && Some(id) == log_view.current_server_id {
1951                    log_view.show_logs_for_server(id, window, cx);
1952                    cx.notify();
1953                } else if enabled {
1954                    log_view.show_rpc_trace_for_server(id, window, cx);
1955                    cx.notify();
1956                }
1957                window.focus(&log_view.focus_handle);
1958            });
1959        }
1960        cx.notify();
1961    }
1962}
1963
1964struct ServerInfo {
1965    id: LanguageServerId,
1966    capabilities: lsp::ServerCapabilities,
1967    binary: Option<LanguageServerBinary>,
1968    name: LanguageServerName,
1969    workspace_folders: Vec<String>,
1970    configuration: Option<serde_json::Value>,
1971}
1972
1973impl ServerInfo {
1974    fn new(server: &LanguageServer) -> Self {
1975        Self {
1976            id: server.server_id(),
1977            capabilities: server.capabilities(),
1978            binary: Some(server.binary().clone()),
1979            name: server.name(),
1980            workspace_folders: server
1981                .workspace_folders()
1982                .into_iter()
1983                .filter_map(|path| {
1984                    path.to_file_path()
1985                        .ok()
1986                        .map(|path| path.to_string_lossy().into_owned())
1987                })
1988                .collect::<Vec<_>>(),
1989            configuration: Some(server.configuration().clone()),
1990        }
1991    }
1992}
1993
1994#[derive(Debug)]
1995pub enum Event {
1996    NewServerLogEntry {
1997        id: LanguageServerId,
1998        kind: LanguageServerLogType,
1999        text: String,
2000    },
2001}
2002
2003impl EventEmitter<Event> for LogStore {}
2004impl EventEmitter<EditorEvent> for LspLogView {}
2005impl EventEmitter<SearchEvent> for LspLogView {}