Support dynamic formatting capabilities [un]registration (#14478)
Closes https://github.com/zed-industries/zed/issues/12661 Release Notes: - Added dynamic [un]registration for LSP formatting capabilities ([#12661](https://github.com/zed-industries/zed/issues/12661))
This commit is contained in:
parent
684d9dde56
commit
977a1b7a82
3 changed files with 167 additions and 38 deletions
|
@ -7,7 +7,7 @@ use anyhow::{anyhow, Context, Result};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use futures::{channel::oneshot, io::BufWriter, select, AsyncRead, AsyncWrite, Future, FutureExt};
|
use futures::{channel::oneshot, io::BufWriter, select, AsyncRead, AsyncWrite, Future, FutureExt};
|
||||||
use gpui::{AppContext, AsyncAppContext, BackgroundExecutor, Task};
|
use gpui::{AppContext, AsyncAppContext, BackgroundExecutor, Task};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::{Mutex, RwLock};
|
||||||
use postage::{barrier, prelude::Stream};
|
use postage::{barrier, prelude::Stream};
|
||||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||||
use serde_json::{json, value::RawValue, Value};
|
use serde_json::{json, value::RawValue, Value};
|
||||||
|
@ -24,6 +24,7 @@ use std::{
|
||||||
ffi::OsString,
|
ffi::OsString,
|
||||||
fmt,
|
fmt,
|
||||||
io::Write,
|
io::Write,
|
||||||
|
ops::DerefMut,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
sync::{
|
sync::{
|
||||||
|
@ -69,7 +70,7 @@ pub struct LanguageServer {
|
||||||
next_id: AtomicI32,
|
next_id: AtomicI32,
|
||||||
outbound_tx: channel::Sender<String>,
|
outbound_tx: channel::Sender<String>,
|
||||||
name: Arc<str>,
|
name: Arc<str>,
|
||||||
capabilities: ServerCapabilities,
|
capabilities: RwLock<ServerCapabilities>,
|
||||||
code_action_kinds: Option<Vec<CodeActionKind>>,
|
code_action_kinds: Option<Vec<CodeActionKind>>,
|
||||||
notification_handlers: Arc<Mutex<HashMap<&'static str, NotificationHandler>>>,
|
notification_handlers: Arc<Mutex<HashMap<&'static str, NotificationHandler>>>,
|
||||||
response_handlers: Arc<Mutex<Option<HashMap<RequestId, ResponseHandler>>>>,
|
response_handlers: Arc<Mutex<Option<HashMap<RequestId, ResponseHandler>>>>,
|
||||||
|
@ -640,10 +641,13 @@ impl LanguageServer {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
formatting: Some(DynamicRegistrationClientCapabilities {
|
formatting: Some(DynamicRegistrationClientCapabilities {
|
||||||
dynamic_registration: None,
|
dynamic_registration: Some(true),
|
||||||
|
}),
|
||||||
|
range_formatting: Some(DynamicRegistrationClientCapabilities {
|
||||||
|
dynamic_registration: Some(true),
|
||||||
}),
|
}),
|
||||||
on_type_formatting: Some(DynamicRegistrationClientCapabilities {
|
on_type_formatting: Some(DynamicRegistrationClientCapabilities {
|
||||||
dynamic_registration: None,
|
dynamic_registration: Some(true),
|
||||||
}),
|
}),
|
||||||
signature_help: Some(SignatureHelpClientCapabilities {
|
signature_help: Some(SignatureHelpClientCapabilities {
|
||||||
signature_information: Some(SignatureInformationSettings {
|
signature_information: Some(SignatureInformationSettings {
|
||||||
|
@ -693,7 +697,7 @@ impl LanguageServer {
|
||||||
if let Some(info) = response.server_info {
|
if let Some(info) = response.server_info {
|
||||||
self.name = info.name.into();
|
self.name = info.name.into();
|
||||||
}
|
}
|
||||||
self.capabilities = response.capabilities;
|
self.capabilities = RwLock::new(response.capabilities);
|
||||||
|
|
||||||
self.notify::<notification::Initialized>(InitializedParams {})?;
|
self.notify::<notification::Initialized>(InitializedParams {})?;
|
||||||
Ok(Arc::new(self))
|
Ok(Arc::new(self))
|
||||||
|
@ -908,8 +912,12 @@ impl LanguageServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the reported capabilities of the running language server.
|
/// Get the reported capabilities of the running language server.
|
||||||
pub fn capabilities(&self) -> &ServerCapabilities {
|
pub fn capabilities(&self) -> ServerCapabilities {
|
||||||
&self.capabilities
|
self.capabilities.read().clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_capabilities(&self, update: impl FnOnce(&mut ServerCapabilities)) {
|
||||||
|
update(self.capabilities.write().deref_mut());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the id of the running language server.
|
/// Get the id of the running language server.
|
||||||
|
|
|
@ -2596,7 +2596,7 @@ impl LspCommand for InlayHints {
|
||||||
lsp_adapter.name.0.as_ref() == "typescript-language-server";
|
lsp_adapter.name.0.as_ref() == "typescript-language-server";
|
||||||
|
|
||||||
let hints = message.unwrap_or_default().into_iter().map(|lsp_hint| {
|
let hints = message.unwrap_or_default().into_iter().map(|lsp_hint| {
|
||||||
let resolve_state = if InlayHints::can_resolve_inlays(lsp_server.capabilities()) {
|
let resolve_state = if InlayHints::can_resolve_inlays(&lsp_server.capabilities()) {
|
||||||
ResolveState::CanResolve(lsp_server.server_id(), lsp_hint.data.clone())
|
ResolveState::CanResolve(lsp_server.server_id(), lsp_hint.data.clone())
|
||||||
} else {
|
} else {
|
||||||
ResolveState::Resolved
|
ResolveState::Resolved
|
||||||
|
|
|
@ -3174,7 +3174,7 @@ impl Project {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn setup_pending_language_server(
|
async fn setup_pending_language_server(
|
||||||
this: WeakModel<Self>,
|
project: WeakModel<Self>,
|
||||||
override_options: Option<serde_json::Value>,
|
override_options: Option<serde_json::Value>,
|
||||||
pending_server: PendingLanguageServer,
|
pending_server: PendingLanguageServer,
|
||||||
delegate: Arc<dyn LspAdapterDelegate>,
|
delegate: Arc<dyn LspAdapterDelegate>,
|
||||||
|
@ -3193,7 +3193,7 @@ impl Project {
|
||||||
language_server
|
language_server
|
||||||
.on_notification::<lsp::notification::PublishDiagnostics, _>({
|
.on_notification::<lsp::notification::PublishDiagnostics, _>({
|
||||||
let adapter = adapter.clone();
|
let adapter = adapter.clone();
|
||||||
let this = this.clone();
|
let this = project.clone();
|
||||||
move |mut params, mut cx| {
|
move |mut params, mut cx| {
|
||||||
let adapter = adapter.clone();
|
let adapter = adapter.clone();
|
||||||
if let Some(this) = this.upgrade() {
|
if let Some(this) = this.upgrade() {
|
||||||
|
@ -3247,7 +3247,7 @@ impl Project {
|
||||||
// to these requests when initializing.
|
// to these requests when initializing.
|
||||||
language_server
|
language_server
|
||||||
.on_request::<lsp::request::WorkDoneProgressCreate, _, _>({
|
.on_request::<lsp::request::WorkDoneProgressCreate, _, _>({
|
||||||
let this = this.clone();
|
let this = project.clone();
|
||||||
move |params, mut cx| {
|
move |params, mut cx| {
|
||||||
let this = this.clone();
|
let this = this.clone();
|
||||||
async move {
|
async move {
|
||||||
|
@ -3268,20 +3268,103 @@ impl Project {
|
||||||
|
|
||||||
language_server
|
language_server
|
||||||
.on_request::<lsp::request::RegisterCapability, _, _>({
|
.on_request::<lsp::request::RegisterCapability, _, _>({
|
||||||
let this = this.clone();
|
let project = project.clone();
|
||||||
move |params, mut cx| {
|
move |params, mut cx| {
|
||||||
let this = this.clone();
|
let project = project.clone();
|
||||||
async move {
|
async move {
|
||||||
for reg in params.registrations {
|
for reg in params.registrations {
|
||||||
if reg.method == "workspace/didChangeWatchedFiles" {
|
match reg.method.as_str() {
|
||||||
if let Some(options) = reg.register_options {
|
"workspace/didChangeWatchedFiles" => {
|
||||||
let options = serde_json::from_value(options)?;
|
if let Some(options) = reg.register_options {
|
||||||
this.update(&mut cx, |this, cx| {
|
let options = serde_json::from_value(options)?;
|
||||||
this.on_lsp_did_change_watched_files(
|
project.update(&mut cx, |project, cx| {
|
||||||
server_id, ®.id, options, cx,
|
project.on_lsp_did_change_watched_files(
|
||||||
);
|
server_id, ®.id, options, cx,
|
||||||
})?;
|
);
|
||||||
|
})?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
"textDocument/rangeFormatting" => {
|
||||||
|
project.update(&mut cx, |project, _| {
|
||||||
|
if let Some(server) =
|
||||||
|
project.language_server_for_id(server_id)
|
||||||
|
{
|
||||||
|
let options = reg
|
||||||
|
.register_options
|
||||||
|
.map(|options| {
|
||||||
|
serde_json::from_value::<
|
||||||
|
lsp::DocumentRangeFormattingOptions,
|
||||||
|
>(
|
||||||
|
options
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.transpose()?;
|
||||||
|
let provider = match options {
|
||||||
|
None => OneOf::Left(true),
|
||||||
|
Some(options) => OneOf::Right(options),
|
||||||
|
};
|
||||||
|
server.update_capabilities(|capabilities| {
|
||||||
|
capabilities.document_range_formatting_provider =
|
||||||
|
Some(provider);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
anyhow::Ok(())
|
||||||
|
})??;
|
||||||
|
}
|
||||||
|
"textDocument/onTypeFormatting" => {
|
||||||
|
project.update(&mut cx, |project, _| {
|
||||||
|
if let Some(server) =
|
||||||
|
project.language_server_for_id(server_id)
|
||||||
|
{
|
||||||
|
let options = reg
|
||||||
|
.register_options
|
||||||
|
.map(|options| {
|
||||||
|
serde_json::from_value::<
|
||||||
|
lsp::DocumentOnTypeFormattingOptions,
|
||||||
|
>(
|
||||||
|
options
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.transpose()?;
|
||||||
|
if let Some(options) = options {
|
||||||
|
server.update_capabilities(|capabilities| {
|
||||||
|
capabilities
|
||||||
|
.document_on_type_formatting_provider =
|
||||||
|
Some(options);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
anyhow::Ok(())
|
||||||
|
})??;
|
||||||
|
}
|
||||||
|
"textDocument/formatting" => {
|
||||||
|
project.update(&mut cx, |project, _| {
|
||||||
|
if let Some(server) =
|
||||||
|
project.language_server_for_id(server_id)
|
||||||
|
{
|
||||||
|
let options = reg
|
||||||
|
.register_options
|
||||||
|
.map(|options| {
|
||||||
|
serde_json::from_value::<
|
||||||
|
lsp::DocumentFormattingOptions,
|
||||||
|
>(
|
||||||
|
options
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.transpose()?;
|
||||||
|
let provider = match options {
|
||||||
|
None => OneOf::Left(true),
|
||||||
|
Some(options) => OneOf::Right(options),
|
||||||
|
};
|
||||||
|
server.update_capabilities(|capabilities| {
|
||||||
|
capabilities.document_formatting_provider =
|
||||||
|
Some(provider);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
anyhow::Ok(())
|
||||||
|
})??;
|
||||||
|
}
|
||||||
|
_ => log::warn!("unhandled capability registration: {reg:?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -3292,17 +3375,55 @@ impl Project {
|
||||||
|
|
||||||
language_server
|
language_server
|
||||||
.on_request::<lsp::request::UnregisterCapability, _, _>({
|
.on_request::<lsp::request::UnregisterCapability, _, _>({
|
||||||
let this = this.clone();
|
let this = project.clone();
|
||||||
move |params, mut cx| {
|
move |params, mut cx| {
|
||||||
let this = this.clone();
|
let project = this.clone();
|
||||||
async move {
|
async move {
|
||||||
for unreg in params.unregisterations.iter() {
|
for unreg in params.unregisterations.iter() {
|
||||||
if unreg.method == "workspace/didChangeWatchedFiles" {
|
match unreg.method.as_str() {
|
||||||
this.update(&mut cx, |this, cx| {
|
"workspace/didChangeWatchedFiles" => {
|
||||||
this.on_lsp_unregister_did_change_watched_files(
|
project.update(&mut cx, |project, cx| {
|
||||||
server_id, &unreg.id, cx,
|
project.on_lsp_unregister_did_change_watched_files(
|
||||||
);
|
server_id, &unreg.id, cx,
|
||||||
})?;
|
);
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
"textDocument/rangeFormatting" => {
|
||||||
|
project.update(&mut cx, |project, _| {
|
||||||
|
if let Some(server) =
|
||||||
|
project.language_server_for_id(server_id)
|
||||||
|
{
|
||||||
|
server.update_capabilities(|capabilities| {
|
||||||
|
capabilities.document_range_formatting_provider =
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
"textDocument/onTypeFormatting" => {
|
||||||
|
project.update(&mut cx, |project, _| {
|
||||||
|
if let Some(server) =
|
||||||
|
project.language_server_for_id(server_id)
|
||||||
|
{
|
||||||
|
server.update_capabilities(|capabilities| {
|
||||||
|
capabilities.document_on_type_formatting_provider =
|
||||||
|
None;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
"textDocument/formatting" => {
|
||||||
|
project.update(&mut cx, |project, _| {
|
||||||
|
if let Some(server) =
|
||||||
|
project.language_server_for_id(server_id)
|
||||||
|
{
|
||||||
|
server.update_capabilities(|capabilities| {
|
||||||
|
capabilities.document_formatting_provider = None;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
_ => log::warn!("unhandled capability unregistration: {unreg:?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -3314,7 +3435,7 @@ impl Project {
|
||||||
language_server
|
language_server
|
||||||
.on_request::<lsp::request::ApplyWorkspaceEdit, _, _>({
|
.on_request::<lsp::request::ApplyWorkspaceEdit, _, _>({
|
||||||
let adapter = adapter.clone();
|
let adapter = adapter.clone();
|
||||||
let this = this.clone();
|
let this = project.clone();
|
||||||
move |params, cx| {
|
move |params, cx| {
|
||||||
Self::on_lsp_workspace_edit(
|
Self::on_lsp_workspace_edit(
|
||||||
this.clone(),
|
this.clone(),
|
||||||
|
@ -3329,7 +3450,7 @@ impl Project {
|
||||||
|
|
||||||
language_server
|
language_server
|
||||||
.on_request::<lsp::request::InlayHintRefreshRequest, _, _>({
|
.on_request::<lsp::request::InlayHintRefreshRequest, _, _>({
|
||||||
let this = this.clone();
|
let this = project.clone();
|
||||||
move |(), mut cx| {
|
move |(), mut cx| {
|
||||||
let this = this.clone();
|
let this = this.clone();
|
||||||
async move {
|
async move {
|
||||||
|
@ -3348,7 +3469,7 @@ impl Project {
|
||||||
|
|
||||||
language_server
|
language_server
|
||||||
.on_request::<lsp::request::ShowMessageRequest, _, _>({
|
.on_request::<lsp::request::ShowMessageRequest, _, _>({
|
||||||
let this = this.clone();
|
let this = project.clone();
|
||||||
let name = name.to_string();
|
let name = name.to_string();
|
||||||
move |params, mut cx| {
|
move |params, mut cx| {
|
||||||
let this = this.clone();
|
let this = this.clone();
|
||||||
|
@ -3387,7 +3508,7 @@ impl Project {
|
||||||
|
|
||||||
language_server
|
language_server
|
||||||
.on_notification::<ServerStatus, _>({
|
.on_notification::<ServerStatus, _>({
|
||||||
let this = this.clone();
|
let this = project.clone();
|
||||||
let name = name.to_string();
|
let name = name.to_string();
|
||||||
move |params, mut cx| {
|
move |params, mut cx| {
|
||||||
let this = this.clone();
|
let this = this.clone();
|
||||||
|
@ -3430,7 +3551,7 @@ impl Project {
|
||||||
.detach();
|
.detach();
|
||||||
language_server
|
language_server
|
||||||
.on_notification::<lsp::notification::ShowMessage, _>({
|
.on_notification::<lsp::notification::ShowMessage, _>({
|
||||||
let this = this.clone();
|
let this = project.clone();
|
||||||
let name = name.to_string();
|
let name = name.to_string();
|
||||||
move |params, mut cx| {
|
move |params, mut cx| {
|
||||||
let this = this.clone();
|
let this = this.clone();
|
||||||
|
@ -3457,7 +3578,7 @@ impl Project {
|
||||||
.detach();
|
.detach();
|
||||||
language_server
|
language_server
|
||||||
.on_notification::<lsp::notification::Progress, _>(move |params, mut cx| {
|
.on_notification::<lsp::notification::Progress, _>(move |params, mut cx| {
|
||||||
if let Some(this) = this.upgrade() {
|
if let Some(this) = project.upgrade() {
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.on_lsp_progress(
|
this.on_lsp_progress(
|
||||||
params,
|
params,
|
||||||
|
@ -6774,7 +6895,7 @@ impl Project {
|
||||||
} else {
|
} else {
|
||||||
return Task::ready(Ok(hint));
|
return Task::ready(Ok(hint));
|
||||||
};
|
};
|
||||||
if !InlayHints::can_resolve_inlays(lang_server.capabilities()) {
|
if !InlayHints::can_resolve_inlays(&lang_server.capabilities()) {
|
||||||
return Task::ready(Ok(hint));
|
return Task::ready(Ok(hint));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7186,7 +7307,7 @@ impl Project {
|
||||||
let lsp_params = request.to_lsp(&file.abs_path(cx), buffer, &language_server, cx);
|
let lsp_params = request.to_lsp(&file.abs_path(cx), buffer, &language_server, cx);
|
||||||
let status = request.status();
|
let status = request.status();
|
||||||
return cx.spawn(move |this, cx| async move {
|
return cx.spawn(move |this, cx| async move {
|
||||||
if !request.check_capabilities(language_server.capabilities()) {
|
if !request.check_capabilities(&language_server.capabilities()) {
|
||||||
return Ok(Default::default());
|
return Ok(Default::default());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7280,7 +7401,7 @@ impl Project {
|
||||||
let scope = position.and_then(|position| snapshot.language_scope_at(position));
|
let scope = position.and_then(|position| snapshot.language_scope_at(position));
|
||||||
let mut response_results = self
|
let mut response_results = self
|
||||||
.language_servers_for_buffer(buffer.read(cx), cx)
|
.language_servers_for_buffer(buffer.read(cx), cx)
|
||||||
.filter(|(_, server)| server_capabilities_check(server.capabilities()))
|
.filter(|(_, server)| server_capabilities_check(&server.capabilities()))
|
||||||
.filter(|(adapter, _)| {
|
.filter(|(adapter, _)| {
|
||||||
scope
|
scope
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue