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