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
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -14,6 +14,7 @@ dependencies = [
|
||||||
"gpui",
|
"gpui",
|
||||||
"language",
|
"language",
|
||||||
"project",
|
"project",
|
||||||
|
"proto",
|
||||||
"release_channel",
|
"release_channel",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"ui",
|
"ui",
|
||||||
|
@ -9025,6 +9026,7 @@ dependencies = [
|
||||||
"itertools 0.14.0",
|
"itertools 0.14.0",
|
||||||
"language",
|
"language",
|
||||||
"lsp",
|
"lsp",
|
||||||
|
"picker",
|
||||||
"project",
|
"project",
|
||||||
"release_channel",
|
"release_channel",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|
|
@ -41,7 +41,8 @@
|
||||||
"shift-f11": "debugger::StepOut",
|
"shift-f11": "debugger::StepOut",
|
||||||
"f11": "zed::ToggleFullScreen",
|
"f11": "zed::ToggleFullScreen",
|
||||||
"ctrl-alt-z": "edit_prediction::RateCompletions",
|
"ctrl-alt-z": "edit_prediction::RateCompletions",
|
||||||
"ctrl-shift-i": "edit_prediction::ToggleMenu"
|
"ctrl-shift-i": "edit_prediction::ToggleMenu",
|
||||||
|
"ctrl-alt-l": "lsp_tool::ToggleMenu"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -47,7 +47,8 @@
|
||||||
"fn-f": "zed::ToggleFullScreen",
|
"fn-f": "zed::ToggleFullScreen",
|
||||||
"ctrl-cmd-f": "zed::ToggleFullScreen",
|
"ctrl-cmd-f": "zed::ToggleFullScreen",
|
||||||
"ctrl-cmd-z": "edit_prediction::RateCompletions",
|
"ctrl-cmd-z": "edit_prediction::RateCompletions",
|
||||||
"ctrl-cmd-i": "edit_prediction::ToggleMenu"
|
"ctrl-cmd-i": "edit_prediction::ToggleMenu",
|
||||||
|
"ctrl-cmd-l": "lsp_tool::ToggleMenu"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -1720,6 +1720,11 @@
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
},
|
},
|
||||||
|
// Common language server settings.
|
||||||
|
"global_lsp_settings": {
|
||||||
|
// Whether to show the LSP servers button in the status bar.
|
||||||
|
"button": true
|
||||||
|
},
|
||||||
// Jupyter settings
|
// Jupyter settings
|
||||||
"jupyter": {
|
"jupyter": {
|
||||||
"enabled": true
|
"enabled": true
|
||||||
|
|
|
@ -21,6 +21,7 @@ futures.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
language.workspace = true
|
language.workspace = true
|
||||||
project.workspace = true
|
project.workspace = true
|
||||||
|
proto.workspace = true
|
||||||
smallvec.workspace = true
|
smallvec.workspace = true
|
||||||
ui.workspace = true
|
ui.workspace = true
|
||||||
util.workspace = true
|
util.workspace = true
|
||||||
|
|
|
@ -80,10 +80,13 @@ impl ActivityIndicator {
|
||||||
let this = cx.new(|cx| {
|
let this = cx.new(|cx| {
|
||||||
let mut status_events = languages.language_server_binary_statuses();
|
let mut status_events = languages.language_server_binary_statuses();
|
||||||
cx.spawn(async move |this, cx| {
|
cx.spawn(async move |this, cx| {
|
||||||
while let Some((name, status)) = status_events.next().await {
|
while let Some((name, binary_status)) = status_events.next().await {
|
||||||
this.update(cx, |this: &mut ActivityIndicator, cx| {
|
this.update(cx, |this: &mut ActivityIndicator, cx| {
|
||||||
this.statuses.retain(|s| s.name != name);
|
this.statuses.retain(|s| s.name != name);
|
||||||
this.statuses.push(ServerStatus { name, status });
|
this.statuses.push(ServerStatus {
|
||||||
|
name,
|
||||||
|
status: LanguageServerStatusUpdate::Binary(binary_status),
|
||||||
|
});
|
||||||
cx.notify();
|
cx.notify();
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
@ -112,8 +115,76 @@ impl ActivityIndicator {
|
||||||
|
|
||||||
cx.subscribe(
|
cx.subscribe(
|
||||||
&project.read(cx).lsp_store(),
|
&project.read(cx).lsp_store(),
|
||||||
|_, _, event, cx| match event {
|
|activity_indicator, _, event, cx| match event {
|
||||||
LspStoreEvent::LanguageServerUpdate { .. } => cx.notify(),
|
LspStoreEvent::LanguageServerUpdate { name, message, .. } => {
|
||||||
|
if let proto::update_language_server::Variant::StatusUpdate(status_update) =
|
||||||
|
message
|
||||||
|
{
|
||||||
|
let Some(name) = name.clone() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let status = match &status_update.status {
|
||||||
|
Some(proto::status_update::Status::Binary(binary_status)) => {
|
||||||
|
if let Some(binary_status) =
|
||||||
|
proto::ServerBinaryStatus::from_i32(*binary_status)
|
||||||
|
{
|
||||||
|
let binary_status = match binary_status {
|
||||||
|
proto::ServerBinaryStatus::None => BinaryStatus::None,
|
||||||
|
proto::ServerBinaryStatus::CheckingForUpdate => {
|
||||||
|
BinaryStatus::CheckingForUpdate
|
||||||
|
}
|
||||||
|
proto::ServerBinaryStatus::Downloading => {
|
||||||
|
BinaryStatus::Downloading
|
||||||
|
}
|
||||||
|
proto::ServerBinaryStatus::Starting => {
|
||||||
|
BinaryStatus::Starting
|
||||||
|
}
|
||||||
|
proto::ServerBinaryStatus::Stopping => {
|
||||||
|
BinaryStatus::Stopping
|
||||||
|
}
|
||||||
|
proto::ServerBinaryStatus::Stopped => {
|
||||||
|
BinaryStatus::Stopped
|
||||||
|
}
|
||||||
|
proto::ServerBinaryStatus::Failed => {
|
||||||
|
let Some(error) = status_update.message.clone()
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
BinaryStatus::Failed { error }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
LanguageServerStatusUpdate::Binary(binary_status)
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(proto::status_update::Status::Health(health_status)) => {
|
||||||
|
if let Some(health) =
|
||||||
|
proto::ServerHealth::from_i32(*health_status)
|
||||||
|
{
|
||||||
|
let health = match health {
|
||||||
|
proto::ServerHealth::Ok => ServerHealth::Ok,
|
||||||
|
proto::ServerHealth::Warning => ServerHealth::Warning,
|
||||||
|
proto::ServerHealth::Error => ServerHealth::Error,
|
||||||
|
};
|
||||||
|
LanguageServerStatusUpdate::Health(
|
||||||
|
health,
|
||||||
|
status_update.message.clone().map(SharedString::from),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
activity_indicator.statuses.retain(|s| s.name != name);
|
||||||
|
activity_indicator
|
||||||
|
.statuses
|
||||||
|
.push(ServerStatus { name, status });
|
||||||
|
}
|
||||||
|
cx.notify()
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -228,9 +299,23 @@ impl ActivityIndicator {
|
||||||
_: &mut Window,
|
_: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
if let Some(updater) = &self.auto_updater {
|
let error_dismissed = if let Some(updater) = &self.auto_updater {
|
||||||
updater.update(cx, |updater, cx| updater.dismiss_error(cx));
|
updater.update(cx, |updater, cx| updater.dismiss_error(cx))
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
if error_dismissed {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.project.update(cx, |project, cx| {
|
||||||
|
if project.last_formatting_failure(cx).is_some() {
|
||||||
|
project.reset_last_formatting_failure(cx);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pending_language_server_work<'a>(
|
fn pending_language_server_work<'a>(
|
||||||
|
@ -399,6 +484,12 @@ impl ActivityIndicator {
|
||||||
let mut servers_to_clear_statuses = HashSet::<LanguageServerName>::default();
|
let mut servers_to_clear_statuses = HashSet::<LanguageServerName>::default();
|
||||||
for status in &self.statuses {
|
for status in &self.statuses {
|
||||||
match &status.status {
|
match &status.status {
|
||||||
|
LanguageServerStatusUpdate::Binary(
|
||||||
|
BinaryStatus::Starting | BinaryStatus::Stopping,
|
||||||
|
) => {}
|
||||||
|
LanguageServerStatusUpdate::Binary(BinaryStatus::Stopped) => {
|
||||||
|
servers_to_clear_statuses.insert(status.name.clone());
|
||||||
|
}
|
||||||
LanguageServerStatusUpdate::Binary(BinaryStatus::CheckingForUpdate) => {
|
LanguageServerStatusUpdate::Binary(BinaryStatus::CheckingForUpdate) => {
|
||||||
checking_for_update.push(status.name.clone());
|
checking_for_update.push(status.name.clone());
|
||||||
}
|
}
|
||||||
|
|
|
@ -2008,6 +2008,7 @@ async fn join_project(
|
||||||
session.connection_id,
|
session.connection_id,
|
||||||
proto::UpdateLanguageServer {
|
proto::UpdateLanguageServer {
|
||||||
project_id: project_id.to_proto(),
|
project_id: project_id.to_proto(),
|
||||||
|
server_name: Some(language_server.name.clone()),
|
||||||
language_server_id: language_server.id,
|
language_server_id: language_server.id,
|
||||||
variant: Some(
|
variant: Some(
|
||||||
proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated(
|
proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated(
|
||||||
|
|
|
@ -16164,7 +16164,7 @@ impl Editor {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn restart_language_server(
|
pub fn restart_language_server(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: &RestartLanguageServer,
|
_: &RestartLanguageServer,
|
||||||
_: &mut Window,
|
_: &mut Window,
|
||||||
|
@ -16175,6 +16175,7 @@ impl Editor {
|
||||||
project.update(cx, |project, cx| {
|
project.update(cx, |project, cx| {
|
||||||
project.restart_language_servers_for_buffers(
|
project.restart_language_servers_for_buffers(
|
||||||
multi_buffer.all_buffers().into_iter().collect(),
|
multi_buffer.all_buffers().into_iter().collect(),
|
||||||
|
HashSet::default(),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -16182,7 +16183,7 @@ impl Editor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stop_language_server(
|
pub fn stop_language_server(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: &StopLanguageServer,
|
_: &StopLanguageServer,
|
||||||
_: &mut Window,
|
_: &mut Window,
|
||||||
|
@ -16193,6 +16194,7 @@ impl Editor {
|
||||||
project.update(cx, |project, cx| {
|
project.update(cx, |project, cx| {
|
||||||
project.stop_language_servers_for_buffers(
|
project.stop_language_servers_for_buffers(
|
||||||
multi_buffer.all_buffers().into_iter().collect(),
|
multi_buffer.all_buffers().into_iter().collect(),
|
||||||
|
HashSet::default(),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
cx.emit(project::Event::RefreshInlayHints);
|
cx.emit(project::Event::RefreshInlayHints);
|
||||||
|
|
|
@ -4,13 +4,13 @@ use crate::{
|
||||||
GrammarManifestEntry, RELOAD_DEBOUNCE_DURATION, SchemaVersion,
|
GrammarManifestEntry, RELOAD_DEBOUNCE_DURATION, SchemaVersion,
|
||||||
};
|
};
|
||||||
use async_compression::futures::bufread::GzipEncoder;
|
use async_compression::futures::bufread::GzipEncoder;
|
||||||
use collections::BTreeMap;
|
use collections::{BTreeMap, HashSet};
|
||||||
use extension::ExtensionHostProxy;
|
use extension::ExtensionHostProxy;
|
||||||
use fs::{FakeFs, Fs, RealFs};
|
use fs::{FakeFs, Fs, RealFs};
|
||||||
use futures::{AsyncReadExt, StreamExt, io::BufReader};
|
use futures::{AsyncReadExt, StreamExt, io::BufReader};
|
||||||
use gpui::{AppContext as _, SemanticVersion, TestAppContext};
|
use gpui::{AppContext as _, SemanticVersion, TestAppContext};
|
||||||
use http_client::{FakeHttpClient, Response};
|
use http_client::{FakeHttpClient, Response};
|
||||||
use language::{BinaryStatus, LanguageMatcher, LanguageRegistry, LanguageServerStatusUpdate};
|
use language::{BinaryStatus, LanguageMatcher, LanguageRegistry};
|
||||||
use lsp::LanguageServerName;
|
use lsp::LanguageServerName;
|
||||||
use node_runtime::NodeRuntime;
|
use node_runtime::NodeRuntime;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
@ -720,20 +720,22 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
|
||||||
status_updates.next().await.unwrap(),
|
status_updates.next().await.unwrap(),
|
||||||
status_updates.next().await.unwrap(),
|
status_updates.next().await.unwrap(),
|
||||||
status_updates.next().await.unwrap(),
|
status_updates.next().await.unwrap(),
|
||||||
|
status_updates.next().await.unwrap(),
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
LanguageServerName::new_static("gleam"),
|
LanguageServerName::new_static("gleam"),
|
||||||
LanguageServerStatusUpdate::Binary(BinaryStatus::CheckingForUpdate)
|
BinaryStatus::Starting
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
LanguageServerName::new_static("gleam"),
|
LanguageServerName::new_static("gleam"),
|
||||||
LanguageServerStatusUpdate::Binary(BinaryStatus::Downloading)
|
BinaryStatus::CheckingForUpdate
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
LanguageServerName::new_static("gleam"),
|
LanguageServerName::new_static("gleam"),
|
||||||
LanguageServerStatusUpdate::Binary(BinaryStatus::None)
|
BinaryStatus::Downloading
|
||||||
)
|
),
|
||||||
|
(LanguageServerName::new_static("gleam"), BinaryStatus::None)
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -794,7 +796,7 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
|
||||||
|
|
||||||
// Start a new instance of the language server.
|
// Start a new instance of the language server.
|
||||||
project.update(cx, |project, cx| {
|
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)
|
||||||
});
|
});
|
||||||
cx.executor().run_until_parked();
|
cx.executor().run_until_parked();
|
||||||
|
|
||||||
|
@ -816,7 +818,7 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
|
||||||
|
|
||||||
cx.executor().run_until_parked();
|
cx.executor().run_until_parked();
|
||||||
project.update(cx, |project, cx| {
|
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 extension re-fetches the latest version of the language server.
|
// The extension re-fetches the latest version of the language server.
|
||||||
|
|
|
@ -413,10 +413,6 @@ impl PickerDelegate for BranchListDelegate {
|
||||||
cx.emit(DismissEvent);
|
cx.emit(DismissEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_header(&self, _: &mut Window, _cx: &mut Context<Picker<Self>>) -> Option<AnyElement> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_match(
|
fn render_match(
|
||||||
&self,
|
&self,
|
||||||
ix: usize,
|
ix: usize,
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
use gpui::{
|
use gpui::{App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Task, WeakEntity};
|
||||||
AnyElement, App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Task, WeakEntity,
|
|
||||||
};
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use picker::{Picker, PickerDelegate};
|
use picker::{Picker, PickerDelegate};
|
||||||
use project::{Project, git_store::Repository};
|
use project::{Project, git_store::Repository};
|
||||||
|
@ -207,15 +205,6 @@ impl PickerDelegate for RepositorySelectorDelegate {
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_header(
|
|
||||||
&self,
|
|
||||||
_window: &mut Window,
|
|
||||||
_cx: &mut Context<Picker<Self>>,
|
|
||||||
) -> Option<AnyElement> {
|
|
||||||
// TODO: Implement header rendering if needed
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_match(
|
fn render_match(
|
||||||
&self,
|
&self,
|
||||||
ix: usize,
|
ix: usize,
|
||||||
|
|
|
@ -157,6 +157,9 @@ pub enum BinaryStatus {
|
||||||
None,
|
None,
|
||||||
CheckingForUpdate,
|
CheckingForUpdate,
|
||||||
Downloading,
|
Downloading,
|
||||||
|
Starting,
|
||||||
|
Stopping,
|
||||||
|
Stopped,
|
||||||
Failed { error: String },
|
Failed { error: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,7 +251,7 @@ pub struct LanguageQueries {
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
struct ServerStatusSender {
|
struct ServerStatusSender {
|
||||||
txs: Arc<Mutex<Vec<mpsc::UnboundedSender<(LanguageServerName, LanguageServerStatusUpdate)>>>>,
|
txs: Arc<Mutex<Vec<mpsc::UnboundedSender<(LanguageServerName, BinaryStatus)>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct LoadedLanguage {
|
pub struct LoadedLanguage {
|
||||||
|
@ -1085,11 +1088,7 @@ impl LanguageRegistry {
|
||||||
self.state.read().all_lsp_adapters.get(name).cloned()
|
self.state.read().all_lsp_adapters.get(name).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_lsp_status(
|
pub fn update_lsp_binary_status(&self, server_name: LanguageServerName, status: BinaryStatus) {
|
||||||
&self,
|
|
||||||
server_name: LanguageServerName,
|
|
||||||
status: LanguageServerStatusUpdate,
|
|
||||||
) {
|
|
||||||
self.lsp_binary_status_tx.send(server_name, status);
|
self.lsp_binary_status_tx.send(server_name, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1145,7 +1144,7 @@ impl LanguageRegistry {
|
||||||
|
|
||||||
pub fn language_server_binary_statuses(
|
pub fn language_server_binary_statuses(
|
||||||
&self,
|
&self,
|
||||||
) -> mpsc::UnboundedReceiver<(LanguageServerName, LanguageServerStatusUpdate)> {
|
) -> mpsc::UnboundedReceiver<(LanguageServerName, BinaryStatus)> {
|
||||||
self.lsp_binary_status_tx.subscribe()
|
self.lsp_binary_status_tx.subscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1260,15 +1259,13 @@ impl LanguageRegistryState {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServerStatusSender {
|
impl ServerStatusSender {
|
||||||
fn subscribe(
|
fn subscribe(&self) -> mpsc::UnboundedReceiver<(LanguageServerName, BinaryStatus)> {
|
||||||
&self,
|
|
||||||
) -> mpsc::UnboundedReceiver<(LanguageServerName, LanguageServerStatusUpdate)> {
|
|
||||||
let (tx, rx) = mpsc::unbounded();
|
let (tx, rx) = mpsc::unbounded();
|
||||||
self.txs.lock().push(tx);
|
self.txs.lock().push(tx);
|
||||||
rx
|
rx
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send(&self, name: LanguageServerName, status: LanguageServerStatusUpdate) {
|
fn send(&self, name: LanguageServerName, status: BinaryStatus) {
|
||||||
let mut txs = self.txs.lock();
|
let mut txs = self.txs.lock();
|
||||||
txs.retain(|tx| tx.unbounded_send((name.clone(), status.clone())).is_ok());
|
txs.retain(|tx| tx.unbounded_send((name.clone(), status.clone())).is_ok());
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,8 @@ use fs::Fs;
|
||||||
use futures::{Future, FutureExt};
|
use futures::{Future, FutureExt};
|
||||||
use gpui::AsyncApp;
|
use gpui::AsyncApp;
|
||||||
use language::{
|
use language::{
|
||||||
BinaryStatus, CodeLabel, HighlightId, Language, LanguageName, LanguageServerStatusUpdate,
|
BinaryStatus, CodeLabel, HighlightId, Language, LanguageName, LanguageToolchainStore,
|
||||||
LanguageToolchainStore, LspAdapter, LspAdapterDelegate,
|
LspAdapter, LspAdapterDelegate,
|
||||||
};
|
};
|
||||||
use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerBinaryOptions, LanguageServerName};
|
use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerBinaryOptions, LanguageServerName};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
@ -82,10 +82,8 @@ impl ExtensionLanguageServerProxy for LanguageServerRegistryProxy {
|
||||||
language_server_id: LanguageServerName,
|
language_server_id: LanguageServerName,
|
||||||
status: BinaryStatus,
|
status: BinaryStatus,
|
||||||
) {
|
) {
|
||||||
self.language_registry.update_lsp_status(
|
self.language_registry
|
||||||
language_server_id,
|
.update_lsp_binary_status(language_server_id, status);
|
||||||
LanguageServerStatusUpdate::Binary(status),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
|
client.workspace = true
|
||||||
collections.workspace = true
|
collections.workspace = true
|
||||||
copilot.workspace = true
|
copilot.workspace = true
|
||||||
editor.workspace = true
|
editor.workspace = true
|
||||||
|
@ -22,18 +23,19 @@ gpui.workspace = true
|
||||||
itertools.workspace = true
|
itertools.workspace = true
|
||||||
language.workspace = true
|
language.workspace = true
|
||||||
lsp.workspace = true
|
lsp.workspace = true
|
||||||
|
picker.workspace = true
|
||||||
project.workspace = true
|
project.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
settings.workspace = true
|
settings.workspace = true
|
||||||
theme.workspace = true
|
theme.workspace = true
|
||||||
tree-sitter.workspace = true
|
tree-sitter.workspace = true
|
||||||
ui.workspace = true
|
ui.workspace = true
|
||||||
|
util.workspace = true
|
||||||
workspace.workspace = true
|
workspace.workspace = true
|
||||||
zed_actions.workspace = true
|
zed_actions.workspace = true
|
||||||
workspace-hack.workspace = true
|
workspace-hack.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
client = { workspace = true, features = ["test-support"] }
|
|
||||||
editor = { workspace = true, features = ["test-support"] }
|
editor = { workspace = true, features = ["test-support"] }
|
||||||
release_channel.workspace = true
|
release_channel.workspace = true
|
||||||
gpui = { workspace = true, features = ["test-support"] }
|
gpui = { workspace = true, features = ["test-support"] }
|
||||||
|
|
|
@ -1,17 +1,54 @@
|
||||||
mod key_context_view;
|
mod key_context_view;
|
||||||
mod lsp_log;
|
mod lsp_log;
|
||||||
|
pub mod lsp_tool;
|
||||||
mod syntax_tree_view;
|
mod syntax_tree_view;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod lsp_log_tests;
|
mod lsp_log_tests;
|
||||||
|
|
||||||
use gpui::App;
|
use gpui::{App, AppContext, Entity};
|
||||||
|
|
||||||
pub use lsp_log::{LogStore, LspLogToolbarItemView, LspLogView};
|
pub use lsp_log::{LogStore, LspLogToolbarItemView, LspLogView};
|
||||||
pub use syntax_tree_view::{SyntaxTreeToolbarItemView, SyntaxTreeView};
|
pub use syntax_tree_view::{SyntaxTreeToolbarItemView, SyntaxTreeView};
|
||||||
|
use ui::{Context, Window};
|
||||||
|
use workspace::{Item, ItemHandle, SplitDirection, Workspace};
|
||||||
|
|
||||||
pub fn init(cx: &mut App) {
|
pub fn init(cx: &mut App) {
|
||||||
lsp_log::init(cx);
|
lsp_log::init(cx);
|
||||||
syntax_tree_view::init(cx);
|
syntax_tree_view::init(cx);
|
||||||
key_context_view::init(cx);
|
key_context_view::init(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_or_create_tool<T>(
|
||||||
|
workspace: &mut Workspace,
|
||||||
|
destination: SplitDirection,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Workspace>,
|
||||||
|
new_tool: impl FnOnce(&mut Window, &mut Context<T>) -> T,
|
||||||
|
) -> Entity<T>
|
||||||
|
where
|
||||||
|
T: Item,
|
||||||
|
{
|
||||||
|
if let Some(item) = workspace.item_of_type::<T>(cx) {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_tool = cx.new(|cx| new_tool(window, cx));
|
||||||
|
match workspace.find_pane_in_direction(destination, cx) {
|
||||||
|
Some(right_pane) => {
|
||||||
|
workspace.add_item(
|
||||||
|
right_pane,
|
||||||
|
new_tool.boxed_clone(),
|
||||||
|
None,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
workspace.split_item(destination, new_tool.boxed_clone(), window, cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
new_tool
|
||||||
|
}
|
||||||
|
|
|
@ -3,14 +3,14 @@ use copilot::Copilot;
|
||||||
use editor::{Editor, EditorEvent, actions::MoveToEnd, scroll::Autoscroll};
|
use editor::{Editor, EditorEvent, actions::MoveToEnd, scroll::Autoscroll};
|
||||||
use futures::{StreamExt, channel::mpsc};
|
use futures::{StreamExt, channel::mpsc};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyView, App, Context, Corner, Entity, EventEmitter, FocusHandle, Focusable, IntoElement,
|
AnyView, App, Context, Corner, Entity, EventEmitter, FocusHandle, Focusable, Global,
|
||||||
ParentElement, Render, Styled, Subscription, WeakEntity, Window, actions, div,
|
IntoElement, ParentElement, Render, Styled, Subscription, WeakEntity, Window, actions, div,
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use language::{LanguageServerId, language_settings::SoftWrap};
|
use language::{LanguageServerId, language_settings::SoftWrap};
|
||||||
use lsp::{
|
use lsp::{
|
||||||
IoKind, LanguageServer, LanguageServerName, MessageType, SetTraceParams, TraceValue,
|
IoKind, LanguageServer, LanguageServerName, LanguageServerSelector, MessageType,
|
||||||
notification::SetTrace,
|
SetTraceParams, TraceValue, notification::SetTrace,
|
||||||
};
|
};
|
||||||
use project::{Project, WorktreeId, search::SearchQuery};
|
use project::{Project, WorktreeId, search::SearchQuery};
|
||||||
use std::{any::TypeId, borrow::Cow, sync::Arc};
|
use std::{any::TypeId, borrow::Cow, sync::Arc};
|
||||||
|
@ -21,6 +21,8 @@ use workspace::{
|
||||||
searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},
|
searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::get_or_create_tool;
|
||||||
|
|
||||||
const SEND_LINE: &str = "\n// Send:";
|
const SEND_LINE: &str = "\n// Send:";
|
||||||
const RECEIVE_LINE: &str = "\n// Receive:";
|
const RECEIVE_LINE: &str = "\n// Receive:";
|
||||||
const MAX_STORED_LOG_ENTRIES: usize = 2000;
|
const MAX_STORED_LOG_ENTRIES: usize = 2000;
|
||||||
|
@ -44,7 +46,7 @@ trait Message: AsRef<str> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LogMessage {
|
pub(super) struct LogMessage {
|
||||||
message: String,
|
message: String,
|
||||||
typ: MessageType,
|
typ: MessageType,
|
||||||
}
|
}
|
||||||
|
@ -71,7 +73,7 @@ impl Message for LogMessage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TraceMessage {
|
pub(super) struct TraceMessage {
|
||||||
message: String,
|
message: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,7 +101,7 @@ impl Message for RpcMessage {
|
||||||
type Level = ();
|
type Level = ();
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LanguageServerState {
|
pub(super) struct LanguageServerState {
|
||||||
name: Option<LanguageServerName>,
|
name: Option<LanguageServerName>,
|
||||||
worktree_id: Option<WorktreeId>,
|
worktree_id: Option<WorktreeId>,
|
||||||
kind: LanguageServerKind,
|
kind: LanguageServerKind,
|
||||||
|
@ -204,8 +206,13 @@ pub(crate) struct LogMenuItem {
|
||||||
|
|
||||||
actions!(dev, [OpenLanguageServerLogs]);
|
actions!(dev, [OpenLanguageServerLogs]);
|
||||||
|
|
||||||
|
pub(super) struct GlobalLogStore(pub WeakEntity<LogStore>);
|
||||||
|
|
||||||
|
impl Global for GlobalLogStore {}
|
||||||
|
|
||||||
pub fn init(cx: &mut App) {
|
pub fn init(cx: &mut App) {
|
||||||
let log_store = cx.new(LogStore::new);
|
let log_store = cx.new(LogStore::new);
|
||||||
|
cx.set_global(GlobalLogStore(log_store.downgrade()));
|
||||||
|
|
||||||
cx.observe_new(move |workspace: &mut Workspace, _, cx| {
|
cx.observe_new(move |workspace: &mut Workspace, _, cx| {
|
||||||
let project = workspace.project();
|
let project = workspace.project();
|
||||||
|
@ -219,13 +226,14 @@ pub fn init(cx: &mut App) {
|
||||||
workspace.register_action(move |workspace, _: &OpenLanguageServerLogs, window, cx| {
|
workspace.register_action(move |workspace, _: &OpenLanguageServerLogs, window, cx| {
|
||||||
let project = workspace.project().read(cx);
|
let project = workspace.project().read(cx);
|
||||||
if project.is_local() || project.is_via_ssh() {
|
if project.is_local() || project.is_via_ssh() {
|
||||||
workspace.split_item(
|
let project = workspace.project().clone();
|
||||||
|
let log_store = log_store.clone();
|
||||||
|
get_or_create_tool(
|
||||||
|
workspace,
|
||||||
SplitDirection::Right,
|
SplitDirection::Right,
|
||||||
Box::new(cx.new(|cx| {
|
|
||||||
LspLogView::new(workspace.project().clone(), log_store.clone(), window, cx)
|
|
||||||
})),
|
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
|
move |window, cx| LspLogView::new(project, log_store, window, cx),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -354,7 +362,7 @@ impl LogStore {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_language_server_state(
|
pub(super) fn get_language_server_state(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: LanguageServerId,
|
id: LanguageServerId,
|
||||||
) -> Option<&mut LanguageServerState> {
|
) -> Option<&mut LanguageServerState> {
|
||||||
|
@ -480,11 +488,14 @@ impl LogStore {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn server_logs(&self, server_id: LanguageServerId) -> Option<&VecDeque<LogMessage>> {
|
pub(super) fn server_logs(&self, server_id: LanguageServerId) -> Option<&VecDeque<LogMessage>> {
|
||||||
Some(&self.language_servers.get(&server_id)?.log_messages)
|
Some(&self.language_servers.get(&server_id)?.log_messages)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn server_trace(&self, server_id: LanguageServerId) -> Option<&VecDeque<TraceMessage>> {
|
pub(super) fn server_trace(
|
||||||
|
&self,
|
||||||
|
server_id: LanguageServerId,
|
||||||
|
) -> Option<&VecDeque<TraceMessage>> {
|
||||||
Some(&self.language_servers.get(&server_id)?.trace_messages)
|
Some(&self.language_servers.get(&server_id)?.trace_messages)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -529,6 +540,110 @@ impl LogStore {
|
||||||
Some(())
|
Some(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn has_server_logs(&self, server: &LanguageServerSelector) -> bool {
|
||||||
|
match server {
|
||||||
|
LanguageServerSelector::Id(id) => self.language_servers.contains_key(id),
|
||||||
|
LanguageServerSelector::Name(name) => self
|
||||||
|
.language_servers
|
||||||
|
.iter()
|
||||||
|
.any(|(_, state)| state.name.as_ref() == Some(name)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open_server_log(
|
||||||
|
&mut self,
|
||||||
|
workspace: WeakEntity<Workspace>,
|
||||||
|
server: LanguageServerSelector,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
cx.spawn_in(window, async move |log_store, cx| {
|
||||||
|
let Some(log_store) = log_store.upgrade() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
workspace
|
||||||
|
.update_in(cx, |workspace, window, cx| {
|
||||||
|
let project = workspace.project().clone();
|
||||||
|
let tool_log_store = log_store.clone();
|
||||||
|
let log_view = get_or_create_tool(
|
||||||
|
workspace,
|
||||||
|
SplitDirection::Right,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
move |window, cx| LspLogView::new(project, tool_log_store, window, cx),
|
||||||
|
);
|
||||||
|
log_view.update(cx, |log_view, cx| {
|
||||||
|
let server_id = match server {
|
||||||
|
LanguageServerSelector::Id(id) => Some(id),
|
||||||
|
LanguageServerSelector::Name(name) => {
|
||||||
|
log_store.read(cx).language_servers.iter().find_map(
|
||||||
|
|(id, state)| {
|
||||||
|
if state.name.as_ref() == Some(&name) {
|
||||||
|
Some(*id)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Some(server_id) = server_id {
|
||||||
|
log_view.show_logs_for_server(server_id, window, cx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open_server_trace(
|
||||||
|
&mut self,
|
||||||
|
workspace: WeakEntity<Workspace>,
|
||||||
|
server: LanguageServerSelector,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
cx.spawn_in(window, async move |log_store, cx| {
|
||||||
|
let Some(log_store) = log_store.upgrade() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
workspace
|
||||||
|
.update_in(cx, |workspace, window, cx| {
|
||||||
|
let project = workspace.project().clone();
|
||||||
|
let tool_log_store = log_store.clone();
|
||||||
|
let log_view = get_or_create_tool(
|
||||||
|
workspace,
|
||||||
|
SplitDirection::Right,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
move |window, cx| LspLogView::new(project, tool_log_store, window, cx),
|
||||||
|
);
|
||||||
|
log_view.update(cx, |log_view, cx| {
|
||||||
|
let server_id = match server {
|
||||||
|
LanguageServerSelector::Id(id) => Some(id),
|
||||||
|
LanguageServerSelector::Name(name) => {
|
||||||
|
log_store.read(cx).language_servers.iter().find_map(
|
||||||
|
|(id, state)| {
|
||||||
|
if state.name.as_ref() == Some(&name) {
|
||||||
|
Some(*id)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Some(server_id) = server_id {
|
||||||
|
log_view.show_rpc_trace_for_server(server_id, window, cx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
|
||||||
fn on_io(
|
fn on_io(
|
||||||
&mut self,
|
&mut self,
|
||||||
language_server_id: LanguageServerId,
|
language_server_id: LanguageServerId,
|
||||||
|
@ -856,7 +971,7 @@ impl LspLogView {
|
||||||
self.editor_subscriptions = editor_subscriptions;
|
self.editor_subscriptions = editor_subscriptions;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
window.focus(&self.focus_handle);
|
self.editor.read(cx).focus_handle(cx).focus(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_log_level(
|
fn update_log_level(
|
||||||
|
@ -882,7 +997,7 @@ impl LspLogView {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
window.focus(&self.focus_handle);
|
self.editor.read(cx).focus_handle(cx).focus(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_trace_for_server(
|
fn show_trace_for_server(
|
||||||
|
@ -904,7 +1019,7 @@ impl LspLogView {
|
||||||
self.editor_subscriptions = editor_subscriptions;
|
self.editor_subscriptions = editor_subscriptions;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
window.focus(&self.focus_handle);
|
self.editor.read(cx).focus_handle(cx).focus(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_rpc_trace_for_server(
|
fn show_rpc_trace_for_server(
|
||||||
|
@ -947,7 +1062,7 @@ impl LspLogView {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
window.focus(&self.focus_handle);
|
self.editor.read(cx).focus_handle(cx).focus(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_rpc_trace_for_server(
|
fn toggle_rpc_trace_for_server(
|
||||||
|
@ -1011,7 +1126,7 @@ impl LspLogView {
|
||||||
self.editor = editor;
|
self.editor = editor;
|
||||||
self.editor_subscriptions = editor_subscriptions;
|
self.editor_subscriptions = editor_subscriptions;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
window.focus(&self.focus_handle);
|
self.editor.read(cx).focus_handle(cx).focus(window);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
917
crates/language_tools/src/lsp_tool.rs
Normal file
917
crates/language_tools/src/lsp_tool.rs
Normal file
|
@ -0,0 +1,917 @@
|
||||||
|
use std::{collections::hash_map, path::PathBuf, sync::Arc, time::Duration};
|
||||||
|
|
||||||
|
use client::proto;
|
||||||
|
use collections::{HashMap, HashSet};
|
||||||
|
use editor::{Editor, EditorEvent};
|
||||||
|
use gpui::{Corner, DismissEvent, Entity, Focusable as _, Subscription, Task, WeakEntity, actions};
|
||||||
|
use language::{BinaryStatus, BufferId, LocalFile, ServerHealth};
|
||||||
|
use lsp::{LanguageServerId, LanguageServerName, LanguageServerSelector};
|
||||||
|
use picker::{Picker, PickerDelegate, popover_menu::PickerPopoverMenu};
|
||||||
|
use project::{LspStore, LspStoreEvent, project_settings::ProjectSettings};
|
||||||
|
use settings::{Settings as _, SettingsStore};
|
||||||
|
use ui::{Context, IconButtonShape, Indicator, Tooltip, Window, prelude::*};
|
||||||
|
|
||||||
|
use workspace::{StatusItemView, Workspace};
|
||||||
|
|
||||||
|
use crate::lsp_log::GlobalLogStore;
|
||||||
|
|
||||||
|
actions!(lsp_tool, [ToggleMenu]);
|
||||||
|
|
||||||
|
pub struct LspTool {
|
||||||
|
state: Entity<PickerState>,
|
||||||
|
lsp_picker: Option<Entity<Picker<LspPickerDelegate>>>,
|
||||||
|
_subscriptions: Vec<Subscription>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PickerState {
|
||||||
|
workspace: WeakEntity<Workspace>,
|
||||||
|
lsp_store: WeakEntity<LspStore>,
|
||||||
|
active_editor: Option<ActiveEditor>,
|
||||||
|
language_servers: LanguageServers,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct LspPickerDelegate {
|
||||||
|
state: Entity<PickerState>,
|
||||||
|
selected_index: usize,
|
||||||
|
items: Vec<LspItem>,
|
||||||
|
other_servers_start_index: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ActiveEditor {
|
||||||
|
editor: WeakEntity<Editor>,
|
||||||
|
_editor_subscription: Subscription,
|
||||||
|
editor_buffers: HashSet<BufferId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone)]
|
||||||
|
struct LanguageServers {
|
||||||
|
health_statuses: HashMap<LanguageServerId, LanguageServerHealthStatus>,
|
||||||
|
binary_statuses: HashMap<LanguageServerName, LanguageServerBinaryStatus>,
|
||||||
|
servers_per_buffer_abs_path:
|
||||||
|
HashMap<PathBuf, HashMap<LanguageServerId, Option<LanguageServerName>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct LanguageServerHealthStatus {
|
||||||
|
name: LanguageServerName,
|
||||||
|
health: Option<(Option<SharedString>, ServerHealth)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct LanguageServerBinaryStatus {
|
||||||
|
status: BinaryStatus,
|
||||||
|
message: Option<SharedString>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LanguageServerHealthStatus {
|
||||||
|
fn health(&self) -> Option<ServerHealth> {
|
||||||
|
self.health.as_ref().map(|(_, health)| *health)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn message(&self) -> Option<SharedString> {
|
||||||
|
self.health
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|(message, _)| message.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LspPickerDelegate {
|
||||||
|
fn regenerate_items(&mut self, cx: &mut Context<Picker<Self>>) {
|
||||||
|
self.state.update(cx, |state, cx| {
|
||||||
|
let editor_buffers = state
|
||||||
|
.active_editor
|
||||||
|
.as_ref()
|
||||||
|
.map(|active_editor| active_editor.editor_buffers.clone())
|
||||||
|
.unwrap_or_default();
|
||||||
|
let editor_buffer_paths = editor_buffers
|
||||||
|
.iter()
|
||||||
|
.filter_map(|buffer_id| {
|
||||||
|
let buffer_path = state
|
||||||
|
.lsp_store
|
||||||
|
.update(cx, |lsp_store, cx| {
|
||||||
|
Some(
|
||||||
|
project::File::from_dyn(
|
||||||
|
lsp_store
|
||||||
|
.buffer_store()
|
||||||
|
.read(cx)
|
||||||
|
.get(*buffer_id)?
|
||||||
|
.read(cx)
|
||||||
|
.file(),
|
||||||
|
)?
|
||||||
|
.abs_path(cx),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.ok()??;
|
||||||
|
Some(buffer_path)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let mut servers_with_health_checks = HashSet::default();
|
||||||
|
let mut server_ids_with_health_checks = HashSet::default();
|
||||||
|
let mut buffer_servers =
|
||||||
|
Vec::with_capacity(state.language_servers.health_statuses.len());
|
||||||
|
let mut other_servers =
|
||||||
|
Vec::with_capacity(state.language_servers.health_statuses.len());
|
||||||
|
let buffer_server_ids = editor_buffer_paths
|
||||||
|
.iter()
|
||||||
|
.filter_map(|buffer_path| {
|
||||||
|
state
|
||||||
|
.language_servers
|
||||||
|
.servers_per_buffer_abs_path
|
||||||
|
.get(buffer_path)
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.fold(HashMap::default(), |mut acc, (server_id, name)| {
|
||||||
|
match acc.entry(*server_id) {
|
||||||
|
hash_map::Entry::Occupied(mut o) => {
|
||||||
|
let old_name: &mut Option<&LanguageServerName> = o.get_mut();
|
||||||
|
if old_name.is_none() {
|
||||||
|
*old_name = name.as_ref();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hash_map::Entry::Vacant(v) => {
|
||||||
|
v.insert(name.as_ref());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
acc
|
||||||
|
});
|
||||||
|
for (server_id, server_state) in &state.language_servers.health_statuses {
|
||||||
|
let binary_status = state
|
||||||
|
.language_servers
|
||||||
|
.binary_statuses
|
||||||
|
.get(&server_state.name);
|
||||||
|
servers_with_health_checks.insert(&server_state.name);
|
||||||
|
server_ids_with_health_checks.insert(*server_id);
|
||||||
|
if buffer_server_ids.contains_key(server_id) {
|
||||||
|
buffer_servers.push(ServerData::WithHealthCheck(
|
||||||
|
*server_id,
|
||||||
|
server_state,
|
||||||
|
binary_status,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
other_servers.push(ServerData::WithHealthCheck(
|
||||||
|
*server_id,
|
||||||
|
server_state,
|
||||||
|
binary_status,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (server_name, status) in state
|
||||||
|
.language_servers
|
||||||
|
.binary_statuses
|
||||||
|
.iter()
|
||||||
|
.filter(|(name, _)| !servers_with_health_checks.contains(name))
|
||||||
|
{
|
||||||
|
let has_matching_server = state
|
||||||
|
.language_servers
|
||||||
|
.servers_per_buffer_abs_path
|
||||||
|
.iter()
|
||||||
|
.filter(|(path, _)| editor_buffer_paths.contains(path))
|
||||||
|
.flat_map(|(_, server_associations)| server_associations.iter())
|
||||||
|
.any(|(_, name)| name.as_ref() == Some(server_name));
|
||||||
|
if has_matching_server {
|
||||||
|
buffer_servers.push(ServerData::WithBinaryStatus(server_name, status));
|
||||||
|
} else {
|
||||||
|
other_servers.push(ServerData::WithBinaryStatus(server_name, status));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer_servers.sort_by_key(|data| data.name().clone());
|
||||||
|
other_servers.sort_by_key(|data| data.name().clone());
|
||||||
|
let mut other_servers_start_index = None;
|
||||||
|
let mut new_lsp_items =
|
||||||
|
Vec::with_capacity(buffer_servers.len() + other_servers.len() + 2);
|
||||||
|
if !buffer_servers.is_empty() {
|
||||||
|
new_lsp_items.push(LspItem::Header(SharedString::new("Current Buffer")));
|
||||||
|
new_lsp_items.extend(buffer_servers.into_iter().map(ServerData::into_lsp_item));
|
||||||
|
}
|
||||||
|
if !other_servers.is_empty() {
|
||||||
|
other_servers_start_index = Some(new_lsp_items.len());
|
||||||
|
new_lsp_items.push(LspItem::Header(SharedString::new("Other Active Servers")));
|
||||||
|
new_lsp_items.extend(other_servers.into_iter().map(ServerData::into_lsp_item));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.items = new_lsp_items;
|
||||||
|
self.other_servers_start_index = other_servers_start_index;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LanguageServers {
|
||||||
|
fn update_binary_status(
|
||||||
|
&mut self,
|
||||||
|
binary_status: BinaryStatus,
|
||||||
|
message: Option<&str>,
|
||||||
|
name: LanguageServerName,
|
||||||
|
) {
|
||||||
|
let binary_status_message = message.map(SharedString::new);
|
||||||
|
if matches!(
|
||||||
|
binary_status,
|
||||||
|
BinaryStatus::Stopped | BinaryStatus::Failed { .. }
|
||||||
|
) {
|
||||||
|
self.health_statuses.retain(|_, server| server.name != name);
|
||||||
|
}
|
||||||
|
self.binary_statuses.insert(
|
||||||
|
name,
|
||||||
|
LanguageServerBinaryStatus {
|
||||||
|
status: binary_status,
|
||||||
|
message: binary_status_message,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_server_health(
|
||||||
|
&mut self,
|
||||||
|
id: LanguageServerId,
|
||||||
|
health: ServerHealth,
|
||||||
|
message: Option<&str>,
|
||||||
|
name: Option<LanguageServerName>,
|
||||||
|
) {
|
||||||
|
if let Some(state) = self.health_statuses.get_mut(&id) {
|
||||||
|
state.health = Some((message.map(SharedString::new), health));
|
||||||
|
if let Some(name) = name {
|
||||||
|
state.name = name;
|
||||||
|
}
|
||||||
|
} else if let Some(name) = name {
|
||||||
|
self.health_statuses.insert(
|
||||||
|
id,
|
||||||
|
LanguageServerHealthStatus {
|
||||||
|
health: Some((message.map(SharedString::new), health)),
|
||||||
|
name,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum ServerData<'a> {
|
||||||
|
WithHealthCheck(
|
||||||
|
LanguageServerId,
|
||||||
|
&'a LanguageServerHealthStatus,
|
||||||
|
Option<&'a LanguageServerBinaryStatus>,
|
||||||
|
),
|
||||||
|
WithBinaryStatus(&'a LanguageServerName, &'a LanguageServerBinaryStatus),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum LspItem {
|
||||||
|
WithHealthCheck(
|
||||||
|
LanguageServerId,
|
||||||
|
LanguageServerHealthStatus,
|
||||||
|
Option<LanguageServerBinaryStatus>,
|
||||||
|
),
|
||||||
|
WithBinaryStatus(LanguageServerName, LanguageServerBinaryStatus),
|
||||||
|
Header(SharedString),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServerData<'_> {
|
||||||
|
fn name(&self) -> &LanguageServerName {
|
||||||
|
match self {
|
||||||
|
Self::WithHealthCheck(_, state, _) => &state.name,
|
||||||
|
Self::WithBinaryStatus(name, ..) => name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_lsp_item(self) -> LspItem {
|
||||||
|
match self {
|
||||||
|
Self::WithHealthCheck(id, name, status) => {
|
||||||
|
LspItem::WithHealthCheck(id, name.clone(), status.cloned())
|
||||||
|
}
|
||||||
|
Self::WithBinaryStatus(name, status) => {
|
||||||
|
LspItem::WithBinaryStatus(name.clone(), status.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PickerDelegate for LspPickerDelegate {
|
||||||
|
type ListItem = AnyElement;
|
||||||
|
|
||||||
|
fn match_count(&self) -> usize {
|
||||||
|
self.items.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn selected_index(&self) -> usize {
|
||||||
|
self.selected_index
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_selected_index(&mut self, ix: usize, _: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||||
|
self.selected_index = ix;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_matches(
|
||||||
|
&mut self,
|
||||||
|
_: String,
|
||||||
|
_: &mut Window,
|
||||||
|
cx: &mut Context<Picker<Self>>,
|
||||||
|
) -> Task<()> {
|
||||||
|
cx.spawn(async move |lsp_picker, cx| {
|
||||||
|
cx.background_executor()
|
||||||
|
.timer(Duration::from_millis(30))
|
||||||
|
.await;
|
||||||
|
lsp_picker
|
||||||
|
.update(cx, |lsp_picker, cx| {
|
||||||
|
lsp_picker.delegate.regenerate_items(cx);
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
|
||||||
|
Arc::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn confirm(&mut self, _: bool, _: &mut Window, _: &mut Context<Picker<Self>>) {}
|
||||||
|
|
||||||
|
fn dismissed(&mut self, _: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||||
|
cx.emit(DismissEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_match(
|
||||||
|
&self,
|
||||||
|
ix: usize,
|
||||||
|
_: bool,
|
||||||
|
_: &mut Window,
|
||||||
|
cx: &mut Context<Picker<Self>>,
|
||||||
|
) -> Option<Self::ListItem> {
|
||||||
|
let is_other_server = self
|
||||||
|
.other_servers_start_index
|
||||||
|
.map_or(false, |start| ix >= start);
|
||||||
|
let server_binary_status;
|
||||||
|
let server_health;
|
||||||
|
let server_message;
|
||||||
|
let server_id;
|
||||||
|
let server_name;
|
||||||
|
match self.items.get(ix)? {
|
||||||
|
LspItem::WithHealthCheck(
|
||||||
|
language_server_id,
|
||||||
|
language_server_health_status,
|
||||||
|
language_server_binary_status,
|
||||||
|
) => {
|
||||||
|
server_binary_status = language_server_binary_status.as_ref();
|
||||||
|
server_health = language_server_health_status.health();
|
||||||
|
server_message = language_server_health_status.message();
|
||||||
|
server_id = Some(*language_server_id);
|
||||||
|
server_name = language_server_health_status.name.clone();
|
||||||
|
}
|
||||||
|
LspItem::WithBinaryStatus(language_server_name, language_server_binary_status) => {
|
||||||
|
server_binary_status = Some(language_server_binary_status);
|
||||||
|
server_health = None;
|
||||||
|
server_message = language_server_binary_status.message.clone();
|
||||||
|
server_id = None;
|
||||||
|
server_name = language_server_name.clone();
|
||||||
|
}
|
||||||
|
LspItem::Header(header) => {
|
||||||
|
return Some(
|
||||||
|
h_flex()
|
||||||
|
.justify_center()
|
||||||
|
.child(Label::new(header.clone()))
|
||||||
|
.into_any_element(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let workspace = self.state.read(cx).workspace.clone();
|
||||||
|
let lsp_logs = cx.global::<GlobalLogStore>().0.upgrade()?;
|
||||||
|
let lsp_store = self.state.read(cx).lsp_store.upgrade()?;
|
||||||
|
let server_selector = server_id
|
||||||
|
.map(LanguageServerSelector::Id)
|
||||||
|
.unwrap_or_else(|| LanguageServerSelector::Name(server_name.clone()));
|
||||||
|
let can_stop = server_binary_status.is_none_or(|status| {
|
||||||
|
matches!(status.status, BinaryStatus::None | BinaryStatus::Starting)
|
||||||
|
});
|
||||||
|
// TODO currently, Zed remote does not work well with the LSP logs
|
||||||
|
// https://github.com/zed-industries/zed/issues/28557
|
||||||
|
let has_logs = lsp_store.read(cx).as_local().is_some()
|
||||||
|
&& lsp_logs.read(cx).has_server_logs(&server_selector);
|
||||||
|
let status_color = server_binary_status
|
||||||
|
.and_then(|binary_status| match binary_status.status {
|
||||||
|
BinaryStatus::None => None,
|
||||||
|
BinaryStatus::CheckingForUpdate
|
||||||
|
| BinaryStatus::Downloading
|
||||||
|
| BinaryStatus::Starting => Some(Color::Modified),
|
||||||
|
BinaryStatus::Stopping => Some(Color::Disabled),
|
||||||
|
BinaryStatus::Stopped => Some(Color::Disabled),
|
||||||
|
BinaryStatus::Failed { .. } => Some(Color::Error),
|
||||||
|
})
|
||||||
|
.or_else(|| {
|
||||||
|
Some(match server_health? {
|
||||||
|
ServerHealth::Ok => Color::Success,
|
||||||
|
ServerHealth::Warning => Color::Warning,
|
||||||
|
ServerHealth::Error => Color::Error,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.unwrap_or(Color::Success);
|
||||||
|
|
||||||
|
Some(
|
||||||
|
h_flex()
|
||||||
|
.w_full()
|
||||||
|
.justify_between()
|
||||||
|
.gap_2()
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.id("server-status-indicator")
|
||||||
|
.gap_2()
|
||||||
|
.child(Indicator::dot().color(status_color))
|
||||||
|
.child(Label::new(server_name.0.clone()))
|
||||||
|
.when_some(server_message.clone(), |div, server_message| {
|
||||||
|
div.tooltip(move |_, cx| Tooltip::simple(server_message.clone(), cx))
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_1()
|
||||||
|
.when(has_logs, |div| {
|
||||||
|
div.child(
|
||||||
|
IconButton::new("debug-language-server", IconName::MessageBubbles)
|
||||||
|
.icon_size(IconSize::XSmall)
|
||||||
|
.tooltip(|_, cx| Tooltip::simple("Debug Language Server", cx))
|
||||||
|
.on_click({
|
||||||
|
let workspace = workspace.clone();
|
||||||
|
let lsp_logs = lsp_logs.downgrade();
|
||||||
|
let server_selector = server_selector.clone();
|
||||||
|
move |_, window, cx| {
|
||||||
|
lsp_logs
|
||||||
|
.update(cx, |lsp_logs, cx| {
|
||||||
|
lsp_logs.open_server_trace(
|
||||||
|
workspace.clone(),
|
||||||
|
server_selector.clone(),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.when(can_stop, |div| {
|
||||||
|
div.child(
|
||||||
|
IconButton::new("stop-server", IconName::Stop)
|
||||||
|
.icon_size(IconSize::Small)
|
||||||
|
.tooltip(|_, cx| Tooltip::simple("Stop server", cx))
|
||||||
|
.on_click({
|
||||||
|
let lsp_store = lsp_store.downgrade();
|
||||||
|
let server_selector = server_selector.clone();
|
||||||
|
move |_, _, cx| {
|
||||||
|
lsp_store
|
||||||
|
.update(cx, |lsp_store, cx| {
|
||||||
|
lsp_store.stop_language_servers_for_buffers(
|
||||||
|
Vec::new(),
|
||||||
|
HashSet::from_iter([
|
||||||
|
server_selector.clone()
|
||||||
|
]),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.child(
|
||||||
|
IconButton::new("restart-server", IconName::Rerun)
|
||||||
|
.icon_size(IconSize::XSmall)
|
||||||
|
.tooltip(|_, cx| Tooltip::simple("Restart server", cx))
|
||||||
|
.on_click({
|
||||||
|
let state = self.state.clone();
|
||||||
|
let workspace = workspace.clone();
|
||||||
|
let lsp_store = lsp_store.downgrade();
|
||||||
|
let editor_buffers = state
|
||||||
|
.read(cx)
|
||||||
|
.active_editor
|
||||||
|
.as_ref()
|
||||||
|
.map(|active_editor| active_editor.editor_buffers.clone())
|
||||||
|
.unwrap_or_default();
|
||||||
|
let server_selector = server_selector.clone();
|
||||||
|
move |_, _, cx| {
|
||||||
|
if let Some(workspace) = workspace.upgrade() {
|
||||||
|
let project = workspace.read(cx).project().clone();
|
||||||
|
let buffer_store =
|
||||||
|
project.read(cx).buffer_store().clone();
|
||||||
|
let buffers = if is_other_server {
|
||||||
|
let worktree_store =
|
||||||
|
project.read(cx).worktree_store();
|
||||||
|
state
|
||||||
|
.read(cx)
|
||||||
|
.language_servers
|
||||||
|
.servers_per_buffer_abs_path
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(abs_path, servers)| {
|
||||||
|
if servers.values().any(|server| {
|
||||||
|
server.as_ref() == Some(&server_name)
|
||||||
|
}) {
|
||||||
|
worktree_store
|
||||||
|
.read(cx)
|
||||||
|
.find_worktree(abs_path, cx)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter_map(|(worktree, relative_path)| {
|
||||||
|
let entry = worktree
|
||||||
|
.read(cx)
|
||||||
|
.entry_for_path(&relative_path)?;
|
||||||
|
project
|
||||||
|
.read(cx)
|
||||||
|
.path_for_entry(entry.id, cx)
|
||||||
|
})
|
||||||
|
.filter_map(|project_path| {
|
||||||
|
buffer_store
|
||||||
|
.read(cx)
|
||||||
|
.get_by_path(&project_path)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
} else {
|
||||||
|
editor_buffers
|
||||||
|
.iter()
|
||||||
|
.flat_map(|buffer_id| {
|
||||||
|
buffer_store.read(cx).get(*buffer_id)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
};
|
||||||
|
if !buffers.is_empty() {
|
||||||
|
lsp_store
|
||||||
|
.update(cx, |lsp_store, cx| {
|
||||||
|
lsp_store
|
||||||
|
.restart_language_servers_for_buffers(
|
||||||
|
buffers,
|
||||||
|
HashSet::from_iter([
|
||||||
|
server_selector.clone(),
|
||||||
|
]),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.cursor_default()
|
||||||
|
.into_any_element(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_editor(
|
||||||
|
&self,
|
||||||
|
editor: &Entity<Editor>,
|
||||||
|
_: &mut Window,
|
||||||
|
cx: &mut Context<Picker<Self>>,
|
||||||
|
) -> Div {
|
||||||
|
div().child(div().track_focus(&editor.focus_handle(cx)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_footer(&self, _: &mut Window, cx: &mut Context<Picker<Self>>) -> Option<AnyElement> {
|
||||||
|
if self.items.is_empty() {
|
||||||
|
Some(
|
||||||
|
h_flex()
|
||||||
|
.w_full()
|
||||||
|
.border_color(cx.theme().colors().border_variant)
|
||||||
|
.child(
|
||||||
|
Button::new("stop-all-servers", "Stop all servers")
|
||||||
|
.disabled(true)
|
||||||
|
.on_click(move |_, _, _| {})
|
||||||
|
.full_width(),
|
||||||
|
)
|
||||||
|
.into_any_element(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
let lsp_store = self.state.read(cx).lsp_store.clone();
|
||||||
|
Some(
|
||||||
|
h_flex()
|
||||||
|
.w_full()
|
||||||
|
.border_color(cx.theme().colors().border_variant)
|
||||||
|
.child(
|
||||||
|
Button::new("stop-all-servers", "Stop all servers")
|
||||||
|
.on_click({
|
||||||
|
move |_, _, cx| {
|
||||||
|
lsp_store
|
||||||
|
.update(cx, |lsp_store, cx| {
|
||||||
|
lsp_store.stop_all_language_servers(cx);
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.full_width(),
|
||||||
|
)
|
||||||
|
.into_any_element(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn separators_after_indices(&self) -> Vec<usize> {
|
||||||
|
if self.items.is_empty() {
|
||||||
|
Vec::new()
|
||||||
|
} else {
|
||||||
|
vec![self.items.len() - 1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO kb keyboard story
|
||||||
|
impl LspTool {
|
||||||
|
pub fn new(workspace: &Workspace, window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||||
|
let settings_subscription =
|
||||||
|
cx.observe_global_in::<SettingsStore>(window, move |lsp_tool, window, cx| {
|
||||||
|
if ProjectSettings::get_global(cx).global_lsp_settings.button {
|
||||||
|
if lsp_tool.lsp_picker.is_none() {
|
||||||
|
lsp_tool.lsp_picker =
|
||||||
|
Some(Self::new_lsp_picker(lsp_tool.state.clone(), window, cx));
|
||||||
|
cx.notify();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if lsp_tool.lsp_picker.take().is_some() {
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let lsp_store = workspace.project().read(cx).lsp_store();
|
||||||
|
let lsp_store_subscription =
|
||||||
|
cx.subscribe_in(&lsp_store, window, |lsp_tool, _, e, window, cx| {
|
||||||
|
lsp_tool.on_lsp_store_event(e, window, cx)
|
||||||
|
});
|
||||||
|
|
||||||
|
let state = cx.new(|_| PickerState {
|
||||||
|
workspace: workspace.weak_handle(),
|
||||||
|
lsp_store: lsp_store.downgrade(),
|
||||||
|
active_editor: None,
|
||||||
|
language_servers: LanguageServers::default(),
|
||||||
|
});
|
||||||
|
|
||||||
|
Self {
|
||||||
|
state,
|
||||||
|
lsp_picker: None,
|
||||||
|
_subscriptions: vec![settings_subscription, lsp_store_subscription],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_lsp_store_event(
|
||||||
|
&mut self,
|
||||||
|
e: &LspStoreEvent,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
let Some(lsp_picker) = self.lsp_picker.clone() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let mut updated = false;
|
||||||
|
|
||||||
|
match e {
|
||||||
|
LspStoreEvent::LanguageServerUpdate {
|
||||||
|
language_server_id,
|
||||||
|
name,
|
||||||
|
message: proto::update_language_server::Variant::StatusUpdate(status_update),
|
||||||
|
} => match &status_update.status {
|
||||||
|
Some(proto::status_update::Status::Binary(binary_status)) => {
|
||||||
|
let Some(name) = name.as_ref() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if let Some(binary_status) = proto::ServerBinaryStatus::from_i32(*binary_status)
|
||||||
|
{
|
||||||
|
let binary_status = match binary_status {
|
||||||
|
proto::ServerBinaryStatus::None => BinaryStatus::None,
|
||||||
|
proto::ServerBinaryStatus::CheckingForUpdate => {
|
||||||
|
BinaryStatus::CheckingForUpdate
|
||||||
|
}
|
||||||
|
proto::ServerBinaryStatus::Downloading => BinaryStatus::Downloading,
|
||||||
|
proto::ServerBinaryStatus::Starting => BinaryStatus::Starting,
|
||||||
|
proto::ServerBinaryStatus::Stopping => BinaryStatus::Stopping,
|
||||||
|
proto::ServerBinaryStatus::Stopped => BinaryStatus::Stopped,
|
||||||
|
proto::ServerBinaryStatus::Failed => {
|
||||||
|
let Some(error) = status_update.message.clone() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
BinaryStatus::Failed { error }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.state.update(cx, |state, _| {
|
||||||
|
state.language_servers.update_binary_status(
|
||||||
|
binary_status,
|
||||||
|
status_update.message.as_deref(),
|
||||||
|
name.clone(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
updated = true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Some(proto::status_update::Status::Health(health_status)) => {
|
||||||
|
if let Some(health) = proto::ServerHealth::from_i32(*health_status) {
|
||||||
|
let health = match health {
|
||||||
|
proto::ServerHealth::Ok => ServerHealth::Ok,
|
||||||
|
proto::ServerHealth::Warning => ServerHealth::Warning,
|
||||||
|
proto::ServerHealth::Error => ServerHealth::Error,
|
||||||
|
};
|
||||||
|
self.state.update(cx, |state, _| {
|
||||||
|
state.language_servers.update_server_health(
|
||||||
|
*language_server_id,
|
||||||
|
health,
|
||||||
|
status_update.message.as_deref(),
|
||||||
|
name.clone(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
},
|
||||||
|
LspStoreEvent::LanguageServerUpdate {
|
||||||
|
language_server_id,
|
||||||
|
name,
|
||||||
|
message: proto::update_language_server::Variant::RegisteredForBuffer(update),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
self.state.update(cx, |state, _| {
|
||||||
|
state
|
||||||
|
.language_servers
|
||||||
|
.servers_per_buffer_abs_path
|
||||||
|
.entry(PathBuf::from(&update.buffer_abs_path))
|
||||||
|
.or_default()
|
||||||
|
.insert(*language_server_id, name.clone());
|
||||||
|
});
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
|
||||||
|
if updated {
|
||||||
|
lsp_picker.update(cx, |lsp_picker, cx| {
|
||||||
|
lsp_picker.refresh(window, cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_lsp_picker(
|
||||||
|
state: Entity<PickerState>,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> Entity<Picker<LspPickerDelegate>> {
|
||||||
|
cx.new(|cx| {
|
||||||
|
let mut delegate = LspPickerDelegate {
|
||||||
|
selected_index: 0,
|
||||||
|
other_servers_start_index: None,
|
||||||
|
items: Vec::new(),
|
||||||
|
state,
|
||||||
|
};
|
||||||
|
delegate.regenerate_items(cx);
|
||||||
|
Picker::list(delegate, window, cx)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StatusItemView for LspTool {
|
||||||
|
fn set_active_pane_item(
|
||||||
|
&mut self,
|
||||||
|
active_pane_item: Option<&dyn workspace::ItemHandle>,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
if ProjectSettings::get_global(cx).global_lsp_settings.button {
|
||||||
|
if let Some(editor) = active_pane_item.and_then(|item| item.downcast::<Editor>()) {
|
||||||
|
if Some(&editor)
|
||||||
|
!= self
|
||||||
|
.state
|
||||||
|
.read(cx)
|
||||||
|
.active_editor
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|active_editor| active_editor.editor.upgrade())
|
||||||
|
.as_ref()
|
||||||
|
{
|
||||||
|
let editor_buffers =
|
||||||
|
HashSet::from_iter(editor.read(cx).buffer().read(cx).excerpt_buffer_ids());
|
||||||
|
let _editor_subscription = cx.subscribe_in(
|
||||||
|
&editor,
|
||||||
|
window,
|
||||||
|
|lsp_tool, _, e: &EditorEvent, window, cx| match e {
|
||||||
|
EditorEvent::ExcerptsAdded { buffer, .. } => {
|
||||||
|
lsp_tool.state.update(cx, |state, cx| {
|
||||||
|
if let Some(active_editor) = state.active_editor.as_mut() {
|
||||||
|
let buffer_id = buffer.read(cx).remote_id();
|
||||||
|
if active_editor.editor_buffers.insert(buffer_id) {
|
||||||
|
if let Some(picker) = &lsp_tool.lsp_picker {
|
||||||
|
picker.update(cx, |picker, cx| {
|
||||||
|
picker.refresh(window, cx)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
EditorEvent::ExcerptsRemoved {
|
||||||
|
removed_buffer_ids, ..
|
||||||
|
} => {
|
||||||
|
lsp_tool.state.update(cx, |state, cx| {
|
||||||
|
if let Some(active_editor) = state.active_editor.as_mut() {
|
||||||
|
let mut removed = false;
|
||||||
|
for id in removed_buffer_ids {
|
||||||
|
active_editor.editor_buffers.retain(|buffer_id| {
|
||||||
|
let retain = buffer_id != id;
|
||||||
|
removed |= !retain;
|
||||||
|
retain
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if removed {
|
||||||
|
if let Some(picker) = &lsp_tool.lsp_picker {
|
||||||
|
picker.update(cx, |picker, cx| {
|
||||||
|
picker.refresh(window, cx)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
self.state.update(cx, |state, _| {
|
||||||
|
state.active_editor = Some(ActiveEditor {
|
||||||
|
editor: editor.downgrade(),
|
||||||
|
_editor_subscription,
|
||||||
|
editor_buffers,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let lsp_picker = Self::new_lsp_picker(self.state.clone(), window, cx);
|
||||||
|
self.lsp_picker = Some(lsp_picker.clone());
|
||||||
|
lsp_picker.update(cx, |lsp_picker, cx| lsp_picker.refresh(window, cx));
|
||||||
|
}
|
||||||
|
} else if self.state.read(cx).active_editor.is_some() {
|
||||||
|
self.state.update(cx, |state, _| {
|
||||||
|
state.active_editor = None;
|
||||||
|
});
|
||||||
|
if let Some(lsp_picker) = self.lsp_picker.as_ref() {
|
||||||
|
lsp_picker.update(cx, |lsp_picker, cx| {
|
||||||
|
lsp_picker.refresh(window, cx);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else if self.state.read(cx).active_editor.is_some() {
|
||||||
|
self.state.update(cx, |state, _| {
|
||||||
|
state.active_editor = None;
|
||||||
|
});
|
||||||
|
if let Some(lsp_picker) = self.lsp_picker.as_ref() {
|
||||||
|
lsp_picker.update(cx, |lsp_picker, cx| {
|
||||||
|
lsp_picker.refresh(window, cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for LspTool {
|
||||||
|
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl ui::IntoElement {
|
||||||
|
let Some(lsp_picker) = self.lsp_picker.clone() else {
|
||||||
|
return div();
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut has_errors = false;
|
||||||
|
let mut has_warnings = false;
|
||||||
|
let mut has_other_notifications = false;
|
||||||
|
let state = self.state.read(cx);
|
||||||
|
for server in state.language_servers.health_statuses.values() {
|
||||||
|
if let Some(binary_status) = &state.language_servers.binary_statuses.get(&server.name) {
|
||||||
|
has_errors |= matches!(binary_status.status, BinaryStatus::Failed { .. });
|
||||||
|
has_other_notifications |= binary_status.message.is_some();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some((message, health)) = &server.health {
|
||||||
|
has_other_notifications |= message.is_some();
|
||||||
|
match health {
|
||||||
|
ServerHealth::Ok => {}
|
||||||
|
ServerHealth::Warning => has_warnings = true,
|
||||||
|
ServerHealth::Error => has_errors = true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let indicator = if has_errors {
|
||||||
|
Some(Indicator::dot().color(Color::Error))
|
||||||
|
} else if has_warnings {
|
||||||
|
Some(Indicator::dot().color(Color::Warning))
|
||||||
|
} else if has_other_notifications {
|
||||||
|
Some(Indicator::dot().color(Color::Modified))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
div().child(
|
||||||
|
PickerPopoverMenu::new(
|
||||||
|
lsp_picker.clone(),
|
||||||
|
IconButton::new("zed-lsp-tool-button", IconName::Bolt)
|
||||||
|
.when_some(indicator, IconButton::indicator)
|
||||||
|
.shape(IconButtonShape::Square)
|
||||||
|
.icon_size(IconSize::XSmall)
|
||||||
|
.indicator_border_color(Some(cx.theme().colors().status_bar_background)),
|
||||||
|
move |_, cx| Tooltip::simple("Language servers", cx),
|
||||||
|
Corner::BottomRight,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.render(window, cx),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -108,6 +108,12 @@ pub struct LanguageServer {
|
||||||
root_uri: Url,
|
root_uri: Url,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum LanguageServerSelector {
|
||||||
|
Id(LanguageServerId),
|
||||||
|
Name(LanguageServerName),
|
||||||
|
}
|
||||||
|
|
||||||
/// Identifies a running language server.
|
/// Identifies a running language server.
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
|
|
|
@ -205,6 +205,7 @@ pub trait PickerDelegate: Sized + 'static {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Picker<Self>>,
|
cx: &mut Context<Picker<Self>>,
|
||||||
) -> Option<Self::ListItem>;
|
) -> Option<Self::ListItem>;
|
||||||
|
|
||||||
fn render_header(
|
fn render_header(
|
||||||
&self,
|
&self,
|
||||||
_window: &mut Window,
|
_window: &mut Window,
|
||||||
|
@ -212,6 +213,7 @@ pub trait PickerDelegate: Sized + 'static {
|
||||||
) -> Option<AnyElement> {
|
) -> Option<AnyElement> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_footer(
|
fn render_footer(
|
||||||
&self,
|
&self,
|
||||||
_window: &mut Window,
|
_window: &mut Window,
|
||||||
|
|
|
@ -783,7 +783,7 @@ impl BufferStore {
|
||||||
project_path: ProjectPath,
|
project_path: ProjectPath,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Task<Result<Entity<Buffer>>> {
|
) -> 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 {
|
cx.emit(BufferStoreEvent::BufferOpened {
|
||||||
buffer: buffer.clone(),
|
buffer: buffer.clone(),
|
||||||
project_path,
|
project_path,
|
||||||
|
@ -946,7 +946,7 @@ impl BufferStore {
|
||||||
self.path_to_buffer_id.get(project_path)
|
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| {
|
self.path_to_buffer_id.get(path).and_then(|buffer_id| {
|
||||||
let buffer = self.get(*buffer_id);
|
let buffer = self.get(*buffer_id);
|
||||||
buffer
|
buffer
|
||||||
|
|
|
@ -275,7 +275,7 @@ impl BreakpointStore {
|
||||||
.context("Could not resolve provided abs path")?;
|
.context("Could not resolve provided abs path")?;
|
||||||
let buffer = this
|
let buffer = this
|
||||||
.update(&mut cx, |this, cx| {
|
.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")?;
|
.context("Could not find buffer for a given path")?;
|
||||||
let breakpoint = message
|
let breakpoint = message
|
||||||
|
|
|
@ -3322,7 +3322,7 @@ impl Repository {
|
||||||
let Some(project_path) = self.repo_path_to_project_path(path, cx) else {
|
let Some(project_path) = self.repo_path_to_project_path(path, cx) else {
|
||||||
continue;
|
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
|
if buffer
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.file()
|
.file()
|
||||||
|
@ -3389,7 +3389,7 @@ impl Repository {
|
||||||
let Some(project_path) = self.repo_path_to_project_path(path, cx) else {
|
let Some(project_path) = self.repo_path_to_project_path(path, cx) else {
|
||||||
continue;
|
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
|
if buffer
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.file()
|
.file()
|
||||||
|
@ -3749,7 +3749,7 @@ impl Repository {
|
||||||
let buffer_id = git_store
|
let buffer_id = git_store
|
||||||
.buffer_store
|
.buffer_store
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.get_by_path(&project_path?, cx)?
|
.get_by_path(&project_path?)?
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.remote_id();
|
.remote_id();
|
||||||
let diff_state = git_store.diffs.get(&buffer_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 ::serde::{Deserialize, Serialize};
|
||||||
use anyhow::Context as _;
|
use anyhow::Context as _;
|
||||||
use gpui::{App, Entity, SharedString, Task, WeakEntity};
|
use gpui::{App, Entity, Task, WeakEntity};
|
||||||
use language::{LanguageServerStatusUpdate, ServerHealth};
|
use language::ServerHealth;
|
||||||
use lsp::LanguageServer;
|
use lsp::LanguageServer;
|
||||||
use rpc::proto;
|
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 RUST_ANALYZER_NAME: &str = "rust-analyzer";
|
||||||
pub const CARGO_DIAGNOSTICS_SOURCE_NAME: &str = "rustc";
|
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, _>({
|
.on_notification::<ServerStatus, _>({
|
||||||
let name = name.clone();
|
let name = name.clone();
|
||||||
move |params, cx| {
|
move |params, cx| {
|
||||||
let status = params.message;
|
let message = params.message;
|
||||||
let log_message =
|
let log_message = message.as_ref().map(|message| {
|
||||||
format!("Language server {name} (id {server_id}) status update: {status:?}");
|
format!("Language server {name} (id {server_id}) status update: {message}")
|
||||||
match ¶ms.health {
|
});
|
||||||
ServerHealth::Ok => log::info!("{log_message}"),
|
let status = match ¶ms.health {
|
||||||
ServerHealth::Warning => log::warn!("{log_message}"),
|
ServerHealth::Ok => {
|
||||||
ServerHealth::Error => log::error!("{log_message}"),
|
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
|
lsp_store
|
||||||
.update(cx, |lsp_store, _| {
|
.update(cx, |_, cx| {
|
||||||
lsp_store.languages.update_lsp_status(
|
cx.emit(LspStoreEvent::LanguageServerUpdate {
|
||||||
name.clone(),
|
language_server_id: server_id,
|
||||||
LanguageServerStatusUpdate::Health(
|
name: Some(name.clone()),
|
||||||
params.health,
|
message: proto::update_language_server::Variant::StatusUpdate(
|
||||||
status.map(SharedString::from),
|
proto::StatusUpdate {
|
||||||
|
message,
|
||||||
|
status: Some(proto::status_update::Status::Health(
|
||||||
|
status as i32,
|
||||||
|
)),
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
});
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,6 +74,7 @@ impl LanguageServerTreeNode {
|
||||||
pub(crate) fn server_id(&self) -> Option<LanguageServerId> {
|
pub(crate) fn server_id(&self) -> Option<LanguageServerId> {
|
||||||
self.0.upgrade()?.id.get().copied()
|
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.
|
/// 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.
|
/// May return None if the node no longer belongs to the server tree it was created in.
|
||||||
pub(crate) fn server_id_or_init(
|
pub(crate) fn server_id_or_init(
|
||||||
|
@ -87,6 +88,11 @@ impl LanguageServerTreeNode {
|
||||||
.get_or_init(|| init(LaunchDisposition::from(&*this))),
|
.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 {
|
impl From<Weak<InnerTreeNode>> for LanguageServerTreeNode {
|
||||||
|
|
|
@ -81,7 +81,7 @@ use language::{
|
||||||
};
|
};
|
||||||
use lsp::{
|
use lsp::{
|
||||||
CodeActionKind, CompletionContext, CompletionItemKind, DocumentHighlightKind, InsertTextMode,
|
CodeActionKind, CompletionContext, CompletionItemKind, DocumentHighlightKind, InsertTextMode,
|
||||||
LanguageServerId, LanguageServerName, MessageActionItem,
|
LanguageServerId, LanguageServerName, LanguageServerSelector, MessageActionItem,
|
||||||
};
|
};
|
||||||
use lsp_command::*;
|
use lsp_command::*;
|
||||||
use lsp_store::{CompletionDocumentation, LspFormatTarget, OpenLspBufferHandle};
|
use lsp_store::{CompletionDocumentation, LspFormatTarget, OpenLspBufferHandle};
|
||||||
|
@ -251,6 +251,7 @@ enum BufferOrderedMessage {
|
||||||
LanguageServerUpdate {
|
LanguageServerUpdate {
|
||||||
language_server_id: LanguageServerId,
|
language_server_id: LanguageServerId,
|
||||||
message: proto::update_language_server::Variant,
|
message: proto::update_language_server::Variant,
|
||||||
|
name: Option<LanguageServerName>,
|
||||||
},
|
},
|
||||||
Resync,
|
Resync,
|
||||||
}
|
}
|
||||||
|
@ -1790,7 +1791,7 @@ impl Project {
|
||||||
pub fn has_open_buffer(&self, path: impl Into<ProjectPath>, cx: &App) -> bool {
|
pub fn has_open_buffer(&self, path: impl Into<ProjectPath>, cx: &App) -> bool {
|
||||||
self.buffer_store
|
self.buffer_store
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.get_by_path(&path.into(), cx)
|
.get_by_path(&path.into())
|
||||||
.is_some()
|
.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2500,7 +2501,7 @@ impl Project {
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> OpenLspBufferHandle {
|
) -> OpenLspBufferHandle {
|
||||||
self.lsp_store.update(cx, |lsp_store, cx| {
|
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>> {
|
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<()> {
|
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(
|
async fn send_buffer_ordered_messages(
|
||||||
this: WeakEntity<Self>,
|
project: WeakEntity<Self>,
|
||||||
rx: UnboundedReceiver<BufferOrderedMessage>,
|
rx: UnboundedReceiver<BufferOrderedMessage>,
|
||||||
cx: &mut AsyncApp,
|
cx: &mut AsyncApp,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
@ -2677,7 +2678,7 @@ impl Project {
|
||||||
let mut changes = rx.ready_chunks(MAX_BATCH_SIZE);
|
let mut changes = rx.ready_chunks(MAX_BATCH_SIZE);
|
||||||
|
|
||||||
while let Some(changes) = changes.next().await {
|
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 {
|
for change in changes {
|
||||||
match change {
|
match change {
|
||||||
|
@ -2697,7 +2698,7 @@ impl Project {
|
||||||
|
|
||||||
BufferOrderedMessage::Resync => {
|
BufferOrderedMessage::Resync => {
|
||||||
operations_by_buffer_id.clear();
|
operations_by_buffer_id.clear();
|
||||||
if this
|
if project
|
||||||
.update(cx, |this, cx| this.synchronize_remote_buffers(cx))?
|
.update(cx, |this, cx| this.synchronize_remote_buffers(cx))?
|
||||||
.await
|
.await
|
||||||
.is_ok()
|
.is_ok()
|
||||||
|
@ -2709,9 +2710,10 @@ impl Project {
|
||||||
BufferOrderedMessage::LanguageServerUpdate {
|
BufferOrderedMessage::LanguageServerUpdate {
|
||||||
language_server_id,
|
language_server_id,
|
||||||
message,
|
message,
|
||||||
|
name,
|
||||||
} => {
|
} => {
|
||||||
flush_operations(
|
flush_operations(
|
||||||
&this,
|
&project,
|
||||||
&mut operations_by_buffer_id,
|
&mut operations_by_buffer_id,
|
||||||
&mut needs_resync_with_host,
|
&mut needs_resync_with_host,
|
||||||
is_local,
|
is_local,
|
||||||
|
@ -2719,12 +2721,14 @@ impl Project {
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
this.read_with(cx, |this, _| {
|
project.read_with(cx, |project, _| {
|
||||||
if let Some(project_id) = this.remote_id() {
|
if let Some(project_id) = project.remote_id() {
|
||||||
this.client
|
project
|
||||||
|
.client
|
||||||
.send(proto::UpdateLanguageServer {
|
.send(proto::UpdateLanguageServer {
|
||||||
project_id,
|
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),
|
variant: Some(message),
|
||||||
})
|
})
|
||||||
.log_err();
|
.log_err();
|
||||||
|
@ -2735,7 +2739,7 @@ impl Project {
|
||||||
}
|
}
|
||||||
|
|
||||||
flush_operations(
|
flush_operations(
|
||||||
&this,
|
&project,
|
||||||
&mut operations_by_buffer_id,
|
&mut operations_by_buffer_id,
|
||||||
&mut needs_resync_with_host,
|
&mut needs_resync_with_host,
|
||||||
is_local,
|
is_local,
|
||||||
|
@ -2856,12 +2860,14 @@ impl Project {
|
||||||
LspStoreEvent::LanguageServerUpdate {
|
LspStoreEvent::LanguageServerUpdate {
|
||||||
language_server_id,
|
language_server_id,
|
||||||
message,
|
message,
|
||||||
|
name,
|
||||||
} => {
|
} => {
|
||||||
if self.is_local() {
|
if self.is_local() {
|
||||||
self.enqueue_buffer_ordered_message(
|
self.enqueue_buffer_ordered_message(
|
||||||
BufferOrderedMessage::LanguageServerUpdate {
|
BufferOrderedMessage::LanguageServerUpdate {
|
||||||
language_server_id: *language_server_id,
|
language_server_id: *language_server_id,
|
||||||
message: message.clone(),
|
message: message.clone(),
|
||||||
|
name: name.clone(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.ok();
|
.ok();
|
||||||
|
@ -3140,20 +3146,22 @@ impl Project {
|
||||||
pub fn restart_language_servers_for_buffers(
|
pub fn restart_language_servers_for_buffers(
|
||||||
&mut self,
|
&mut self,
|
||||||
buffers: Vec<Entity<Buffer>>,
|
buffers: Vec<Entity<Buffer>>,
|
||||||
|
only_restart_servers: HashSet<LanguageServerSelector>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
self.lsp_store.update(cx, |lsp_store, cx| {
|
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(
|
pub fn stop_language_servers_for_buffers(
|
||||||
&mut self,
|
&mut self,
|
||||||
buffers: Vec<Entity<Buffer>>,
|
buffers: Vec<Entity<Buffer>>,
|
||||||
|
also_restart_servers: HashSet<LanguageServerSelector>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
self.lsp_store.update(cx, |lsp_store, cx| {
|
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)]
|
#[serde(default)]
|
||||||
pub lsp: HashMap<LanguageServerName, LspSettings>,
|
pub lsp: HashMap<LanguageServerName, LspSettings>,
|
||||||
|
|
||||||
|
/// Common language server settings.
|
||||||
|
#[serde(default)]
|
||||||
|
pub global_lsp_settings: GlobalLspSettings,
|
||||||
|
|
||||||
/// Configuration for Debugger-related features
|
/// Configuration for Debugger-related features
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub dap: HashMap<DebugAdapterName, DapSettings>,
|
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 {
|
impl ContextServerSettings {
|
||||||
pub fn default_extension() -> Self {
|
pub fn default_extension() -> Self {
|
||||||
Self::Extension {
|
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)]
|
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct CargoDiagnosticsSettings {
|
pub struct CargoDiagnosticsSettings {
|
||||||
/// When enabled, Zed disables rust-analyzer's check on save and starts to query
|
/// 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.update(cx, |project, cx| {
|
||||||
project.restart_language_servers_for_buffers(
|
project.restart_language_servers_for_buffers(
|
||||||
vec![rust_buffer.clone(), json_buffer.clone()],
|
vec![rust_buffer.clone(), json_buffer.clone()],
|
||||||
|
HashSet::default(),
|
||||||
cx,
|
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.
|
// Restart the server before the diagnostics finish updating.
|
||||||
project.update(cx, |project, cx| {
|
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);
|
let mut events = cx.events(&project);
|
||||||
|
|
||||||
// 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::LanguageServerRemoved(LanguageServerId(0))
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
events.next().await.unwrap(),
|
events.next().await.unwrap(),
|
||||||
Event::LanguageServerAdded(
|
Event::LanguageServerAdded(
|
||||||
|
@ -1820,7 +1825,7 @@ async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAp
|
||||||
});
|
});
|
||||||
|
|
||||||
project.update(cx, |project, cx| {
|
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.
|
// 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();
|
cx.executor().run_until_parked();
|
||||||
project.update(cx, |project, cx| {
|
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();
|
let mut fake_server = fake_servers.next().await.unwrap();
|
||||||
|
|
|
@ -534,12 +534,15 @@ message DiagnosticSummary {
|
||||||
message UpdateLanguageServer {
|
message UpdateLanguageServer {
|
||||||
uint64 project_id = 1;
|
uint64 project_id = 1;
|
||||||
uint64 language_server_id = 2;
|
uint64 language_server_id = 2;
|
||||||
|
optional string server_name = 8;
|
||||||
oneof variant {
|
oneof variant {
|
||||||
LspWorkStart work_start = 3;
|
LspWorkStart work_start = 3;
|
||||||
LspWorkProgress work_progress = 4;
|
LspWorkProgress work_progress = 4;
|
||||||
LspWorkEnd work_end = 5;
|
LspWorkEnd work_end = 5;
|
||||||
LspDiskBasedDiagnosticsUpdating disk_based_diagnostics_updating = 6;
|
LspDiskBasedDiagnosticsUpdating disk_based_diagnostics_updating = 6;
|
||||||
LspDiskBasedDiagnosticsUpdated disk_based_diagnostics_updated = 7;
|
LspDiskBasedDiagnosticsUpdated disk_based_diagnostics_updated = 7;
|
||||||
|
StatusUpdate status_update = 9;
|
||||||
|
RegisteredForBuffer registered_for_buffer = 10;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -566,6 +569,34 @@ message LspDiskBasedDiagnosticsUpdating {}
|
||||||
|
|
||||||
message LspDiskBasedDiagnosticsUpdated {}
|
message LspDiskBasedDiagnosticsUpdated {}
|
||||||
|
|
||||||
|
message StatusUpdate {
|
||||||
|
optional string message = 1;
|
||||||
|
oneof status {
|
||||||
|
ServerBinaryStatus binary = 2;
|
||||||
|
ServerHealth health = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ServerHealth {
|
||||||
|
OK = 0;
|
||||||
|
WARNING = 1;
|
||||||
|
ERROR = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ServerBinaryStatus {
|
||||||
|
NONE = 0;
|
||||||
|
CHECKING_FOR_UPDATE = 1;
|
||||||
|
DOWNLOADING = 2;
|
||||||
|
STARTING = 3;
|
||||||
|
STOPPING = 4;
|
||||||
|
STOPPED = 5;
|
||||||
|
FAILED = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RegisteredForBuffer {
|
||||||
|
string buffer_abs_path = 1;
|
||||||
|
}
|
||||||
|
|
||||||
message LanguageServerLog {
|
message LanguageServerLog {
|
||||||
uint64 project_id = 1;
|
uint64 project_id = 1;
|
||||||
uint64 language_server_id = 2;
|
uint64 language_server_id = 2;
|
||||||
|
@ -593,6 +624,7 @@ message ApplyCodeActionKindResponse {
|
||||||
message RegisterBufferWithLanguageServers {
|
message RegisterBufferWithLanguageServers {
|
||||||
uint64 project_id = 1;
|
uint64 project_id = 1;
|
||||||
uint64 buffer_id = 2;
|
uint64 buffer_id = 2;
|
||||||
|
repeated LanguageServerSelector only_servers = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum FormatTrigger {
|
enum FormatTrigger {
|
||||||
|
@ -730,14 +762,25 @@ message MultiLspQuery {
|
||||||
|
|
||||||
message AllLanguageServers {}
|
message AllLanguageServers {}
|
||||||
|
|
||||||
|
message LanguageServerSelector {
|
||||||
|
oneof selector {
|
||||||
|
uint64 server_id = 1;
|
||||||
|
string name = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
message RestartLanguageServers {
|
message RestartLanguageServers {
|
||||||
uint64 project_id = 1;
|
uint64 project_id = 1;
|
||||||
repeated uint64 buffer_ids = 2;
|
repeated uint64 buffer_ids = 2;
|
||||||
|
repeated LanguageServerSelector only_servers = 3;
|
||||||
|
bool all = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message StopLanguageServers {
|
message StopLanguageServers {
|
||||||
uint64 project_id = 1;
|
uint64 project_id = 1;
|
||||||
repeated uint64 buffer_ids = 2;
|
repeated uint64 buffer_ids = 2;
|
||||||
|
repeated LanguageServerSelector also_servers = 3;
|
||||||
|
bool all = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message MultiLspQueryResponse {
|
message MultiLspQueryResponse {
|
||||||
|
|
|
@ -301,11 +301,13 @@ impl HeadlessProject {
|
||||||
match event {
|
match event {
|
||||||
LspStoreEvent::LanguageServerUpdate {
|
LspStoreEvent::LanguageServerUpdate {
|
||||||
language_server_id,
|
language_server_id,
|
||||||
|
name,
|
||||||
message,
|
message,
|
||||||
} => {
|
} => {
|
||||||
self.session
|
self.session
|
||||||
.send(proto::UpdateLanguageServer {
|
.send(proto::UpdateLanguageServer {
|
||||||
project_id: SSH_PROJECT_ID,
|
project_id: SSH_PROJECT_ID,
|
||||||
|
server_name: name.as_ref().map(|name| name.to_string()),
|
||||||
language_server_id: language_server_id.to_proto(),
|
language_server_id: language_server_id.to_proto(),
|
||||||
variant: Some(message.clone()),
|
variant: Some(message.clone()),
|
||||||
})
|
})
|
||||||
|
|
|
@ -5617,7 +5617,6 @@ impl Workspace {
|
||||||
} else if let Some((notification_id, _)) = self.notifications.pop() {
|
} else if let Some((notification_id, _)) = self.notifications.pop() {
|
||||||
dismiss_app_notification(¬ification_id, cx);
|
dismiss_app_notification(¬ification_id, cx);
|
||||||
} else {
|
} else {
|
||||||
cx.emit(Event::ClearActivityIndicator);
|
|
||||||
cx.propagate();
|
cx.propagate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ use gpui::{
|
||||||
px, retain_all,
|
px, retain_all,
|
||||||
};
|
};
|
||||||
use image_viewer::ImageInfo;
|
use image_viewer::ImageInfo;
|
||||||
|
use language_tools::lsp_tool::LspTool;
|
||||||
use migrate::{MigrationBanner, MigrationEvent, MigrationNotification, MigrationType};
|
use migrate::{MigrationBanner, MigrationEvent, MigrationNotification, MigrationType};
|
||||||
use migrator::{migrate_keymap, migrate_settings};
|
use migrator::{migrate_keymap, migrate_settings};
|
||||||
pub use open_listener::*;
|
pub use open_listener::*;
|
||||||
|
@ -295,7 +296,7 @@ pub fn initialize_workspace(
|
||||||
|
|
||||||
let popover_menu_handle = PopoverMenuHandle::default();
|
let popover_menu_handle = PopoverMenuHandle::default();
|
||||||
|
|
||||||
let inline_completion_button = cx.new(|cx| {
|
let edit_prediction_button = cx.new(|cx| {
|
||||||
inline_completion_button::InlineCompletionButton::new(
|
inline_completion_button::InlineCompletionButton::new(
|
||||||
app_state.fs.clone(),
|
app_state.fs.clone(),
|
||||||
app_state.user_store.clone(),
|
app_state.user_store.clone(),
|
||||||
|
@ -315,7 +316,7 @@ pub fn initialize_workspace(
|
||||||
cx.new(|cx| diagnostics::items::DiagnosticIndicator::new(workspace, cx));
|
cx.new(|cx| diagnostics::items::DiagnosticIndicator::new(workspace, cx));
|
||||||
let activity_indicator = activity_indicator::ActivityIndicator::new(
|
let activity_indicator = activity_indicator::ActivityIndicator::new(
|
||||||
workspace,
|
workspace,
|
||||||
app_state.languages.clone(),
|
workspace.project().read(cx).languages().clone(),
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
@ -325,13 +326,16 @@ pub fn initialize_workspace(
|
||||||
cx.new(|cx| toolchain_selector::ActiveToolchain::new(workspace, window, cx));
|
cx.new(|cx| toolchain_selector::ActiveToolchain::new(workspace, window, cx));
|
||||||
let vim_mode_indicator = cx.new(|cx| vim::ModeIndicator::new(window, cx));
|
let vim_mode_indicator = cx.new(|cx| vim::ModeIndicator::new(window, cx));
|
||||||
let image_info = cx.new(|_cx| ImageInfo::new(workspace));
|
let image_info = cx.new(|_cx| ImageInfo::new(workspace));
|
||||||
|
let lsp_tool = cx.new(|cx| LspTool::new(workspace, window, cx));
|
||||||
|
|
||||||
let cursor_position =
|
let cursor_position =
|
||||||
cx.new(|_| go_to_line::cursor_position::CursorPosition::new(workspace));
|
cx.new(|_| go_to_line::cursor_position::CursorPosition::new(workspace));
|
||||||
workspace.status_bar().update(cx, |status_bar, cx| {
|
workspace.status_bar().update(cx, |status_bar, cx| {
|
||||||
status_bar.add_left_item(search_button, window, cx);
|
status_bar.add_left_item(search_button, window, cx);
|
||||||
status_bar.add_left_item(diagnostic_summary, window, cx);
|
status_bar.add_left_item(diagnostic_summary, window, cx);
|
||||||
|
status_bar.add_left_item(lsp_tool, window, cx);
|
||||||
status_bar.add_left_item(activity_indicator, window, cx);
|
status_bar.add_left_item(activity_indicator, window, cx);
|
||||||
status_bar.add_right_item(inline_completion_button, window, cx);
|
status_bar.add_right_item(edit_prediction_button, window, cx);
|
||||||
status_bar.add_right_item(active_buffer_language, window, cx);
|
status_bar.add_right_item(active_buffer_language, window, cx);
|
||||||
status_bar.add_right_item(active_toolchain_language, window, cx);
|
status_bar.add_right_item(active_toolchain_language, window, cx);
|
||||||
status_bar.add_right_item(vim_mode_indicator, window, cx);
|
status_bar.add_right_item(vim_mode_indicator, window, cx);
|
||||||
|
@ -4300,6 +4304,7 @@ mod tests {
|
||||||
"jj",
|
"jj",
|
||||||
"journal",
|
"journal",
|
||||||
"language_selector",
|
"language_selector",
|
||||||
|
"lsp_tool",
|
||||||
"markdown",
|
"markdown",
|
||||||
"menu",
|
"menu",
|
||||||
"notebook",
|
"notebook",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue