Add language server control tool into the status bar (#32490)
Release Notes: - Added the language server control tool into the status bar --------- Co-authored-by: Nate Butler <iamnbutler@gmail.com>
This commit is contained in:
parent
91c9281cea
commit
c0acd8e8b1
32 changed files with 1992 additions and 312 deletions
|
@ -783,7 +783,7 @@ impl BufferStore {
|
|||
project_path: ProjectPath,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Entity<Buffer>>> {
|
||||
if let Some(buffer) = self.get_by_path(&project_path, cx) {
|
||||
if let Some(buffer) = self.get_by_path(&project_path) {
|
||||
cx.emit(BufferStoreEvent::BufferOpened {
|
||||
buffer: buffer.clone(),
|
||||
project_path,
|
||||
|
@ -946,7 +946,7 @@ impl BufferStore {
|
|||
self.path_to_buffer_id.get(project_path)
|
||||
}
|
||||
|
||||
pub fn get_by_path(&self, path: &ProjectPath, _cx: &App) -> Option<Entity<Buffer>> {
|
||||
pub fn get_by_path(&self, path: &ProjectPath) -> Option<Entity<Buffer>> {
|
||||
self.path_to_buffer_id.get(path).and_then(|buffer_id| {
|
||||
let buffer = self.get(*buffer_id);
|
||||
buffer
|
||||
|
|
|
@ -275,7 +275,7 @@ impl BreakpointStore {
|
|||
.context("Could not resolve provided abs path")?;
|
||||
let buffer = this
|
||||
.update(&mut cx, |this, cx| {
|
||||
this.buffer_store().read(cx).get_by_path(&path, cx)
|
||||
this.buffer_store().read(cx).get_by_path(&path)
|
||||
})?
|
||||
.context("Could not find buffer for a given path")?;
|
||||
let breakpoint = message
|
||||
|
|
|
@ -3322,7 +3322,7 @@ impl Repository {
|
|||
let Some(project_path) = self.repo_path_to_project_path(path, cx) else {
|
||||
continue;
|
||||
};
|
||||
if let Some(buffer) = buffer_store.get_by_path(&project_path, cx) {
|
||||
if let Some(buffer) = buffer_store.get_by_path(&project_path) {
|
||||
if buffer
|
||||
.read(cx)
|
||||
.file()
|
||||
|
@ -3389,7 +3389,7 @@ impl Repository {
|
|||
let Some(project_path) = self.repo_path_to_project_path(path, cx) else {
|
||||
continue;
|
||||
};
|
||||
if let Some(buffer) = buffer_store.get_by_path(&project_path, cx) {
|
||||
if let Some(buffer) = buffer_store.get_by_path(&project_path) {
|
||||
if buffer
|
||||
.read(cx)
|
||||
.file()
|
||||
|
@ -3749,7 +3749,7 @@ impl Repository {
|
|||
let buffer_id = git_store
|
||||
.buffer_store
|
||||
.read(cx)
|
||||
.get_by_path(&project_path?, cx)?
|
||||
.get_by_path(&project_path?)?
|
||||
.read(cx)
|
||||
.remote_id();
|
||||
let diff_state = git_store.diffs.get(&buffer_id)?;
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,11 +1,11 @@
|
|||
use ::serde::{Deserialize, Serialize};
|
||||
use anyhow::Context as _;
|
||||
use gpui::{App, Entity, SharedString, Task, WeakEntity};
|
||||
use language::{LanguageServerStatusUpdate, ServerHealth};
|
||||
use gpui::{App, Entity, Task, WeakEntity};
|
||||
use language::ServerHealth;
|
||||
use lsp::LanguageServer;
|
||||
use rpc::proto;
|
||||
|
||||
use crate::{LspStore, Project, ProjectPath, lsp_store};
|
||||
use crate::{LspStore, LspStoreEvent, Project, ProjectPath, lsp_store};
|
||||
|
||||
pub const RUST_ANALYZER_NAME: &str = "rust-analyzer";
|
||||
pub const CARGO_DIAGNOSTICS_SOURCE_NAME: &str = "rustc";
|
||||
|
@ -36,24 +36,45 @@ pub fn register_notifications(lsp_store: WeakEntity<LspStore>, language_server:
|
|||
.on_notification::<ServerStatus, _>({
|
||||
let name = name.clone();
|
||||
move |params, cx| {
|
||||
let status = params.message;
|
||||
let log_message =
|
||||
format!("Language server {name} (id {server_id}) status update: {status:?}");
|
||||
match ¶ms.health {
|
||||
ServerHealth::Ok => log::info!("{log_message}"),
|
||||
ServerHealth::Warning => log::warn!("{log_message}"),
|
||||
ServerHealth::Error => log::error!("{log_message}"),
|
||||
}
|
||||
let message = params.message;
|
||||
let log_message = message.as_ref().map(|message| {
|
||||
format!("Language server {name} (id {server_id}) status update: {message}")
|
||||
});
|
||||
let status = match ¶ms.health {
|
||||
ServerHealth::Ok => {
|
||||
if let Some(log_message) = log_message {
|
||||
log::info!("{log_message}");
|
||||
}
|
||||
proto::ServerHealth::Ok
|
||||
}
|
||||
ServerHealth::Warning => {
|
||||
if let Some(log_message) = log_message {
|
||||
log::warn!("{log_message}");
|
||||
}
|
||||
proto::ServerHealth::Warning
|
||||
}
|
||||
ServerHealth::Error => {
|
||||
if let Some(log_message) = log_message {
|
||||
log::error!("{log_message}");
|
||||
}
|
||||
proto::ServerHealth::Error
|
||||
}
|
||||
};
|
||||
|
||||
lsp_store
|
||||
.update(cx, |lsp_store, _| {
|
||||
lsp_store.languages.update_lsp_status(
|
||||
name.clone(),
|
||||
LanguageServerStatusUpdate::Health(
|
||||
params.health,
|
||||
status.map(SharedString::from),
|
||||
.update(cx, |_, cx| {
|
||||
cx.emit(LspStoreEvent::LanguageServerUpdate {
|
||||
language_server_id: server_id,
|
||||
name: Some(name.clone()),
|
||||
message: proto::update_language_server::Variant::StatusUpdate(
|
||||
proto::StatusUpdate {
|
||||
message,
|
||||
status: Some(proto::status_update::Status::Health(
|
||||
status as i32,
|
||||
)),
|
||||
},
|
||||
),
|
||||
);
|
||||
});
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
|
|
@ -74,6 +74,7 @@ impl LanguageServerTreeNode {
|
|||
pub(crate) fn server_id(&self) -> Option<LanguageServerId> {
|
||||
self.0.upgrade()?.id.get().copied()
|
||||
}
|
||||
|
||||
/// Returns a language server ID for this node if it has already been initialized; otherwise runs the provided closure to initialize the language server node in a tree.
|
||||
/// May return None if the node no longer belongs to the server tree it was created in.
|
||||
pub(crate) fn server_id_or_init(
|
||||
|
@ -87,6 +88,11 @@ impl LanguageServerTreeNode {
|
|||
.get_or_init(|| init(LaunchDisposition::from(&*this))),
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns a language server name as the language server adapter would return.
|
||||
pub fn name(&self) -> Option<LanguageServerName> {
|
||||
self.0.upgrade().map(|node| node.name.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Weak<InnerTreeNode>> for LanguageServerTreeNode {
|
||||
|
|
|
@ -81,7 +81,7 @@ use language::{
|
|||
};
|
||||
use lsp::{
|
||||
CodeActionKind, CompletionContext, CompletionItemKind, DocumentHighlightKind, InsertTextMode,
|
||||
LanguageServerId, LanguageServerName, MessageActionItem,
|
||||
LanguageServerId, LanguageServerName, LanguageServerSelector, MessageActionItem,
|
||||
};
|
||||
use lsp_command::*;
|
||||
use lsp_store::{CompletionDocumentation, LspFormatTarget, OpenLspBufferHandle};
|
||||
|
@ -251,6 +251,7 @@ enum BufferOrderedMessage {
|
|||
LanguageServerUpdate {
|
||||
language_server_id: LanguageServerId,
|
||||
message: proto::update_language_server::Variant,
|
||||
name: Option<LanguageServerName>,
|
||||
},
|
||||
Resync,
|
||||
}
|
||||
|
@ -1790,7 +1791,7 @@ impl Project {
|
|||
pub fn has_open_buffer(&self, path: impl Into<ProjectPath>, cx: &App) -> bool {
|
||||
self.buffer_store
|
||||
.read(cx)
|
||||
.get_by_path(&path.into(), cx)
|
||||
.get_by_path(&path.into())
|
||||
.is_some()
|
||||
}
|
||||
|
||||
|
@ -2500,7 +2501,7 @@ impl Project {
|
|||
cx: &mut App,
|
||||
) -> OpenLspBufferHandle {
|
||||
self.lsp_store.update(cx, |lsp_store, cx| {
|
||||
lsp_store.register_buffer_with_language_servers(&buffer, false, cx)
|
||||
lsp_store.register_buffer_with_language_servers(&buffer, HashSet::default(), false, cx)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -2590,7 +2591,7 @@ impl Project {
|
|||
}
|
||||
|
||||
pub fn get_open_buffer(&self, path: &ProjectPath, cx: &App) -> Option<Entity<Buffer>> {
|
||||
self.buffer_store.read(cx).get_by_path(path, cx)
|
||||
self.buffer_store.read(cx).get_by_path(path)
|
||||
}
|
||||
|
||||
fn register_buffer(&mut self, buffer: &Entity<Buffer>, cx: &mut Context<Self>) -> Result<()> {
|
||||
|
@ -2640,7 +2641,7 @@ impl Project {
|
|||
}
|
||||
|
||||
async fn send_buffer_ordered_messages(
|
||||
this: WeakEntity<Self>,
|
||||
project: WeakEntity<Self>,
|
||||
rx: UnboundedReceiver<BufferOrderedMessage>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<()> {
|
||||
|
@ -2677,7 +2678,7 @@ impl Project {
|
|||
let mut changes = rx.ready_chunks(MAX_BATCH_SIZE);
|
||||
|
||||
while let Some(changes) = changes.next().await {
|
||||
let is_local = this.read_with(cx, |this, _| this.is_local())?;
|
||||
let is_local = project.read_with(cx, |this, _| this.is_local())?;
|
||||
|
||||
for change in changes {
|
||||
match change {
|
||||
|
@ -2697,7 +2698,7 @@ impl Project {
|
|||
|
||||
BufferOrderedMessage::Resync => {
|
||||
operations_by_buffer_id.clear();
|
||||
if this
|
||||
if project
|
||||
.update(cx, |this, cx| this.synchronize_remote_buffers(cx))?
|
||||
.await
|
||||
.is_ok()
|
||||
|
@ -2709,9 +2710,10 @@ impl Project {
|
|||
BufferOrderedMessage::LanguageServerUpdate {
|
||||
language_server_id,
|
||||
message,
|
||||
name,
|
||||
} => {
|
||||
flush_operations(
|
||||
&this,
|
||||
&project,
|
||||
&mut operations_by_buffer_id,
|
||||
&mut needs_resync_with_host,
|
||||
is_local,
|
||||
|
@ -2719,12 +2721,14 @@ impl Project {
|
|||
)
|
||||
.await?;
|
||||
|
||||
this.read_with(cx, |this, _| {
|
||||
if let Some(project_id) = this.remote_id() {
|
||||
this.client
|
||||
project.read_with(cx, |project, _| {
|
||||
if let Some(project_id) = project.remote_id() {
|
||||
project
|
||||
.client
|
||||
.send(proto::UpdateLanguageServer {
|
||||
project_id,
|
||||
language_server_id: language_server_id.0 as u64,
|
||||
server_name: name.map(|name| String::from(name.0)),
|
||||
language_server_id: language_server_id.to_proto(),
|
||||
variant: Some(message),
|
||||
})
|
||||
.log_err();
|
||||
|
@ -2735,7 +2739,7 @@ impl Project {
|
|||
}
|
||||
|
||||
flush_operations(
|
||||
&this,
|
||||
&project,
|
||||
&mut operations_by_buffer_id,
|
||||
&mut needs_resync_with_host,
|
||||
is_local,
|
||||
|
@ -2856,12 +2860,14 @@ impl Project {
|
|||
LspStoreEvent::LanguageServerUpdate {
|
||||
language_server_id,
|
||||
message,
|
||||
name,
|
||||
} => {
|
||||
if self.is_local() {
|
||||
self.enqueue_buffer_ordered_message(
|
||||
BufferOrderedMessage::LanguageServerUpdate {
|
||||
language_server_id: *language_server_id,
|
||||
message: message.clone(),
|
||||
name: name.clone(),
|
||||
},
|
||||
)
|
||||
.ok();
|
||||
|
@ -3140,20 +3146,22 @@ impl Project {
|
|||
pub fn restart_language_servers_for_buffers(
|
||||
&mut self,
|
||||
buffers: Vec<Entity<Buffer>>,
|
||||
only_restart_servers: HashSet<LanguageServerSelector>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.lsp_store.update(cx, |lsp_store, cx| {
|
||||
lsp_store.restart_language_servers_for_buffers(buffers, cx)
|
||||
lsp_store.restart_language_servers_for_buffers(buffers, only_restart_servers, cx)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn stop_language_servers_for_buffers(
|
||||
&mut self,
|
||||
buffers: Vec<Entity<Buffer>>,
|
||||
also_restart_servers: HashSet<LanguageServerSelector>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.lsp_store.update(cx, |lsp_store, cx| {
|
||||
lsp_store.stop_language_servers_for_buffers(buffers, cx)
|
||||
lsp_store.stop_language_servers_for_buffers(buffers, also_restart_servers, cx)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -49,6 +49,10 @@ pub struct ProjectSettings {
|
|||
#[serde(default)]
|
||||
pub lsp: HashMap<LanguageServerName, LspSettings>,
|
||||
|
||||
/// Common language server settings.
|
||||
#[serde(default)]
|
||||
pub global_lsp_settings: GlobalLspSettings,
|
||||
|
||||
/// Configuration for Debugger-related features
|
||||
#[serde(default)]
|
||||
pub dap: HashMap<DebugAdapterName, DapSettings>,
|
||||
|
@ -110,6 +114,16 @@ pub enum ContextServerSettings {
|
|||
},
|
||||
}
|
||||
|
||||
/// Common language server settings.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct GlobalLspSettings {
|
||||
/// Whether to show the LSP servers button in the status bar.
|
||||
///
|
||||
/// Default: `true`
|
||||
#[serde(default = "default_true")]
|
||||
pub button: bool,
|
||||
}
|
||||
|
||||
impl ContextServerSettings {
|
||||
pub fn default_extension() -> Self {
|
||||
Self::Extension {
|
||||
|
@ -271,6 +285,14 @@ impl Default for InlineDiagnosticsSettings {
|
|||
}
|
||||
}
|
||||
|
||||
impl Default for GlobalLspSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
button: default_true(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct CargoDiagnosticsSettings {
|
||||
/// When enabled, Zed disables rust-analyzer's check on save and starts to query
|
||||
|
|
|
@ -918,6 +918,7 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
|
|||
project.update(cx, |project, cx| {
|
||||
project.restart_language_servers_for_buffers(
|
||||
vec![rust_buffer.clone(), json_buffer.clone()],
|
||||
HashSet::default(),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
@ -1715,12 +1716,16 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC
|
|||
|
||||
// Restart the server before the diagnostics finish updating.
|
||||
project.update(cx, |project, cx| {
|
||||
project.restart_language_servers_for_buffers(vec![buffer], cx);
|
||||
project.restart_language_servers_for_buffers(vec![buffer], HashSet::default(), cx);
|
||||
});
|
||||
let mut events = cx.events(&project);
|
||||
|
||||
// Simulate the newly started server sending more diagnostics.
|
||||
let fake_server = fake_servers.next().await.unwrap();
|
||||
assert_eq!(
|
||||
events.next().await.unwrap(),
|
||||
Event::LanguageServerRemoved(LanguageServerId(0))
|
||||
);
|
||||
assert_eq!(
|
||||
events.next().await.unwrap(),
|
||||
Event::LanguageServerAdded(
|
||||
|
@ -1820,7 +1825,7 @@ async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAp
|
|||
});
|
||||
|
||||
project.update(cx, |project, cx| {
|
||||
project.restart_language_servers_for_buffers(vec![buffer.clone()], cx);
|
||||
project.restart_language_servers_for_buffers(vec![buffer.clone()], HashSet::default(), cx);
|
||||
});
|
||||
|
||||
// The diagnostics are cleared.
|
||||
|
@ -1875,7 +1880,7 @@ async fn test_restarted_server_reporting_invalid_buffer_version(cx: &mut gpui::T
|
|||
});
|
||||
cx.executor().run_until_parked();
|
||||
project.update(cx, |project, cx| {
|
||||
project.restart_language_servers_for_buffers(vec![buffer.clone()], cx);
|
||||
project.restart_language_servers_for_buffers(vec![buffer.clone()], HashSet::default(), cx);
|
||||
});
|
||||
|
||||
let mut fake_server = fake_servers.next().await.unwrap();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue