diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index e3d5e698153e39fd4de04893b50a804dc2105b99..8b866563636f6fe494bdd8d941458defa786c0da 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -12915,6 +12915,96 @@ async fn test_document_format_during_save(cx: &mut TestAppContext) { } } +#[gpui::test] +async fn test_auto_formatter_skips_server_without_formatting(cx: &mut TestAppContext) { + init_test(cx, |_| {}); + + let fs = FakeFs::new(cx.executor()); + fs.insert_file(path!("/file.rs"), Default::default()).await; + + let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await; + + let language_registry = project.read_with(cx, |project, _| project.languages().clone()); + language_registry.add(rust_lang()); + + // First server: no formatting capability + let mut no_format_servers = language_registry.register_fake_lsp( + "Rust", + FakeLspAdapter { + name: "no-format-server", + capabilities: lsp::ServerCapabilities { + completion_provider: Some(lsp::CompletionOptions::default()), + ..Default::default() + }, + ..Default::default() + }, + ); + + // Second server: has formatting capability + let mut format_servers = language_registry.register_fake_lsp( + "Rust", + FakeLspAdapter { + name: "format-server", + capabilities: lsp::ServerCapabilities { + document_formatting_provider: Some(lsp::OneOf::Left(true)), + ..Default::default() + }, + ..Default::default() + }, + ); + + let buffer = project + .update(cx, |project, cx| { + project.open_local_buffer(path!("/file.rs"), cx) + }) + .await + .unwrap(); + + let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx)); + let (editor, cx) = cx.add_window_view(|window, cx| { + build_editor_with_project(project.clone(), buffer, window, cx) + }); + editor.update_in(cx, |editor, window, cx| { + editor.set_text("one\ntwo\nthree\n", window, cx) + }); + + let _no_format_server = no_format_servers.next().await.unwrap(); + let format_server = format_servers.next().await.unwrap(); + + format_server.set_request_handler::( + move |params, _| async move { + assert_eq!( + params.text_document.uri, + lsp::Uri::from_file_path(path!("/file.rs")).unwrap() + ); + Ok(Some(vec![lsp::TextEdit::new( + lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)), + ", ".to_string(), + )])) + }, + ); + + let save = editor + .update_in(cx, |editor, window, cx| { + editor.save( + SaveOptions { + format: true, + autosave: false, + }, + project.clone(), + window, + cx, + ) + }) + .unwrap(); + save.await; + + assert_eq!( + editor.update(cx, |editor, cx| editor.text(cx)), + "one, two\nthree\n" + ); +} + #[gpui::test] async fn test_redo_after_noop_format(cx: &mut TestAppContext) { init_test(cx, |settings| { diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 75f9702e12cf31ce4f555940d7d1918884bbc22a..7573af1dc69f33586199c6f9e5e4d2a59f6d2d6f 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -1778,9 +1778,10 @@ impl LocalLspStore { } }) } - settings::LanguageServerFormatterSpecifier::Current => { - adapters_and_servers.first().map(|e| e.1.clone()) - } + settings::LanguageServerFormatterSpecifier::Current => adapters_and_servers + .iter() + .find(|(_, server)| Self::server_supports_formatting(server)) + .map(|(_, server)| server.clone()), }; let Some(language_server) = language_server else { @@ -2285,6 +2286,14 @@ impl LocalLspStore { } } + fn server_supports_formatting(server: &Arc) -> bool { + let capabilities = server.capabilities(); + let formatting = capabilities.document_formatting_provider.as_ref(); + let range_formatting = capabilities.document_range_formatting_provider.as_ref(); + matches!(formatting, Some(p) if *p != OneOf::Left(false)) + || matches!(range_formatting, Some(p) if *p != OneOf::Left(false)) + } + async fn format_via_lsp( this: &WeakEntity, buffer: &Entity,