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                        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        let this = Self {
 290            copilot_log_subscription: None,
 291            _copilot_subscription: copilot_subscription,
 292            projects: HashMap::default(),
 293            language_servers: HashMap::default(),
 294            io_tx,
 295        };
 296
 297        cx.spawn(async move |this, cx| {
 298            while let Some((server_id, io_kind, message)) = io_rx.next().await {
 299                if let Some(this) = this.upgrade() {
 300                    this.update(cx, |this, cx| {
 301                        this.on_io(server_id, io_kind, &message, cx);
 302                    })?;
 303                }
 304            }
 305            anyhow::Ok(())
 306        })
 307        .detach_and_log_err(cx);
 308        this
 309    }
 310
 311    pub fn add_project(&mut self, project: &Entity<Project>, cx: &mut Context<Self>) {
 312        let weak_project = project.downgrade();
 313        self.projects.insert(
 314            project.downgrade(),
 315            ProjectState {
 316                _subscriptions: [
 317                    cx.observe_release(project, move |this, _, _| {
 318                        this.projects.remove(&weak_project);
 319                        this.language_servers
 320                            .retain(|_, state| state.kind.project() != Some(&weak_project));
 321                    }),
 322                    cx.subscribe(project, |this, project, event, cx| {
 323                        let server_kind = if project.read(cx).is_via_ssh() {
 324                            LanguageServerKind::Remote {
 325                                project: project.downgrade(),
 326                            }
 327                        } else {
 328                            LanguageServerKind::Local {
 329                                project: project.downgrade(),
 330                            }
 331                        };
 332
 333                        match event {
 334                            project::Event::LanguageServerAdded(id, name, worktree_id) => {
 335                                this.add_language_server(
 336                                    server_kind,
 337                                    *id,
 338                                    Some(name.clone()),
 339                                    *worktree_id,
 340                                    project
 341                                        .read(cx)
 342                                        .lsp_store()
 343                                        .read(cx)
 344                                        .language_server_for_id(*id),
 345                                    cx,
 346                                );
 347                            }
 348                            project::Event::LanguageServerRemoved(id) => {
 349                                this.remove_language_server(*id, cx);
 350                            }
 351                            project::Event::LanguageServerLog(id, typ, message) => {
 352                                this.add_language_server(server_kind, *id, None, None, None, cx);
 353                                match typ {
 354                                    project::LanguageServerLogType::Log(typ) => {
 355                                        this.add_language_server_log(*id, *typ, message, cx);
 356                                    }
 357                                    project::LanguageServerLogType::Trace(_) => {
 358                                        this.add_language_server_trace(*id, message, cx);
 359                                    }
 360                                }
 361                            }
 362                            _ => {}
 363                        }
 364                    }),
 365                ],
 366            },
 367        );
 368    }
 369
 370    pub(super) fn get_language_server_state(
 371        &mut self,
 372        id: LanguageServerId,
 373    ) -> Option<&mut LanguageServerState> {
 374        self.language_servers.get_mut(&id)
 375    }
 376
 377    fn add_language_server(
 378        &mut self,
 379        kind: LanguageServerKind,
 380        server_id: LanguageServerId,
 381        name: Option<LanguageServerName>,
 382        worktree_id: Option<WorktreeId>,
 383        server: Option<Arc<LanguageServer>>,
 384        cx: &mut Context<Self>,
 385    ) -> Option<&mut LanguageServerState> {
 386        let server_state = self.language_servers.entry(server_id).or_insert_with(|| {
 387            cx.notify();
 388            LanguageServerState {
 389                name: None,
 390                worktree_id: None,
 391                kind,
 392                rpc_state: None,
 393                log_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES),
 394                trace_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES),
 395                trace_level: TraceValue::Off,
 396                log_level: MessageType::LOG,
 397                io_logs_subscription: None,
 398            }
 399        });
 400
 401        if let Some(name) = name {
 402            server_state.name = Some(name);
 403        }
 404        if let Some(worktree_id) = worktree_id {
 405            server_state.worktree_id = Some(worktree_id);
 406        }
 407
 408        if let Some(server) = server
 409            .clone()
 410            .filter(|_| server_state.io_logs_subscription.is_none())
 411        {
 412            let io_tx = self.io_tx.clone();
 413            let server_id = server.server_id();
 414            server_state.io_logs_subscription = Some(server.on_io(move |io_kind, message| {
 415                io_tx
 416                    .unbounded_send((server_id, io_kind, message.to_string()))
 417                    .ok();
 418            }));
 419        }
 420
 421        Some(server_state)
 422    }
 423
 424    fn add_language_server_log(
 425        &mut self,
 426        id: LanguageServerId,
 427        typ: MessageType,
 428        message: &str,
 429        cx: &mut Context<Self>,
 430    ) -> Option<()> {
 431        let language_server_state = self.get_language_server_state(id)?;
 432
 433        let log_lines = &mut language_server_state.log_messages;
 434        Self::add_language_server_message(
 435            log_lines,
 436            id,
 437            LogMessage {
 438                message: message.trim_end().to_string(),
 439                typ,
 440            },
 441            language_server_state.log_level,
 442            LogKind::Logs,
 443            cx,
 444        );
 445        Some(())
 446    }
 447
 448    fn add_language_server_trace(
 449        &mut self,
 450        id: LanguageServerId,
 451        message: &str,
 452        cx: &mut Context<Self>,
 453    ) -> Option<()> {
 454        let language_server_state = self.get_language_server_state(id)?;
 455
 456        let log_lines = &mut language_server_state.trace_messages;
 457        Self::add_language_server_message(
 458            log_lines,
 459            id,
 460            TraceMessage {
 461                message: message.trim().to_string(),
 462            },
 463            (),
 464            LogKind::Trace,
 465            cx,
 466        );
 467        Some(())
 468    }
 469
 470    fn add_language_server_message<T: Message>(
 471        log_lines: &mut VecDeque<T>,
 472        id: LanguageServerId,
 473        message: T,
 474        current_severity: <T as Message>::Level,
 475        kind: LogKind,
 476        cx: &mut Context<Self>,
 477    ) {
 478        while log_lines.len() + 1 >= MAX_STORED_LOG_ENTRIES {
 479            log_lines.pop_front();
 480        }
 481        let text = message.as_ref().to_string();
 482        let visible = message.should_include(current_severity);
 483        log_lines.push_back(message);
 484
 485        if visible {
 486            cx.emit(Event::NewServerLogEntry { id, kind, text });
 487            cx.notify();
 488        }
 489    }
 490
 491    fn remove_language_server(&mut self, id: LanguageServerId, cx: &mut Context<Self>) {
 492        self.language_servers.remove(&id);
 493        cx.notify();
 494    }
 495
 496    pub(super) fn server_logs(&self, server_id: LanguageServerId) -> Option<&VecDeque<LogMessage>> {
 497        Some(&self.language_servers.get(&server_id)?.log_messages)
 498    }
 499
 500    pub(super) fn server_trace(
 501        &self,
 502        server_id: LanguageServerId,
 503    ) -> Option<&VecDeque<TraceMessage>> {
 504        Some(&self.language_servers.get(&server_id)?.trace_messages)
 505    }
 506
 507    fn server_ids_for_project<'a>(
 508        &'a self,
 509        lookup_project: &'a WeakEntity<Project>,
 510    ) -> impl Iterator<Item = LanguageServerId> + 'a {
 511        self.language_servers
 512            .iter()
 513            .filter_map(move |(id, state)| match &state.kind {
 514                LanguageServerKind::Local { project } | LanguageServerKind::Remote { project } => {
 515                    if project == lookup_project {
 516                        Some(*id)
 517                    } else {
 518                        None
 519                    }
 520                }
 521                LanguageServerKind::Global => Some(*id),
 522            })
 523    }
 524
 525    fn enable_rpc_trace_for_language_server(
 526        &mut self,
 527        server_id: LanguageServerId,
 528    ) -> Option<&mut LanguageServerRpcState> {
 529        let rpc_state = self
 530            .language_servers
 531            .get_mut(&server_id)?
 532            .rpc_state
 533            .get_or_insert_with(|| LanguageServerRpcState {
 534                rpc_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES),
 535                last_message_kind: None,
 536            });
 537        Some(rpc_state)
 538    }
 539
 540    pub fn disable_rpc_trace_for_language_server(
 541        &mut self,
 542        server_id: LanguageServerId,
 543    ) -> Option<()> {
 544        self.language_servers.get_mut(&server_id)?.rpc_state.take();
 545        Some(())
 546    }
 547
 548    pub fn has_server_logs(&self, server: &LanguageServerSelector) -> bool {
 549        match server {
 550            LanguageServerSelector::Id(id) => self.language_servers.contains_key(id),
 551            LanguageServerSelector::Name(name) => self
 552                .language_servers
 553                .iter()
 554                .any(|(_, state)| state.name.as_ref() == Some(name)),
 555        }
 556    }
 557
 558    pub fn open_server_log(
 559        &mut self,
 560        workspace: WeakEntity<Workspace>,
 561        server: LanguageServerSelector,
 562        window: &mut Window,
 563        cx: &mut Context<Self>,
 564    ) {
 565        cx.spawn_in(window, async move |log_store, cx| {
 566            let Some(log_store) = log_store.upgrade() else {
 567                return;
 568            };
 569            workspace
 570                .update_in(cx, |workspace, window, cx| {
 571                    let project = workspace.project().clone();
 572                    let tool_log_store = log_store.clone();
 573                    let log_view = get_or_create_tool(
 574                        workspace,
 575                        SplitDirection::Right,
 576                        window,
 577                        cx,
 578                        move |window, cx| LspLogView::new(project, tool_log_store, window, cx),
 579                    );
 580                    log_view.update(cx, |log_view, cx| {
 581                        let server_id = match server {
 582                            LanguageServerSelector::Id(id) => Some(id),
 583                            LanguageServerSelector::Name(name) => {
 584                                log_store.read(cx).language_servers.iter().find_map(
 585                                    |(id, state)| {
 586                                        if state.name.as_ref() == Some(&name) {
 587                                            Some(*id)
 588                                        } else {
 589                                            None
 590                                        }
 591                                    },
 592                                )
 593                            }
 594                        };
 595                        if let Some(server_id) = server_id {
 596                            log_view.show_logs_for_server(server_id, window, cx);
 597                        }
 598                    });
 599                })
 600                .ok();
 601        })
 602        .detach();
 603    }
 604
 605    pub fn open_server_trace(
 606        &mut self,
 607        workspace: WeakEntity<Workspace>,
 608        server: LanguageServerSelector,
 609        window: &mut Window,
 610        cx: &mut Context<Self>,
 611    ) {
 612        cx.spawn_in(window, async move |log_store, cx| {
 613            let Some(log_store) = log_store.upgrade() else {
 614                return;
 615            };
 616            workspace
 617                .update_in(cx, |workspace, window, cx| {
 618                    let project = workspace.project().clone();
 619                    let tool_log_store = log_store.clone();
 620                    let log_view = get_or_create_tool(
 621                        workspace,
 622                        SplitDirection::Right,
 623                        window,
 624                        cx,
 625                        move |window, cx| LspLogView::new(project, tool_log_store, window, cx),
 626                    );
 627                    log_view.update(cx, |log_view, cx| {
 628                        let server_id = match server {
 629                            LanguageServerSelector::Id(id) => Some(id),
 630                            LanguageServerSelector::Name(name) => {
 631                                log_store.read(cx).language_servers.iter().find_map(
 632                                    |(id, state)| {
 633                                        if state.name.as_ref() == Some(&name) {
 634                                            Some(*id)
 635                                        } else {
 636                                            None
 637                                        }
 638                                    },
 639                                )
 640                            }
 641                        };
 642                        if let Some(server_id) = server_id {
 643                            log_view.show_rpc_trace_for_server(server_id, window, cx);
 644                        }
 645                    });
 646                })
 647                .ok();
 648        })
 649        .detach();
 650    }
 651
 652    fn on_io(
 653        &mut self,
 654        language_server_id: LanguageServerId,
 655        io_kind: IoKind,
 656        message: &str,
 657        cx: &mut Context<Self>,
 658    ) -> Option<()> {
 659        let is_received = match io_kind {
 660            IoKind::StdOut => true,
 661            IoKind::StdIn => false,
 662            IoKind::StdErr => {
 663                self.add_language_server_log(language_server_id, MessageType::LOG, message, cx);
 664                return Some(());
 665            }
 666        };
 667
 668        let state = self
 669            .get_language_server_state(language_server_id)?
 670            .rpc_state
 671            .as_mut()?;
 672        let kind = if is_received {
 673            MessageKind::Receive
 674        } else {
 675            MessageKind::Send
 676        };
 677
 678        let rpc_log_lines = &mut state.rpc_messages;
 679        if state.last_message_kind != Some(kind) {
 680            while rpc_log_lines.len() + 1 >= MAX_STORED_LOG_ENTRIES {
 681                rpc_log_lines.pop_front();
 682            }
 683            let line_before_message = match kind {
 684                MessageKind::Send => SEND_LINE,
 685                MessageKind::Receive => RECEIVE_LINE,
 686            };
 687            rpc_log_lines.push_back(RpcMessage {
 688                message: line_before_message.to_string(),
 689            });
 690            cx.emit(Event::NewServerLogEntry {
 691                id: language_server_id,
 692                kind: LogKind::Rpc,
 693                text: line_before_message.to_string(),
 694            });
 695        }
 696
 697        while rpc_log_lines.len() + 1 >= MAX_STORED_LOG_ENTRIES {
 698            rpc_log_lines.pop_front();
 699        }
 700
 701        let message = message.trim();
 702        rpc_log_lines.push_back(RpcMessage {
 703            message: message.to_string(),
 704        });
 705        cx.emit(Event::NewServerLogEntry {
 706            id: language_server_id,
 707            kind: LogKind::Rpc,
 708            text: message.to_string(),
 709        });
 710        cx.notify();
 711        Some(())
 712    }
 713}
 714
 715impl LspLogView {
 716    pub fn new(
 717        project: Entity<Project>,
 718        log_store: Entity<LogStore>,
 719        window: &mut Window,
 720        cx: &mut Context<Self>,
 721    ) -> Self {
 722        let server_id = log_store
 723            .read(cx)
 724            .language_servers
 725            .iter()
 726            .find(|(_, server)| server.kind.project() == Some(&project.downgrade()))
 727            .map(|(id, _)| *id);
 728
 729        let weak_project = project.downgrade();
 730        let model_changes_subscription =
 731            cx.observe_in(&log_store, window, move |this, store, window, cx| {
 732                let first_server_id_for_project =
 733                    store.read(cx).server_ids_for_project(&weak_project).next();
 734                if let Some(current_lsp) = this.current_server_id {
 735                    if !store.read(cx).language_servers.contains_key(&current_lsp)
 736                        && let Some(server_id) = first_server_id_for_project {
 737                            match this.active_entry_kind {
 738                                LogKind::Rpc => {
 739                                    this.show_rpc_trace_for_server(server_id, window, cx)
 740                                }
 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                                        editor.fold_ranges(
 782                                            vec![
 783                                                last_offset + fold_offset..last_offset + text.len(),
 784                                            ],
 785                                            false,
 786                                            window,
 787                                            cx,
 788                                        );
 789                                    }
 790
 791                            if newest_cursor_is_at_end {
 792                                editor.request_autoscroll(Autoscroll::bottom(), cx);
 793                            }
 794                            editor.set_read_only(true);
 795                        });
 796                    }
 797                }
 798            },
 799        );
 800        let (editor, editor_subscriptions) = Self::editor_for_logs(String::new(), window, cx);
 801
 802        let focus_handle = cx.focus_handle();
 803        let focus_subscription = cx.on_focus(&focus_handle, window, |log_view, window, cx| {
 804            window.focus(&log_view.editor.focus_handle(cx));
 805        });
 806
 807        let mut this = Self {
 808            focus_handle,
 809            editor,
 810            editor_subscriptions,
 811            project,
 812            log_store,
 813            current_server_id: None,
 814            active_entry_kind: LogKind::Logs,
 815            _log_store_subscriptions: vec![
 816                model_changes_subscription,
 817                events_subscriptions,
 818                focus_subscription,
 819            ],
 820        };
 821        if let Some(server_id) = server_id {
 822            this.show_logs_for_server(server_id, window, cx);
 823        }
 824        this
 825    }
 826
 827    fn editor_for_logs(
 828        log_contents: String,
 829        window: &mut Window,
 830        cx: &mut Context<Self>,
 831    ) -> (Entity<Editor>, Vec<Subscription>) {
 832        let editor = initialize_new_editor(log_contents, true, window, cx);
 833        let editor_subscription = cx.subscribe(
 834            &editor,
 835            |_, _, event: &EditorEvent, cx: &mut Context<LspLogView>| cx.emit(event.clone()),
 836        );
 837        let search_subscription = cx.subscribe(
 838            &editor,
 839            |_, _, event: &SearchEvent, cx: &mut Context<LspLogView>| cx.emit(event.clone()),
 840        );
 841        (editor, vec![editor_subscription, search_subscription])
 842    }
 843
 844    fn editor_for_server_info(
 845        server: &LanguageServer,
 846        window: &mut Window,
 847        cx: &mut Context<Self>,
 848    ) -> (Entity<Editor>, Vec<Subscription>) {
 849        let server_info = format!(
 850            "* Server: {NAME} (id {ID})
 851
 852* Binary: {BINARY:#?}
 853
 854* Registered workspace folders:
 855{WORKSPACE_FOLDERS}
 856
 857* Capabilities: {CAPABILITIES}
 858
 859* Configuration: {CONFIGURATION}",
 860            NAME = server.name(),
 861            ID = server.server_id(),
 862            BINARY = server.binary(),
 863            WORKSPACE_FOLDERS = server
 864                .workspace_folders()
 865                .into_iter()
 866                .filter_map(|path| path
 867                    .to_file_path()
 868                    .ok()
 869                    .map(|path| path.to_string_lossy().into_owned()))
 870                .collect::<Vec<_>>()
 871                .join(", "),
 872            CAPABILITIES = serde_json::to_string_pretty(&server.capabilities())
 873                .unwrap_or_else(|e| format!("Failed to serialize capabilities: {e}")),
 874            CONFIGURATION = serde_json::to_string_pretty(server.configuration())
 875                .unwrap_or_else(|e| format!("Failed to serialize configuration: {e}")),
 876        );
 877        let editor = initialize_new_editor(server_info, false, window, cx);
 878        let editor_subscription = cx.subscribe(
 879            &editor,
 880            |_, _, event: &EditorEvent, cx: &mut Context<LspLogView>| cx.emit(event.clone()),
 881        );
 882        let search_subscription = cx.subscribe(
 883            &editor,
 884            |_, _, event: &SearchEvent, cx: &mut Context<LspLogView>| cx.emit(event.clone()),
 885        );
 886        (editor, vec![editor_subscription, search_subscription])
 887    }
 888
 889    pub(crate) fn menu_items<'a>(&'a self, cx: &'a App) -> Option<Vec<LogMenuItem>> {
 890        let log_store = self.log_store.read(cx);
 891
 892        let unknown_server = LanguageServerName::new_static("unknown server");
 893
 894        let mut rows = log_store
 895            .language_servers
 896            .iter()
 897            .map(|(server_id, state)| match &state.kind {
 898                LanguageServerKind::Local { .. } | LanguageServerKind::Remote { .. } => {
 899                    let worktree_root_name = state
 900                        .worktree_id
 901                        .and_then(|id| self.project.read(cx).worktree_for_id(id, cx))
 902                        .map(|worktree| worktree.read(cx).root_name().to_string())
 903                        .unwrap_or_else(|| "Unknown worktree".to_string());
 904
 905                    LogMenuItem {
 906                        server_id: *server_id,
 907                        server_name: state.name.clone().unwrap_or(unknown_server.clone()),
 908                        server_kind: state.kind.clone(),
 909                        worktree_root_name,
 910                        rpc_trace_enabled: state.rpc_state.is_some(),
 911                        selected_entry: self.active_entry_kind,
 912                        trace_level: lsp::TraceValue::Off,
 913                    }
 914                }
 915
 916                LanguageServerKind::Global => LogMenuItem {
 917                    server_id: *server_id,
 918                    server_name: state.name.clone().unwrap_or(unknown_server.clone()),
 919                    server_kind: state.kind.clone(),
 920                    worktree_root_name: "supplementary".to_string(),
 921                    rpc_trace_enabled: state.rpc_state.is_some(),
 922                    selected_entry: self.active_entry_kind,
 923                    trace_level: lsp::TraceValue::Off,
 924                },
 925            })
 926            .chain(
 927                self.project
 928                    .read(cx)
 929                    .supplementary_language_servers(cx)
 930                    .filter_map(|(server_id, name)| {
 931                        let state = log_store.language_servers.get(&server_id)?;
 932                        Some(LogMenuItem {
 933                            server_id,
 934                            server_name: name.clone(),
 935                            server_kind: state.kind.clone(),
 936                            worktree_root_name: "supplementary".to_string(),
 937                            rpc_trace_enabled: state.rpc_state.is_some(),
 938                            selected_entry: self.active_entry_kind,
 939                            trace_level: lsp::TraceValue::Off,
 940                        })
 941                    }),
 942            )
 943            .collect::<Vec<_>>();
 944        rows.sort_by_key(|row| row.server_id);
 945        rows.dedup_by_key(|row| row.server_id);
 946        Some(rows)
 947    }
 948
 949    fn show_logs_for_server(
 950        &mut self,
 951        server_id: LanguageServerId,
 952        window: &mut Window,
 953        cx: &mut Context<Self>,
 954    ) {
 955        let typ = self
 956            .log_store
 957            .read(cx)
 958            .language_servers
 959            .get(&server_id)
 960            .map(|v| v.log_level)
 961            .unwrap_or(MessageType::LOG);
 962        let log_contents = self
 963            .log_store
 964            .read(cx)
 965            .server_logs(server_id)
 966            .map(|v| log_contents(v, typ));
 967        if let Some(log_contents) = log_contents {
 968            self.current_server_id = Some(server_id);
 969            self.active_entry_kind = LogKind::Logs;
 970            let (editor, editor_subscriptions) = Self::editor_for_logs(log_contents, window, cx);
 971            self.editor = editor;
 972            self.editor_subscriptions = editor_subscriptions;
 973            cx.notify();
 974        }
 975        self.editor.read(cx).focus_handle(cx).focus(window);
 976    }
 977
 978    fn update_log_level(
 979        &self,
 980        server_id: LanguageServerId,
 981        level: MessageType,
 982        window: &mut Window,
 983        cx: &mut Context<Self>,
 984    ) {
 985        let log_contents = self.log_store.update(cx, |this, _| {
 986            if let Some(state) = this.get_language_server_state(server_id) {
 987                state.log_level = level;
 988            }
 989
 990            this.server_logs(server_id).map(|v| log_contents(v, level))
 991        });
 992
 993        if let Some(log_contents) = log_contents {
 994            self.editor.update(cx, |editor, cx| {
 995                editor.set_text(log_contents, window, cx);
 996                editor.move_to_end(&MoveToEnd, window, cx);
 997            });
 998            cx.notify();
 999        }
1000
1001        self.editor.read(cx).focus_handle(cx).focus(window);
1002    }
1003
1004    fn show_trace_for_server(
1005        &mut self,
1006        server_id: LanguageServerId,
1007        window: &mut Window,
1008        cx: &mut Context<Self>,
1009    ) {
1010        let log_contents = self
1011            .log_store
1012            .read(cx)
1013            .server_trace(server_id)
1014            .map(|v| log_contents(v, ()));
1015        if let Some(log_contents) = log_contents {
1016            self.current_server_id = Some(server_id);
1017            self.active_entry_kind = LogKind::Trace;
1018            let (editor, editor_subscriptions) = Self::editor_for_logs(log_contents, window, cx);
1019            self.editor = editor;
1020            self.editor_subscriptions = editor_subscriptions;
1021            cx.notify();
1022        }
1023        self.editor.read(cx).focus_handle(cx).focus(window);
1024    }
1025
1026    fn show_rpc_trace_for_server(
1027        &mut self,
1028        server_id: LanguageServerId,
1029        window: &mut Window,
1030        cx: &mut Context<Self>,
1031    ) {
1032        let rpc_log = self.log_store.update(cx, |log_store, _| {
1033            log_store
1034                .enable_rpc_trace_for_language_server(server_id)
1035                .map(|state| log_contents(&state.rpc_messages, ()))
1036        });
1037        if let Some(rpc_log) = rpc_log {
1038            self.current_server_id = Some(server_id);
1039            self.active_entry_kind = LogKind::Rpc;
1040            let (editor, editor_subscriptions) = Self::editor_for_logs(rpc_log, window, cx);
1041            let language = self.project.read(cx).languages().language_for_name("JSON");
1042            editor
1043                .read(cx)
1044                .buffer()
1045                .read(cx)
1046                .as_singleton()
1047                .expect("log buffer should be a singleton")
1048                .update(cx, |_, cx| {
1049                    cx.spawn({
1050                        let buffer = cx.entity();
1051                        async move |_, cx| {
1052                            let language = language.await.ok();
1053                            buffer.update(cx, |buffer, cx| {
1054                                buffer.set_language(language, cx);
1055                            })
1056                        }
1057                    })
1058                    .detach_and_log_err(cx);
1059                });
1060
1061            self.editor = editor;
1062            self.editor_subscriptions = editor_subscriptions;
1063            cx.notify();
1064        }
1065
1066        self.editor.read(cx).focus_handle(cx).focus(window);
1067    }
1068
1069    fn toggle_rpc_trace_for_server(
1070        &mut self,
1071        server_id: LanguageServerId,
1072        enabled: bool,
1073        window: &mut Window,
1074        cx: &mut Context<Self>,
1075    ) {
1076        self.log_store.update(cx, |log_store, _| {
1077            if enabled {
1078                log_store.enable_rpc_trace_for_language_server(server_id);
1079            } else {
1080                log_store.disable_rpc_trace_for_language_server(server_id);
1081            }
1082        });
1083        if !enabled && Some(server_id) == self.current_server_id {
1084            self.show_logs_for_server(server_id, window, cx);
1085            cx.notify();
1086        }
1087    }
1088
1089    fn update_trace_level(
1090        &self,
1091        server_id: LanguageServerId,
1092        level: TraceValue,
1093        cx: &mut Context<Self>,
1094    ) {
1095        if let Some(server) = self
1096            .project
1097            .read(cx)
1098            .lsp_store()
1099            .read(cx)
1100            .language_server_for_id(server_id)
1101        {
1102            self.log_store.update(cx, |this, _| {
1103                if let Some(state) = this.get_language_server_state(server_id) {
1104                    state.trace_level = level;
1105                }
1106            });
1107
1108            server
1109                .notify::<SetTrace>(&SetTraceParams { value: level })
1110                .ok();
1111        }
1112    }
1113
1114    fn show_server_info(
1115        &mut self,
1116        server_id: LanguageServerId,
1117        window: &mut Window,
1118        cx: &mut Context<Self>,
1119    ) {
1120        let lsp_store = self.project.read(cx).lsp_store();
1121        let Some(server) = lsp_store.read(cx).language_server_for_id(server_id) else {
1122            return;
1123        };
1124        self.current_server_id = Some(server_id);
1125        self.active_entry_kind = LogKind::ServerInfo;
1126        let (editor, editor_subscriptions) = Self::editor_for_server_info(&server, window, cx);
1127        self.editor = editor;
1128        self.editor_subscriptions = editor_subscriptions;
1129        cx.notify();
1130        self.editor.read(cx).focus_handle(cx).focus(window);
1131    }
1132}
1133
1134fn log_contents<T: Message>(lines: &VecDeque<T>, level: <T as Message>::Level) -> String {
1135    lines
1136        .iter()
1137        .filter(|message| message.should_include(level))
1138        .flat_map(|message| [message.as_ref(), "\n"])
1139        .collect()
1140}
1141
1142impl Render for LspLogView {
1143    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1144        self.editor.update(cx, |editor, cx| {
1145            editor.render(window, cx).into_any_element()
1146        })
1147    }
1148}
1149
1150impl Focusable for LspLogView {
1151    fn focus_handle(&self, _: &App) -> FocusHandle {
1152        self.focus_handle.clone()
1153    }
1154}
1155
1156impl Item for LspLogView {
1157    type Event = EditorEvent;
1158
1159    fn to_item_events(event: &Self::Event, f: impl FnMut(workspace::item::ItemEvent)) {
1160        Editor::to_item_events(event, f)
1161    }
1162
1163    fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
1164        "LSP Logs".into()
1165    }
1166
1167    fn telemetry_event_text(&self) -> Option<&'static str> {
1168        None
1169    }
1170
1171    fn as_searchable(&self, handle: &Entity<Self>) -> Option<Box<dyn SearchableItemHandle>> {
1172        Some(Box::new(handle.clone()))
1173    }
1174
1175    fn act_as_type<'a>(
1176        &'a self,
1177        type_id: TypeId,
1178        self_handle: &'a Entity<Self>,
1179        _: &'a App,
1180    ) -> Option<AnyView> {
1181        if type_id == TypeId::of::<Self>() {
1182            Some(self_handle.to_any())
1183        } else if type_id == TypeId::of::<Editor>() {
1184            Some(self.editor.to_any())
1185        } else {
1186            None
1187        }
1188    }
1189
1190    fn clone_on_split(
1191        &self,
1192        _workspace_id: Option<WorkspaceId>,
1193        window: &mut Window,
1194        cx: &mut Context<Self>,
1195    ) -> Option<Entity<Self>>
1196    where
1197        Self: Sized,
1198    {
1199        Some(cx.new(|cx| {
1200            let mut new_view = Self::new(self.project.clone(), self.log_store.clone(), window, cx);
1201            if let Some(server_id) = self.current_server_id {
1202                match self.active_entry_kind {
1203                    LogKind::Rpc => new_view.show_rpc_trace_for_server(server_id, window, cx),
1204                    LogKind::Trace => new_view.show_trace_for_server(server_id, window, cx),
1205                    LogKind::Logs => new_view.show_logs_for_server(server_id, window, cx),
1206                    LogKind::ServerInfo => new_view.show_server_info(server_id, window, cx),
1207                }
1208            }
1209            new_view
1210        }))
1211    }
1212}
1213
1214impl SearchableItem for LspLogView {
1215    type Match = <Editor as SearchableItem>::Match;
1216
1217    fn clear_matches(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1218        self.editor.update(cx, |e, cx| e.clear_matches(window, cx))
1219    }
1220
1221    fn update_matches(
1222        &mut self,
1223        matches: &[Self::Match],
1224        window: &mut Window,
1225        cx: &mut Context<Self>,
1226    ) {
1227        self.editor
1228            .update(cx, |e, cx| e.update_matches(matches, window, cx))
1229    }
1230
1231    fn query_suggestion(&mut self, window: &mut Window, cx: &mut Context<Self>) -> String {
1232        self.editor
1233            .update(cx, |e, cx| e.query_suggestion(window, cx))
1234    }
1235
1236    fn activate_match(
1237        &mut self,
1238        index: usize,
1239        matches: &[Self::Match],
1240        window: &mut Window,
1241        cx: &mut Context<Self>,
1242    ) {
1243        self.editor
1244            .update(cx, |e, cx| e.activate_match(index, matches, window, cx))
1245    }
1246
1247    fn select_matches(
1248        &mut self,
1249        matches: &[Self::Match],
1250        window: &mut Window,
1251        cx: &mut Context<Self>,
1252    ) {
1253        self.editor
1254            .update(cx, |e, cx| e.select_matches(matches, window, cx))
1255    }
1256
1257    fn find_matches(
1258        &mut self,
1259        query: Arc<project::search::SearchQuery>,
1260        window: &mut Window,
1261        cx: &mut Context<Self>,
1262    ) -> gpui::Task<Vec<Self::Match>> {
1263        self.editor
1264            .update(cx, |e, cx| e.find_matches(query, window, cx))
1265    }
1266
1267    fn replace(
1268        &mut self,
1269        _: &Self::Match,
1270        _: &SearchQuery,
1271        _window: &mut Window,
1272        _: &mut Context<Self>,
1273    ) {
1274        // Since LSP Log is read-only, it doesn't make sense to support replace operation.
1275    }
1276    fn supported_options(&self) -> workspace::searchable::SearchOptions {
1277        workspace::searchable::SearchOptions {
1278            case: true,
1279            word: true,
1280            regex: true,
1281            find_in_results: false,
1282            // LSP log is read-only.
1283            replacement: false,
1284            selection: false,
1285        }
1286    }
1287    fn active_match_index(
1288        &mut self,
1289        direction: Direction,
1290        matches: &[Self::Match],
1291        window: &mut Window,
1292        cx: &mut Context<Self>,
1293    ) -> Option<usize> {
1294        self.editor.update(cx, |e, cx| {
1295            e.active_match_index(direction, matches, window, cx)
1296        })
1297    }
1298}
1299
1300impl EventEmitter<ToolbarItemEvent> for LspLogToolbarItemView {}
1301
1302impl ToolbarItemView for LspLogToolbarItemView {
1303    fn set_active_pane_item(
1304        &mut self,
1305        active_pane_item: Option<&dyn ItemHandle>,
1306        _: &mut Window,
1307        cx: &mut Context<Self>,
1308    ) -> workspace::ToolbarItemLocation {
1309        if let Some(item) = active_pane_item
1310            && let Some(log_view) = item.downcast::<LspLogView>() {
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 {}