Restructure LSP log view to show log messages in addition to RPC trace
This commit is contained in:
parent
78f9642ac2
commit
66f215cd13
5 changed files with 337 additions and 138 deletions
|
@ -748,6 +748,15 @@ impl fmt::Display for LanguageServerId {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for LanguageServer {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("LanguageServer")
|
||||||
|
.field("id", &self.server_id.0)
|
||||||
|
.field("name", &self.name)
|
||||||
|
.finish_non_exhaustive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Drop for Subscription {
|
impl Drop for Subscription {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
match self {
|
match self {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use collections::{hash_map, HashMap};
|
use collections::HashMap;
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use futures::{channel::mpsc, StreamExt};
|
use futures::{channel::mpsc, StreamExt};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
|
@ -12,28 +12,33 @@ use gpui::{
|
||||||
ViewHandle, WeakModelHandle,
|
ViewHandle, WeakModelHandle,
|
||||||
};
|
};
|
||||||
use language::{Buffer, LanguageServerId, LanguageServerName};
|
use language::{Buffer, LanguageServerId, LanguageServerName};
|
||||||
use project::{Project, WorktreeId};
|
use project::{Project, Worktree};
|
||||||
use std::{borrow::Cow, sync::Arc};
|
use std::{borrow::Cow, sync::Arc};
|
||||||
use theme::{ui, Theme};
|
use theme::{ui, Theme};
|
||||||
use workspace::{
|
use workspace::{
|
||||||
item::{Item, ItemHandle},
|
item::{Item, ItemHandle},
|
||||||
ToolbarItemLocation, ToolbarItemView, Workspace,
|
ToolbarItemLocation, ToolbarItemView, Workspace, WorkspaceCreated,
|
||||||
};
|
};
|
||||||
|
|
||||||
const SEND_LINE: &str = "// Send:\n";
|
const SEND_LINE: &str = "// Send:\n";
|
||||||
const RECEIVE_LINE: &str = "// Receive:\n";
|
const RECEIVE_LINE: &str = "// Receive:\n";
|
||||||
|
|
||||||
struct LogStore {
|
struct LogStore {
|
||||||
projects: HashMap<WeakModelHandle<Project>, LogStoreProject>,
|
projects: HashMap<WeakModelHandle<Project>, ProjectState>,
|
||||||
io_tx: mpsc::UnboundedSender<(WeakModelHandle<Project>, LanguageServerId, bool, String)>,
|
io_tx: mpsc::UnboundedSender<(WeakModelHandle<Project>, LanguageServerId, bool, String)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LogStoreProject {
|
struct ProjectState {
|
||||||
servers: HashMap<LanguageServerId, LogStoreLanguageServer>,
|
servers: HashMap<LanguageServerId, LanguageServerState>,
|
||||||
_subscription: gpui::Subscription,
|
_subscriptions: [gpui::Subscription; 2],
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LogStoreLanguageServer {
|
struct LanguageServerState {
|
||||||
|
log_buffer: ModelHandle<Buffer>,
|
||||||
|
rpc_state: Option<LanguageServerRpcState>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LanguageServerRpcState {
|
||||||
buffer: ModelHandle<Buffer>,
|
buffer: ModelHandle<Buffer>,
|
||||||
last_message_kind: Option<MessageKind>,
|
last_message_kind: Option<MessageKind>,
|
||||||
_subscription: lsp::Subscription,
|
_subscription: lsp::Subscription,
|
||||||
|
@ -42,6 +47,7 @@ struct LogStoreLanguageServer {
|
||||||
pub struct LspLogView {
|
pub struct LspLogView {
|
||||||
log_store: ModelHandle<LogStore>,
|
log_store: ModelHandle<LogStore>,
|
||||||
current_server_id: Option<LanguageServerId>,
|
current_server_id: Option<LanguageServerId>,
|
||||||
|
is_showing_rpc_trace: bool,
|
||||||
editor: Option<ViewHandle<Editor>>,
|
editor: Option<ViewHandle<Editor>>,
|
||||||
project: ModelHandle<Project>,
|
project: ModelHandle<Project>,
|
||||||
}
|
}
|
||||||
|
@ -49,7 +55,6 @@ pub struct LspLogView {
|
||||||
pub struct LspLogToolbarItemView {
|
pub struct LspLogToolbarItemView {
|
||||||
log_view: Option<ViewHandle<LspLogView>>,
|
log_view: Option<ViewHandle<LspLogView>>,
|
||||||
menu_open: bool,
|
menu_open: bool,
|
||||||
project: ModelHandle<Project>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||||
|
@ -58,10 +63,36 @@ enum MessageKind {
|
||||||
Receive,
|
Receive,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
struct LogMenuItem {
|
||||||
|
server_id: LanguageServerId,
|
||||||
|
server_name: LanguageServerName,
|
||||||
|
worktree: ModelHandle<Worktree>,
|
||||||
|
rpc_trace_enabled: bool,
|
||||||
|
rpc_trace_selected: bool,
|
||||||
|
logs_selected: bool,
|
||||||
|
}
|
||||||
|
|
||||||
actions!(log, [OpenLanguageServerLogs]);
|
actions!(log, [OpenLanguageServerLogs]);
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
let log_set = cx.add_model(|cx| LogStore::new(cx));
|
let log_store = cx.add_model(|cx| LogStore::new(cx));
|
||||||
|
|
||||||
|
cx.subscribe_global::<WorkspaceCreated, _>({
|
||||||
|
let log_store = log_store.clone();
|
||||||
|
move |event, cx| {
|
||||||
|
let workspace = &event.0;
|
||||||
|
if let Some(workspace) = workspace.upgrade(cx) {
|
||||||
|
let project = workspace.read(cx).project().clone();
|
||||||
|
if project.read(cx).is_local() {
|
||||||
|
log_store.update(cx, |store, cx| {
|
||||||
|
store.add_project(&project, cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
|
||||||
cx.add_action(
|
cx.add_action(
|
||||||
move |workspace: &mut Workspace, _: &OpenLanguageServerLogs, cx: _| {
|
move |workspace: &mut Workspace, _: &OpenLanguageServerLogs, cx: _| {
|
||||||
|
@ -69,7 +100,7 @@ pub fn init(cx: &mut AppContext) {
|
||||||
if project.is_local() {
|
if project.is_local() {
|
||||||
workspace.add_item(
|
workspace.add_item(
|
||||||
Box::new(cx.add_view(|cx| {
|
Box::new(cx.add_view(|cx| {
|
||||||
LspLogView::new(workspace.project().clone(), log_set.clone(), cx)
|
LspLogView::new(workspace.project().clone(), log_store.clone(), cx)
|
||||||
})),
|
})),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
@ -100,34 +131,113 @@ impl LogStore {
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_enabled_logs_for_language_server(
|
pub fn add_project(&mut self, project: &ModelHandle<Project>, cx: &mut ModelContext<Self>) {
|
||||||
|
use project::Event::*;
|
||||||
|
|
||||||
|
let weak_project = project.downgrade();
|
||||||
|
self.projects.insert(
|
||||||
|
weak_project,
|
||||||
|
ProjectState {
|
||||||
|
servers: HashMap::default(),
|
||||||
|
_subscriptions: [
|
||||||
|
cx.observe_release(&project, move |this, _, _| {
|
||||||
|
this.projects.remove(&weak_project);
|
||||||
|
}),
|
||||||
|
cx.subscribe(project, |this, project, event, cx| match event {
|
||||||
|
LanguageServerAdded(id) => {
|
||||||
|
this.add_language_server(&project, *id, cx);
|
||||||
|
}
|
||||||
|
LanguageServerRemoved(id) => {
|
||||||
|
this.remove_language_server(&project, *id, cx);
|
||||||
|
}
|
||||||
|
LanguageServerLog(id, message) => {
|
||||||
|
this.add_language_server_log(&project, *id, message, cx);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_language_server(
|
||||||
|
&mut self,
|
||||||
|
project: &ModelHandle<Project>,
|
||||||
|
id: LanguageServerId,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) -> Option<ModelHandle<Buffer>> {
|
||||||
|
let project_state = self.projects.get_mut(&project.downgrade())?;
|
||||||
|
Some(
|
||||||
|
project_state
|
||||||
|
.servers
|
||||||
|
.entry(id)
|
||||||
|
.or_insert_with(|| {
|
||||||
|
cx.notify();
|
||||||
|
LanguageServerState {
|
||||||
|
rpc_state: None,
|
||||||
|
log_buffer: cx.add_model(|cx| Buffer::new(0, "", cx)).clone(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.log_buffer
|
||||||
|
.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_language_server_log(
|
||||||
|
&mut self,
|
||||||
|
project: &ModelHandle<Project>,
|
||||||
|
id: LanguageServerId,
|
||||||
|
message: &str,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) -> Option<()> {
|
||||||
|
let buffer = self.add_language_server(&project, id, cx)?;
|
||||||
|
buffer.update(cx, |buffer, cx| {
|
||||||
|
let len = buffer.len();
|
||||||
|
let has_newline = message.ends_with("\n");
|
||||||
|
buffer.edit([(len..len, message)], None, cx);
|
||||||
|
if !has_newline {
|
||||||
|
let len = buffer.len();
|
||||||
|
buffer.edit([(len..len, "\n")], None, cx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
cx.notify();
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_language_server(
|
||||||
|
&mut self,
|
||||||
|
project: &ModelHandle<Project>,
|
||||||
|
id: LanguageServerId,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) -> Option<()> {
|
||||||
|
let project_state = self.projects.get_mut(&project.downgrade())?;
|
||||||
|
project_state.servers.remove(&id);
|
||||||
|
cx.notify();
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn log_buffer_for_server(
|
||||||
&self,
|
&self,
|
||||||
project: &ModelHandle<Project>,
|
project: &ModelHandle<Project>,
|
||||||
server_id: LanguageServerId,
|
server_id: LanguageServerId,
|
||||||
) -> bool {
|
) -> Option<ModelHandle<Buffer>> {
|
||||||
self.projects
|
let weak_project = project.downgrade();
|
||||||
.get(&project.downgrade())
|
let project_state = self.projects.get(&weak_project)?;
|
||||||
.map_or(false, |store| store.servers.contains_key(&server_id))
|
let server_state = project_state.servers.get(&server_id)?;
|
||||||
|
Some(server_state.log_buffer.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn enable_logs_for_language_server(
|
pub fn enable_rpc_trace_for_language_server(
|
||||||
&mut self,
|
&mut self,
|
||||||
project: &ModelHandle<Project>,
|
project: &ModelHandle<Project>,
|
||||||
server_id: LanguageServerId,
|
server_id: LanguageServerId,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Option<ModelHandle<Buffer>> {
|
) -> Option<ModelHandle<Buffer>> {
|
||||||
let server = project.read(cx).language_server_for_id(server_id)?;
|
|
||||||
let weak_project = project.downgrade();
|
let weak_project = project.downgrade();
|
||||||
let project_logs = match self.projects.entry(weak_project) {
|
let project_state = self.projects.get_mut(&weak_project)?;
|
||||||
hash_map::Entry::Occupied(entry) => entry.into_mut(),
|
let server_state = project_state.servers.get_mut(&server_id)?;
|
||||||
hash_map::Entry::Vacant(entry) => entry.insert(LogStoreProject {
|
let server = project.read(cx).language_server_for_id(server_id)?;
|
||||||
servers: HashMap::default(),
|
let rpc_state = server_state.rpc_state.get_or_insert_with(|| {
|
||||||
_subscription: cx.observe_release(&project, move |this, _, _| {
|
|
||||||
this.projects.remove(&weak_project);
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
let server_log_state = project_logs.servers.entry(server_id).or_insert_with(|| {
|
|
||||||
let io_tx = self.io_tx.clone();
|
let io_tx = self.io_tx.clone();
|
||||||
let language = project.read(cx).languages().language_for_name("JSON");
|
let language = project.read(cx).languages().language_for_name("JSON");
|
||||||
let buffer = cx.add_model(|cx| Buffer::new(0, "", cx));
|
let buffer = cx.add_model(|cx| Buffer::new(0, "", cx));
|
||||||
|
@ -142,33 +252,30 @@ impl LogStore {
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
let project = project.downgrade();
|
LanguageServerRpcState {
|
||||||
LogStoreLanguageServer {
|
|
||||||
buffer,
|
buffer,
|
||||||
last_message_kind: None,
|
last_message_kind: None,
|
||||||
_subscription: server.on_io(move |is_received, json| {
|
_subscription: server.on_io(move |is_received, json| {
|
||||||
io_tx
|
io_tx
|
||||||
.unbounded_send((project, server_id, is_received, json.to_string()))
|
.unbounded_send((weak_project, server_id, is_received, json.to_string()))
|
||||||
.ok();
|
.ok();
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Some(server_log_state.buffer.clone())
|
Some(rpc_state.buffer.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn disable_logs_for_language_server(
|
pub fn disable_rpc_trace_for_language_server(
|
||||||
&mut self,
|
&mut self,
|
||||||
project: &ModelHandle<Project>,
|
project: &ModelHandle<Project>,
|
||||||
server_id: LanguageServerId,
|
server_id: LanguageServerId,
|
||||||
_: &mut ModelContext<Self>,
|
_: &mut ModelContext<Self>,
|
||||||
) {
|
) -> Option<()> {
|
||||||
let project = project.downgrade();
|
let project = project.downgrade();
|
||||||
if let Some(store) = self.projects.get_mut(&project) {
|
let project_state = self.projects.get_mut(&project)?;
|
||||||
store.servers.remove(&server_id);
|
let server_state = project_state.servers.get_mut(&server_id)?;
|
||||||
if store.servers.is_empty() {
|
server_state.rpc_state.take();
|
||||||
self.projects.remove(&project);
|
Some(())
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_io(
|
fn on_io(
|
||||||
|
@ -183,7 +290,9 @@ impl LogStore {
|
||||||
.projects
|
.projects
|
||||||
.get_mut(&project)?
|
.get_mut(&project)?
|
||||||
.servers
|
.servers
|
||||||
.get_mut(&language_server_id)?;
|
.get_mut(&language_server_id)?
|
||||||
|
.rpc_state
|
||||||
|
.as_mut()?;
|
||||||
state.buffer.update(cx, |buffer, cx| {
|
state.buffer.update(cx, |buffer, cx| {
|
||||||
let kind = if is_received {
|
let kind = if is_received {
|
||||||
MessageKind::Receive
|
MessageKind::Receive
|
||||||
|
@ -209,23 +318,62 @@ impl LogStore {
|
||||||
impl LspLogView {
|
impl LspLogView {
|
||||||
fn new(
|
fn new(
|
||||||
project: ModelHandle<Project>,
|
project: ModelHandle<Project>,
|
||||||
log_set: ModelHandle<LogStore>,
|
log_store: ModelHandle<LogStore>,
|
||||||
_: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
let server_id = log_store
|
||||||
|
.read(cx)
|
||||||
|
.projects
|
||||||
|
.get(&project.downgrade())
|
||||||
|
.and_then(|project| project.servers.keys().copied().next());
|
||||||
|
let mut this = Self {
|
||||||
project,
|
project,
|
||||||
log_store: log_set,
|
log_store,
|
||||||
editor: None,
|
editor: None,
|
||||||
current_server_id: None,
|
current_server_id: None,
|
||||||
|
is_showing_rpc_trace: false,
|
||||||
|
};
|
||||||
|
if let Some(server_id) = server_id {
|
||||||
|
this.show_logs_for_server(server_id, cx);
|
||||||
}
|
}
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
fn menu_items<'a>(&'a self, cx: &'a AppContext) -> Option<Vec<LogMenuItem>> {
|
||||||
|
let log_store = self.log_store.read(cx);
|
||||||
|
let state = log_store.projects.get(&self.project.downgrade())?;
|
||||||
|
let mut rows = self
|
||||||
|
.project
|
||||||
|
.read(cx)
|
||||||
|
.language_servers()
|
||||||
|
.filter_map(|(server_id, language_server_name, worktree_id)| {
|
||||||
|
let worktree = self.project.read(cx).worktree_for_id(worktree_id, cx)?;
|
||||||
|
let state = state.servers.get(&server_id)?;
|
||||||
|
Some(LogMenuItem {
|
||||||
|
server_id,
|
||||||
|
server_name: language_server_name,
|
||||||
|
worktree,
|
||||||
|
rpc_trace_enabled: state.rpc_state.is_some(),
|
||||||
|
rpc_trace_selected: self.is_showing_rpc_trace
|
||||||
|
&& self.current_server_id == Some(server_id),
|
||||||
|
logs_selected: !self.is_showing_rpc_trace
|
||||||
|
&& self.current_server_id == Some(server_id),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
rows.sort_by_key(|row| row.server_id);
|
||||||
|
rows.dedup_by_key(|row| row.server_id);
|
||||||
|
Some(rows)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_logs_for_server(&mut self, server_id: LanguageServerId, cx: &mut ViewContext<Self>) {
|
fn show_logs_for_server(&mut self, server_id: LanguageServerId, cx: &mut ViewContext<Self>) {
|
||||||
let buffer = self.log_store.update(cx, |log_set, cx| {
|
let buffer = self
|
||||||
log_set.enable_logs_for_language_server(&self.project, server_id, cx)
|
.log_store
|
||||||
});
|
.read(cx)
|
||||||
|
.log_buffer_for_server(&self.project, server_id);
|
||||||
if let Some(buffer) = buffer {
|
if let Some(buffer) = buffer {
|
||||||
self.current_server_id = Some(server_id);
|
self.current_server_id = Some(server_id);
|
||||||
|
self.is_showing_rpc_trace = false;
|
||||||
self.editor = Some(cx.add_view(|cx| {
|
self.editor = Some(cx.add_view(|cx| {
|
||||||
let mut editor = Editor::for_buffer(buffer, Some(self.project.clone()), cx);
|
let mut editor = Editor::for_buffer(buffer, Some(self.project.clone()), cx);
|
||||||
editor.set_read_only(true);
|
editor.set_read_only(true);
|
||||||
|
@ -236,7 +384,28 @@ impl LspLogView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_logging_for_server(
|
fn show_rpc_trace_for_server(
|
||||||
|
&mut self,
|
||||||
|
server_id: LanguageServerId,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) {
|
||||||
|
let buffer = self.log_store.update(cx, |log_set, cx| {
|
||||||
|
log_set.enable_rpc_trace_for_language_server(&self.project, server_id, cx)
|
||||||
|
});
|
||||||
|
if let Some(buffer) = buffer {
|
||||||
|
self.current_server_id = Some(server_id);
|
||||||
|
self.is_showing_rpc_trace = true;
|
||||||
|
self.editor = Some(cx.add_view(|cx| {
|
||||||
|
let mut editor = Editor::for_buffer(buffer, Some(self.project.clone()), cx);
|
||||||
|
editor.set_read_only(true);
|
||||||
|
editor.move_to_end(&Default::default(), cx);
|
||||||
|
editor
|
||||||
|
}));
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn toggle_rpc_trace_for_server(
|
||||||
&mut self,
|
&mut self,
|
||||||
server_id: LanguageServerId,
|
server_id: LanguageServerId,
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
|
@ -244,11 +413,15 @@ impl LspLogView {
|
||||||
) {
|
) {
|
||||||
self.log_store.update(cx, |log_store, cx| {
|
self.log_store.update(cx, |log_store, cx| {
|
||||||
if enabled {
|
if enabled {
|
||||||
log_store.enable_logs_for_language_server(&self.project, server_id, cx);
|
log_store.enable_rpc_trace_for_language_server(&self.project, server_id, cx);
|
||||||
} else {
|
} else {
|
||||||
log_store.disable_logs_for_language_server(&self.project, server_id, cx);
|
log_store.disable_rpc_trace_for_language_server(&self.project, server_id, cx);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
if !enabled && Some(server_id) == self.current_server_id {
|
||||||
|
self.show_logs_for_server(server_id, cx);
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -305,28 +478,18 @@ impl View for LspLogToolbarItemView {
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||||
let theme = theme::current(cx).clone();
|
let theme = theme::current(cx).clone();
|
||||||
let Some(log_view) = self.log_view.as_ref() else { return Empty::new().into_any() };
|
let Some(log_view) = self.log_view.as_ref() else { return Empty::new().into_any() };
|
||||||
let project = self.project.read(cx);
|
|
||||||
let log_view = log_view.read(cx);
|
let log_view = log_view.read(cx);
|
||||||
let log_store = log_view.log_store.read(cx);
|
|
||||||
|
|
||||||
let mut language_servers = project
|
let menu_rows = self
|
||||||
.language_servers()
|
.log_view
|
||||||
.map(|(id, name, worktree)| {
|
.as_ref()
|
||||||
(
|
.and_then(|view| view.read(cx).menu_items(cx))
|
||||||
id,
|
.unwrap_or_default();
|
||||||
name,
|
|
||||||
worktree,
|
|
||||||
log_store.has_enabled_logs_for_language_server(&self.project, id),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
language_servers.sort_by_key(|a| (a.0, a.2));
|
|
||||||
language_servers.dedup_by_key(|a| a.0);
|
|
||||||
|
|
||||||
let current_server_id = log_view.current_server_id;
|
let current_server_id = log_view.current_server_id;
|
||||||
let current_server = current_server_id.and_then(|current_server_id| {
|
let current_server = current_server_id.and_then(|current_server_id| {
|
||||||
if let Ok(ix) = language_servers.binary_search_by_key(¤t_server_id, |e| e.0) {
|
if let Ok(ix) = menu_rows.binary_search_by_key(¤t_server_id, |e| e.server_id) {
|
||||||
Some(language_servers[ix].clone())
|
Some(menu_rows[ix].clone())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -337,7 +500,6 @@ impl View for LspLogToolbarItemView {
|
||||||
Stack::new()
|
Stack::new()
|
||||||
.with_child(Self::render_language_server_menu_header(
|
.with_child(Self::render_language_server_menu_header(
|
||||||
current_server,
|
current_server,
|
||||||
&self.project,
|
|
||||||
&theme,
|
&theme,
|
||||||
cx,
|
cx,
|
||||||
))
|
))
|
||||||
|
@ -346,20 +508,18 @@ impl View for LspLogToolbarItemView {
|
||||||
Overlay::new(
|
Overlay::new(
|
||||||
MouseEventHandler::<Menu, _>::new(0, cx, move |_, cx| {
|
MouseEventHandler::<Menu, _>::new(0, cx, move |_, cx| {
|
||||||
Flex::column()
|
Flex::column()
|
||||||
.with_children(language_servers.into_iter().filter_map(
|
.with_children(menu_rows.into_iter().map(|row| {
|
||||||
|(id, name, worktree_id, logging_enabled)| {
|
|
||||||
Self::render_language_server_menu_item(
|
Self::render_language_server_menu_item(
|
||||||
id,
|
row.server_id,
|
||||||
name,
|
row.server_name,
|
||||||
worktree_id,
|
row.worktree,
|
||||||
logging_enabled,
|
row.rpc_trace_enabled,
|
||||||
Some(id) == current_server_id,
|
row.logs_selected,
|
||||||
&self.project,
|
row.rpc_trace_selected,
|
||||||
&theme,
|
&theme,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
},
|
}))
|
||||||
))
|
|
||||||
.contained()
|
.contained()
|
||||||
.with_style(theme.context_menu.container)
|
.with_style(theme.context_menu.container)
|
||||||
.constrained()
|
.constrained()
|
||||||
|
@ -389,11 +549,10 @@ impl View for LspLogToolbarItemView {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LspLogToolbarItemView {
|
impl LspLogToolbarItemView {
|
||||||
pub fn new(project: ModelHandle<Project>) -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
menu_open: false,
|
menu_open: false,
|
||||||
log_view: None,
|
log_view: None,
|
||||||
project,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -410,10 +569,9 @@ impl LspLogToolbarItemView {
|
||||||
) {
|
) {
|
||||||
if let Some(log_view) = &self.log_view {
|
if let Some(log_view) = &self.log_view {
|
||||||
log_view.update(cx, |log_view, cx| {
|
log_view.update(cx, |log_view, cx| {
|
||||||
log_view.toggle_logging_for_server(id, enabled, cx);
|
log_view.toggle_rpc_trace_for_server(id, enabled, cx);
|
||||||
if !enabled && Some(id) == log_view.current_server_id {
|
if !enabled && Some(id) == log_view.current_server_id {
|
||||||
log_view.current_server_id = None;
|
log_view.show_logs_for_server(id, cx);
|
||||||
log_view.editor = None;
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -423,28 +581,31 @@ impl LspLogToolbarItemView {
|
||||||
|
|
||||||
fn show_logs_for_server(&mut self, id: LanguageServerId, cx: &mut ViewContext<Self>) {
|
fn show_logs_for_server(&mut self, id: LanguageServerId, cx: &mut ViewContext<Self>) {
|
||||||
if let Some(log_view) = &self.log_view {
|
if let Some(log_view) = &self.log_view {
|
||||||
log_view.update(cx, |log_view, cx| {
|
log_view.update(cx, |view, cx| view.show_logs_for_server(id, cx));
|
||||||
log_view.show_logs_for_server(id, cx);
|
|
||||||
});
|
|
||||||
self.menu_open = false;
|
self.menu_open = false;
|
||||||
}
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_rpc_trace_for_server(&mut self, id: LanguageServerId, cx: &mut ViewContext<Self>) {
|
||||||
|
if let Some(log_view) = &self.log_view {
|
||||||
|
log_view.update(cx, |view, cx| view.show_rpc_trace_for_server(id, cx));
|
||||||
|
self.menu_open = false;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn render_language_server_menu_header(
|
fn render_language_server_menu_header(
|
||||||
current_server: Option<(LanguageServerId, LanguageServerName, WorktreeId, bool)>,
|
current_server: Option<LogMenuItem>,
|
||||||
project: &ModelHandle<Project>,
|
|
||||||
theme: &Arc<Theme>,
|
theme: &Arc<Theme>,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> impl Element<Self> {
|
) -> impl Element<Self> {
|
||||||
enum ToggleMenu {}
|
enum ToggleMenu {}
|
||||||
MouseEventHandler::<ToggleMenu, Self>::new(0, cx, move |state, cx| {
|
MouseEventHandler::<ToggleMenu, Self>::new(0, cx, move |state, cx| {
|
||||||
let project = project.read(cx);
|
|
||||||
let label: Cow<str> = current_server
|
let label: Cow<str> = current_server
|
||||||
.and_then(|(_, server_name, worktree_id, _)| {
|
.and_then(|row| {
|
||||||
let worktree = project.worktree_for_id(worktree_id, cx)?;
|
let worktree = row.worktree.read(cx);
|
||||||
let worktree = &worktree.read(cx);
|
Some(format!("{} - ({})", row.server_name.0, worktree.root_name()).into())
|
||||||
Some(format!("{} - ({})", server_name.0, worktree.root_name()).into())
|
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|| "No server selected".into());
|
.unwrap_or_else(|| "No server selected".into());
|
||||||
Label::new(
|
Label::new(
|
||||||
|
@ -466,25 +627,42 @@ impl LspLogToolbarItemView {
|
||||||
fn render_language_server_menu_item(
|
fn render_language_server_menu_item(
|
||||||
id: LanguageServerId,
|
id: LanguageServerId,
|
||||||
name: LanguageServerName,
|
name: LanguageServerName,
|
||||||
worktree_id: WorktreeId,
|
worktree: ModelHandle<Worktree>,
|
||||||
logging_enabled: bool,
|
logging_enabled: bool,
|
||||||
is_selected: bool,
|
logs_selected: bool,
|
||||||
project: &ModelHandle<Project>,
|
rpc_trace_selected: bool,
|
||||||
theme: &Arc<Theme>,
|
theme: &Arc<Theme>,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Option<impl Element<Self>> {
|
) -> impl Element<Self> {
|
||||||
enum ActivateLog {}
|
enum ActivateLog {}
|
||||||
let project = project.read(cx);
|
enum ActivateRpcTrace {}
|
||||||
let worktree = project.worktree_for_id(worktree_id, cx)?;
|
|
||||||
let worktree = &worktree.read(cx);
|
|
||||||
if !worktree.is_visible() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let label = format!("{} - ({})", name.0, worktree.root_name());
|
|
||||||
|
|
||||||
Some(
|
let header = format!("{} - ({})", name.0, worktree.read(cx).root_name());
|
||||||
MouseEventHandler::<ActivateLog, _>::new(id.0, cx, move |state, cx| {
|
|
||||||
let item_style = theme.context_menu.item.style_for(state, is_selected);
|
let item_style = &theme.context_menu.item.default;
|
||||||
|
Flex::column()
|
||||||
|
.with_child(
|
||||||
|
Label::new(header, item_style.label.clone())
|
||||||
|
.aligned()
|
||||||
|
.left(),
|
||||||
|
)
|
||||||
|
.with_child(
|
||||||
|
MouseEventHandler::<ActivateLog, _>::new(id.0, cx, move |state, _| {
|
||||||
|
let item_style = &theme.context_menu.item.style_for(state, logs_selected);
|
||||||
|
Label::new("logs", item_style.label.clone())
|
||||||
|
.aligned()
|
||||||
|
.left()
|
||||||
|
.contained()
|
||||||
|
.with_style(item_style.container)
|
||||||
|
})
|
||||||
|
.with_cursor_style(CursorStyle::PointingHand)
|
||||||
|
.on_click(MouseButton::Left, move |_, view, cx| {
|
||||||
|
view.show_logs_for_server(id, cx);
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.with_child(
|
||||||
|
MouseEventHandler::<ActivateRpcTrace, _>::new(id.0, cx, move |state, cx| {
|
||||||
|
let item_style = &theme.context_menu.item.style_for(state, rpc_trace_selected);
|
||||||
Flex::row()
|
Flex::row()
|
||||||
.with_child(ui::checkbox_with_label::<Self, _, Self, _>(
|
.with_child(ui::checkbox_with_label::<Self, _, Self, _>(
|
||||||
Empty::new(),
|
Empty::new(),
|
||||||
|
@ -496,14 +674,18 @@ impl LspLogToolbarItemView {
|
||||||
this.toggle_logging_for_server(id, enabled, cx);
|
this.toggle_logging_for_server(id, enabled, cx);
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
.with_child(Label::new(label, item_style.label.clone()).aligned().left())
|
.with_child(
|
||||||
|
Label::new("rpc trace", item_style.label.clone())
|
||||||
|
.aligned()
|
||||||
|
.left(),
|
||||||
|
)
|
||||||
.align_children_center()
|
.align_children_center()
|
||||||
.contained()
|
.contained()
|
||||||
.with_style(item_style.container)
|
.with_style(item_style.container)
|
||||||
})
|
})
|
||||||
.with_cursor_style(CursorStyle::PointingHand)
|
.with_cursor_style(CursorStyle::PointingHand)
|
||||||
.on_click(MouseButton::Left, move |_, view, cx| {
|
.on_click(MouseButton::Left, move |_, view, cx| {
|
||||||
view.show_logs_for_server(id, cx);
|
view.show_rpc_trace_for_server(id, cx);
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -826,6 +826,11 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) {
|
||||||
let mut events = subscribe(&project, cx);
|
let mut events = subscribe(&project, cx);
|
||||||
|
|
||||||
let fake_server = fake_servers.next().await.unwrap();
|
let fake_server = fake_servers.next().await.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
events.next().await.unwrap(),
|
||||||
|
Event::LanguageServerAdded(LanguageServerId(0)),
|
||||||
|
);
|
||||||
|
|
||||||
fake_server
|
fake_server
|
||||||
.start_progress(format!("{}/0", progress_token))
|
.start_progress(format!("{}/0", progress_token))
|
||||||
.await;
|
.await;
|
||||||
|
@ -953,6 +958,10 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC
|
||||||
|
|
||||||
// Simulate the newly started server sending more diagnostics.
|
// Simulate the newly started server sending more diagnostics.
|
||||||
let fake_server = fake_servers.next().await.unwrap();
|
let fake_server = fake_servers.next().await.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
events.next().await.unwrap(),
|
||||||
|
Event::LanguageServerAdded(LanguageServerId(1))
|
||||||
|
);
|
||||||
fake_server.start_progress(progress_token).await;
|
fake_server.start_progress(progress_token).await;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
events.next().await.unwrap(),
|
events.next().await.unwrap(),
|
||||||
|
|
|
@ -3500,7 +3500,7 @@ impl std::fmt::Debug for OpenPaths {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct WorkspaceCreated(WeakViewHandle<Workspace>);
|
pub struct WorkspaceCreated(pub WeakViewHandle<Workspace>);
|
||||||
|
|
||||||
pub fn activate_workspace_for_project(
|
pub fn activate_workspace_for_project(
|
||||||
cx: &mut AsyncAppContext,
|
cx: &mut AsyncAppContext,
|
||||||
|
|
|
@ -311,9 +311,8 @@ pub fn initialize_workspace(
|
||||||
toolbar.add_item(submit_feedback_button, cx);
|
toolbar.add_item(submit_feedback_button, cx);
|
||||||
let feedback_info_text = cx.add_view(|_| FeedbackInfoText::new());
|
let feedback_info_text = cx.add_view(|_| FeedbackInfoText::new());
|
||||||
toolbar.add_item(feedback_info_text, cx);
|
toolbar.add_item(feedback_info_text, cx);
|
||||||
let lsp_log_item = cx.add_view(|_| {
|
let lsp_log_item =
|
||||||
lsp_log::LspLogToolbarItemView::new(workspace.project().clone())
|
cx.add_view(|_| lsp_log::LspLogToolbarItemView::new());
|
||||||
});
|
|
||||||
toolbar.add_item(lsp_log_item, cx);
|
toolbar.add_item(lsp_log_item, cx);
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue