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, LanguageServerName};
  11use lsp::{
  12    notification::SetTrace, IoKind, LanguageServer, MessageType, ServerCapabilities,
  13    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            .filter_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                    let state = log_store.language_servers.get(&server_id)?;
 763                    Some(LogMenuItem {
 764                        server_id: *server_id,
 765                        server_name: state.name.clone().unwrap_or(unknown_server.clone()),
 766                        server_kind: state.kind.clone(),
 767                        worktree_root_name,
 768                        rpc_trace_enabled: state.rpc_state.is_some(),
 769                        selected_entry: self.active_entry_kind,
 770                        trace_level: lsp::TraceValue::Off,
 771                    })
 772                }
 773
 774                LanguageServerKind::Global => Some(LogMenuItem {
 775                    server_id: *server_id,
 776                    server_name: state.name.clone().unwrap_or(unknown_server.clone()),
 777                    server_kind: state.kind.clone(),
 778                    worktree_root_name: "supplementary".to_string(),
 779                    rpc_trace_enabled: state.rpc_state.is_some(),
 780                    selected_entry: self.active_entry_kind,
 781                    trace_level: lsp::TraceValue::Off,
 782                }),
 783            })
 784            .chain(
 785                self.project
 786                    .read(cx)
 787                    .supplementary_language_servers(cx)
 788                    .filter_map(|(server_id, name)| {
 789                        let state = log_store.language_servers.get(&server_id)?;
 790                        Some(LogMenuItem {
 791                            server_id,
 792                            server_name: name.clone(),
 793                            server_kind: state.kind.clone(),
 794                            worktree_root_name: "supplementary".to_string(),
 795                            rpc_trace_enabled: state.rpc_state.is_some(),
 796                            selected_entry: self.active_entry_kind,
 797                            trace_level: lsp::TraceValue::Off,
 798                        })
 799                    }),
 800            )
 801            .collect::<Vec<_>>();
 802        rows.sort_by_key(|row| row.server_id);
 803        rows.dedup_by_key(|row| row.server_id);
 804        Some(rows)
 805    }
 806
 807    fn show_logs_for_server(&mut self, server_id: LanguageServerId, cx: &mut ViewContext<Self>) {
 808        let typ = self
 809            .log_store
 810            .read_with(cx, |v, _| {
 811                v.language_servers.get(&server_id).map(|v| v.log_level)
 812            })
 813            .unwrap_or(MessageType::LOG);
 814        let log_contents = self
 815            .log_store
 816            .read(cx)
 817            .server_logs(server_id)
 818            .map(|v| log_contents(v, typ));
 819        if let Some(log_contents) = log_contents {
 820            self.current_server_id = Some(server_id);
 821            self.active_entry_kind = LogKind::Logs;
 822            let (editor, editor_subscriptions) = Self::editor_for_logs(log_contents, cx);
 823            self.editor = editor;
 824            self.editor_subscriptions = editor_subscriptions;
 825            cx.notify();
 826        }
 827        cx.focus(&self.focus_handle);
 828    }
 829
 830    fn update_log_level(
 831        &self,
 832        server_id: LanguageServerId,
 833        level: MessageType,
 834        cx: &mut ViewContext<Self>,
 835    ) {
 836        let log_contents = self.log_store.update(cx, |this, _| {
 837            if let Some(state) = this.get_language_server_state(server_id) {
 838                state.log_level = level;
 839            }
 840
 841            this.server_logs(server_id).map(|v| log_contents(v, level))
 842        });
 843
 844        if let Some(log_contents) = log_contents {
 845            self.editor.update(cx, move |editor, cx| {
 846                editor.set_text(log_contents, cx);
 847                editor.move_to_end(&MoveToEnd, cx);
 848            });
 849            cx.notify();
 850        }
 851
 852        cx.focus(&self.focus_handle);
 853    }
 854
 855    fn show_trace_for_server(&mut self, server_id: LanguageServerId, cx: &mut ViewContext<Self>) {
 856        let log_contents = self
 857            .log_store
 858            .read(cx)
 859            .server_trace(server_id)
 860            .map(|v| log_contents(v, ()));
 861        if let Some(log_contents) = log_contents {
 862            self.current_server_id = Some(server_id);
 863            self.active_entry_kind = LogKind::Trace;
 864            let (editor, editor_subscriptions) = Self::editor_for_logs(log_contents, cx);
 865            self.editor = editor;
 866            self.editor_subscriptions = editor_subscriptions;
 867            cx.notify();
 868        }
 869        cx.focus(&self.focus_handle);
 870    }
 871
 872    fn show_rpc_trace_for_server(
 873        &mut self,
 874        server_id: LanguageServerId,
 875        cx: &mut ViewContext<Self>,
 876    ) {
 877        let rpc_log = self.log_store.update(cx, |log_store, _| {
 878            log_store
 879                .enable_rpc_trace_for_language_server(server_id)
 880                .map(|state| log_contents(&state.rpc_messages, ()))
 881        });
 882        if let Some(rpc_log) = rpc_log {
 883            self.current_server_id = Some(server_id);
 884            self.active_entry_kind = LogKind::Rpc;
 885            let (editor, editor_subscriptions) = Self::editor_for_logs(rpc_log, cx);
 886            let language = self.project.read(cx).languages().language_for_name("JSON");
 887            editor
 888                .read(cx)
 889                .buffer()
 890                .read(cx)
 891                .as_singleton()
 892                .expect("log buffer should be a singleton")
 893                .update(cx, |_, cx| {
 894                    cx.spawn({
 895                        let buffer = cx.handle();
 896                        |_, mut cx| async move {
 897                            let language = language.await.ok();
 898                            buffer.update(&mut cx, |buffer, cx| {
 899                                buffer.set_language(language, cx);
 900                            })
 901                        }
 902                    })
 903                    .detach_and_log_err(cx);
 904                });
 905
 906            self.editor = editor;
 907            self.editor_subscriptions = editor_subscriptions;
 908            cx.notify();
 909        }
 910
 911        cx.focus(&self.focus_handle);
 912    }
 913
 914    fn toggle_rpc_trace_for_server(
 915        &mut self,
 916        server_id: LanguageServerId,
 917        enabled: bool,
 918        cx: &mut ViewContext<Self>,
 919    ) {
 920        self.log_store.update(cx, |log_store, _| {
 921            if enabled {
 922                log_store.enable_rpc_trace_for_language_server(server_id);
 923            } else {
 924                log_store.disable_rpc_trace_for_language_server(server_id);
 925            }
 926        });
 927        if !enabled && Some(server_id) == self.current_server_id {
 928            self.show_logs_for_server(server_id, cx);
 929            cx.notify();
 930        }
 931    }
 932
 933    fn update_trace_level(
 934        &self,
 935        server_id: LanguageServerId,
 936        level: TraceValue,
 937        cx: &mut ViewContext<Self>,
 938    ) {
 939        if let Some(server) = self.project.read(cx).language_server_for_id(server_id, cx) {
 940            self.log_store.update(cx, |this, _| {
 941                if let Some(state) = this.get_language_server_state(server_id) {
 942                    state.trace_level = level;
 943                }
 944            });
 945
 946            server
 947                .notify::<SetTrace>(SetTraceParams { value: level })
 948                .ok();
 949        }
 950    }
 951
 952    fn show_capabilities_for_server(
 953        &mut self,
 954        server_id: LanguageServerId,
 955        cx: &mut ViewContext<Self>,
 956    ) {
 957        let capabilities = self.log_store.read(cx).server_capabilities(server_id);
 958
 959        if let Some(capabilities) = capabilities {
 960            self.current_server_id = Some(server_id);
 961            self.active_entry_kind = LogKind::Capabilities;
 962            let (editor, editor_subscriptions) =
 963                Self::editor_for_capabilities(capabilities.clone(), cx);
 964            self.editor = editor;
 965            self.editor_subscriptions = editor_subscriptions;
 966            cx.notify();
 967        }
 968        cx.focus(&self.focus_handle);
 969    }
 970}
 971
 972fn log_filter<T: Message>(line: &T, cmp: <T as Message>::Level) -> Option<&str> {
 973    if line.should_include(cmp) {
 974        Some(line.as_ref())
 975    } else {
 976        None
 977    }
 978}
 979
 980fn log_contents<T: Message>(lines: &VecDeque<T>, cmp: <T as Message>::Level) -> String {
 981    let (a, b) = lines.as_slices();
 982    let a = a.iter().filter_map(move |v| log_filter(v, cmp));
 983    let b = b.iter().filter_map(move |v| log_filter(v, cmp));
 984    a.chain(b).fold(String::new(), |mut acc, el| {
 985        acc.push_str(el);
 986        acc.push('\n');
 987        acc
 988    })
 989}
 990
 991impl Render for LspLogView {
 992    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
 993        self.editor
 994            .update(cx, |editor, cx| editor.render(cx).into_any_element())
 995    }
 996}
 997
 998impl FocusableView for LspLogView {
 999    fn focus_handle(&self, _: &AppContext) -> FocusHandle {
1000        self.focus_handle.clone()
1001    }
1002}
1003
1004impl Item for LspLogView {
1005    type Event = EditorEvent;
1006
1007    fn to_item_events(event: &Self::Event, f: impl FnMut(workspace::item::ItemEvent)) {
1008        Editor::to_item_events(event, f)
1009    }
1010
1011    fn tab_content_text(&self, _cx: &WindowContext) -> Option<SharedString> {
1012        Some("LSP Logs".into())
1013    }
1014
1015    fn telemetry_event_text(&self) -> Option<&'static str> {
1016        None
1017    }
1018
1019    fn as_searchable(&self, handle: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
1020        Some(Box::new(handle.clone()))
1021    }
1022
1023    fn clone_on_split(
1024        &self,
1025        _workspace_id: Option<WorkspaceId>,
1026        cx: &mut ViewContext<Self>,
1027    ) -> Option<View<Self>>
1028    where
1029        Self: Sized,
1030    {
1031        Some(cx.new_view(|cx| {
1032            let mut new_view = Self::new(self.project.clone(), self.log_store.clone(), cx);
1033            if let Some(server_id) = self.current_server_id {
1034                match self.active_entry_kind {
1035                    LogKind::Rpc => new_view.show_rpc_trace_for_server(server_id, cx),
1036                    LogKind::Trace => new_view.show_trace_for_server(server_id, cx),
1037                    LogKind::Logs => new_view.show_logs_for_server(server_id, cx),
1038                    LogKind::Capabilities => new_view.show_capabilities_for_server(server_id, cx),
1039                }
1040            }
1041            new_view
1042        }))
1043    }
1044}
1045
1046impl SearchableItem for LspLogView {
1047    type Match = <Editor as SearchableItem>::Match;
1048
1049    fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {
1050        self.editor.update(cx, |e, cx| e.clear_matches(cx))
1051    }
1052
1053    fn update_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>) {
1054        self.editor
1055            .update(cx, |e, cx| e.update_matches(matches, cx))
1056    }
1057
1058    fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String {
1059        self.editor.update(cx, |e, cx| e.query_suggestion(cx))
1060    }
1061
1062    fn activate_match(
1063        &mut self,
1064        index: usize,
1065        matches: &[Self::Match],
1066        cx: &mut ViewContext<Self>,
1067    ) {
1068        self.editor
1069            .update(cx, |e, cx| e.activate_match(index, matches, cx))
1070    }
1071
1072    fn select_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>) {
1073        self.editor
1074            .update(cx, |e, cx| e.select_matches(matches, cx))
1075    }
1076
1077    fn find_matches(
1078        &mut self,
1079        query: Arc<project::search::SearchQuery>,
1080        cx: &mut ViewContext<Self>,
1081    ) -> gpui::Task<Vec<Self::Match>> {
1082        self.editor.update(cx, |e, cx| e.find_matches(query, cx))
1083    }
1084
1085    fn replace(&mut self, _: &Self::Match, _: &SearchQuery, _: &mut ViewContext<Self>) {
1086        // Since LSP Log is read-only, it doesn't make sense to support replace operation.
1087    }
1088    fn supported_options() -> workspace::searchable::SearchOptions {
1089        workspace::searchable::SearchOptions {
1090            case: true,
1091            word: true,
1092            regex: true,
1093            // LSP log is read-only.
1094            replacement: false,
1095            selection: false,
1096        }
1097    }
1098    fn active_match_index(
1099        &mut self,
1100        matches: &[Self::Match],
1101        cx: &mut ViewContext<Self>,
1102    ) -> Option<usize> {
1103        self.editor
1104            .update(cx, |e, cx| e.active_match_index(matches, cx))
1105    }
1106}
1107
1108impl EventEmitter<ToolbarItemEvent> for LspLogToolbarItemView {}
1109
1110impl ToolbarItemView for LspLogToolbarItemView {
1111    fn set_active_pane_item(
1112        &mut self,
1113        active_pane_item: Option<&dyn ItemHandle>,
1114        cx: &mut ViewContext<Self>,
1115    ) -> workspace::ToolbarItemLocation {
1116        if let Some(item) = active_pane_item {
1117            if let Some(log_view) = item.downcast::<LspLogView>() {
1118                self.log_view = Some(log_view.clone());
1119                self._log_view_subscription = Some(cx.observe(&log_view, |_, _, cx| {
1120                    cx.notify();
1121                }));
1122                return ToolbarItemLocation::PrimaryLeft;
1123            }
1124        }
1125        self.log_view = None;
1126        self._log_view_subscription = None;
1127        ToolbarItemLocation::Hidden
1128    }
1129}
1130
1131impl Render for LspLogToolbarItemView {
1132    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
1133        let Some(log_view) = self.log_view.clone() else {
1134            return div();
1135        };
1136        let (menu_rows, current_server_id) = log_view.update(cx, |log_view, cx| {
1137            let menu_rows = log_view.menu_items(cx).unwrap_or_default();
1138            let current_server_id = log_view.current_server_id;
1139            (menu_rows, current_server_id)
1140        });
1141
1142        let current_server = current_server_id.and_then(|current_server_id| {
1143            if let Ok(ix) = menu_rows.binary_search_by_key(&current_server_id, |e| e.server_id) {
1144                Some(menu_rows[ix].clone())
1145            } else {
1146                None
1147            }
1148        });
1149
1150        let log_toolbar_view = cx.view().clone();
1151        let lsp_menu = PopoverMenu::new("LspLogView")
1152            .anchor(AnchorCorner::TopLeft)
1153            .trigger(Button::new(
1154                "language_server_menu_header",
1155                current_server
1156                    .map(|row| {
1157                        Cow::Owned(format!(
1158                            "{} ({}) - {}",
1159                            row.server_name.0,
1160                            row.worktree_root_name,
1161                            row.selected_entry.label()
1162                        ))
1163                    })
1164                    .unwrap_or_else(|| "No server selected".into()),
1165            ))
1166            .menu({
1167                let log_view = log_view.clone();
1168                move |cx| {
1169                    let menu_rows = menu_rows.clone();
1170                    let log_view = log_view.clone();
1171                    let log_toolbar_view = log_toolbar_view.clone();
1172                    ContextMenu::build(cx, move |mut menu, cx| {
1173                        for (ix, row) in menu_rows.into_iter().enumerate() {
1174                            let server_selected = Some(row.server_id) == current_server_id;
1175                            menu = menu
1176                                .header(format!(
1177                                    "{} ({})",
1178                                    row.server_name.0, row.worktree_root_name
1179                                ))
1180                                .entry(
1181                                    SERVER_LOGS,
1182                                    None,
1183                                    cx.handler_for(&log_view, move |view, cx| {
1184                                        view.show_logs_for_server(row.server_id, cx);
1185                                    }),
1186                                );
1187                            // We do not support tracing for remote language servers right now
1188                            if row.server_kind.is_remote() {
1189                                return menu;
1190                            }
1191                            menu = menu.entry(
1192                                SERVER_TRACE,
1193                                None,
1194                                cx.handler_for(&log_view, move |view, cx| {
1195                                    view.show_trace_for_server(row.server_id, cx);
1196                                }),
1197                            );
1198                            menu = menu.custom_entry(
1199                                {
1200                                    let log_toolbar_view = log_toolbar_view.clone();
1201                                    move |cx| {
1202                                        h_flex()
1203                                            .w_full()
1204                                            .justify_between()
1205                                            .child(Label::new(RPC_MESSAGES))
1206                                            .child(
1207                                                div().child(
1208                                                    Checkbox::new(
1209                                                        ix,
1210                                                        if row.rpc_trace_enabled {
1211                                                            Selection::Selected
1212                                                        } else {
1213                                                            Selection::Unselected
1214                                                        },
1215                                                    )
1216                                                    .on_click(cx.listener_for(
1217                                                        &log_toolbar_view,
1218                                                        move |view, selection, cx| {
1219                                                            let enabled = matches!(
1220                                                                selection,
1221                                                                Selection::Selected
1222                                                            );
1223                                                            view.toggle_rpc_logging_for_server(
1224                                                                row.server_id,
1225                                                                enabled,
1226                                                                cx,
1227                                                            );
1228                                                            cx.stop_propagation();
1229                                                        },
1230                                                    )),
1231                                                ),
1232                                            )
1233                                            .into_any_element()
1234                                    }
1235                                },
1236                                cx.handler_for(&log_view, move |view, cx| {
1237                                    view.show_rpc_trace_for_server(row.server_id, cx);
1238                                }),
1239                            );
1240                            if server_selected && row.selected_entry == LogKind::Rpc {
1241                                let selected_ix = menu.select_last();
1242                                // Each language server has:
1243                                // 1. A title.
1244                                // 2. Server logs.
1245                                // 3. Server trace.
1246                                // 4. RPC messages.
1247                                // 5. Server capabilities
1248                                // Thus, if nth server's RPC is selected, the index of selected entry should match this formula
1249                                let _expected_index = ix * 5 + 3;
1250                                debug_assert_eq!(
1251                                    Some(_expected_index),
1252                                    selected_ix,
1253                                    "Could not scroll to a just added LSP menu item"
1254                                );
1255                            }
1256                            menu = menu.entry(
1257                                SERVER_CAPABILITIES,
1258                                None,
1259                                cx.handler_for(&log_view, move |view, cx| {
1260                                    view.show_capabilities_for_server(row.server_id, cx);
1261                                }),
1262                            );
1263                        }
1264                        menu
1265                    })
1266                    .into()
1267                }
1268            });
1269
1270        h_flex()
1271            .size_full()
1272            .child(lsp_menu)
1273            .child(
1274                div()
1275                    .child(
1276                        Button::new("clear_log_button", "Clear").on_click(cx.listener(
1277                            |this, _, cx| {
1278                                if let Some(log_view) = this.log_view.as_ref() {
1279                                    log_view.update(cx, |log_view, cx| {
1280                                        log_view.editor.update(cx, |editor, cx| {
1281                                            editor.set_read_only(false);
1282                                            editor.clear(cx);
1283                                            editor.set_read_only(true);
1284                                        });
1285                                    })
1286                                }
1287                            },
1288                        )),
1289                    )
1290                    .ml_2(),
1291            )
1292            .child(log_view.update(cx, |this, _| match this.active_entry_kind {
1293                LogKind::Trace => {
1294                    let log_view = log_view.clone();
1295                    div().child(
1296                        PopoverMenu::new("lsp-trace-level-menu")
1297                            .anchor(AnchorCorner::TopLeft)
1298                            .trigger(Button::new(
1299                                "language_server_trace_level_selector",
1300                                "Trace level",
1301                            ))
1302                            .menu({
1303                                let log_view = log_view.clone();
1304
1305                                move |cx| {
1306                                    let id = log_view.read(cx).current_server_id?;
1307
1308                                    let trace_level = log_view.update(cx, |this, cx| {
1309                                        this.log_store.update(cx, |this, _| {
1310                                            Some(this.get_language_server_state(id)?.trace_level)
1311                                        })
1312                                    })?;
1313
1314                                    ContextMenu::build(cx, |mut menu, _| {
1315                                        let log_view = log_view.clone();
1316
1317                                        for (option, label) in [
1318                                            (TraceValue::Off, "Off"),
1319                                            (TraceValue::Messages, "Messages"),
1320                                            (TraceValue::Verbose, "Verbose"),
1321                                        ] {
1322                                            menu = menu.entry(label, None, {
1323                                                let log_view = log_view.clone();
1324                                                move |cx| {
1325                                                    log_view.update(cx, |this, cx| {
1326                                                        if let Some(id) = this.current_server_id {
1327                                                            this.update_trace_level(id, option, cx);
1328                                                        }
1329                                                    });
1330                                                }
1331                                            });
1332                                            if option == trace_level {
1333                                                menu.select_last();
1334                                            }
1335                                        }
1336
1337                                        menu
1338                                    })
1339                                    .into()
1340                                }
1341                            }),
1342                    )
1343                }
1344                LogKind::Logs => {
1345                    let log_view = log_view.clone();
1346                    div().child(
1347                        PopoverMenu::new("lsp-log-level-menu")
1348                            .anchor(AnchorCorner::TopLeft)
1349                            .trigger(Button::new(
1350                                "language_server_log_level_selector",
1351                                "Log level",
1352                            ))
1353                            .menu({
1354                                let log_view = log_view.clone();
1355
1356                                move |cx| {
1357                                    let id = log_view.read(cx).current_server_id?;
1358
1359                                    let log_level = log_view.update(cx, |this, cx| {
1360                                        this.log_store.update(cx, |this, _| {
1361                                            Some(this.get_language_server_state(id)?.log_level)
1362                                        })
1363                                    })?;
1364
1365                                    ContextMenu::build(cx, |mut menu, _| {
1366                                        let log_view = log_view.clone();
1367
1368                                        for (option, label) in [
1369                                            (MessageType::LOG, "Log"),
1370                                            (MessageType::INFO, "Info"),
1371                                            (MessageType::WARNING, "Warning"),
1372                                            (MessageType::ERROR, "Error"),
1373                                        ] {
1374                                            menu = menu.entry(label, None, {
1375                                                let log_view = log_view.clone();
1376                                                move |cx| {
1377                                                    log_view.update(cx, |this, cx| {
1378                                                        if let Some(id) = this.current_server_id {
1379                                                            this.update_log_level(id, option, cx);
1380                                                        }
1381                                                    });
1382                                                }
1383                                            });
1384                                            if option == log_level {
1385                                                menu.select_last();
1386                                            }
1387                                        }
1388
1389                                        menu
1390                                    })
1391                                    .into()
1392                                }
1393                            }),
1394                    )
1395                }
1396                _ => div(),
1397            }))
1398    }
1399}
1400
1401const RPC_MESSAGES: &str = "RPC Messages";
1402const SERVER_LOGS: &str = "Server Logs";
1403const SERVER_TRACE: &str = "Server Trace";
1404const SERVER_CAPABILITIES: &str = "Server Capabilities";
1405
1406impl Default for LspLogToolbarItemView {
1407    fn default() -> Self {
1408        Self::new()
1409    }
1410}
1411
1412impl LspLogToolbarItemView {
1413    pub fn new() -> Self {
1414        Self {
1415            log_view: None,
1416            _log_view_subscription: None,
1417        }
1418    }
1419
1420    fn toggle_rpc_logging_for_server(
1421        &mut self,
1422        id: LanguageServerId,
1423        enabled: bool,
1424        cx: &mut ViewContext<Self>,
1425    ) {
1426        if let Some(log_view) = &self.log_view {
1427            log_view.update(cx, |log_view, cx| {
1428                log_view.toggle_rpc_trace_for_server(id, enabled, cx);
1429                if !enabled && Some(id) == log_view.current_server_id {
1430                    log_view.show_logs_for_server(id, cx);
1431                    cx.notify();
1432                } else if enabled {
1433                    log_view.show_rpc_trace_for_server(id, cx);
1434                    cx.notify();
1435                }
1436                cx.focus(&log_view.focus_handle);
1437            });
1438        }
1439        cx.notify();
1440    }
1441}
1442
1443pub enum Event {
1444    NewServerLogEntry {
1445        id: LanguageServerId,
1446        entry: String,
1447        kind: LogKind,
1448    },
1449}
1450
1451impl EventEmitter<Event> for LogStore {}
1452impl EventEmitter<Event> for LspLogView {}
1453impl EventEmitter<EditorEvent> for LspLogView {}
1454impl EventEmitter<SearchEvent> for LspLogView {}