@@ -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();
@@ -696,7 +696,7 @@ pub struct LspStore {
}
pub enum LspStoreEvent {
- LanguageServerAdded(LanguageServerId),
+ LanguageServerAdded(LanguageServerId, LanguageServerName, Option<WorktreeId>),
LanguageServerRemoved(LanguageServerId),
LanguageServerUpdate {
language_server_id: LanguageServerId,
@@ -752,6 +752,7 @@ impl LspStore {
client.add_model_request_handler(Self::handle_restart_language_servers);
client.add_model_message_handler(Self::handle_start_language_server);
client.add_model_message_handler(Self::handle_update_language_server);
+ client.add_model_message_handler(Self::handle_language_server_log);
client.add_model_message_handler(Self::handle_update_diagnostic_summary);
client.add_model_request_handler(Self::handle_format_buffers);
client.add_model_request_handler(Self::handle_resolve_completion_documentation);
@@ -3087,6 +3088,7 @@ impl LspStore {
server: Some(proto::LanguageServer {
id: server_id.0 as u64,
name: status.name.clone(),
+ worktree_id: None,
}),
})
.log_err();
@@ -3907,16 +3909,23 @@ impl LspStore {
.payload
.server
.ok_or_else(|| anyhow!("invalid server"))?;
+
this.update(&mut cx, |this, cx| {
+ let server_id = LanguageServerId(server.id as usize);
this.language_server_statuses.insert(
- LanguageServerId(server.id as usize),
+ server_id,
LanguageServerStatus {
- name: server.name,
+ name: server.name.clone(),
pending_work: Default::default(),
has_pending_diagnostic_updates: false,
progress_tokens: Default::default(),
},
);
+ cx.emit(LspStoreEvent::LanguageServerAdded(
+ server_id,
+ LanguageServerName(server.name.into()),
+ server.worktree_id.map(WorktreeId::from_proto),
+ ));
cx.notify();
})?;
Ok(())
@@ -3984,6 +3993,29 @@ impl LspStore {
})?
}
+ async fn handle_language_server_log(
+ this: Model<Self>,
+ envelope: TypedEnvelope<proto::LanguageServerLog>,
+ mut cx: AsyncAppContext,
+ ) -> Result<()> {
+ let language_server_id = LanguageServerId(envelope.payload.language_server_id as usize);
+ let log_type = envelope
+ .payload
+ .log_type
+ .map(LanguageServerLogType::from_proto)
+ .context("invalid language server log type")?;
+
+ let message = envelope.payload.message;
+
+ this.update(&mut cx, |_, cx| {
+ cx.emit(LspStoreEvent::LanguageServerLog(
+ language_server_id,
+ log_type,
+ message,
+ ));
+ })
+ }
+
pub fn disk_based_diagnostics_started(
&mut self,
language_server_id: LanguageServerId,
@@ -6356,7 +6388,11 @@ impl LspStore {
},
);
- cx.emit(LspStoreEvent::LanguageServerAdded(server_id));
+ cx.emit(LspStoreEvent::LanguageServerAdded(
+ server_id,
+ language_server.name().into(),
+ Some(key.0),
+ ));
if let Some((downstream_client, project_id)) = self.downstream_client.as_ref() {
downstream_client
@@ -6365,6 +6401,7 @@ impl LspStore {
server: Some(proto::LanguageServer {
id: server_id.0 as u64,
name: language_server.name().to_string(),
+ worktree_id: Some(key.0.to_proto()),
}),
})
.log_err();
@@ -6546,8 +6583,8 @@ impl LspStore {
if let Some(local) = self.as_local_mut() {
local
.supplementary_language_servers
- .insert(id, (name, server));
- cx.emit(LspStoreEvent::LanguageServerAdded(id));
+ .insert(id, (name.clone(), server));
+ cx.emit(LspStoreEvent::LanguageServerAdded(id, name, None));
}
}
@@ -7289,6 +7326,46 @@ pub enum LanguageServerLogType {
Trace(Option<String>),
}
+impl LanguageServerLogType {
+ pub fn to_proto(&self) -> proto::language_server_log::LogType {
+ match self {
+ Self::Log(log_type) => {
+ let message_type = match *log_type {
+ MessageType::ERROR => 1,
+ MessageType::WARNING => 2,
+ MessageType::INFO => 3,
+ MessageType::LOG => 4,
+ other => {
+ log::warn!("Unknown lsp log message type: {:?}", other);
+ 4
+ }
+ };
+ proto::language_server_log::LogType::LogMessageType(message_type)
+ }
+ Self::Trace(message) => {
+ proto::language_server_log::LogType::LogTrace(proto::LspLogTrace {
+ message: message.clone(),
+ })
+ }
+ }
+ }
+
+ pub fn from_proto(log_type: proto::language_server_log::LogType) -> Self {
+ match log_type {
+ proto::language_server_log::LogType::LogMessageType(message_type) => {
+ Self::Log(match message_type {
+ 1 => MessageType::ERROR,
+ 2 => MessageType::WARNING,
+ 3 => MessageType::INFO,
+ 4 => MessageType::LOG,
+ _ => MessageType::LOG,
+ })
+ }
+ proto::language_server_log::LogType::LogTrace(trace) => Self::Trace(trace.message),
+ }
+ }
+}
+
pub enum LanguageServerState {
Starting(Task<Option<Arc<LanguageServer>>>),