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