lsp_log.rs

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