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