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