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                    && 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_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                        && let Some(server_id) = first_server_id_for_project
 738                    {
 739                        match this.active_entry_kind {
 740                            LogKind::Rpc => this.show_rpc_trace_for_server(server_id, window, cx),
 741                            LogKind::Trace => this.show_trace_for_server(server_id, window, cx),
 742                            LogKind::Logs => this.show_logs_for_server(server_id, window, cx),
 743                            LogKind::ServerInfo => this.show_server_info(server_id, window, cx),
 744                        }
 745                    }
 746                } else if let Some(server_id) = first_server_id_for_project {
 747                    match this.active_entry_kind {
 748                        LogKind::Rpc => this.show_rpc_trace_for_server(server_id, window, cx),
 749                        LogKind::Trace => this.show_trace_for_server(server_id, window, cx),
 750                        LogKind::Logs => this.show_logs_for_server(server_id, window, cx),
 751                        LogKind::ServerInfo => this.show_server_info(server_id, window, cx),
 752                    }
 753                }
 754
 755                cx.notify();
 756            });
 757        let events_subscriptions = cx.subscribe_in(
 758            &log_store,
 759            window,
 760            move |log_view, _, e, window, cx| match e {
 761                Event::NewServerLogEntry { id, kind, text } => {
 762                    if log_view.current_server_id == Some(*id)
 763                        && *kind == log_view.active_entry_kind
 764                    {
 765                        log_view.editor.update(cx, |editor, cx| {
 766                            editor.set_read_only(false);
 767                            let last_offset = editor.buffer().read(cx).len(cx);
 768                            let newest_cursor_is_at_end =
 769                                editor.selections.newest::<usize>(cx).start >= last_offset;
 770                            editor.edit(
 771                                vec![
 772                                    (last_offset..last_offset, text.as_str()),
 773                                    (last_offset..last_offset, "\n"),
 774                                ],
 775                                cx,
 776                            );
 777                            if text.len() > 1024
 778                                && let Some((fold_offset, _)) =
 779                                    text.char_indices().dropping(1024).next()
 780                                && fold_offset < text.len()
 781                            {
 782                                editor.fold_ranges(
 783                                    vec![last_offset + fold_offset..last_offset + text.len()],
 784                                    false,
 785                                    window,
 786                                    cx,
 787                                );
 788                            }
 789
 790                            if newest_cursor_is_at_end {
 791                                editor.request_autoscroll(Autoscroll::bottom(), cx);
 792                            }
 793                            editor.set_read_only(true);
 794                        });
 795                    }
 796                }
 797            },
 798        );
 799        let (editor, editor_subscriptions) = Self::editor_for_logs(String::new(), window, cx);
 800
 801        let focus_handle = cx.focus_handle();
 802        let focus_subscription = cx.on_focus(&focus_handle, window, |log_view, window, cx| {
 803            window.focus(&log_view.editor.focus_handle(cx));
 804        });
 805
 806        let mut this = Self {
 807            focus_handle,
 808            editor,
 809            editor_subscriptions,
 810            project,
 811            log_store,
 812            current_server_id: None,
 813            active_entry_kind: LogKind::Logs,
 814            _log_store_subscriptions: vec![
 815                model_changes_subscription,
 816                events_subscriptions,
 817                focus_subscription,
 818            ],
 819        };
 820        if let Some(server_id) = server_id {
 821            this.show_logs_for_server(server_id, window, cx);
 822        }
 823        this
 824    }
 825
 826    fn editor_for_logs(
 827        log_contents: String,
 828        window: &mut Window,
 829        cx: &mut Context<Self>,
 830    ) -> (Entity<Editor>, Vec<Subscription>) {
 831        let editor = initialize_new_editor(log_contents, true, window, cx);
 832        let editor_subscription = cx.subscribe(
 833            &editor,
 834            |_, _, event: &EditorEvent, cx: &mut Context<LspLogView>| cx.emit(event.clone()),
 835        );
 836        let search_subscription = cx.subscribe(
 837            &editor,
 838            |_, _, event: &SearchEvent, cx: &mut Context<LspLogView>| cx.emit(event.clone()),
 839        );
 840        (editor, vec![editor_subscription, search_subscription])
 841    }
 842
 843    fn editor_for_server_info(
 844        server: &LanguageServer,
 845        window: &mut Window,
 846        cx: &mut Context<Self>,
 847    ) -> (Entity<Editor>, Vec<Subscription>) {
 848        let server_info = format!(
 849            "* Server: {NAME} (id {ID})
 850
 851* Binary: {BINARY:#?}
 852
 853* Registered workspace folders:
 854{WORKSPACE_FOLDERS}
 855
 856* Capabilities: {CAPABILITIES}
 857
 858* Configuration: {CONFIGURATION}",
 859            NAME = server.name(),
 860            ID = server.server_id(),
 861            BINARY = server.binary(),
 862            WORKSPACE_FOLDERS = server
 863                .workspace_folders()
 864                .into_iter()
 865                .filter_map(|path| path
 866                    .to_file_path()
 867                    .ok()
 868                    .map(|path| path.to_string_lossy().into_owned()))
 869                .collect::<Vec<_>>()
 870                .join(", "),
 871            CAPABILITIES = serde_json::to_string_pretty(&server.capabilities())
 872                .unwrap_or_else(|e| format!("Failed to serialize capabilities: {e}")),
 873            CONFIGURATION = serde_json::to_string_pretty(server.configuration())
 874                .unwrap_or_else(|e| format!("Failed to serialize configuration: {e}")),
 875        );
 876        let editor = initialize_new_editor(server_info, false, window, cx);
 877        let editor_subscription = cx.subscribe(
 878            &editor,
 879            |_, _, event: &EditorEvent, cx: &mut Context<LspLogView>| cx.emit(event.clone()),
 880        );
 881        let search_subscription = cx.subscribe(
 882            &editor,
 883            |_, _, event: &SearchEvent, cx: &mut Context<LspLogView>| cx.emit(event.clone()),
 884        );
 885        (editor, vec![editor_subscription, search_subscription])
 886    }
 887
 888    pub(crate) fn menu_items<'a>(&'a self, cx: &'a App) -> Option<Vec<LogMenuItem>> {
 889        let log_store = self.log_store.read(cx);
 890
 891        let unknown_server = LanguageServerName::new_static("unknown server");
 892
 893        let mut rows = log_store
 894            .language_servers
 895            .iter()
 896            .map(|(server_id, state)| match &state.kind {
 897                LanguageServerKind::Local { .. } | LanguageServerKind::Remote { .. } => {
 898                    let worktree_root_name = state
 899                        .worktree_id
 900                        .and_then(|id| self.project.read(cx).worktree_for_id(id, cx))
 901                        .map(|worktree| worktree.read(cx).root_name().to_string())
 902                        .unwrap_or_else(|| "Unknown worktree".to_string());
 903
 904                    LogMenuItem {
 905                        server_id: *server_id,
 906                        server_name: state.name.clone().unwrap_or(unknown_server.clone()),
 907                        server_kind: state.kind.clone(),
 908                        worktree_root_name,
 909                        rpc_trace_enabled: state.rpc_state.is_some(),
 910                        selected_entry: self.active_entry_kind,
 911                        trace_level: lsp::TraceValue::Off,
 912                    }
 913                }
 914
 915                LanguageServerKind::Global => LogMenuItem {
 916                    server_id: *server_id,
 917                    server_name: state.name.clone().unwrap_or(unknown_server.clone()),
 918                    server_kind: state.kind.clone(),
 919                    worktree_root_name: "supplementary".to_string(),
 920                    rpc_trace_enabled: state.rpc_state.is_some(),
 921                    selected_entry: self.active_entry_kind,
 922                    trace_level: lsp::TraceValue::Off,
 923                },
 924            })
 925            .chain(
 926                self.project
 927                    .read(cx)
 928                    .supplementary_language_servers(cx)
 929                    .filter_map(|(server_id, name)| {
 930                        let state = log_store.language_servers.get(&server_id)?;
 931                        Some(LogMenuItem {
 932                            server_id,
 933                            server_name: name.clone(),
 934                            server_kind: state.kind.clone(),
 935                            worktree_root_name: "supplementary".to_string(),
 936                            rpc_trace_enabled: state.rpc_state.is_some(),
 937                            selected_entry: self.active_entry_kind,
 938                            trace_level: lsp::TraceValue::Off,
 939                        })
 940                    }),
 941            )
 942            .collect::<Vec<_>>();
 943        rows.sort_by_key(|row| row.server_id);
 944        rows.dedup_by_key(|row| row.server_id);
 945        Some(rows)
 946    }
 947
 948    fn show_logs_for_server(
 949        &mut self,
 950        server_id: LanguageServerId,
 951        window: &mut Window,
 952        cx: &mut Context<Self>,
 953    ) {
 954        let typ = self
 955            .log_store
 956            .read(cx)
 957            .language_servers
 958            .get(&server_id)
 959            .map(|v| v.log_level)
 960            .unwrap_or(MessageType::LOG);
 961        let log_contents = self
 962            .log_store
 963            .read(cx)
 964            .server_logs(server_id)
 965            .map(|v| log_contents(v, typ));
 966        if let Some(log_contents) = log_contents {
 967            self.current_server_id = Some(server_id);
 968            self.active_entry_kind = LogKind::Logs;
 969            let (editor, editor_subscriptions) = Self::editor_for_logs(log_contents, window, cx);
 970            self.editor = editor;
 971            self.editor_subscriptions = editor_subscriptions;
 972            cx.notify();
 973        }
 974        self.editor.read(cx).focus_handle(cx).focus(window);
 975    }
 976
 977    fn update_log_level(
 978        &self,
 979        server_id: LanguageServerId,
 980        level: MessageType,
 981        window: &mut Window,
 982        cx: &mut Context<Self>,
 983    ) {
 984        let log_contents = self.log_store.update(cx, |this, _| {
 985            if let Some(state) = this.get_language_server_state(server_id) {
 986                state.log_level = level;
 987            }
 988
 989            this.server_logs(server_id).map(|v| log_contents(v, level))
 990        });
 991
 992        if let Some(log_contents) = log_contents {
 993            self.editor.update(cx, |editor, cx| {
 994                editor.set_text(log_contents, window, cx);
 995                editor.move_to_end(&MoveToEnd, window, cx);
 996            });
 997            cx.notify();
 998        }
 999
1000        self.editor.read(cx).focus_handle(cx).focus(window);
1001    }
1002
1003    fn show_trace_for_server(
1004        &mut self,
1005        server_id: LanguageServerId,
1006        window: &mut Window,
1007        cx: &mut Context<Self>,
1008    ) {
1009        let log_contents = self
1010            .log_store
1011            .read(cx)
1012            .server_trace(server_id)
1013            .map(|v| log_contents(v, ()));
1014        if let Some(log_contents) = log_contents {
1015            self.current_server_id = Some(server_id);
1016            self.active_entry_kind = LogKind::Trace;
1017            let (editor, editor_subscriptions) = Self::editor_for_logs(log_contents, window, cx);
1018            self.editor = editor;
1019            self.editor_subscriptions = editor_subscriptions;
1020            cx.notify();
1021        }
1022        self.editor.read(cx).focus_handle(cx).focus(window);
1023    }
1024
1025    fn show_rpc_trace_for_server(
1026        &mut self,
1027        server_id: LanguageServerId,
1028        window: &mut Window,
1029        cx: &mut Context<Self>,
1030    ) {
1031        let rpc_log = self.log_store.update(cx, |log_store, _| {
1032            log_store
1033                .enable_rpc_trace_for_language_server(server_id)
1034                .map(|state| log_contents(&state.rpc_messages, ()))
1035        });
1036        if let Some(rpc_log) = rpc_log {
1037            self.current_server_id = Some(server_id);
1038            self.active_entry_kind = LogKind::Rpc;
1039            let (editor, editor_subscriptions) = Self::editor_for_logs(rpc_log, window, cx);
1040            let language = self.project.read(cx).languages().language_for_name("JSON");
1041            editor
1042                .read(cx)
1043                .buffer()
1044                .read(cx)
1045                .as_singleton()
1046                .expect("log buffer should be a singleton")
1047                .update(cx, |_, cx| {
1048                    cx.spawn({
1049                        let buffer = cx.entity();
1050                        async move |_, cx| {
1051                            let language = language.await.ok();
1052                            buffer.update(cx, |buffer, cx| {
1053                                buffer.set_language(language, cx);
1054                            })
1055                        }
1056                    })
1057                    .detach_and_log_err(cx);
1058                });
1059
1060            self.editor = editor;
1061            self.editor_subscriptions = editor_subscriptions;
1062            cx.notify();
1063        }
1064
1065        self.editor.read(cx).focus_handle(cx).focus(window);
1066    }
1067
1068    fn toggle_rpc_trace_for_server(
1069        &mut self,
1070        server_id: LanguageServerId,
1071        enabled: bool,
1072        window: &mut Window,
1073        cx: &mut Context<Self>,
1074    ) {
1075        self.log_store.update(cx, |log_store, _| {
1076            if enabled {
1077                log_store.enable_rpc_trace_for_language_server(server_id);
1078            } else {
1079                log_store.disable_rpc_trace_for_language_server(server_id);
1080            }
1081        });
1082        if !enabled && Some(server_id) == self.current_server_id {
1083            self.show_logs_for_server(server_id, window, cx);
1084            cx.notify();
1085        }
1086    }
1087
1088    fn update_trace_level(
1089        &self,
1090        server_id: LanguageServerId,
1091        level: TraceValue,
1092        cx: &mut Context<Self>,
1093    ) {
1094        if let Some(server) = self
1095            .project
1096            .read(cx)
1097            .lsp_store()
1098            .read(cx)
1099            .language_server_for_id(server_id)
1100        {
1101            self.log_store.update(cx, |this, _| {
1102                if let Some(state) = this.get_language_server_state(server_id) {
1103                    state.trace_level = level;
1104                }
1105            });
1106
1107            server
1108                .notify::<SetTrace>(&SetTraceParams { value: level })
1109                .ok();
1110        }
1111    }
1112
1113    fn show_server_info(
1114        &mut self,
1115        server_id: LanguageServerId,
1116        window: &mut Window,
1117        cx: &mut Context<Self>,
1118    ) {
1119        let lsp_store = self.project.read(cx).lsp_store();
1120        let Some(server) = lsp_store.read(cx).language_server_for_id(server_id) else {
1121            return;
1122        };
1123        self.current_server_id = Some(server_id);
1124        self.active_entry_kind = LogKind::ServerInfo;
1125        let (editor, editor_subscriptions) = Self::editor_for_server_info(&server, window, cx);
1126        self.editor = editor;
1127        self.editor_subscriptions = editor_subscriptions;
1128        cx.notify();
1129        self.editor.read(cx).focus_handle(cx).focus(window);
1130    }
1131}
1132
1133fn log_contents<T: Message>(lines: &VecDeque<T>, level: <T as Message>::Level) -> String {
1134    lines
1135        .iter()
1136        .filter(|message| message.should_include(level))
1137        .flat_map(|message| [message.as_ref(), "\n"])
1138        .collect()
1139}
1140
1141impl Render for LspLogView {
1142    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1143        self.editor.update(cx, |editor, cx| {
1144            editor.render(window, cx).into_any_element()
1145        })
1146    }
1147}
1148
1149impl Focusable for LspLogView {
1150    fn focus_handle(&self, _: &App) -> FocusHandle {
1151        self.focus_handle.clone()
1152    }
1153}
1154
1155impl Item for LspLogView {
1156    type Event = EditorEvent;
1157
1158    fn to_item_events(event: &Self::Event, f: impl FnMut(workspace::item::ItemEvent)) {
1159        Editor::to_item_events(event, f)
1160    }
1161
1162    fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
1163        "LSP Logs".into()
1164    }
1165
1166    fn telemetry_event_text(&self) -> Option<&'static str> {
1167        None
1168    }
1169
1170    fn as_searchable(&self, handle: &Entity<Self>) -> Option<Box<dyn SearchableItemHandle>> {
1171        Some(Box::new(handle.clone()))
1172    }
1173
1174    fn act_as_type<'a>(
1175        &'a self,
1176        type_id: TypeId,
1177        self_handle: &'a Entity<Self>,
1178        _: &'a App,
1179    ) -> Option<AnyView> {
1180        if type_id == TypeId::of::<Self>() {
1181            Some(self_handle.to_any())
1182        } else if type_id == TypeId::of::<Editor>() {
1183            Some(self.editor.to_any())
1184        } else {
1185            None
1186        }
1187    }
1188
1189    fn clone_on_split(
1190        &self,
1191        _workspace_id: Option<WorkspaceId>,
1192        window: &mut Window,
1193        cx: &mut Context<Self>,
1194    ) -> Option<Entity<Self>>
1195    where
1196        Self: Sized,
1197    {
1198        Some(cx.new(|cx| {
1199            let mut new_view = Self::new(self.project.clone(), self.log_store.clone(), window, cx);
1200            if let Some(server_id) = self.current_server_id {
1201                match self.active_entry_kind {
1202                    LogKind::Rpc => new_view.show_rpc_trace_for_server(server_id, window, cx),
1203                    LogKind::Trace => new_view.show_trace_for_server(server_id, window, cx),
1204                    LogKind::Logs => new_view.show_logs_for_server(server_id, window, cx),
1205                    LogKind::ServerInfo => new_view.show_server_info(server_id, window, cx),
1206                }
1207            }
1208            new_view
1209        }))
1210    }
1211}
1212
1213impl SearchableItem for LspLogView {
1214    type Match = <Editor as SearchableItem>::Match;
1215
1216    fn clear_matches(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1217        self.editor.update(cx, |e, cx| e.clear_matches(window, cx))
1218    }
1219
1220    fn update_matches(
1221        &mut self,
1222        matches: &[Self::Match],
1223        window: &mut Window,
1224        cx: &mut Context<Self>,
1225    ) {
1226        self.editor
1227            .update(cx, |e, cx| e.update_matches(matches, window, cx))
1228    }
1229
1230    fn query_suggestion(&mut self, window: &mut Window, cx: &mut Context<Self>) -> String {
1231        self.editor
1232            .update(cx, |e, cx| e.query_suggestion(window, cx))
1233    }
1234
1235    fn activate_match(
1236        &mut self,
1237        index: usize,
1238        matches: &[Self::Match],
1239        window: &mut Window,
1240        cx: &mut Context<Self>,
1241    ) {
1242        self.editor
1243            .update(cx, |e, cx| e.activate_match(index, matches, window, cx))
1244    }
1245
1246    fn select_matches(
1247        &mut self,
1248        matches: &[Self::Match],
1249        window: &mut Window,
1250        cx: &mut Context<Self>,
1251    ) {
1252        self.editor
1253            .update(cx, |e, cx| e.select_matches(matches, window, cx))
1254    }
1255
1256    fn find_matches(
1257        &mut self,
1258        query: Arc<project::search::SearchQuery>,
1259        window: &mut Window,
1260        cx: &mut Context<Self>,
1261    ) -> gpui::Task<Vec<Self::Match>> {
1262        self.editor
1263            .update(cx, |e, cx| e.find_matches(query, window, cx))
1264    }
1265
1266    fn replace(
1267        &mut self,
1268        _: &Self::Match,
1269        _: &SearchQuery,
1270        _window: &mut Window,
1271        _: &mut Context<Self>,
1272    ) {
1273        // Since LSP Log is read-only, it doesn't make sense to support replace operation.
1274    }
1275    fn supported_options(&self) -> workspace::searchable::SearchOptions {
1276        workspace::searchable::SearchOptions {
1277            case: true,
1278            word: true,
1279            regex: true,
1280            find_in_results: false,
1281            // LSP log is read-only.
1282            replacement: false,
1283            selection: false,
1284        }
1285    }
1286    fn active_match_index(
1287        &mut self,
1288        direction: Direction,
1289        matches: &[Self::Match],
1290        window: &mut Window,
1291        cx: &mut Context<Self>,
1292    ) -> Option<usize> {
1293        self.editor.update(cx, |e, cx| {
1294            e.active_match_index(direction, matches, window, cx)
1295        })
1296    }
1297}
1298
1299impl EventEmitter<ToolbarItemEvent> for LspLogToolbarItemView {}
1300
1301impl ToolbarItemView for LspLogToolbarItemView {
1302    fn set_active_pane_item(
1303        &mut self,
1304        active_pane_item: Option<&dyn ItemHandle>,
1305        _: &mut Window,
1306        cx: &mut Context<Self>,
1307    ) -> workspace::ToolbarItemLocation {
1308        if let Some(item) = active_pane_item
1309            && let Some(log_view) = item.downcast::<LspLogView>()
1310        {
1311            self.log_view = Some(log_view.clone());
1312            self._log_view_subscription = Some(cx.observe(&log_view, |_, _, cx| {
1313                cx.notify();
1314            }));
1315            return ToolbarItemLocation::PrimaryLeft;
1316        }
1317        self.log_view = None;
1318        self._log_view_subscription = None;
1319        ToolbarItemLocation::Hidden
1320    }
1321}
1322
1323impl Render for LspLogToolbarItemView {
1324    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1325        let Some(log_view) = self.log_view.clone() else {
1326            return div();
1327        };
1328
1329        let (menu_rows, current_server_id) = log_view.update(cx, |log_view, cx| {
1330            let menu_rows = log_view.menu_items(cx).unwrap_or_default();
1331            let current_server_id = log_view.current_server_id;
1332            (menu_rows, current_server_id)
1333        });
1334
1335        let current_server = current_server_id.and_then(|current_server_id| {
1336            if let Ok(ix) = menu_rows.binary_search_by_key(&current_server_id, |e| e.server_id) {
1337                Some(menu_rows[ix].clone())
1338            } else {
1339                None
1340            }
1341        });
1342
1343        let available_language_servers: Vec<_> = menu_rows
1344            .into_iter()
1345            .map(|row| {
1346                (
1347                    row.server_id,
1348                    row.server_name,
1349                    row.worktree_root_name,
1350                    row.selected_entry,
1351                )
1352            })
1353            .collect();
1354
1355        let log_toolbar_view = cx.entity();
1356
1357        let lsp_menu = PopoverMenu::new("LspLogView")
1358            .anchor(Corner::TopLeft)
1359            .trigger(
1360                Button::new(
1361                    "language_server_menu_header",
1362                    current_server
1363                        .as_ref()
1364                        .map(|row| {
1365                            Cow::Owned(format!(
1366                                "{} ({})",
1367                                row.server_name.0, row.worktree_root_name,
1368                            ))
1369                        })
1370                        .unwrap_or_else(|| "No server selected".into()),
1371                )
1372                .icon(IconName::ChevronDown)
1373                .icon_size(IconSize::Small)
1374                .icon_color(Color::Muted),
1375            )
1376            .menu({
1377                let log_view = log_view.clone();
1378                move |window, cx| {
1379                    let log_view = log_view.clone();
1380                    ContextMenu::build(window, cx, |mut menu, window, _| {
1381                        for (server_id, name, worktree_root, active_entry_kind) in
1382                            available_language_servers.iter()
1383                        {
1384                            let label = format!("{} ({})", name, worktree_root);
1385                            let server_id = *server_id;
1386                            let active_entry_kind = *active_entry_kind;
1387                            menu = menu.entry(
1388                                label,
1389                                None,
1390                                window.handler_for(&log_view, move |view, window, cx| {
1391                                    view.current_server_id = Some(server_id);
1392                                    view.active_entry_kind = active_entry_kind;
1393                                    match view.active_entry_kind {
1394                                        LogKind::Rpc => {
1395                                            view.toggle_rpc_trace_for_server(
1396                                                server_id, true, window, cx,
1397                                            );
1398                                            view.show_rpc_trace_for_server(server_id, window, cx);
1399                                        }
1400                                        LogKind::Trace => {
1401                                            view.show_trace_for_server(server_id, window, cx)
1402                                        }
1403                                        LogKind::Logs => {
1404                                            view.show_logs_for_server(server_id, window, cx)
1405                                        }
1406                                        LogKind::ServerInfo => {
1407                                            view.show_server_info(server_id, window, cx)
1408                                        }
1409                                    }
1410                                    cx.notify();
1411                                }),
1412                            );
1413                        }
1414                        menu
1415                    })
1416                    .into()
1417                }
1418            });
1419
1420        let view_selector = current_server.map(|server| {
1421            let server_id = server.server_id;
1422            let is_remote = server.server_kind.is_remote();
1423            let rpc_trace_enabled = server.rpc_trace_enabled;
1424            let log_view = log_view.clone();
1425            PopoverMenu::new("LspViewSelector")
1426                .anchor(Corner::TopLeft)
1427                .trigger(
1428                    Button::new("language_server_menu_header", server.selected_entry.label())
1429                        .icon(IconName::ChevronDown)
1430                        .icon_size(IconSize::Small)
1431                        .icon_color(Color::Muted),
1432                )
1433                .menu(move |window, cx| {
1434                    let log_toolbar_view = log_toolbar_view.clone();
1435                    let log_view = log_view.clone();
1436                    Some(ContextMenu::build(window, cx, move |this, window, _| {
1437                        this.entry(
1438                            SERVER_LOGS,
1439                            None,
1440                            window.handler_for(&log_view, move |view, window, cx| {
1441                                view.show_logs_for_server(server_id, window, cx);
1442                            }),
1443                        )
1444                        .when(!is_remote, |this| {
1445                            this.entry(
1446                                SERVER_TRACE,
1447                                None,
1448                                window.handler_for(&log_view, move |view, window, cx| {
1449                                    view.show_trace_for_server(server_id, window, cx);
1450                                }),
1451                            )
1452                            .custom_entry(
1453                                {
1454                                    let log_toolbar_view = log_toolbar_view.clone();
1455                                    move |window, _| {
1456                                        h_flex()
1457                                            .w_full()
1458                                            .justify_between()
1459                                            .child(Label::new(RPC_MESSAGES))
1460                                            .child(
1461                                                div().child(
1462                                                    Checkbox::new(
1463                                                        "LspLogEnableRpcTrace",
1464                                                        if rpc_trace_enabled {
1465                                                            ToggleState::Selected
1466                                                        } else {
1467                                                            ToggleState::Unselected
1468                                                        },
1469                                                    )
1470                                                    .on_click(window.listener_for(
1471                                                        &log_toolbar_view,
1472                                                        move |view, selection, window, cx| {
1473                                                            let enabled = matches!(
1474                                                                selection,
1475                                                                ToggleState::Selected
1476                                                            );
1477                                                            view.toggle_rpc_logging_for_server(
1478                                                                server_id, enabled, window, cx,
1479                                                            );
1480                                                            cx.stop_propagation();
1481                                                        },
1482                                                    )),
1483                                                ),
1484                                            )
1485                                            .into_any_element()
1486                                    }
1487                                },
1488                                window.handler_for(&log_view, move |view, window, cx| {
1489                                    view.show_rpc_trace_for_server(server_id, window, cx);
1490                                }),
1491                            )
1492                        })
1493                        .entry(
1494                            SERVER_INFO,
1495                            None,
1496                            window.handler_for(&log_view, move |view, window, cx| {
1497                                view.show_server_info(server_id, window, cx);
1498                            }),
1499                        )
1500                    }))
1501                })
1502        });
1503
1504        h_flex()
1505            .size_full()
1506            .gap_1()
1507            .justify_between()
1508            .child(
1509                h_flex()
1510                    .gap_0p5()
1511                    .child(lsp_menu)
1512                    .children(view_selector)
1513                    .child(
1514                        log_view.update(cx, |this, _cx| match this.active_entry_kind {
1515                            LogKind::Trace => {
1516                                let log_view = log_view.clone();
1517                                div().child(
1518                                    PopoverMenu::new("lsp-trace-level-menu")
1519                                        .anchor(Corner::TopLeft)
1520                                        .trigger(
1521                                            Button::new(
1522                                                "language_server_trace_level_selector",
1523                                                "Trace level",
1524                                            )
1525                                            .icon(IconName::ChevronDown)
1526                                            .icon_size(IconSize::Small)
1527                                            .icon_color(Color::Muted),
1528                                        )
1529                                        .menu({
1530                                            let log_view = log_view.clone();
1531
1532                                            move |window, cx| {
1533                                                let id = log_view.read(cx).current_server_id?;
1534
1535                                                let trace_level =
1536                                                    log_view.update(cx, |this, cx| {
1537                                                        this.log_store.update(cx, |this, _| {
1538                                                            Some(
1539                                                                this.get_language_server_state(id)?
1540                                                                    .trace_level,
1541                                                            )
1542                                                        })
1543                                                    })?;
1544
1545                                                ContextMenu::build(
1546                                                    window,
1547                                                    cx,
1548                                                    |mut menu, window, cx| {
1549                                                        let log_view = log_view.clone();
1550
1551                                                        for (option, label) in [
1552                                                            (TraceValue::Off, "Off"),
1553                                                            (TraceValue::Messages, "Messages"),
1554                                                            (TraceValue::Verbose, "Verbose"),
1555                                                        ] {
1556                                                            menu = menu.entry(label, None, {
1557                                                                let log_view = log_view.clone();
1558                                                                move |_, cx| {
1559                                                                    log_view.update(cx, |this, cx| {
1560                                                                    if let Some(id) =
1561                                                                        this.current_server_id
1562                                                                    {
1563                                                                        this.update_trace_level(
1564                                                                            id, option, cx,
1565                                                                        );
1566                                                                    }
1567                                                                });
1568                                                                }
1569                                                            });
1570                                                            if option == trace_level {
1571                                                                menu.select_last(window, cx);
1572                                                            }
1573                                                        }
1574
1575                                                        menu
1576                                                    },
1577                                                )
1578                                                .into()
1579                                            }
1580                                        }),
1581                                )
1582                            }
1583                            LogKind::Logs => {
1584                                let log_view = log_view.clone();
1585                                div().child(
1586                                    PopoverMenu::new("lsp-log-level-menu")
1587                                        .anchor(Corner::TopLeft)
1588                                        .trigger(
1589                                            Button::new(
1590                                                "language_server_log_level_selector",
1591                                                "Log level",
1592                                            )
1593                                            .icon(IconName::ChevronDown)
1594                                            .icon_size(IconSize::Small)
1595                                            .icon_color(Color::Muted),
1596                                        )
1597                                        .menu({
1598                                            let log_view = log_view.clone();
1599
1600                                            move |window, cx| {
1601                                                let id = log_view.read(cx).current_server_id?;
1602
1603                                                let log_level =
1604                                                    log_view.update(cx, |this, cx| {
1605                                                        this.log_store.update(cx, |this, _| {
1606                                                            Some(
1607                                                                this.get_language_server_state(id)?
1608                                                                    .log_level,
1609                                                            )
1610                                                        })
1611                                                    })?;
1612
1613                                                ContextMenu::build(
1614                                                    window,
1615                                                    cx,
1616                                                    |mut menu, window, cx| {
1617                                                        let log_view = log_view.clone();
1618
1619                                                        for (option, label) in [
1620                                                            (MessageType::LOG, "Log"),
1621                                                            (MessageType::INFO, "Info"),
1622                                                            (MessageType::WARNING, "Warning"),
1623                                                            (MessageType::ERROR, "Error"),
1624                                                        ] {
1625                                                            menu = menu.entry(label, None, {
1626                                                                let log_view = log_view.clone();
1627                                                                move |window, cx| {
1628                                                                    log_view.update(cx, |this, cx| {
1629                                                                    if let Some(id) =
1630                                                                        this.current_server_id
1631                                                                    {
1632                                                                        this.update_log_level(
1633                                                                            id, option, window, cx,
1634                                                                        );
1635                                                                    }
1636                                                                });
1637                                                                }
1638                                                            });
1639                                                            if option == log_level {
1640                                                                menu.select_last(window, cx);
1641                                                            }
1642                                                        }
1643
1644                                                        menu
1645                                                    },
1646                                                )
1647                                                .into()
1648                                            }
1649                                        }),
1650                                )
1651                            }
1652                            _ => div(),
1653                        }),
1654                    ),
1655            )
1656            .child(
1657                Button::new("clear_log_button", "Clear").on_click(cx.listener(
1658                    |this, _, window, cx| {
1659                        if let Some(log_view) = this.log_view.as_ref() {
1660                            log_view.update(cx, |log_view, cx| {
1661                                log_view.editor.update(cx, |editor, cx| {
1662                                    editor.set_read_only(false);
1663                                    editor.clear(window, cx);
1664                                    editor.set_read_only(true);
1665                                });
1666                            })
1667                        }
1668                    },
1669                )),
1670            )
1671    }
1672}
1673
1674fn initialize_new_editor(
1675    content: String,
1676    move_to_end: bool,
1677    window: &mut Window,
1678    cx: &mut App,
1679) -> Entity<Editor> {
1680    cx.new(|cx| {
1681        let mut editor = Editor::multi_line(window, cx);
1682        editor.hide_minimap_by_default(window, cx);
1683        editor.set_text(content, window, cx);
1684        editor.set_show_git_diff_gutter(false, cx);
1685        editor.set_show_runnables(false, cx);
1686        editor.set_show_breakpoints(false, cx);
1687        editor.set_read_only(true);
1688        editor.set_show_edit_predictions(Some(false), window, cx);
1689        editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
1690        if move_to_end {
1691            editor.move_to_end(&MoveToEnd, window, cx);
1692        }
1693        editor
1694    })
1695}
1696
1697const RPC_MESSAGES: &str = "RPC Messages";
1698const SERVER_LOGS: &str = "Server Logs";
1699const SERVER_TRACE: &str = "Server Trace";
1700const SERVER_INFO: &str = "Server Info";
1701
1702impl Default for LspLogToolbarItemView {
1703    fn default() -> Self {
1704        Self::new()
1705    }
1706}
1707
1708impl LspLogToolbarItemView {
1709    pub fn new() -> Self {
1710        Self {
1711            log_view: None,
1712            _log_view_subscription: None,
1713        }
1714    }
1715
1716    fn toggle_rpc_logging_for_server(
1717        &mut self,
1718        id: LanguageServerId,
1719        enabled: bool,
1720        window: &mut Window,
1721        cx: &mut Context<Self>,
1722    ) {
1723        if let Some(log_view) = &self.log_view {
1724            log_view.update(cx, |log_view, cx| {
1725                log_view.toggle_rpc_trace_for_server(id, enabled, window, cx);
1726                if !enabled && Some(id) == log_view.current_server_id {
1727                    log_view.show_logs_for_server(id, window, cx);
1728                    cx.notify();
1729                } else if enabled {
1730                    log_view.show_rpc_trace_for_server(id, window, cx);
1731                    cx.notify();
1732                }
1733                window.focus(&log_view.focus_handle);
1734            });
1735        }
1736        cx.notify();
1737    }
1738}
1739
1740pub enum Event {
1741    NewServerLogEntry {
1742        id: LanguageServerId,
1743        kind: LogKind,
1744        text: String,
1745    },
1746}
1747
1748impl EventEmitter<Event> for LogStore {}
1749impl EventEmitter<Event> for LspLogView {}
1750impl EventEmitter<EditorEvent> for LspLogView {}
1751impl EventEmitter<SearchEvent> for LspLogView {}