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!(dev, [OpenLanguageServerLogs]);
 208
 209pub(super) struct GlobalLogStore(pub WeakEntity<LogStore>);
 210
 211impl Global for GlobalLogStore {}
 212
 213pub fn init(cx: &mut App) {
 214    let log_store = cx.new(LogStore::new);
 215    cx.set_global(GlobalLogStore(log_store.downgrade()));
 216
 217    cx.observe_new(move |workspace: &mut Workspace, _, cx| {
 218        let project = workspace.project();
 219        if project.read(cx).is_local() || project.read(cx).is_via_ssh() {
 220            log_store.update(cx, |store, cx| {
 221                store.add_project(project, cx);
 222            });
 223        }
 224
 225        let log_store = log_store.clone();
 226        workspace.register_action(move |workspace, _: &OpenLanguageServerLogs, window, cx| {
 227            let project = workspace.project().read(cx);
 228            if project.is_local() || project.is_via_ssh() {
 229                let project = workspace.project().clone();
 230                let log_store = log_store.clone();
 231                get_or_create_tool(
 232                    workspace,
 233                    SplitDirection::Right,
 234                    window,
 235                    cx,
 236                    move |window, cx| LspLogView::new(project, log_store, window, cx),
 237                );
 238            }
 239        });
 240    })
 241    .detach();
 242}
 243
 244impl LogStore {
 245    pub fn new(cx: &mut Context<Self>) -> Self {
 246        let (io_tx, mut io_rx) = mpsc::unbounded();
 247
 248        let copilot_subscription = Copilot::global(cx).map(|copilot| {
 249            let copilot = &copilot;
 250            cx.subscribe(copilot, |this, copilot, inline_completion_event, cx| {
 251                if let copilot::Event::CopilotLanguageServerStarted = inline_completion_event {
 252                    if let Some(server) = copilot.read(cx).language_server() {
 253                        let server_id = server.server_id();
 254                        let weak_this = cx.weak_entity();
 255                        this.copilot_log_subscription =
 256                            Some(server.on_notification::<copilot::request::LogMessage, _>(
 257                                move |params, cx| {
 258                                    weak_this
 259                                        .update(cx, |this, cx| {
 260                                            this.add_language_server_log(
 261                                                server_id,
 262                                                MessageType::LOG,
 263                                                &params.message,
 264                                                cx,
 265                                            );
 266                                        })
 267                                        .ok();
 268                                },
 269                            ));
 270                        let name = LanguageServerName::new_static("copilot");
 271                        this.add_language_server(
 272                            LanguageServerKind::Global,
 273                            server.server_id(),
 274                            Some(name),
 275                            None,
 276                            Some(server.clone()),
 277                            cx,
 278                        );
 279                    }
 280                }
 281            })
 282        });
 283
 284        let this = Self {
 285            copilot_log_subscription: None,
 286            _copilot_subscription: copilot_subscription,
 287            projects: HashMap::default(),
 288            language_servers: HashMap::default(),
 289            io_tx,
 290        };
 291
 292        cx.spawn(async move |this, cx| {
 293            while let Some((server_id, io_kind, message)) = io_rx.next().await {
 294                if let Some(this) = this.upgrade() {
 295                    this.update(cx, |this, cx| {
 296                        this.on_io(server_id, io_kind, &message, cx);
 297                    })?;
 298                }
 299            }
 300            anyhow::Ok(())
 301        })
 302        .detach_and_log_err(cx);
 303        this
 304    }
 305
 306    pub fn add_project(&mut self, project: &Entity<Project>, cx: &mut Context<Self>) {
 307        let weak_project = project.downgrade();
 308        self.projects.insert(
 309            project.downgrade(),
 310            ProjectState {
 311                _subscriptions: [
 312                    cx.observe_release(project, move |this, _, _| {
 313                        this.projects.remove(&weak_project);
 314                        this.language_servers
 315                            .retain(|_, state| state.kind.project() != Some(&weak_project));
 316                    }),
 317                    cx.subscribe(project, |this, project, event, cx| {
 318                        let server_kind = if project.read(cx).is_via_ssh() {
 319                            LanguageServerKind::Remote {
 320                                project: project.downgrade(),
 321                            }
 322                        } else {
 323                            LanguageServerKind::Local {
 324                                project: project.downgrade(),
 325                            }
 326                        };
 327
 328                        match event {
 329                            project::Event::LanguageServerAdded(id, name, worktree_id) => {
 330                                this.add_language_server(
 331                                    server_kind,
 332                                    *id,
 333                                    Some(name.clone()),
 334                                    *worktree_id,
 335                                    project
 336                                        .read(cx)
 337                                        .lsp_store()
 338                                        .read(cx)
 339                                        .language_server_for_id(*id),
 340                                    cx,
 341                                );
 342                            }
 343                            project::Event::LanguageServerRemoved(id) => {
 344                                this.remove_language_server(*id, cx);
 345                            }
 346                            project::Event::LanguageServerLog(id, typ, message) => {
 347                                this.add_language_server(server_kind, *id, None, None, None, cx);
 348                                match typ {
 349                                    project::LanguageServerLogType::Log(typ) => {
 350                                        this.add_language_server_log(*id, *typ, message, cx);
 351                                    }
 352                                    project::LanguageServerLogType::Trace(_) => {
 353                                        this.add_language_server_trace(*id, message, cx);
 354                                    }
 355                                }
 356                            }
 357                            _ => {}
 358                        }
 359                    }),
 360                ],
 361            },
 362        );
 363    }
 364
 365    pub(super) fn get_language_server_state(
 366        &mut self,
 367        id: LanguageServerId,
 368    ) -> Option<&mut LanguageServerState> {
 369        self.language_servers.get_mut(&id)
 370    }
 371
 372    fn add_language_server(
 373        &mut self,
 374        kind: LanguageServerKind,
 375        server_id: LanguageServerId,
 376        name: Option<LanguageServerName>,
 377        worktree_id: Option<WorktreeId>,
 378        server: Option<Arc<LanguageServer>>,
 379        cx: &mut Context<Self>,
 380    ) -> Option<&mut LanguageServerState> {
 381        let server_state = self.language_servers.entry(server_id).or_insert_with(|| {
 382            cx.notify();
 383            LanguageServerState {
 384                name: None,
 385                worktree_id: None,
 386                kind,
 387                rpc_state: None,
 388                log_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES),
 389                trace_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES),
 390                trace_level: TraceValue::Off,
 391                log_level: MessageType::LOG,
 392                io_logs_subscription: None,
 393            }
 394        });
 395
 396        if let Some(name) = name {
 397            server_state.name = Some(name);
 398        }
 399        if let Some(worktree_id) = worktree_id {
 400            server_state.worktree_id = Some(worktree_id);
 401        }
 402
 403        if let Some(server) = server
 404            .clone()
 405            .filter(|_| server_state.io_logs_subscription.is_none())
 406        {
 407            let io_tx = self.io_tx.clone();
 408            let server_id = server.server_id();
 409            server_state.io_logs_subscription = Some(server.on_io(move |io_kind, message| {
 410                io_tx
 411                    .unbounded_send((server_id, io_kind, message.to_string()))
 412                    .ok();
 413            }));
 414        }
 415
 416        Some(server_state)
 417    }
 418
 419    fn add_language_server_log(
 420        &mut self,
 421        id: LanguageServerId,
 422        typ: MessageType,
 423        message: &str,
 424        cx: &mut Context<Self>,
 425    ) -> Option<()> {
 426        let language_server_state = self.get_language_server_state(id)?;
 427
 428        let log_lines = &mut language_server_state.log_messages;
 429        Self::add_language_server_message(
 430            log_lines,
 431            id,
 432            LogMessage {
 433                message: message.trim_end().to_string(),
 434                typ,
 435            },
 436            language_server_state.log_level,
 437            LogKind::Logs,
 438            cx,
 439        );
 440        Some(())
 441    }
 442
 443    fn add_language_server_trace(
 444        &mut self,
 445        id: LanguageServerId,
 446        message: &str,
 447        cx: &mut Context<Self>,
 448    ) -> Option<()> {
 449        let language_server_state = self.get_language_server_state(id)?;
 450
 451        let log_lines = &mut language_server_state.trace_messages;
 452        Self::add_language_server_message(
 453            log_lines,
 454            id,
 455            TraceMessage {
 456                message: message.trim().to_string(),
 457            },
 458            (),
 459            LogKind::Trace,
 460            cx,
 461        );
 462        Some(())
 463    }
 464
 465    fn add_language_server_message<T: Message>(
 466        log_lines: &mut VecDeque<T>,
 467        id: LanguageServerId,
 468        message: T,
 469        current_severity: <T as Message>::Level,
 470        kind: LogKind,
 471        cx: &mut Context<Self>,
 472    ) {
 473        while log_lines.len() + 1 >= MAX_STORED_LOG_ENTRIES {
 474            log_lines.pop_front();
 475        }
 476        let text = message.as_ref().to_string();
 477        let visible = message.should_include(current_severity);
 478        log_lines.push_back(message);
 479
 480        if visible {
 481            cx.emit(Event::NewServerLogEntry { id, kind, text });
 482            cx.notify();
 483        }
 484    }
 485
 486    fn remove_language_server(&mut self, id: LanguageServerId, cx: &mut Context<Self>) {
 487        self.language_servers.remove(&id);
 488        cx.notify();
 489    }
 490
 491    pub(super) fn server_logs(&self, server_id: LanguageServerId) -> Option<&VecDeque<LogMessage>> {
 492        Some(&self.language_servers.get(&server_id)?.log_messages)
 493    }
 494
 495    pub(super) fn server_trace(
 496        &self,
 497        server_id: LanguageServerId,
 498    ) -> Option<&VecDeque<TraceMessage>> {
 499        Some(&self.language_servers.get(&server_id)?.trace_messages)
 500    }
 501
 502    fn server_ids_for_project<'a>(
 503        &'a self,
 504        lookup_project: &'a WeakEntity<Project>,
 505    ) -> impl Iterator<Item = LanguageServerId> + 'a {
 506        self.language_servers
 507            .iter()
 508            .filter_map(move |(id, state)| match &state.kind {
 509                LanguageServerKind::Local { project } | LanguageServerKind::Remote { project } => {
 510                    if project == lookup_project {
 511                        Some(*id)
 512                    } else {
 513                        None
 514                    }
 515                }
 516                LanguageServerKind::Global => Some(*id),
 517            })
 518    }
 519
 520    fn enable_rpc_trace_for_language_server(
 521        &mut self,
 522        server_id: LanguageServerId,
 523    ) -> Option<&mut LanguageServerRpcState> {
 524        let rpc_state = self
 525            .language_servers
 526            .get_mut(&server_id)?
 527            .rpc_state
 528            .get_or_insert_with(|| LanguageServerRpcState {
 529                rpc_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES),
 530                last_message_kind: None,
 531            });
 532        Some(rpc_state)
 533    }
 534
 535    pub fn disable_rpc_trace_for_language_server(
 536        &mut self,
 537        server_id: LanguageServerId,
 538    ) -> Option<()> {
 539        self.language_servers.get_mut(&server_id)?.rpc_state.take();
 540        Some(())
 541    }
 542
 543    pub fn has_server_logs(&self, server: &LanguageServerSelector) -> bool {
 544        match server {
 545            LanguageServerSelector::Id(id) => self.language_servers.contains_key(id),
 546            LanguageServerSelector::Name(name) => self
 547                .language_servers
 548                .iter()
 549                .any(|(_, state)| state.name.as_ref() == Some(name)),
 550        }
 551    }
 552
 553    pub fn open_server_log(
 554        &mut self,
 555        workspace: WeakEntity<Workspace>,
 556        server: LanguageServerSelector,
 557        window: &mut Window,
 558        cx: &mut Context<Self>,
 559    ) {
 560        cx.spawn_in(window, async move |log_store, cx| {
 561            let Some(log_store) = log_store.upgrade() else {
 562                return;
 563            };
 564            workspace
 565                .update_in(cx, |workspace, window, cx| {
 566                    let project = workspace.project().clone();
 567                    let tool_log_store = log_store.clone();
 568                    let log_view = get_or_create_tool(
 569                        workspace,
 570                        SplitDirection::Right,
 571                        window,
 572                        cx,
 573                        move |window, cx| LspLogView::new(project, tool_log_store, window, cx),
 574                    );
 575                    log_view.update(cx, |log_view, cx| {
 576                        let server_id = match server {
 577                            LanguageServerSelector::Id(id) => Some(id),
 578                            LanguageServerSelector::Name(name) => {
 579                                log_store.read(cx).language_servers.iter().find_map(
 580                                    |(id, state)| {
 581                                        if state.name.as_ref() == Some(&name) {
 582                                            Some(*id)
 583                                        } else {
 584                                            None
 585                                        }
 586                                    },
 587                                )
 588                            }
 589                        };
 590                        if let Some(server_id) = server_id {
 591                            log_view.show_logs_for_server(server_id, window, cx);
 592                        }
 593                    });
 594                })
 595                .ok();
 596        })
 597        .detach();
 598    }
 599
 600    pub fn open_server_trace(
 601        &mut self,
 602        workspace: WeakEntity<Workspace>,
 603        server: LanguageServerSelector,
 604        window: &mut Window,
 605        cx: &mut Context<Self>,
 606    ) {
 607        cx.spawn_in(window, async move |log_store, cx| {
 608            let Some(log_store) = log_store.upgrade() else {
 609                return;
 610            };
 611            workspace
 612                .update_in(cx, |workspace, window, cx| {
 613                    let project = workspace.project().clone();
 614                    let tool_log_store = log_store.clone();
 615                    let log_view = get_or_create_tool(
 616                        workspace,
 617                        SplitDirection::Right,
 618                        window,
 619                        cx,
 620                        move |window, cx| LspLogView::new(project, tool_log_store, window, cx),
 621                    );
 622                    log_view.update(cx, |log_view, cx| {
 623                        let server_id = match server {
 624                            LanguageServerSelector::Id(id) => Some(id),
 625                            LanguageServerSelector::Name(name) => {
 626                                log_store.read(cx).language_servers.iter().find_map(
 627                                    |(id, state)| {
 628                                        if state.name.as_ref() == Some(&name) {
 629                                            Some(*id)
 630                                        } else {
 631                                            None
 632                                        }
 633                                    },
 634                                )
 635                            }
 636                        };
 637                        if let Some(server_id) = server_id {
 638                            log_view.show_rpc_trace_for_server(server_id, window, cx);
 639                        }
 640                    });
 641                })
 642                .ok();
 643        })
 644        .detach();
 645    }
 646
 647    fn on_io(
 648        &mut self,
 649        language_server_id: LanguageServerId,
 650        io_kind: IoKind,
 651        message: &str,
 652        cx: &mut Context<Self>,
 653    ) -> Option<()> {
 654        let is_received = match io_kind {
 655            IoKind::StdOut => true,
 656            IoKind::StdIn => false,
 657            IoKind::StdErr => {
 658                self.add_language_server_log(language_server_id, MessageType::LOG, &message, cx);
 659                return Some(());
 660            }
 661        };
 662
 663        let state = self
 664            .get_language_server_state(language_server_id)?
 665            .rpc_state
 666            .as_mut()?;
 667        let kind = if is_received {
 668            MessageKind::Receive
 669        } else {
 670            MessageKind::Send
 671        };
 672
 673        let rpc_log_lines = &mut state.rpc_messages;
 674        if state.last_message_kind != Some(kind) {
 675            while rpc_log_lines.len() + 1 >= MAX_STORED_LOG_ENTRIES {
 676                rpc_log_lines.pop_front();
 677            }
 678            let line_before_message = match kind {
 679                MessageKind::Send => SEND_LINE,
 680                MessageKind::Receive => RECEIVE_LINE,
 681            };
 682            rpc_log_lines.push_back(RpcMessage {
 683                message: line_before_message.to_string(),
 684            });
 685            cx.emit(Event::NewServerLogEntry {
 686                id: language_server_id,
 687                kind: LogKind::Rpc,
 688                text: line_before_message.to_string(),
 689            });
 690        }
 691
 692        while rpc_log_lines.len() + 1 >= MAX_STORED_LOG_ENTRIES {
 693            rpc_log_lines.pop_front();
 694        }
 695
 696        let message = message.trim();
 697        rpc_log_lines.push_back(RpcMessage {
 698            message: message.to_string(),
 699        });
 700        cx.emit(Event::NewServerLogEntry {
 701            id: language_server_id,
 702            kind: LogKind::Rpc,
 703            text: message.to_string(),
 704        });
 705        cx.notify();
 706        Some(())
 707    }
 708}
 709
 710impl LspLogView {
 711    pub fn new(
 712        project: Entity<Project>,
 713        log_store: Entity<LogStore>,
 714        window: &mut Window,
 715        cx: &mut Context<Self>,
 716    ) -> Self {
 717        let server_id = log_store
 718            .read(cx)
 719            .language_servers
 720            .iter()
 721            .find(|(_, server)| server.kind.project() == Some(&project.downgrade()))
 722            .map(|(id, _)| *id);
 723
 724        let weak_project = project.downgrade();
 725        let model_changes_subscription =
 726            cx.observe_in(&log_store, window, move |this, store, window, cx| {
 727                let first_server_id_for_project =
 728                    store.read(cx).server_ids_for_project(&weak_project).next();
 729                if let Some(current_lsp) = this.current_server_id {
 730                    if !store.read(cx).language_servers.contains_key(&current_lsp) {
 731                        if let Some(server_id) = first_server_id_for_project {
 732                            match this.active_entry_kind {
 733                                LogKind::Rpc => {
 734                                    this.show_rpc_trace_for_server(server_id, window, cx)
 735                                }
 736                                LogKind::Trace => this.show_trace_for_server(server_id, window, cx),
 737                                LogKind::Logs => this.show_logs_for_server(server_id, window, cx),
 738                                LogKind::ServerInfo => this.show_server_info(server_id, window, cx),
 739                            }
 740                        }
 741                    }
 742                } else if let Some(server_id) = first_server_id_for_project {
 743                    match this.active_entry_kind {
 744                        LogKind::Rpc => this.show_rpc_trace_for_server(server_id, window, cx),
 745                        LogKind::Trace => this.show_trace_for_server(server_id, window, cx),
 746                        LogKind::Logs => this.show_logs_for_server(server_id, window, cx),
 747                        LogKind::ServerInfo => this.show_server_info(server_id, window, cx),
 748                    }
 749                }
 750
 751                cx.notify();
 752            });
 753        let events_subscriptions = cx.subscribe_in(
 754            &log_store,
 755            window,
 756            move |log_view, _, e, window, cx| match e {
 757                Event::NewServerLogEntry { id, kind, text } => {
 758                    if log_view.current_server_id == Some(*id)
 759                        && *kind == log_view.active_entry_kind
 760                    {
 761                        log_view.editor.update(cx, |editor, cx| {
 762                            editor.set_read_only(false);
 763                            let last_offset = editor.buffer().read(cx).len(cx);
 764                            let newest_cursor_is_at_end =
 765                                editor.selections.newest::<usize>(cx).start >= last_offset;
 766                            editor.edit(
 767                                vec![
 768                                    (last_offset..last_offset, text.as_str()),
 769                                    (last_offset..last_offset, "\n"),
 770                                ],
 771                                cx,
 772                            );
 773                            if text.len() > 1024 {
 774                                if let Some((fold_offset, _)) =
 775                                    text.char_indices().dropping(1024).next()
 776                                {
 777                                    if fold_offset < text.len() {
 778                                        editor.fold_ranges(
 779                                            vec![
 780                                                last_offset + fold_offset..last_offset + text.len(),
 781                                            ],
 782                                            false,
 783                                            window,
 784                                            cx,
 785                                        );
 786                                    }
 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                .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            if let Some(log_view) = item.downcast::<LspLogView>() {
1310                self.log_view = Some(log_view.clone());
1311                self._log_view_subscription = Some(cx.observe(&log_view, |_, _, cx| {
1312                    cx.notify();
1313                }));
1314                return ToolbarItemLocation::PrimaryLeft;
1315            }
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        let (menu_rows, current_server_id) = log_view.update(cx, |log_view, cx| {
1329            let menu_rows = log_view.menu_items(cx).unwrap_or_default();
1330            let current_server_id = log_view.current_server_id;
1331            (menu_rows, current_server_id)
1332        });
1333
1334        let current_server = current_server_id.and_then(|current_server_id| {
1335            if let Ok(ix) = menu_rows.binary_search_by_key(&current_server_id, |e| e.server_id) {
1336                Some(menu_rows[ix].clone())
1337            } else {
1338                None
1339            }
1340        });
1341        let available_language_servers: Vec<_> = menu_rows
1342            .into_iter()
1343            .map(|row| {
1344                (
1345                    row.server_id,
1346                    row.server_name,
1347                    row.worktree_root_name,
1348                    row.selected_entry,
1349                )
1350            })
1351            .collect();
1352        let log_toolbar_view = cx.entity().clone();
1353        let lsp_menu = PopoverMenu::new("LspLogView")
1354            .anchor(Corner::TopLeft)
1355            .trigger(Button::new(
1356                "language_server_menu_header",
1357                current_server
1358                    .as_ref()
1359                    .map(|row| {
1360                        Cow::Owned(format!(
1361                            "{} ({})",
1362                            row.server_name.0, row.worktree_root_name,
1363                        ))
1364                    })
1365                    .unwrap_or_else(|| "No server selected".into()),
1366            ))
1367            .menu({
1368                let log_view = log_view.clone();
1369                move |window, cx| {
1370                    let log_view = log_view.clone();
1371                    ContextMenu::build(window, cx, |mut menu, window, _| {
1372                        for (server_id, name, worktree_root, active_entry_kind) in
1373                            available_language_servers.iter()
1374                        {
1375                            let label = format!("{} ({})", name, worktree_root);
1376                            let server_id = *server_id;
1377                            let active_entry_kind = *active_entry_kind;
1378                            menu = menu.entry(
1379                                label,
1380                                None,
1381                                window.handler_for(&log_view, move |view, window, cx| {
1382                                    view.current_server_id = Some(server_id);
1383                                    view.active_entry_kind = active_entry_kind;
1384                                    match view.active_entry_kind {
1385                                        LogKind::Rpc => {
1386                                            view.toggle_rpc_trace_for_server(
1387                                                server_id, true, window, cx,
1388                                            );
1389                                            view.show_rpc_trace_for_server(server_id, window, cx);
1390                                        }
1391                                        LogKind::Trace => {
1392                                            view.show_trace_for_server(server_id, window, cx)
1393                                        }
1394                                        LogKind::Logs => {
1395                                            view.show_logs_for_server(server_id, window, cx)
1396                                        }
1397                                        LogKind::ServerInfo => {
1398                                            view.show_server_info(server_id, window, cx)
1399                                        }
1400                                    }
1401                                    cx.notify();
1402                                }),
1403                            );
1404                        }
1405                        menu
1406                    })
1407                    .into()
1408                }
1409            });
1410        let view_selector = current_server.map(|server| {
1411            let server_id = server.server_id;
1412            let is_remote = server.server_kind.is_remote();
1413            let rpc_trace_enabled = server.rpc_trace_enabled;
1414            let log_view = log_view.clone();
1415            PopoverMenu::new("LspViewSelector")
1416                .anchor(Corner::TopLeft)
1417                .trigger(Button::new(
1418                    "language_server_menu_header",
1419                    server.selected_entry.label(),
1420                ))
1421                .menu(move |window, cx| {
1422                    let log_toolbar_view = log_toolbar_view.clone();
1423                    let log_view = log_view.clone();
1424                    Some(ContextMenu::build(window, cx, move |this, window, _| {
1425                        this.entry(
1426                            SERVER_LOGS,
1427                            None,
1428                            window.handler_for(&log_view, move |view, window, cx| {
1429                                view.show_logs_for_server(server_id, window, cx);
1430                            }),
1431                        )
1432                        .when(!is_remote, |this| {
1433                            this.entry(
1434                                SERVER_TRACE,
1435                                None,
1436                                window.handler_for(&log_view, move |view, window, cx| {
1437                                    view.show_trace_for_server(server_id, window, cx);
1438                                }),
1439                            )
1440                            .custom_entry(
1441                                {
1442                                    let log_toolbar_view = log_toolbar_view.clone();
1443                                    move |window, _| {
1444                                        h_flex()
1445                                            .w_full()
1446                                            .justify_between()
1447                                            .child(Label::new(RPC_MESSAGES))
1448                                            .child(
1449                                                div().child(
1450                                                    Checkbox::new(
1451                                                        "LspLogEnableRpcTrace",
1452                                                        if rpc_trace_enabled {
1453                                                            ToggleState::Selected
1454                                                        } else {
1455                                                            ToggleState::Unselected
1456                                                        },
1457                                                    )
1458                                                    .on_click(window.listener_for(
1459                                                        &log_toolbar_view,
1460                                                        move |view, selection, window, cx| {
1461                                                            let enabled = matches!(
1462                                                                selection,
1463                                                                ToggleState::Selected
1464                                                            );
1465                                                            view.toggle_rpc_logging_for_server(
1466                                                                server_id, enabled, window, cx,
1467                                                            );
1468                                                            cx.stop_propagation();
1469                                                        },
1470                                                    )),
1471                                                ),
1472                                            )
1473                                            .into_any_element()
1474                                    }
1475                                },
1476                                window.handler_for(&log_view, move |view, window, cx| {
1477                                    view.show_rpc_trace_for_server(server_id, window, cx);
1478                                }),
1479                            )
1480                        })
1481                        .entry(
1482                            SERVER_INFO,
1483                            None,
1484                            window.handler_for(&log_view, move |view, window, cx| {
1485                                view.show_server_info(server_id, window, cx);
1486                            }),
1487                        )
1488                    }))
1489                })
1490        });
1491        h_flex()
1492            .size_full()
1493            .justify_between()
1494            .child(
1495                h_flex()
1496                    .child(lsp_menu)
1497                    .children(view_selector)
1498                    .child(
1499                        log_view.update(cx, |this, _cx| match this.active_entry_kind {
1500                            LogKind::Trace => {
1501                                let log_view = log_view.clone();
1502                                div().child(
1503                                    PopoverMenu::new("lsp-trace-level-menu")
1504                                        .anchor(Corner::TopLeft)
1505                                        .trigger(Button::new(
1506                                            "language_server_trace_level_selector",
1507                                            "Trace level",
1508                                        ))
1509                                        .menu({
1510                                            let log_view = log_view.clone();
1511
1512                                            move |window, cx| {
1513                                                let id = log_view.read(cx).current_server_id?;
1514
1515                                                let trace_level =
1516                                                    log_view.update(cx, |this, cx| {
1517                                                        this.log_store.update(cx, |this, _| {
1518                                                            Some(
1519                                                                this.get_language_server_state(id)?
1520                                                                    .trace_level,
1521                                                            )
1522                                                        })
1523                                                    })?;
1524
1525                                                ContextMenu::build(
1526                                                    window,
1527                                                    cx,
1528                                                    |mut menu, window, cx| {
1529                                                        let log_view = log_view.clone();
1530
1531                                                        for (option, label) in [
1532                                                            (TraceValue::Off, "Off"),
1533                                                            (TraceValue::Messages, "Messages"),
1534                                                            (TraceValue::Verbose, "Verbose"),
1535                                                        ] {
1536                                                            menu = menu.entry(label, None, {
1537                                                                let log_view = log_view.clone();
1538                                                                move |_, cx| {
1539                                                                    log_view.update(cx, |this, cx| {
1540                                                                    if let Some(id) =
1541                                                                        this.current_server_id
1542                                                                    {
1543                                                                        this.update_trace_level(
1544                                                                            id, option, cx,
1545                                                                        );
1546                                                                    }
1547                                                                });
1548                                                                }
1549                                                            });
1550                                                            if option == trace_level {
1551                                                                menu.select_last(window, cx);
1552                                                            }
1553                                                        }
1554
1555                                                        menu
1556                                                    },
1557                                                )
1558                                                .into()
1559                                            }
1560                                        }),
1561                                )
1562                            }
1563                            LogKind::Logs => {
1564                                let log_view = log_view.clone();
1565                                div().child(
1566                                    PopoverMenu::new("lsp-log-level-menu")
1567                                        .anchor(Corner::TopLeft)
1568                                        .trigger(Button::new(
1569                                            "language_server_log_level_selector",
1570                                            "Log level",
1571                                        ))
1572                                        .menu({
1573                                            let log_view = log_view.clone();
1574
1575                                            move |window, cx| {
1576                                                let id = log_view.read(cx).current_server_id?;
1577
1578                                                let log_level =
1579                                                    log_view.update(cx, |this, cx| {
1580                                                        this.log_store.update(cx, |this, _| {
1581                                                            Some(
1582                                                                this.get_language_server_state(id)?
1583                                                                    .log_level,
1584                                                            )
1585                                                        })
1586                                                    })?;
1587
1588                                                ContextMenu::build(
1589                                                    window,
1590                                                    cx,
1591                                                    |mut menu, window, cx| {
1592                                                        let log_view = log_view.clone();
1593
1594                                                        for (option, label) in [
1595                                                            (MessageType::LOG, "Log"),
1596                                                            (MessageType::INFO, "Info"),
1597                                                            (MessageType::WARNING, "Warning"),
1598                                                            (MessageType::ERROR, "Error"),
1599                                                        ] {
1600                                                            menu = menu.entry(label, None, {
1601                                                                let log_view = log_view.clone();
1602                                                                move |window, cx| {
1603                                                                    log_view.update(cx, |this, cx| {
1604                                                                    if let Some(id) =
1605                                                                        this.current_server_id
1606                                                                    {
1607                                                                        this.update_log_level(
1608                                                                            id, option, window, cx,
1609                                                                        );
1610                                                                    }
1611                                                                });
1612                                                                }
1613                                                            });
1614                                                            if option == log_level {
1615                                                                menu.select_last(window, cx);
1616                                                            }
1617                                                        }
1618
1619                                                        menu
1620                                                    },
1621                                                )
1622                                                .into()
1623                                            }
1624                                        }),
1625                                )
1626                            }
1627                            _ => div(),
1628                        }),
1629                    ),
1630            )
1631            .child(
1632                div()
1633                    .child(
1634                        Button::new("clear_log_button", "Clear").on_click(cx.listener(
1635                            |this, _, window, cx| {
1636                                if let Some(log_view) = this.log_view.as_ref() {
1637                                    log_view.update(cx, |log_view, cx| {
1638                                        log_view.editor.update(cx, |editor, cx| {
1639                                            editor.set_read_only(false);
1640                                            editor.clear(window, cx);
1641                                            editor.set_read_only(true);
1642                                        });
1643                                    })
1644                                }
1645                            },
1646                        )),
1647                    )
1648                    .ml_2(),
1649            )
1650    }
1651}
1652
1653fn initialize_new_editor(
1654    content: String,
1655    move_to_end: bool,
1656    window: &mut Window,
1657    cx: &mut App,
1658) -> Entity<Editor> {
1659    cx.new(|cx| {
1660        let mut editor = Editor::multi_line(window, cx);
1661        editor.hide_minimap_by_default(window, cx);
1662        editor.set_text(content, window, cx);
1663        editor.set_show_git_diff_gutter(false, cx);
1664        editor.set_show_runnables(false, cx);
1665        editor.set_show_breakpoints(false, cx);
1666        editor.set_read_only(true);
1667        editor.set_show_edit_predictions(Some(false), window, cx);
1668        editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
1669        if move_to_end {
1670            editor.move_to_end(&MoveToEnd, window, cx);
1671        }
1672        editor
1673    })
1674}
1675
1676const RPC_MESSAGES: &str = "RPC Messages";
1677const SERVER_LOGS: &str = "Server Logs";
1678const SERVER_TRACE: &str = "Server Trace";
1679const SERVER_INFO: &str = "Server Info";
1680
1681impl Default for LspLogToolbarItemView {
1682    fn default() -> Self {
1683        Self::new()
1684    }
1685}
1686
1687impl LspLogToolbarItemView {
1688    pub fn new() -> Self {
1689        Self {
1690            log_view: None,
1691            _log_view_subscription: None,
1692        }
1693    }
1694
1695    fn toggle_rpc_logging_for_server(
1696        &mut self,
1697        id: LanguageServerId,
1698        enabled: bool,
1699        window: &mut Window,
1700        cx: &mut Context<Self>,
1701    ) {
1702        if let Some(log_view) = &self.log_view {
1703            log_view.update(cx, |log_view, cx| {
1704                log_view.toggle_rpc_trace_for_server(id, enabled, window, cx);
1705                if !enabled && Some(id) == log_view.current_server_id {
1706                    log_view.show_logs_for_server(id, window, cx);
1707                    cx.notify();
1708                } else if enabled {
1709                    log_view.show_rpc_trace_for_server(id, window, cx);
1710                    cx.notify();
1711                }
1712                window.focus(&log_view.focus_handle);
1713            });
1714        }
1715        cx.notify();
1716    }
1717}
1718
1719pub enum Event {
1720    NewServerLogEntry {
1721        id: LanguageServerId,
1722        kind: LogKind,
1723        text: String,
1724    },
1725}
1726
1727impl EventEmitter<Event> for LogStore {}
1728impl EventEmitter<Event> for LspLogView {}
1729impl EventEmitter<EditorEvent> for LspLogView {}
1730impl EventEmitter<SearchEvent> for LspLogView {}