Merge pull request #1156 from zed-industries/disable-language-server
Introduce a new language-overrideable `enable_language_server` setting
This commit is contained in:
@@ -9089,7 +9089,6 @@ mod tests {
|
||||
#[gpui::test]
|
||||
async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
|
||||
cx.foreground().forbid_parking();
|
||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
@@ -9202,7 +9201,6 @@ mod tests {
|
||||
#[gpui::test]
|
||||
async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
|
||||
cx.foreground().forbid_parking();
|
||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
@@ -9316,8 +9314,6 @@ mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_completion(cx: &mut gpui::TestAppContext) {
|
||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
|
||||
@@ -3120,6 +3120,19 @@ impl<'a, T: Entity> ModelContext<'a, T> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn observe_global<G, F>(&mut self, mut callback: F) -> Subscription
|
||||
where
|
||||
G: Any,
|
||||
F: 'static + FnMut(&mut T, &mut ModelContext<T>),
|
||||
{
|
||||
let observer = self.weak_handle();
|
||||
self.app.observe_global::<G, _>(move |cx| {
|
||||
if let Some(observer) = observer.upgrade(cx) {
|
||||
observer.update(cx, |observer, cx| callback(observer, cx));
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn observe_release<S, F>(
|
||||
&mut self,
|
||||
handle: &ModelHandle<S>,
|
||||
|
||||
@@ -240,6 +240,10 @@ impl LanguageRegistry {
|
||||
.cloned()
|
||||
}
|
||||
|
||||
pub fn to_vec(&self) -> Vec<Arc<Language>> {
|
||||
self.languages.read().iter().cloned().collect()
|
||||
}
|
||||
|
||||
pub fn language_names(&self) -> Vec<String> {
|
||||
self.languages
|
||||
.read()
|
||||
|
||||
@@ -11,6 +11,7 @@ doctest = false
|
||||
test-support = [
|
||||
"client/test-support",
|
||||
"language/test-support",
|
||||
"settings/test-support",
|
||||
"text/test-support",
|
||||
]
|
||||
|
||||
@@ -56,6 +57,7 @@ collections = { path = "../collections", features = ["test-support"] }
|
||||
gpui = { path = "../gpui", features = ["test-support"] }
|
||||
language = { path = "../language", features = ["test-support"] }
|
||||
lsp = { path = "../lsp", features = ["test-support"] }
|
||||
settings = { path = "../settings", features = ["test-support"] }
|
||||
util = { path = "../util", features = ["test-support"] }
|
||||
rpc = { path = "../rpc", features = ["test-support"] }
|
||||
tempdir = { version = "0.3.7" }
|
||||
|
||||
+231
-19
@@ -90,7 +90,8 @@ pub struct Project {
|
||||
fs: Arc<dyn Fs>,
|
||||
client_state: ProjectClientState,
|
||||
collaborators: HashMap<PeerId, Collaborator>,
|
||||
subscriptions: Vec<client::Subscription>,
|
||||
client_subscriptions: Vec<client::Subscription>,
|
||||
_subscriptions: Vec<gpui::Subscription>,
|
||||
opened_buffer: (Rc<RefCell<watch::Sender<()>>>, watch::Receiver<()>),
|
||||
shared_buffers: HashMap<PeerId, HashSet<u64>>,
|
||||
loading_buffers: HashMap<
|
||||
@@ -418,7 +419,8 @@ impl Project {
|
||||
_maintain_remote_id_task,
|
||||
},
|
||||
opened_buffer: (Rc::new(RefCell::new(opened_buffer_tx)), opened_buffer_rx),
|
||||
subscriptions: Vec::new(),
|
||||
client_subscriptions: Vec::new(),
|
||||
_subscriptions: vec![cx.observe_global::<Settings, _>(Self::on_settings_changed)],
|
||||
active_entry: None,
|
||||
languages,
|
||||
client,
|
||||
@@ -503,7 +505,8 @@ impl Project {
|
||||
fs,
|
||||
next_entry_id: Default::default(),
|
||||
next_diagnostic_group_id: Default::default(),
|
||||
subscriptions: vec![client.add_model_for_remote_entity(remote_id, cx)],
|
||||
client_subscriptions: vec![client.add_model_for_remote_entity(remote_id, cx)],
|
||||
_subscriptions: Default::default(),
|
||||
client: client.clone(),
|
||||
client_state: ProjectClientState::Remote {
|
||||
sharing_has_stopped: false,
|
||||
@@ -582,6 +585,10 @@ impl Project {
|
||||
root_paths: impl IntoIterator<Item = &Path>,
|
||||
cx: &mut gpui::TestAppContext,
|
||||
) -> ModelHandle<Project> {
|
||||
if !cx.read(|cx| cx.has_global::<Settings>()) {
|
||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||
}
|
||||
|
||||
let languages = Arc::new(LanguageRegistry::test());
|
||||
let http_client = client::test::FakeHttpClient::with_404_response();
|
||||
let client = client::Client::new(http_client.clone());
|
||||
@@ -650,6 +657,55 @@ impl Project {
|
||||
})
|
||||
}
|
||||
|
||||
fn on_settings_changed(&mut self, cx: &mut ModelContext<Self>) {
|
||||
let settings = cx.global::<Settings>();
|
||||
|
||||
let mut language_servers_to_start = Vec::new();
|
||||
for buffer in self.opened_buffers.values() {
|
||||
if let Some(buffer) = buffer.upgrade(cx) {
|
||||
let buffer = buffer.read(cx);
|
||||
if let Some((file, language)) = File::from_dyn(buffer.file()).zip(buffer.language())
|
||||
{
|
||||
if settings.enable_language_server(Some(&language.name())) {
|
||||
let worktree = file.worktree.read(cx);
|
||||
language_servers_to_start.push((
|
||||
worktree.id(),
|
||||
worktree.as_local().unwrap().abs_path().clone(),
|
||||
language.clone(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut language_servers_to_stop = Vec::new();
|
||||
for language in self.languages.to_vec() {
|
||||
if let Some(lsp_adapter) = language.lsp_adapter() {
|
||||
if !settings.enable_language_server(Some(&language.name())) {
|
||||
let lsp_name = lsp_adapter.name();
|
||||
for (worktree_id, started_lsp_name) in self.started_language_servers.keys() {
|
||||
if lsp_name == *started_lsp_name {
|
||||
language_servers_to_stop.push((*worktree_id, started_lsp_name.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stop all newly-disabled language servers.
|
||||
for (worktree_id, adapter_name) in language_servers_to_stop {
|
||||
self.stop_language_server(worktree_id, adapter_name, cx)
|
||||
.detach();
|
||||
}
|
||||
|
||||
// Start all the newly-enabled language servers.
|
||||
for (worktree_id, worktree_path, language) in language_servers_to_start {
|
||||
self.start_language_server(worktree_id, worktree_path, language, cx);
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn buffer_for_id(&self, remote_id: u64, cx: &AppContext) -> Option<ModelHandle<Buffer>> {
|
||||
self.opened_buffers
|
||||
.get(&remote_id)
|
||||
@@ -775,7 +831,7 @@ impl Project {
|
||||
{
|
||||
*remote_id_tx.borrow_mut() = None;
|
||||
}
|
||||
this.subscriptions.clear();
|
||||
this.client_subscriptions.clear();
|
||||
this.metadata_changed(false, cx);
|
||||
});
|
||||
response.map(drop)
|
||||
@@ -802,7 +858,7 @@ impl Project {
|
||||
|
||||
this.metadata_changed(false, cx);
|
||||
cx.emit(Event::RemoteIdChanged(Some(remote_id)));
|
||||
this.subscriptions
|
||||
this.client_subscriptions
|
||||
.push(this.client.add_model_for_remote_entity(remote_id, cx));
|
||||
Ok(())
|
||||
})
|
||||
@@ -1858,6 +1914,13 @@ impl Project {
|
||||
language: Arc<Language>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
if !cx
|
||||
.global::<Settings>()
|
||||
.enable_language_server(Some(&language.name()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let adapter = if let Some(adapter) = language.lsp_adapter() {
|
||||
adapter
|
||||
} else {
|
||||
@@ -2060,6 +2123,40 @@ impl Project {
|
||||
});
|
||||
}
|
||||
|
||||
fn stop_language_server(
|
||||
&mut self,
|
||||
worktree_id: WorktreeId,
|
||||
adapter_name: LanguageServerName,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<()> {
|
||||
let key = (worktree_id, adapter_name);
|
||||
if let Some((_, language_server)) = self.language_servers.remove(&key) {
|
||||
self.language_server_statuses
|
||||
.remove(&language_server.server_id());
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
if let Some(started_language_server) = self.started_language_servers.remove(&key) {
|
||||
cx.spawn_weak(|this, mut cx| async move {
|
||||
if let Some(language_server) = started_language_server.await {
|
||||
if let Some(shutdown) = language_server.shutdown() {
|
||||
shutdown.await;
|
||||
}
|
||||
|
||||
if let Some(this) = this.upgrade(&cx) {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.language_server_statuses
|
||||
.remove(&language_server.server_id());
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
Task::ready(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn restart_language_servers_for_buffers(
|
||||
&mut self,
|
||||
buffers: impl IntoIterator<Item = ModelHandle<Buffer>>,
|
||||
@@ -2096,20 +2193,11 @@ impl Project {
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
let key = (worktree_id, adapter.name());
|
||||
let server_to_shutdown = self.language_servers.remove(&key);
|
||||
self.started_language_servers.remove(&key);
|
||||
server_to_shutdown
|
||||
.as_ref()
|
||||
.map(|(_, server)| self.language_server_statuses.remove(&server.server_id()));
|
||||
cx.spawn_weak(|this, mut cx| async move {
|
||||
if let Some(this) = this.upgrade(&cx) {
|
||||
if let Some((_, server_to_shutdown)) = server_to_shutdown {
|
||||
if let Some(shutdown_task) = server_to_shutdown.shutdown() {
|
||||
shutdown_task.await;
|
||||
}
|
||||
}
|
||||
|
||||
let stop = self.stop_language_server(worktree_id, adapter.name(), cx);
|
||||
cx.spawn_weak(|this, mut cx| async move {
|
||||
stop.await;
|
||||
if let Some(this) = this.upgrade(&cx) {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.start_language_server(worktree_id, worktree_path, language, cx);
|
||||
});
|
||||
@@ -5672,7 +5760,7 @@ mod tests {
|
||||
use super::{Event, *};
|
||||
use fs::RealFs;
|
||||
use futures::{future, StreamExt};
|
||||
use gpui::test::subscribe;
|
||||
use gpui::{executor::Deterministic, test::subscribe};
|
||||
use language::{
|
||||
tree_sitter_rust, tree_sitter_typescript, Diagnostic, FakeLspAdapter, LanguageConfig,
|
||||
OffsetRangeExt, Point, ToPoint,
|
||||
@@ -6424,6 +6512,130 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_toggling_enable_language_server(
|
||||
deterministic: Arc<Deterministic>,
|
||||
cx: &mut gpui::TestAppContext,
|
||||
) {
|
||||
deterministic.forbid_parking();
|
||||
|
||||
let mut rust = Language::new(
|
||||
LanguageConfig {
|
||||
name: Arc::from("Rust"),
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
);
|
||||
let mut fake_rust_servers = rust.set_fake_lsp_adapter(FakeLspAdapter {
|
||||
name: "rust-lsp",
|
||||
..Default::default()
|
||||
});
|
||||
let mut js = Language::new(
|
||||
LanguageConfig {
|
||||
name: Arc::from("JavaScript"),
|
||||
path_suffixes: vec!["js".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
);
|
||||
let mut fake_js_servers = js.set_fake_lsp_adapter(FakeLspAdapter {
|
||||
name: "js-lsp",
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let fs = FakeFs::new(cx.background());
|
||||
fs.insert_tree("/dir", json!({ "a.rs": "", "b.js": "" }))
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs, ["/dir".as_ref()], cx).await;
|
||||
project.update(cx, |project, _| {
|
||||
project.languages.add(Arc::new(rust));
|
||||
project.languages.add(Arc::new(js));
|
||||
});
|
||||
|
||||
let _rs_buffer = project
|
||||
.update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let _js_buffer = project
|
||||
.update(cx, |project, cx| project.open_local_buffer("/dir/b.js", cx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut fake_rust_server_1 = fake_rust_servers.next().await.unwrap();
|
||||
assert_eq!(
|
||||
fake_rust_server_1
|
||||
.receive_notification::<lsp::notification::DidOpenTextDocument>()
|
||||
.await
|
||||
.text_document
|
||||
.uri
|
||||
.as_str(),
|
||||
"file:///dir/a.rs"
|
||||
);
|
||||
|
||||
let mut fake_js_server = fake_js_servers.next().await.unwrap();
|
||||
assert_eq!(
|
||||
fake_js_server
|
||||
.receive_notification::<lsp::notification::DidOpenTextDocument>()
|
||||
.await
|
||||
.text_document
|
||||
.uri
|
||||
.as_str(),
|
||||
"file:///dir/b.js"
|
||||
);
|
||||
|
||||
// Disable Rust language server, ensuring only that server gets stopped.
|
||||
cx.update(|cx| {
|
||||
cx.update_global(|settings: &mut Settings, _| {
|
||||
settings.language_overrides.insert(
|
||||
Arc::from("Rust"),
|
||||
settings::LanguageOverride {
|
||||
enable_language_server: Some(false),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
})
|
||||
});
|
||||
fake_rust_server_1
|
||||
.receive_notification::<lsp::notification::Exit>()
|
||||
.await;
|
||||
|
||||
// Enable Rust and disable JavaScript language servers, ensuring that the
|
||||
// former gets started again and that the latter stops.
|
||||
cx.update(|cx| {
|
||||
cx.update_global(|settings: &mut Settings, _| {
|
||||
settings.language_overrides.insert(
|
||||
Arc::from("Rust"),
|
||||
settings::LanguageOverride {
|
||||
enable_language_server: Some(true),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
settings.language_overrides.insert(
|
||||
Arc::from("JavaScript"),
|
||||
settings::LanguageOverride {
|
||||
enable_language_server: Some(false),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
})
|
||||
});
|
||||
let mut fake_rust_server_2 = fake_rust_servers.next().await.unwrap();
|
||||
assert_eq!(
|
||||
fake_rust_server_2
|
||||
.receive_notification::<lsp::notification::DidOpenTextDocument>()
|
||||
.await
|
||||
.text_document
|
||||
.uri
|
||||
.as_str(),
|
||||
"file:///dir/a.rs"
|
||||
);
|
||||
fake_js_server
|
||||
.receive_notification::<lsp::notification::Exit>()
|
||||
.await;
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
|
||||
cx.foreground().forbid_parking();
|
||||
|
||||
@@ -28,6 +28,7 @@ pub struct Settings {
|
||||
pub soft_wrap: SoftWrap,
|
||||
pub preferred_line_length: u32,
|
||||
pub format_on_save: bool,
|
||||
pub enable_language_server: bool,
|
||||
pub language_overrides: HashMap<Arc<str>, LanguageOverride>,
|
||||
pub theme: Arc<Theme>,
|
||||
}
|
||||
@@ -38,6 +39,7 @@ pub struct LanguageOverride {
|
||||
pub soft_wrap: Option<SoftWrap>,
|
||||
pub preferred_line_length: Option<u32>,
|
||||
pub format_on_save: Option<bool>,
|
||||
pub enable_language_server: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
@@ -60,6 +62,8 @@ pub struct SettingsFileContent {
|
||||
pub vim_mode: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub format_on_save: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub enable_language_server: Option<bool>,
|
||||
#[serde(flatten)]
|
||||
pub editor: LanguageOverride,
|
||||
#[serde(default)]
|
||||
@@ -84,6 +88,7 @@ impl Settings {
|
||||
preferred_line_length: 80,
|
||||
language_overrides: Default::default(),
|
||||
format_on_save: true,
|
||||
enable_language_server: true,
|
||||
projects_online_by_default: true,
|
||||
theme,
|
||||
})
|
||||
@@ -127,6 +132,13 @@ impl Settings {
|
||||
.unwrap_or(self.format_on_save)
|
||||
}
|
||||
|
||||
pub fn enable_language_server(&self, language: Option<&str>) -> bool {
|
||||
language
|
||||
.and_then(|language| self.language_overrides.get(language))
|
||||
.and_then(|settings| settings.enable_language_server)
|
||||
.unwrap_or(self.enable_language_server)
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn test(cx: &gpui::AppContext) -> Settings {
|
||||
Settings {
|
||||
@@ -138,6 +150,7 @@ impl Settings {
|
||||
soft_wrap: SoftWrap::None,
|
||||
preferred_line_length: 80,
|
||||
format_on_save: true,
|
||||
enable_language_server: true,
|
||||
language_overrides: Default::default(),
|
||||
projects_online_by_default: true,
|
||||
theme: gpui::fonts::with_font_cache(cx.font_cache().clone(), || Default::default()),
|
||||
@@ -177,6 +190,10 @@ impl Settings {
|
||||
merge(&mut self.default_buffer_font_size, data.buffer_font_size);
|
||||
merge(&mut self.vim_mode, data.vim_mode);
|
||||
merge(&mut self.format_on_save, data.format_on_save);
|
||||
merge(
|
||||
&mut self.enable_language_server,
|
||||
data.enable_language_server,
|
||||
);
|
||||
merge(&mut self.soft_wrap, data.editor.soft_wrap);
|
||||
merge(&mut self.tab_size, data.editor.tab_size);
|
||||
merge(
|
||||
@@ -193,6 +210,10 @@ impl Settings {
|
||||
merge_option(&mut target.tab_size, settings.tab_size);
|
||||
merge_option(&mut target.soft_wrap, settings.soft_wrap);
|
||||
merge_option(&mut target.format_on_save, settings.format_on_save);
|
||||
merge_option(
|
||||
&mut target.enable_language_server,
|
||||
settings.enable_language_server,
|
||||
);
|
||||
merge_option(
|
||||
&mut target.preferred_line_length,
|
||||
settings.preferred_line_length,
|
||||
|
||||
Reference in New Issue
Block a user