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