lsp_log.rs

   1use collections::{HashMap, VecDeque};
   2use copilot::Copilot;
   3use editor::{actions::MoveToEnd, Editor, EditorEvent};
   4use futures::{channel::mpsc, StreamExt};
   5use gpui::{
   6    actions, div, AnchorCorner, AppContext, Context, EventEmitter, FocusHandle, FocusableView,
   7    IntoElement, Model, ModelContext, ParentElement, Render, Styled, Subscription, View,
   8    ViewContext, VisualContext, WeakModel, WindowContext,
   9};
  10use language::LanguageServerId;
  11use lsp::{
  12    notification::SetTrace, IoKind, LanguageServer, LanguageServerName, MessageType,
  13    ServerCapabilities, SetTraceParams, TraceValue,
  14};
  15use project::{search::SearchQuery, Project, WorktreeId};
  16use std::{borrow::Cow, sync::Arc};
  17use ui::{prelude::*, Button, Checkbox, ContextMenu, Label, PopoverMenu, Selection};
  18use workspace::{
  19    item::{Item, ItemHandle},
  20    searchable::{SearchEvent, SearchableItem, SearchableItemHandle},
  21    SplitDirection, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, WorkspaceId,
  22};
  23
  24const SEND_LINE: &str = "// Send:";
  25const RECEIVE_LINE: &str = "// Receive:";
  26const MAX_STORED_LOG_ENTRIES: usize = 2000;
  27
  28pub struct LogStore {
  29    projects: HashMap<WeakModel<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    capabilities: ServerCapabilities,
 112    io_logs_subscription: Option<lsp::Subscription>,
 113}
 114
 115#[derive(PartialEq, Clone)]
 116pub enum LanguageServerKind {
 117    Local { project: WeakModel<Project> },
 118    Remote { project: WeakModel<Project> },
 119    Global,
 120}
 121
 122impl LanguageServerKind {
 123    fn is_remote(&self) -> bool {
 124        matches!(self, LanguageServerKind::Remote { .. })
 125    }
 126}
 127
 128impl std::fmt::Debug for LanguageServerKind {
 129    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 130        match self {
 131            LanguageServerKind::Local { .. } => write!(f, "LanguageServerKind::Local"),
 132            LanguageServerKind::Remote { .. } => write!(f, "LanguageServerKind::Remote"),
 133            LanguageServerKind::Global => write!(f, "LanguageServerKind::Global"),
 134        }
 135    }
 136}
 137
 138impl LanguageServerKind {
 139    fn project(&self) -> Option<&WeakModel<Project>> {
 140        match self {
 141            Self::Local { project } => Some(project),
 142            Self::Remote { project } => Some(project),
 143            Self::Global { .. } => None,
 144        }
 145    }
 146}
 147
 148struct LanguageServerRpcState {
 149    rpc_messages: VecDeque<RpcMessage>,
 150    last_message_kind: Option<MessageKind>,
 151}
 152
 153pub struct LspLogView {
 154    pub(crate) editor: View<Editor>,
 155    editor_subscriptions: Vec<Subscription>,
 156    log_store: Model<LogStore>,
 157    current_server_id: Option<LanguageServerId>,
 158    active_entry_kind: LogKind,
 159    project: Model<Project>,
 160    focus_handle: FocusHandle,
 161    _log_store_subscriptions: Vec<Subscription>,
 162}
 163
 164pub struct LspLogToolbarItemView {
 165    log_view: Option<View<LspLogView>>,
 166    _log_view_subscription: Option<Subscription>,
 167}
 168
 169#[derive(Copy, Clone, PartialEq, Eq)]
 170enum MessageKind {
 171    Send,
 172    Receive,
 173}
 174
 175#[derive(Clone, Copy, Debug, Default, PartialEq)]
 176pub enum LogKind {
 177    Rpc,
 178    Trace,
 179    #[default]
 180    Logs,
 181    Capabilities,
 182}
 183
 184impl LogKind {
 185    fn label(&self) -> &'static str {
 186        match self {
 187            LogKind::Rpc => RPC_MESSAGES,
 188            LogKind::Trace => SERVER_TRACE,
 189            LogKind::Logs => SERVER_LOGS,
 190            LogKind::Capabilities => SERVER_CAPABILITIES,
 191        }
 192    }
 193}
 194
 195#[derive(Clone, Debug, PartialEq)]
 196pub(crate) struct LogMenuItem {
 197    pub server_id: LanguageServerId,
 198    pub server_name: LanguageServerName,
 199    pub worktree_root_name: String,
 200    pub rpc_trace_enabled: bool,
 201    pub selected_entry: LogKind,
 202    pub trace_level: lsp::TraceValue,
 203    pub server_kind: LanguageServerKind,
 204}
 205
 206actions!(debug, [OpenLanguageServerLogs]);
 207
 208pub fn init(cx: &mut AppContext) {
 209    let log_store = cx.new_model(LogStore::new);
 210
 211    cx.observe_new_views(move |workspace: &mut Workspace, cx| {
 212        let project = workspace.project();
 213        if project.read(cx).is_local() || project.read(cx).is_via_ssh() {
 214            log_store.update(cx, |store, cx| {
 215                store.add_project(project, cx);
 216            });
 217        }
 218
 219        let log_store = log_store.clone();
 220        workspace.register_action(move |workspace, _: &OpenLanguageServerLogs, cx| {
 221            let project = workspace.project().read(cx);
 222            if project.is_local() || project.is_via_ssh() {
 223                workspace.split_item(
 224                    SplitDirection::Right,
 225                    Box::new(cx.new_view(|cx| {
 226                        LspLogView::new(workspace.project().clone(), log_store.clone(), cx)
 227                    })),
 228                    cx,
 229                );
 230            }
 231        });
 232    })
 233    .detach();
 234}
 235
 236impl LogStore {
 237    pub fn new(cx: &mut ModelContext<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_model();
 247                        this.copilot_log_subscription =
 248                            Some(server.on_notification::<copilot::request::LogMessage, _>(
 249                                move |params, mut cx| {
 250                                    weak_this
 251                                        .update(&mut 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(|this, mut cx| async move {
 285            while let Some((server_id, io_kind, message)) = io_rx.next().await {
 286                if let Some(this) = this.upgrade() {
 287                    this.update(&mut 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: &Model<Project>, cx: &mut ModelContext<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.read(cx).language_server_for_id(*id, cx),
 328                                    cx,
 329                                );
 330                            }
 331                            project::Event::LanguageServerRemoved(id) => {
 332                                this.remove_language_server(*id, cx);
 333                            }
 334                            project::Event::LanguageServerLog(id, typ, message) => {
 335                                this.add_language_server(server_kind, *id, None, None, None, cx);
 336                                match typ {
 337                                    project::LanguageServerLogType::Log(typ) => {
 338                                        this.add_language_server_log(*id, *typ, message, cx);
 339                                    }
 340                                    project::LanguageServerLogType::Trace(_) => {
 341                                        this.add_language_server_trace(*id, message, cx);
 342                                    }
 343                                }
 344                            }
 345                            _ => {}
 346                        }
 347                    }),
 348                ],
 349            },
 350        );
 351    }
 352
 353    fn get_language_server_state(
 354        &mut self,
 355        id: LanguageServerId,
 356    ) -> Option<&mut LanguageServerState> {
 357        self.language_servers.get_mut(&id)
 358    }
 359
 360    fn add_language_server(
 361        &mut self,
 362        kind: LanguageServerKind,
 363        server_id: LanguageServerId,
 364        name: Option<LanguageServerName>,
 365        worktree_id: Option<WorktreeId>,
 366        server: Option<Arc<LanguageServer>>,
 367        cx: &mut ModelContext<Self>,
 368    ) -> Option<&mut LanguageServerState> {
 369        let server_state = self.language_servers.entry(server_id).or_insert_with(|| {
 370            cx.notify();
 371            LanguageServerState {
 372                name: None,
 373                worktree_id: None,
 374                kind,
 375                rpc_state: None,
 376                log_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES),
 377                trace_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES),
 378                trace_level: TraceValue::Off,
 379                log_level: MessageType::LOG,
 380                io_logs_subscription: None,
 381                capabilities: ServerCapabilities::default(),
 382            }
 383        });
 384
 385        if let Some(name) = name {
 386            server_state.name = Some(name);
 387        }
 388        if let Some(worktree_id) = worktree_id {
 389            server_state.worktree_id = Some(worktree_id);
 390        }
 391
 392        if let Some(server) = server
 393            .clone()
 394            .filter(|_| server_state.io_logs_subscription.is_none())
 395        {
 396            let io_tx = self.io_tx.clone();
 397            let server_id = server.server_id();
 398            server_state.io_logs_subscription = Some(server.on_io(move |io_kind, message| {
 399                io_tx
 400                    .unbounded_send((server_id, io_kind, message.to_string()))
 401                    .ok();
 402            }));
 403        }
 404
 405        if let Some(server) = server {
 406            server_state.capabilities = server.capabilities();
 407        }
 408
 409        Some(server_state)
 410    }
 411
 412    fn add_language_server_log(
 413        &mut self,
 414        id: LanguageServerId,
 415        typ: MessageType,
 416        message: &str,
 417        cx: &mut ModelContext<Self>,
 418    ) -> Option<()> {
 419        let language_server_state = self.get_language_server_state(id)?;
 420
 421        let log_lines = &mut language_server_state.log_messages;
 422        Self::add_language_server_message(
 423            log_lines,
 424            id,
 425            LogMessage {
 426                message: message.trim_end().to_string(),
 427                typ,
 428            },
 429            language_server_state.log_level,
 430            LogKind::Logs,
 431            cx,
 432        );
 433        Some(())
 434    }
 435
 436    fn add_language_server_trace(
 437        &mut self,
 438        id: LanguageServerId,
 439        message: &str,
 440        cx: &mut ModelContext<Self>,
 441    ) -> Option<()> {
 442        let language_server_state = self.get_language_server_state(id)?;
 443
 444        let log_lines = &mut language_server_state.trace_messages;
 445        Self::add_language_server_message(
 446            log_lines,
 447            id,
 448            TraceMessage {
 449                message: message.trim_end().to_string(),
 450            },
 451            (),
 452            LogKind::Trace,
 453            cx,
 454        );
 455        Some(())
 456    }
 457
 458    fn add_language_server_message<T: Message>(
 459        log_lines: &mut VecDeque<T>,
 460        id: LanguageServerId,
 461        message: T,
 462        current_severity: <T as Message>::Level,
 463        kind: LogKind,
 464        cx: &mut ModelContext<Self>,
 465    ) {
 466        while log_lines.len() >= MAX_STORED_LOG_ENTRIES {
 467            log_lines.pop_front();
 468        }
 469        let entry: &str = message.as_ref();
 470        let entry = entry.to_string();
 471        let visible = message.should_include(current_severity);
 472        log_lines.push_back(message);
 473
 474        if visible {
 475            cx.emit(Event::NewServerLogEntry { id, entry, kind });
 476            cx.notify();
 477        }
 478    }
 479
 480    fn remove_language_server(&mut self, id: LanguageServerId, cx: &mut ModelContext<Self>) {
 481        self.language_servers.remove(&id);
 482        cx.notify();
 483    }
 484
 485    fn server_logs(&self, server_id: LanguageServerId) -> Option<&VecDeque<LogMessage>> {
 486        Some(&self.language_servers.get(&server_id)?.log_messages)
 487    }
 488
 489    fn server_trace(&self, server_id: LanguageServerId) -> Option<&VecDeque<TraceMessage>> {
 490        Some(&self.language_servers.get(&server_id)?.trace_messages)
 491    }
 492
 493    fn server_capabilities(&self, server_id: LanguageServerId) -> Option<&ServerCapabilities> {
 494        Some(&self.language_servers.get(&server_id)?.capabilities)
 495    }
 496
 497    fn server_ids_for_project<'a>(
 498        &'a self,
 499        lookup_project: &'a WeakModel<Project>,
 500    ) -> impl Iterator<Item = LanguageServerId> + 'a {
 501        self.language_servers
 502            .iter()
 503            .filter_map(move |(id, state)| match &state.kind {
 504                LanguageServerKind::Local { project } | LanguageServerKind::Remote { project } => {
 505                    if project == lookup_project {
 506                        Some(*id)
 507                    } else {
 508                        None
 509                    }
 510                }
 511                LanguageServerKind::Global => Some(*id),
 512            })
 513    }
 514
 515    fn enable_rpc_trace_for_language_server(
 516        &mut self,
 517        server_id: LanguageServerId,
 518    ) -> Option<&mut LanguageServerRpcState> {
 519        let rpc_state = self
 520            .language_servers
 521            .get_mut(&server_id)?
 522            .rpc_state
 523            .get_or_insert_with(|| LanguageServerRpcState {
 524                rpc_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES),
 525                last_message_kind: None,
 526            });
 527        Some(rpc_state)
 528    }
 529
 530    pub fn disable_rpc_trace_for_language_server(
 531        &mut self,
 532        server_id: LanguageServerId,
 533    ) -> Option<()> {
 534        self.language_servers.get_mut(&server_id)?.rpc_state.take();
 535        Some(())
 536    }
 537
 538    fn on_io(
 539        &mut self,
 540        language_server_id: LanguageServerId,
 541        io_kind: IoKind,
 542        message: &str,
 543        cx: &mut ModelContext<Self>,
 544    ) -> Option<()> {
 545        let is_received = match io_kind {
 546            IoKind::StdOut => true,
 547            IoKind::StdIn => false,
 548            IoKind::StdErr => {
 549                let message = format!("stderr: {}", message.trim());
 550                self.add_language_server_log(language_server_id, MessageType::LOG, &message, cx);
 551                return Some(());
 552            }
 553        };
 554
 555        let state = self
 556            .get_language_server_state(language_server_id)?
 557            .rpc_state
 558            .as_mut()?;
 559        let kind = if is_received {
 560            MessageKind::Receive
 561        } else {
 562            MessageKind::Send
 563        };
 564
 565        let rpc_log_lines = &mut state.rpc_messages;
 566        if state.last_message_kind != Some(kind) {
 567            let line_before_message = match kind {
 568                MessageKind::Send => SEND_LINE,
 569                MessageKind::Receive => RECEIVE_LINE,
 570            };
 571            rpc_log_lines.push_back(RpcMessage {
 572                message: line_before_message.to_string(),
 573            });
 574            cx.emit(Event::NewServerLogEntry {
 575                id: language_server_id,
 576                entry: line_before_message.to_string(),
 577                kind: LogKind::Rpc,
 578            });
 579        }
 580
 581        while rpc_log_lines.len() >= MAX_STORED_LOG_ENTRIES {
 582            rpc_log_lines.pop_front();
 583        }
 584        let message = message.trim();
 585        rpc_log_lines.push_back(RpcMessage {
 586            message: message.to_string(),
 587        });
 588        cx.emit(Event::NewServerLogEntry {
 589            id: language_server_id,
 590            entry: message.to_string(),
 591            kind: LogKind::Rpc,
 592        });
 593        cx.notify();
 594        Some(())
 595    }
 596}
 597
 598impl LspLogView {
 599    pub fn new(
 600        project: Model<Project>,
 601        log_store: Model<LogStore>,
 602        cx: &mut ViewContext<Self>,
 603    ) -> Self {
 604        let server_id = log_store
 605            .read(cx)
 606            .language_servers
 607            .iter()
 608            .find(|(_, server)| server.kind.project() == Some(&project.downgrade()))
 609            .map(|(id, _)| *id);
 610
 611        let weak_project = project.downgrade();
 612        let model_changes_subscription = cx.observe(&log_store, move |this, store, cx| {
 613            let first_server_id_for_project =
 614                store.read(cx).server_ids_for_project(&weak_project).next();
 615            if let Some(current_lsp) = this.current_server_id {
 616                if !store.read(cx).language_servers.contains_key(&current_lsp) {
 617                    if let Some(server_id) = first_server_id_for_project {
 618                        match this.active_entry_kind {
 619                            LogKind::Rpc => this.show_rpc_trace_for_server(server_id, cx),
 620                            LogKind::Trace => this.show_trace_for_server(server_id, cx),
 621                            LogKind::Logs => this.show_logs_for_server(server_id, cx),
 622                            LogKind::Capabilities => {
 623                                this.show_capabilities_for_server(server_id, cx)
 624                            }
 625                        }
 626                    } else {
 627                        this.current_server_id = None;
 628                        this.editor.update(cx, |editor, cx| {
 629                            editor.set_read_only(false);
 630                            editor.clear(cx);
 631                            editor.set_read_only(true);
 632                        });
 633                        cx.notify();
 634                    }
 635                }
 636            } else if let Some(server_id) = first_server_id_for_project {
 637                match this.active_entry_kind {
 638                    LogKind::Rpc => this.show_rpc_trace_for_server(server_id, cx),
 639                    LogKind::Trace => this.show_trace_for_server(server_id, cx),
 640                    LogKind::Logs => this.show_logs_for_server(server_id, cx),
 641                    LogKind::Capabilities => this.show_capabilities_for_server(server_id, cx),
 642                }
 643            }
 644
 645            cx.notify();
 646        });
 647        let events_subscriptions = cx.subscribe(&log_store, |log_view, _, e, cx| match e {
 648            Event::NewServerLogEntry { id, entry, kind } => {
 649                if log_view.current_server_id == Some(*id) && *kind == log_view.active_entry_kind {
 650                    log_view.editor.update(cx, |editor, cx| {
 651                        editor.set_read_only(false);
 652                        let last_point = editor.buffer().read(cx).len(cx);
 653                        editor.edit(
 654                            vec![
 655                                (last_point..last_point, entry.trim()),
 656                                (last_point..last_point, "\n"),
 657                            ],
 658                            cx,
 659                        );
 660                        editor.set_read_only(true);
 661                    });
 662                }
 663            }
 664        });
 665        let (editor, editor_subscriptions) = Self::editor_for_logs(String::new(), cx);
 666
 667        let focus_handle = cx.focus_handle();
 668        let focus_subscription = cx.on_focus(&focus_handle, |log_view, cx| {
 669            cx.focus_view(&log_view.editor);
 670        });
 671
 672        let mut this = Self {
 673            focus_handle,
 674            editor,
 675            editor_subscriptions,
 676            project,
 677            log_store,
 678            current_server_id: None,
 679            active_entry_kind: LogKind::Logs,
 680            _log_store_subscriptions: vec![
 681                model_changes_subscription,
 682                events_subscriptions,
 683                focus_subscription,
 684            ],
 685        };
 686        if let Some(server_id) = server_id {
 687            this.show_logs_for_server(server_id, cx);
 688        }
 689        this
 690    }
 691
 692    fn editor_for_logs(
 693        log_contents: String,
 694        cx: &mut ViewContext<Self>,
 695    ) -> (View<Editor>, Vec<Subscription>) {
 696        let editor = cx.new_view(|cx| {
 697            let mut editor = Editor::multi_line(cx);
 698            editor.set_text(log_contents, cx);
 699            editor.move_to_end(&MoveToEnd, cx);
 700            editor.set_read_only(true);
 701            editor.set_show_inline_completions(Some(false), cx);
 702            editor
 703        });
 704        let editor_subscription = cx.subscribe(
 705            &editor,
 706            |_, _, event: &EditorEvent, cx: &mut ViewContext<'_, LspLogView>| {
 707                cx.emit(event.clone())
 708            },
 709        );
 710        let search_subscription = cx.subscribe(
 711            &editor,
 712            |_, _, event: &SearchEvent, cx: &mut ViewContext<'_, LspLogView>| {
 713                cx.emit(event.clone())
 714            },
 715        );
 716        (editor, vec![editor_subscription, search_subscription])
 717    }
 718
 719    fn editor_for_capabilities(
 720        capabilities: ServerCapabilities,
 721        cx: &mut ViewContext<Self>,
 722    ) -> (View<Editor>, Vec<Subscription>) {
 723        let editor = cx.new_view(|cx| {
 724            let mut editor = Editor::multi_line(cx);
 725            editor.set_text(serde_json::to_string_pretty(&capabilities).unwrap(), cx);
 726            editor.move_to_end(&MoveToEnd, cx);
 727            editor.set_read_only(true);
 728            editor.set_show_inline_completions(Some(false), cx);
 729            editor
 730        });
 731        let editor_subscription = cx.subscribe(
 732            &editor,
 733            |_, _, event: &EditorEvent, cx: &mut ViewContext<'_, LspLogView>| {
 734                cx.emit(event.clone())
 735            },
 736        );
 737        let search_subscription = cx.subscribe(
 738            &editor,
 739            |_, _, event: &SearchEvent, cx: &mut ViewContext<'_, LspLogView>| {
 740                cx.emit(event.clone())
 741            },
 742        );
 743        (editor, vec![editor_subscription, search_subscription])
 744    }
 745
 746    pub(crate) fn menu_items<'a>(&'a self, cx: &'a AppContext) -> Option<Vec<LogMenuItem>> {
 747        let log_store = self.log_store.read(cx);
 748
 749        let unknown_server = LanguageServerName::new_static("unknown server");
 750
 751        let mut rows = log_store
 752            .language_servers
 753            .iter()
 754            .map(|(server_id, state)| match &state.kind {
 755                LanguageServerKind::Local { .. } | LanguageServerKind::Remote { .. } => {
 756                    let worktree_root_name = state
 757                        .worktree_id
 758                        .and_then(|id| self.project.read(cx).worktree_for_id(id, cx))
 759                        .map(|worktree| worktree.read(cx).root_name().to_string())
 760                        .unwrap_or_else(|| "Unknown worktree".to_string());
 761
 762                    LogMenuItem {
 763                        server_id: *server_id,
 764                        server_name: state.name.clone().unwrap_or(unknown_server.clone()),
 765                        server_kind: state.kind.clone(),
 766                        worktree_root_name,
 767                        rpc_trace_enabled: state.rpc_state.is_some(),
 768                        selected_entry: self.active_entry_kind,
 769                        trace_level: lsp::TraceValue::Off,
 770                    }
 771                }
 772
 773                LanguageServerKind::Global => LogMenuItem {
 774                    server_id: *server_id,
 775                    server_name: state.name.clone().unwrap_or(unknown_server.clone()),
 776                    server_kind: state.kind.clone(),
 777                    worktree_root_name: "supplementary".to_string(),
 778                    rpc_trace_enabled: state.rpc_state.is_some(),
 779                    selected_entry: self.active_entry_kind,
 780                    trace_level: lsp::TraceValue::Off,
 781                },
 782            })
 783            .chain(
 784                self.project
 785                    .read(cx)
 786                    .supplementary_language_servers(cx)
 787                    .filter_map(|(server_id, name)| {
 788                        let state = log_store.language_servers.get(&server_id)?;
 789                        Some(LogMenuItem {
 790                            server_id,
 791                            server_name: name.clone(),
 792                            server_kind: state.kind.clone(),
 793                            worktree_root_name: "supplementary".to_string(),
 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            .collect::<Vec<_>>();
 801        rows.sort_by_key(|row| row.server_id);
 802        rows.dedup_by_key(|row| row.server_id);
 803        Some(rows)
 804    }
 805
 806    fn show_logs_for_server(&mut self, server_id: LanguageServerId, cx: &mut ViewContext<Self>) {
 807        let typ = self
 808            .log_store
 809            .read_with(cx, |v, _| {
 810                v.language_servers.get(&server_id).map(|v| v.log_level)
 811            })
 812            .unwrap_or(MessageType::LOG);
 813        let log_contents = self
 814            .log_store
 815            .read(cx)
 816            .server_logs(server_id)
 817            .map(|v| log_contents(v, typ));
 818        if let Some(log_contents) = log_contents {
 819            self.current_server_id = Some(server_id);
 820            self.active_entry_kind = LogKind::Logs;
 821            let (editor, editor_subscriptions) = Self::editor_for_logs(log_contents, cx);
 822            self.editor = editor;
 823            self.editor_subscriptions = editor_subscriptions;
 824            cx.notify();
 825        }
 826        cx.focus(&self.focus_handle);
 827    }
 828
 829    fn update_log_level(
 830        &self,
 831        server_id: LanguageServerId,
 832        level: MessageType,
 833        cx: &mut ViewContext<Self>,
 834    ) {
 835        let log_contents = self.log_store.update(cx, |this, _| {
 836            if let Some(state) = this.get_language_server_state(server_id) {
 837                state.log_level = level;
 838            }
 839
 840            this.server_logs(server_id).map(|v| log_contents(v, level))
 841        });
 842
 843        if let Some(log_contents) = log_contents {
 844            self.editor.update(cx, move |editor, cx| {
 845                editor.set_text(log_contents, cx);
 846                editor.move_to_end(&MoveToEnd, cx);
 847            });
 848            cx.notify();
 849        }
 850
 851        cx.focus(&self.focus_handle);
 852    }
 853
 854    fn show_trace_for_server(&mut self, server_id: LanguageServerId, cx: &mut ViewContext<Self>) {
 855        let log_contents = self
 856            .log_store
 857            .read(cx)
 858            .server_trace(server_id)
 859            .map(|v| log_contents(v, ()));
 860        if let Some(log_contents) = log_contents {
 861            self.current_server_id = Some(server_id);
 862            self.active_entry_kind = LogKind::Trace;
 863            let (editor, editor_subscriptions) = Self::editor_for_logs(log_contents, cx);
 864            self.editor = editor;
 865            self.editor_subscriptions = editor_subscriptions;
 866            cx.notify();
 867        }
 868        cx.focus(&self.focus_handle);
 869    }
 870
 871    fn show_rpc_trace_for_server(
 872        &mut self,
 873        server_id: LanguageServerId,
 874        cx: &mut ViewContext<Self>,
 875    ) {
 876        let rpc_log = self.log_store.update(cx, |log_store, _| {
 877            log_store
 878                .enable_rpc_trace_for_language_server(server_id)
 879                .map(|state| log_contents(&state.rpc_messages, ()))
 880        });
 881        if let Some(rpc_log) = rpc_log {
 882            self.current_server_id = Some(server_id);
 883            self.active_entry_kind = LogKind::Rpc;
 884            let (editor, editor_subscriptions) = Self::editor_for_logs(rpc_log, cx);
 885            let language = self.project.read(cx).languages().language_for_name("JSON");
 886            editor
 887                .read(cx)
 888                .buffer()
 889                .read(cx)
 890                .as_singleton()
 891                .expect("log buffer should be a singleton")
 892                .update(cx, |_, cx| {
 893                    cx.spawn({
 894                        let buffer = cx.handle();
 895                        |_, mut cx| async move {
 896                            let language = language.await.ok();
 897                            buffer.update(&mut cx, |buffer, cx| {
 898                                buffer.set_language(language, cx);
 899                            })
 900                        }
 901                    })
 902                    .detach_and_log_err(cx);
 903                });
 904
 905            self.editor = editor;
 906            self.editor_subscriptions = editor_subscriptions;
 907            cx.notify();
 908        }
 909
 910        cx.focus(&self.focus_handle);
 911    }
 912
 913    fn toggle_rpc_trace_for_server(
 914        &mut self,
 915        server_id: LanguageServerId,
 916        enabled: bool,
 917        cx: &mut ViewContext<Self>,
 918    ) {
 919        self.log_store.update(cx, |log_store, _| {
 920            if enabled {
 921                log_store.enable_rpc_trace_for_language_server(server_id);
 922            } else {
 923                log_store.disable_rpc_trace_for_language_server(server_id);
 924            }
 925        });
 926        if !enabled && Some(server_id) == self.current_server_id {
 927            self.show_logs_for_server(server_id, cx);
 928            cx.notify();
 929        }
 930    }
 931
 932    fn update_trace_level(
 933        &self,
 934        server_id: LanguageServerId,
 935        level: TraceValue,
 936        cx: &mut ViewContext<Self>,
 937    ) {
 938        if let Some(server) = self.project.read(cx).language_server_for_id(server_id, cx) {
 939            self.log_store.update(cx, |this, _| {
 940                if let Some(state) = this.get_language_server_state(server_id) {
 941                    state.trace_level = level;
 942                }
 943            });
 944
 945            server
 946                .notify::<SetTrace>(SetTraceParams { value: level })
 947                .ok();
 948        }
 949    }
 950
 951    fn show_capabilities_for_server(
 952        &mut self,
 953        server_id: LanguageServerId,
 954        cx: &mut ViewContext<Self>,
 955    ) {
 956        let capabilities = self.log_store.read(cx).server_capabilities(server_id);
 957
 958        if let Some(capabilities) = capabilities {
 959            self.current_server_id = Some(server_id);
 960            self.active_entry_kind = LogKind::Capabilities;
 961            let (editor, editor_subscriptions) =
 962                Self::editor_for_capabilities(capabilities.clone(), cx);
 963            self.editor = editor;
 964            self.editor_subscriptions = editor_subscriptions;
 965            cx.notify();
 966        }
 967        cx.focus(&self.focus_handle);
 968    }
 969}
 970
 971fn log_filter<T: Message>(line: &T, cmp: <T as Message>::Level) -> Option<&str> {
 972    if line.should_include(cmp) {
 973        Some(line.as_ref())
 974    } else {
 975        None
 976    }
 977}
 978
 979fn log_contents<T: Message>(lines: &VecDeque<T>, cmp: <T as Message>::Level) -> String {
 980    let (a, b) = lines.as_slices();
 981    let a = a.iter().filter_map(move |v| log_filter(v, cmp));
 982    let b = b.iter().filter_map(move |v| log_filter(v, cmp));
 983    a.chain(b).fold(String::new(), |mut acc, el| {
 984        acc.push_str(el);
 985        acc.push('\n');
 986        acc
 987    })
 988}
 989
 990impl Render for LspLogView {
 991    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
 992        self.editor
 993            .update(cx, |editor, cx| editor.render(cx).into_any_element())
 994    }
 995}
 996
 997impl FocusableView for LspLogView {
 998    fn focus_handle(&self, _: &AppContext) -> FocusHandle {
 999        self.focus_handle.clone()
1000    }
1001}
1002
1003impl Item for LspLogView {
1004    type Event = EditorEvent;
1005
1006    fn to_item_events(event: &Self::Event, f: impl FnMut(workspace::item::ItemEvent)) {
1007        Editor::to_item_events(event, f)
1008    }
1009
1010    fn tab_content_text(&self, _cx: &WindowContext) -> Option<SharedString> {
1011        Some("LSP Logs".into())
1012    }
1013
1014    fn telemetry_event_text(&self) -> Option<&'static str> {
1015        None
1016    }
1017
1018    fn as_searchable(&self, handle: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
1019        Some(Box::new(handle.clone()))
1020    }
1021
1022    fn clone_on_split(
1023        &self,
1024        _workspace_id: Option<WorkspaceId>,
1025        cx: &mut ViewContext<Self>,
1026    ) -> Option<View<Self>>
1027    where
1028        Self: Sized,
1029    {
1030        Some(cx.new_view(|cx| {
1031            let mut new_view = Self::new(self.project.clone(), self.log_store.clone(), cx);
1032            if let Some(server_id) = self.current_server_id {
1033                match self.active_entry_kind {
1034                    LogKind::Rpc => new_view.show_rpc_trace_for_server(server_id, cx),
1035                    LogKind::Trace => new_view.show_trace_for_server(server_id, cx),
1036                    LogKind::Logs => new_view.show_logs_for_server(server_id, cx),
1037                    LogKind::Capabilities => new_view.show_capabilities_for_server(server_id, cx),
1038                }
1039            }
1040            new_view
1041        }))
1042    }
1043}
1044
1045impl SearchableItem for LspLogView {
1046    type Match = <Editor as SearchableItem>::Match;
1047
1048    fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {
1049        self.editor.update(cx, |e, cx| e.clear_matches(cx))
1050    }
1051
1052    fn update_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>) {
1053        self.editor
1054            .update(cx, |e, cx| e.update_matches(matches, cx))
1055    }
1056
1057    fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String {
1058        self.editor.update(cx, |e, cx| e.query_suggestion(cx))
1059    }
1060
1061    fn activate_match(
1062        &mut self,
1063        index: usize,
1064        matches: &[Self::Match],
1065        cx: &mut ViewContext<Self>,
1066    ) {
1067        self.editor
1068            .update(cx, |e, cx| e.activate_match(index, matches, cx))
1069    }
1070
1071    fn select_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>) {
1072        self.editor
1073            .update(cx, |e, cx| e.select_matches(matches, cx))
1074    }
1075
1076    fn find_matches(
1077        &mut self,
1078        query: Arc<project::search::SearchQuery>,
1079        cx: &mut ViewContext<Self>,
1080    ) -> gpui::Task<Vec<Self::Match>> {
1081        self.editor.update(cx, |e, cx| e.find_matches(query, cx))
1082    }
1083
1084    fn replace(&mut self, _: &Self::Match, _: &SearchQuery, _: &mut ViewContext<Self>) {
1085        // Since LSP Log is read-only, it doesn't make sense to support replace operation.
1086    }
1087    fn supported_options() -> workspace::searchable::SearchOptions {
1088        workspace::searchable::SearchOptions {
1089            case: true,
1090            word: true,
1091            regex: true,
1092            // LSP log is read-only.
1093            replacement: false,
1094            selection: false,
1095        }
1096    }
1097    fn active_match_index(
1098        &mut self,
1099        matches: &[Self::Match],
1100        cx: &mut ViewContext<Self>,
1101    ) -> Option<usize> {
1102        self.editor
1103            .update(cx, |e, cx| e.active_match_index(matches, cx))
1104    }
1105}
1106
1107impl EventEmitter<ToolbarItemEvent> for LspLogToolbarItemView {}
1108
1109impl ToolbarItemView for LspLogToolbarItemView {
1110    fn set_active_pane_item(
1111        &mut self,
1112        active_pane_item: Option<&dyn ItemHandle>,
1113        cx: &mut ViewContext<Self>,
1114    ) -> workspace::ToolbarItemLocation {
1115        if let Some(item) = active_pane_item {
1116            if let Some(log_view) = item.downcast::<LspLogView>() {
1117                self.log_view = Some(log_view.clone());
1118                self._log_view_subscription = Some(cx.observe(&log_view, |_, _, cx| {
1119                    cx.notify();
1120                }));
1121                return ToolbarItemLocation::PrimaryLeft;
1122            }
1123        }
1124        self.log_view = None;
1125        self._log_view_subscription = None;
1126        ToolbarItemLocation::Hidden
1127    }
1128}
1129
1130impl Render for LspLogToolbarItemView {
1131    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
1132        let Some(log_view) = self.log_view.clone() else {
1133            return div();
1134        };
1135        let (menu_rows, current_server_id) = log_view.update(cx, |log_view, cx| {
1136            let menu_rows = log_view.menu_items(cx).unwrap_or_default();
1137            let current_server_id = log_view.current_server_id;
1138            (menu_rows, current_server_id)
1139        });
1140
1141        let current_server = current_server_id.and_then(|current_server_id| {
1142            if let Ok(ix) = menu_rows.binary_search_by_key(&current_server_id, |e| e.server_id) {
1143                Some(menu_rows[ix].clone())
1144            } else {
1145                None
1146            }
1147        });
1148
1149        let log_toolbar_view = cx.view().clone();
1150        let lsp_menu = PopoverMenu::new("LspLogView")
1151            .anchor(AnchorCorner::TopLeft)
1152            .trigger(Button::new(
1153                "language_server_menu_header",
1154                current_server
1155                    .map(|row| {
1156                        Cow::Owned(format!(
1157                            "{} ({}) - {}",
1158                            row.server_name.0,
1159                            row.worktree_root_name,
1160                            row.selected_entry.label()
1161                        ))
1162                    })
1163                    .unwrap_or_else(|| "No server selected".into()),
1164            ))
1165            .menu({
1166                let log_view = log_view.clone();
1167                move |cx| {
1168                    let menu_rows = menu_rows.clone();
1169                    let log_view = log_view.clone();
1170                    let log_toolbar_view = log_toolbar_view.clone();
1171                    ContextMenu::build(cx, move |mut menu, cx| {
1172                        for (ix, row) in menu_rows.into_iter().enumerate() {
1173                            let server_selected = Some(row.server_id) == current_server_id;
1174                            menu = menu
1175                                .header(format!(
1176                                    "{} ({})",
1177                                    row.server_name.0, row.worktree_root_name
1178                                ))
1179                                .entry(
1180                                    SERVER_LOGS,
1181                                    None,
1182                                    cx.handler_for(&log_view, move |view, cx| {
1183                                        view.show_logs_for_server(row.server_id, cx);
1184                                    }),
1185                                );
1186                            // We do not support tracing for remote language servers right now
1187                            if row.server_kind.is_remote() {
1188                                continue;
1189                            }
1190                            menu = menu.entry(
1191                                SERVER_TRACE,
1192                                None,
1193                                cx.handler_for(&log_view, move |view, cx| {
1194                                    view.show_trace_for_server(row.server_id, cx);
1195                                }),
1196                            );
1197                            menu = menu.custom_entry(
1198                                {
1199                                    let log_toolbar_view = log_toolbar_view.clone();
1200                                    move |cx| {
1201                                        h_flex()
1202                                            .w_full()
1203                                            .justify_between()
1204                                            .child(Label::new(RPC_MESSAGES))
1205                                            .child(
1206                                                div().child(
1207                                                    Checkbox::new(
1208                                                        ix,
1209                                                        if row.rpc_trace_enabled {
1210                                                            Selection::Selected
1211                                                        } else {
1212                                                            Selection::Unselected
1213                                                        },
1214                                                    )
1215                                                    .on_click(cx.listener_for(
1216                                                        &log_toolbar_view,
1217                                                        move |view, selection, cx| {
1218                                                            let enabled = matches!(
1219                                                                selection,
1220                                                                Selection::Selected
1221                                                            );
1222                                                            view.toggle_rpc_logging_for_server(
1223                                                                row.server_id,
1224                                                                enabled,
1225                                                                cx,
1226                                                            );
1227                                                            cx.stop_propagation();
1228                                                        },
1229                                                    )),
1230                                                ),
1231                                            )
1232                                            .into_any_element()
1233                                    }
1234                                },
1235                                cx.handler_for(&log_view, move |view, cx| {
1236                                    view.show_rpc_trace_for_server(row.server_id, cx);
1237                                }),
1238                            );
1239                            if server_selected && row.selected_entry == LogKind::Rpc {
1240                                let selected_ix = menu.select_last();
1241                                // Each language server has:
1242                                // 1. A title.
1243                                // 2. Server logs.
1244                                // 3. Server trace.
1245                                // 4. RPC messages.
1246                                // 5. Server capabilities
1247                                // Thus, if nth server's RPC is selected, the index of selected entry should match this formula
1248                                let _expected_index = ix * 5 + 3;
1249                                debug_assert_eq!(
1250                                    Some(_expected_index),
1251                                    selected_ix,
1252                                    "Could not scroll to a just added LSP menu item"
1253                                );
1254                            }
1255                            menu = menu.entry(
1256                                SERVER_CAPABILITIES,
1257                                None,
1258                                cx.handler_for(&log_view, move |view, cx| {
1259                                    view.show_capabilities_for_server(row.server_id, cx);
1260                                }),
1261                            );
1262                        }
1263                        menu
1264                    })
1265                    .into()
1266                }
1267            });
1268
1269        h_flex()
1270            .size_full()
1271            .child(lsp_menu)
1272            .child(
1273                div()
1274                    .child(
1275                        Button::new("clear_log_button", "Clear").on_click(cx.listener(
1276                            |this, _, cx| {
1277                                if let Some(log_view) = this.log_view.as_ref() {
1278                                    log_view.update(cx, |log_view, cx| {
1279                                        log_view.editor.update(cx, |editor, cx| {
1280                                            editor.set_read_only(false);
1281                                            editor.clear(cx);
1282                                            editor.set_read_only(true);
1283                                        });
1284                                    })
1285                                }
1286                            },
1287                        )),
1288                    )
1289                    .ml_2(),
1290            )
1291            .child(log_view.update(cx, |this, _| match this.active_entry_kind {
1292                LogKind::Trace => {
1293                    let log_view = log_view.clone();
1294                    div().child(
1295                        PopoverMenu::new("lsp-trace-level-menu")
1296                            .anchor(AnchorCorner::TopLeft)
1297                            .trigger(Button::new(
1298                                "language_server_trace_level_selector",
1299                                "Trace level",
1300                            ))
1301                            .menu({
1302                                let log_view = log_view.clone();
1303
1304                                move |cx| {
1305                                    let id = log_view.read(cx).current_server_id?;
1306
1307                                    let trace_level = log_view.update(cx, |this, cx| {
1308                                        this.log_store.update(cx, |this, _| {
1309                                            Some(this.get_language_server_state(id)?.trace_level)
1310                                        })
1311                                    })?;
1312
1313                                    ContextMenu::build(cx, |mut menu, _| {
1314                                        let log_view = log_view.clone();
1315
1316                                        for (option, label) in [
1317                                            (TraceValue::Off, "Off"),
1318                                            (TraceValue::Messages, "Messages"),
1319                                            (TraceValue::Verbose, "Verbose"),
1320                                        ] {
1321                                            menu = menu.entry(label, None, {
1322                                                let log_view = log_view.clone();
1323                                                move |cx| {
1324                                                    log_view.update(cx, |this, cx| {
1325                                                        if let Some(id) = this.current_server_id {
1326                                                            this.update_trace_level(id, option, cx);
1327                                                        }
1328                                                    });
1329                                                }
1330                                            });
1331                                            if option == trace_level {
1332                                                menu.select_last();
1333                                            }
1334                                        }
1335
1336                                        menu
1337                                    })
1338                                    .into()
1339                                }
1340                            }),
1341                    )
1342                }
1343                LogKind::Logs => {
1344                    let log_view = log_view.clone();
1345                    div().child(
1346                        PopoverMenu::new("lsp-log-level-menu")
1347                            .anchor(AnchorCorner::TopLeft)
1348                            .trigger(Button::new(
1349                                "language_server_log_level_selector",
1350                                "Log level",
1351                            ))
1352                            .menu({
1353                                let log_view = log_view.clone();
1354
1355                                move |cx| {
1356                                    let id = log_view.read(cx).current_server_id?;
1357
1358                                    let log_level = log_view.update(cx, |this, cx| {
1359                                        this.log_store.update(cx, |this, _| {
1360                                            Some(this.get_language_server_state(id)?.log_level)
1361                                        })
1362                                    })?;
1363
1364                                    ContextMenu::build(cx, |mut menu, _| {
1365                                        let log_view = log_view.clone();
1366
1367                                        for (option, label) in [
1368                                            (MessageType::LOG, "Log"),
1369                                            (MessageType::INFO, "Info"),
1370                                            (MessageType::WARNING, "Warning"),
1371                                            (MessageType::ERROR, "Error"),
1372                                        ] {
1373                                            menu = menu.entry(label, None, {
1374                                                let log_view = log_view.clone();
1375                                                move |cx| {
1376                                                    log_view.update(cx, |this, cx| {
1377                                                        if let Some(id) = this.current_server_id {
1378                                                            this.update_log_level(id, option, cx);
1379                                                        }
1380                                                    });
1381                                                }
1382                                            });
1383                                            if option == log_level {
1384                                                menu.select_last();
1385                                            }
1386                                        }
1387
1388                                        menu
1389                                    })
1390                                    .into()
1391                                }
1392                            }),
1393                    )
1394                }
1395                _ => div(),
1396            }))
1397    }
1398}
1399
1400const RPC_MESSAGES: &str = "RPC Messages";
1401const SERVER_LOGS: &str = "Server Logs";
1402const SERVER_TRACE: &str = "Server Trace";
1403const SERVER_CAPABILITIES: &str = "Server Capabilities";
1404
1405impl Default for LspLogToolbarItemView {
1406    fn default() -> Self {
1407        Self::new()
1408    }
1409}
1410
1411impl LspLogToolbarItemView {
1412    pub fn new() -> Self {
1413        Self {
1414            log_view: None,
1415            _log_view_subscription: None,
1416        }
1417    }
1418
1419    fn toggle_rpc_logging_for_server(
1420        &mut self,
1421        id: LanguageServerId,
1422        enabled: bool,
1423        cx: &mut ViewContext<Self>,
1424    ) {
1425        if let Some(log_view) = &self.log_view {
1426            log_view.update(cx, |log_view, cx| {
1427                log_view.toggle_rpc_trace_for_server(id, enabled, cx);
1428                if !enabled && Some(id) == log_view.current_server_id {
1429                    log_view.show_logs_for_server(id, cx);
1430                    cx.notify();
1431                } else if enabled {
1432                    log_view.show_rpc_trace_for_server(id, cx);
1433                    cx.notify();
1434                }
1435                cx.focus(&log_view.focus_handle);
1436            });
1437        }
1438        cx.notify();
1439    }
1440}
1441
1442pub enum Event {
1443    NewServerLogEntry {
1444        id: LanguageServerId,
1445        entry: String,
1446        kind: LogKind,
1447    },
1448}
1449
1450impl EventEmitter<Event> for LogStore {}
1451impl EventEmitter<Event> for LspLogView {}
1452impl EventEmitter<EditorEvent> for LspLogView {}
1453impl EventEmitter<SearchEvent> for LspLogView {}