ssh remoting: Forward LSP logs to client (#19212)

Release Notes:

- N/A

---------

Co-authored-by: Bennet Bo Fenner <bennet@zed.dev>
This commit is contained in:
Thorsten Ball 2024-10-15 16:04:29 +02:00 committed by GitHub
parent db7417f3b5
commit 397e4bee0a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 245 additions and 95 deletions

View file

@ -11,7 +11,7 @@ use language::{LanguageServerId, LanguageServerName};
use lsp::{
notification::SetTrace, IoKind, LanguageServer, MessageType, SetTraceParams, TraceValue,
};
use project::{search::SearchQuery, Project};
use project::{search::SearchQuery, Project, WorktreeId};
use std::{borrow::Cow, sync::Arc};
use ui::{prelude::*, Button, Checkbox, ContextMenu, Label, PopoverMenu, Selection};
use workspace::{
@ -99,6 +99,8 @@ impl Message for RpcMessage {
}
struct LanguageServerState {
name: Option<LanguageServerName>,
worktree_id: Option<WorktreeId>,
kind: LanguageServerKind,
log_messages: VecDeque<LogMessage>,
trace_messages: VecDeque<TraceMessage>,
@ -108,15 +110,34 @@ struct LanguageServerState {
io_logs_subscription: Option<lsp::Subscription>,
}
enum LanguageServerKind {
#[derive(PartialEq, Clone)]
pub enum LanguageServerKind {
Local { project: WeakModel<Project> },
Global { name: LanguageServerName },
Remote { project: WeakModel<Project> },
Global,
}
impl LanguageServerKind {
fn is_remote(&self) -> bool {
matches!(self, LanguageServerKind::Remote { .. })
}
}
impl std::fmt::Debug for LanguageServerKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
LanguageServerKind::Local { .. } => write!(f, "LanguageServerKind::Local"),
LanguageServerKind::Remote { .. } => write!(f, "LanguageServerKind::Remote"),
LanguageServerKind::Global => write!(f, "LanguageServerKind::Global"),
}
}
}
impl LanguageServerKind {
fn project(&self) -> Option<&WeakModel<Project>> {
match self {
Self::Local { project } => Some(project),
Self::Remote { project } => Some(project),
Self::Global { .. } => None,
}
}
@ -175,6 +196,7 @@ pub(crate) struct LogMenuItem {
pub rpc_trace_enabled: bool,
pub selected_entry: LogKind,
pub trace_level: lsp::TraceValue,
pub server_kind: LanguageServerKind,
}
actions!(debug, [OpenLanguageServerLogs]);
@ -184,7 +206,7 @@ pub fn init(cx: &mut AppContext) {
cx.observe_new_views(move |workspace: &mut Workspace, cx| {
let project = workspace.project();
if project.read(cx).is_local() {
if project.read(cx).is_local() || project.read(cx).is_via_ssh() {
log_store.update(cx, |store, cx| {
store.add_project(project, cx);
});
@ -193,7 +215,7 @@ pub fn init(cx: &mut AppContext) {
let log_store = log_store.clone();
workspace.register_action(move |workspace, _: &OpenLanguageServerLogs, cx| {
let project = workspace.project().read(cx);
if project.is_local() {
if project.is_local() || project.is_via_ssh() {
workspace.split_item(
SplitDirection::Right,
Box::new(cx.new_view(|cx| {
@ -233,11 +255,12 @@ impl LogStore {
.ok();
},
));
let name = LanguageServerName::new_static("copilot");
this.add_language_server(
LanguageServerKind::Global {
name: LanguageServerName::new_static("copilot"),
},
LanguageServerKind::Global,
server.server_id(),
Some(name),
None,
Some(server.clone()),
cx,
);
@ -279,42 +302,44 @@ impl LogStore {
this.language_servers
.retain(|_, state| state.kind.project() != Some(&weak_project));
}),
cx.subscribe(project, |this, project, event, cx| match event {
project::Event::LanguageServerAdded(id) => {
let read_project = project.read(cx);
if let Some(server) = read_project.language_server_for_id(*id, cx) {
cx.subscribe(project, |this, project, event, cx| {
let server_kind = if project.read(cx).is_via_ssh() {
LanguageServerKind::Remote {
project: project.downgrade(),
}
} else {
LanguageServerKind::Local {
project: project.downgrade(),
}
};
match event {
project::Event::LanguageServerAdded(id, name, worktree_id) => {
this.add_language_server(
LanguageServerKind::Local {
project: project.downgrade(),
},
server.server_id(),
Some(server),
server_kind,
*id,
Some(name.clone()),
*worktree_id,
project.read(cx).language_server_for_id(*id, cx),
cx,
);
}
}
project::Event::LanguageServerRemoved(id) => {
this.remove_language_server(*id, cx);
}
project::Event::LanguageServerLog(id, typ, message) => {
this.add_language_server(
LanguageServerKind::Local {
project: project.downgrade(),
},
*id,
None,
cx,
);
match typ {
project::LanguageServerLogType::Log(typ) => {
this.add_language_server_log(*id, *typ, message, cx);
}
project::LanguageServerLogType::Trace(_) => {
this.add_language_server_trace(*id, message, cx);
project::Event::LanguageServerRemoved(id) => {
this.remove_language_server(*id, cx);
}
project::Event::LanguageServerLog(id, typ, message) => {
this.add_language_server(server_kind, *id, None, None, None, cx);
match typ {
project::LanguageServerLogType::Log(typ) => {
this.add_language_server_log(*id, *typ, message, cx);
}
project::LanguageServerLogType::Trace(_) => {
this.add_language_server_trace(*id, message, cx);
}
}
}
_ => {}
}
_ => {}
}),
],
},
@ -332,12 +357,16 @@ impl LogStore {
&mut self,
kind: LanguageServerKind,
server_id: LanguageServerId,
name: Option<LanguageServerName>,
worktree_id: Option<WorktreeId>,
server: Option<Arc<LanguageServer>>,
cx: &mut ModelContext<Self>,
) -> Option<&mut LanguageServerState> {
let server_state = self.language_servers.entry(server_id).or_insert_with(|| {
cx.notify();
LanguageServerState {
name: None,
worktree_id: None,
kind,
rpc_state: None,
log_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES),
@ -348,6 +377,13 @@ impl LogStore {
}
});
if let Some(name) = name {
server_state.name = Some(name);
}
if let Some(worktree_id) = worktree_id {
server_state.worktree_id = Some(worktree_id);
}
if let Some(server) = server.filter(|_| server_state.io_logs_subscription.is_none()) {
let io_tx = self.io_tx.clone();
let server_id = server.server_id();
@ -448,14 +484,14 @@ impl LogStore {
self.language_servers
.iter()
.filter_map(move |(id, state)| match &state.kind {
LanguageServerKind::Local { project } => {
LanguageServerKind::Local { project } | LanguageServerKind::Remote { project } => {
if project == lookup_project {
Some(*id)
} else {
None
}
}
LanguageServerKind::Global { .. } => Some(*id),
LanguageServerKind::Global => Some(*id),
})
}
@ -662,21 +698,40 @@ impl LspLogView {
pub(crate) fn menu_items<'a>(&'a self, cx: &'a AppContext) -> Option<Vec<LogMenuItem>> {
let log_store = self.log_store.read(cx);
let mut rows = self
.project
.read(cx)
.language_servers(cx)
.filter_map(|(server_id, language_server_name, worktree_id)| {
let worktree = self.project.read(cx).worktree_for_id(worktree_id, cx)?;
let state = log_store.language_servers.get(&server_id)?;
Some(LogMenuItem {
server_id,
server_name: language_server_name,
worktree_root_name: worktree.read(cx).root_name().to_string(),
let unknown_server = LanguageServerName::new_static("unknown server");
let mut rows = log_store
.language_servers
.iter()
.filter_map(|(server_id, state)| match &state.kind {
LanguageServerKind::Local { .. } | LanguageServerKind::Remote { .. } => {
let worktree_root_name = state
.worktree_id
.and_then(|id| self.project.read(cx).worktree_for_id(id, cx))
.map(|worktree| worktree.read(cx).root_name().to_string())
.unwrap_or_else(|| "Unknown worktree".to_string());
let state = log_store.language_servers.get(&server_id)?;
Some(LogMenuItem {
server_id: *server_id,
server_name: state.name.clone().unwrap_or(unknown_server.clone()),
server_kind: state.kind.clone(),
worktree_root_name,
rpc_trace_enabled: state.rpc_state.is_some(),
selected_entry: self.active_entry_kind,
trace_level: lsp::TraceValue::Off,
})
}
LanguageServerKind::Global => Some(LogMenuItem {
server_id: *server_id,
server_name: state.name.clone().unwrap_or(unknown_server.clone()),
server_kind: state.kind.clone(),
worktree_root_name: "supplementary".to_string(),
rpc_trace_enabled: state.rpc_state.is_some(),
selected_entry: self.active_entry_kind,
trace_level: lsp::TraceValue::Off,
})
}),
})
.chain(
self.project
@ -687,6 +742,7 @@ impl LspLogView {
Some(LogMenuItem {
server_id,
server_name: name.clone(),
server_kind: state.kind.clone(),
worktree_root_name: "supplementary".to_string(),
rpc_trace_enabled: state.rpc_state.is_some(),
selected_entry: self.active_entry_kind,
@ -694,22 +750,6 @@ impl LspLogView {
})
}),
)
.chain(
log_store
.language_servers
.iter()
.filter_map(|(server_id, state)| match &state.kind {
LanguageServerKind::Global { name } => Some(LogMenuItem {
server_id: *server_id,
server_name: name.clone(),
worktree_root_name: "supplementary".to_string(),
rpc_trace_enabled: state.rpc_state.is_some(),
selected_entry: self.active_entry_kind,
trace_level: lsp::TraceValue::Off,
}),
_ => None,
}),
)
.collect::<Vec<_>>();
rows.sort_by_key(|row| row.server_id);
rows.dedup_by_key(|row| row.server_id);
@ -1075,13 +1115,9 @@ impl Render for LspLogToolbarItemView {
view.show_logs_for_server(row.server_id, cx);
}),
);
if server_selected && row.selected_entry == LogKind::Logs {
let selected_ix = menu.select_last();
debug_assert_eq!(
Some(ix * 4 + 1),
selected_ix,
"Could not scroll to a just added LSP menu item"
);
// We do not support tracing for remote language servers right now
if row.server_kind.is_remote() {
return menu;
}
menu = menu.entry(
SERVER_TRACE,
@ -1090,14 +1126,6 @@ impl Render for LspLogToolbarItemView {
view.show_trace_for_server(row.server_id, cx);
}),
);
if server_selected && row.selected_entry == LogKind::Trace {
let selected_ix = menu.select_last();
debug_assert_eq!(
Some(ix * 4 + 2),
selected_ix,
"Could not scroll to a just added LSP menu item"
);
}
menu = menu.custom_entry(
{
let log_toolbar_view = log_toolbar_view.clone();

View file

@ -95,6 +95,9 @@ async fn test_lsp_logs(cx: &mut TestAppContext) {
rpc_trace_enabled: false,
selected_entry: LogKind::Logs,
trace_level: lsp::TraceValue::Off,
server_kind: lsp_log::LanguageServerKind::Local {
project: project.downgrade()
}
}]
);
assert_eq!(view.editor.read(cx).text(cx), "hello from the server\n");