diff options
Diffstat (limited to 'crates')
-rw-r--r-- | crates/hir_ty/src/infer.rs | 5 | ||||
-rw-r--r-- | crates/hir_ty/src/infer/expr.rs | 2 | ||||
-rw-r--r-- | crates/hir_ty/src/tests/coercion.rs | 101 | ||||
-rw-r--r-- | crates/hir_ty/src/tests/traits.rs | 37 | ||||
-rw-r--r-- | crates/rust-analyzer/src/caps.rs | 4 | ||||
-rw-r--r-- | crates/rust-analyzer/src/config.rs | 13 | ||||
-rw-r--r-- | crates/rust-analyzer/src/handlers.rs | 239 | ||||
-rw-r--r-- | crates/rust-analyzer/src/main_loop.rs | 1 |
8 files changed, 293 insertions, 109 deletions
diff --git a/crates/hir_ty/src/infer.rs b/crates/hir_ty/src/infer.rs index db3c937ff..edb65622f 100644 --- a/crates/hir_ty/src/infer.rs +++ b/crates/hir_ty/src/infer.rs | |||
@@ -580,7 +580,10 @@ impl<'a> InferenceContext<'a> { | |||
580 | fn resolve_ops_try_ok(&self) -> Option<TypeAliasId> { | 580 | fn resolve_ops_try_ok(&self) -> Option<TypeAliasId> { |
581 | let path = path![core::ops::Try]; | 581 | let path = path![core::ops::Try]; |
582 | let trait_ = self.resolver.resolve_known_trait(self.db.upcast(), &path)?; | 582 | let trait_ = self.resolver.resolve_known_trait(self.db.upcast(), &path)?; |
583 | self.db.trait_data(trait_).associated_type_by_name(&name![Ok]) | 583 | let trait_data = self.db.trait_data(trait_); |
584 | trait_data | ||
585 | .associated_type_by_name(&name![Ok]) | ||
586 | .or_else(|| trait_data.associated_type_by_name(&name![Output])) | ||
584 | } | 587 | } |
585 | 588 | ||
586 | fn resolve_ops_neg_output(&self) -> Option<TypeAliasId> { | 589 | fn resolve_ops_neg_output(&self) -> Option<TypeAliasId> { |
diff --git a/crates/hir_ty/src/infer/expr.rs b/crates/hir_ty/src/infer/expr.rs index eab8fac91..79a732106 100644 --- a/crates/hir_ty/src/infer/expr.rs +++ b/crates/hir_ty/src/infer/expr.rs | |||
@@ -805,7 +805,7 @@ impl<'a> InferenceContext<'a> { | |||
805 | None => self.table.new_float_var(), | 805 | None => self.table.new_float_var(), |
806 | }, | 806 | }, |
807 | }, | 807 | }, |
808 | Expr::MacroStmts { tail } => self.infer_expr(*tail, expected), | 808 | Expr::MacroStmts { tail } => self.infer_expr_inner(*tail, expected), |
809 | }; | 809 | }; |
810 | // use a new type variable if we got unknown here | 810 | // use a new type variable if we got unknown here |
811 | let ty = self.insert_type_vars_shallow(ty); | 811 | let ty = self.insert_type_vars_shallow(ty); |
diff --git a/crates/hir_ty/src/tests/coercion.rs b/crates/hir_ty/src/tests/coercion.rs index bb568ea37..6dac7e103 100644 --- a/crates/hir_ty/src/tests/coercion.rs +++ b/crates/hir_ty/src/tests/coercion.rs | |||
@@ -832,11 +832,9 @@ fn coerce_unsize_super_trait_cycle() { | |||
832 | ); | 832 | ); |
833 | } | 833 | } |
834 | 834 | ||
835 | #[ignore] | ||
836 | #[test] | 835 | #[test] |
837 | fn coerce_unsize_generic() { | 836 | fn coerce_unsize_generic() { |
838 | // FIXME: Implement this | 837 | // FIXME: fix the type mismatches here |
839 | // https://doc.rust-lang.org/reference/type-coercions.html#unsized-coercions | ||
840 | check_infer_with_mismatches( | 838 | check_infer_with_mismatches( |
841 | r#" | 839 | r#" |
842 | #[lang = "unsize"] | 840 | #[lang = "unsize"] |
@@ -854,8 +852,58 @@ fn coerce_unsize_generic() { | |||
854 | let _: &Bar<[usize]> = &Bar(Foo { t: [1, 2, 3] }); | 852 | let _: &Bar<[usize]> = &Bar(Foo { t: [1, 2, 3] }); |
855 | } | 853 | } |
856 | "#, | 854 | "#, |
857 | expect![[r" | 855 | expect![[r#" |
858 | "]], | 856 | 209..317 '{ ... }); }': () |
857 | 219..220 '_': &Foo<[usize]> | ||
858 | 238..259 '&Foo {..., 3] }': &Foo<[usize]> | ||
859 | 239..259 'Foo { ..., 3] }': Foo<[usize]> | ||
860 | 248..257 '[1, 2, 3]': [usize; 3] | ||
861 | 249..250 '1': usize | ||
862 | 252..253 '2': usize | ||
863 | 255..256 '3': usize | ||
864 | 269..270 '_': &Bar<[usize]> | ||
865 | 288..314 '&Bar(F... 3] })': &Bar<[i32; 3]> | ||
866 | 289..292 'Bar': Bar<[i32; 3]>(Foo<[i32; 3]>) -> Bar<[i32; 3]> | ||
867 | 289..314 'Bar(Fo... 3] })': Bar<[i32; 3]> | ||
868 | 293..313 'Foo { ..., 3] }': Foo<[i32; 3]> | ||
869 | 302..311 '[1, 2, 3]': [i32; 3] | ||
870 | 303..304 '1': i32 | ||
871 | 306..307 '2': i32 | ||
872 | 309..310 '3': i32 | ||
873 | 248..257: expected [usize], got [usize; 3] | ||
874 | 288..314: expected &Bar<[usize]>, got &Bar<[i32; 3]> | ||
875 | "#]], | ||
876 | ); | ||
877 | } | ||
878 | |||
879 | #[test] | ||
880 | fn coerce_unsize_apit() { | ||
881 | // FIXME: #8984 | ||
882 | check_infer_with_mismatches( | ||
883 | r#" | ||
884 | #[lang = "sized"] | ||
885 | pub trait Sized {} | ||
886 | #[lang = "unsize"] | ||
887 | pub trait Unsize<T> {} | ||
888 | #[lang = "coerce_unsized"] | ||
889 | pub trait CoerceUnsized<T> {} | ||
890 | |||
891 | impl<T: Unsize<U>, U> CoerceUnsized<&U> for &T {} | ||
892 | |||
893 | trait Foo {} | ||
894 | |||
895 | fn test(f: impl Foo) { | ||
896 | let _: &dyn Foo = &f; | ||
897 | } | ||
898 | "#, | ||
899 | expect![[r#" | ||
900 | 210..211 'f': impl Foo | ||
901 | 223..252 '{ ... &f; }': () | ||
902 | 233..234 '_': &dyn Foo | ||
903 | 247..249 '&f': &impl Foo | ||
904 | 248..249 'f': impl Foo | ||
905 | 247..249: expected &dyn Foo, got &impl Foo | ||
906 | "#]], | ||
859 | ); | 907 | ); |
860 | } | 908 | } |
861 | 909 | ||
@@ -912,3 +960,46 @@ fn test() -> i32 { | |||
912 | "#, | 960 | "#, |
913 | ) | 961 | ) |
914 | } | 962 | } |
963 | |||
964 | #[test] | ||
965 | fn panic_macro() { | ||
966 | check_infer_with_mismatches( | ||
967 | r#" | ||
968 | mod panic { | ||
969 | #[macro_export] | ||
970 | pub macro panic_2015 { | ||
971 | () => ( | ||
972 | $crate::panicking::panic() | ||
973 | ), | ||
974 | } | ||
975 | } | ||
976 | |||
977 | mod panicking { | ||
978 | pub fn panic() -> ! { loop {} } | ||
979 | } | ||
980 | |||
981 | #[rustc_builtin_macro = "core_panic"] | ||
982 | macro_rules! panic { | ||
983 | // Expands to either `$crate::panic::panic_2015` or `$crate::panic::panic_2021` | ||
984 | // depending on the edition of the caller. | ||
985 | ($($arg:tt)*) => { | ||
986 | /* compiler built-in */ | ||
987 | }; | ||
988 | } | ||
989 | |||
990 | fn main() { | ||
991 | panic!() | ||
992 | } | ||
993 | "#, | ||
994 | expect![[r#" | ||
995 | 174..185 '{ loop {} }': ! | ||
996 | 176..183 'loop {}': ! | ||
997 | 181..183 '{}': () | ||
998 | !0..24 '$crate...:panic': fn panic() -> ! | ||
999 | !0..26 '$crate...anic()': ! | ||
1000 | !0..26 '$crate...anic()': ! | ||
1001 | !0..28 '$crate...015!()': ! | ||
1002 | 454..470 '{ ...c!() }': () | ||
1003 | "#]], | ||
1004 | ); | ||
1005 | } | ||
diff --git a/crates/hir_ty/src/tests/traits.rs b/crates/hir_ty/src/tests/traits.rs index a5a2df54c..71905baeb 100644 --- a/crates/hir_ty/src/tests/traits.rs +++ b/crates/hir_ty/src/tests/traits.rs | |||
@@ -161,6 +161,43 @@ mod result { | |||
161 | } | 161 | } |
162 | 162 | ||
163 | #[test] | 163 | #[test] |
164 | fn infer_tryv2() { | ||
165 | check_types( | ||
166 | r#" | ||
167 | //- /main.rs crate:main deps:core | ||
168 | fn test() { | ||
169 | let r: Result<i32, u64> = Result::Ok(1); | ||
170 | let v = r?; | ||
171 | v; | ||
172 | } //^ i32 | ||
173 | |||
174 | //- /core.rs crate:core | ||
175 | #[prelude_import] use ops::*; | ||
176 | mod ops { | ||
177 | trait Try { | ||
178 | type Output; | ||
179 | type Residual; | ||
180 | } | ||
181 | } | ||
182 | |||
183 | #[prelude_import] use result::*; | ||
184 | mod result { | ||
185 | enum Infallible {} | ||
186 | enum Result<O, E> { | ||
187 | Ok(O), | ||
188 | Err(E) | ||
189 | } | ||
190 | |||
191 | impl<O, E> crate::ops::Try for Result<O, E> { | ||
192 | type Output = O; | ||
193 | type Error = Result<Infallible, E>; | ||
194 | } | ||
195 | } | ||
196 | "#, | ||
197 | ); | ||
198 | } | ||
199 | |||
200 | #[test] | ||
164 | fn infer_for_loop() { | 201 | fn infer_for_loop() { |
165 | check_types( | 202 | check_types( |
166 | r#" | 203 | r#" |
diff --git a/crates/rust-analyzer/src/caps.rs b/crates/rust-analyzer/src/caps.rs index b2317618a..4d88932ca 100644 --- a/crates/rust-analyzer/src/caps.rs +++ b/crates/rust-analyzer/src/caps.rs | |||
@@ -1,4 +1,4 @@ | |||
1 | //! Advertizes the capabilities of the LSP Server. | 1 | //! Advertises the capabilities of the LSP Server. |
2 | use std::env; | 2 | use std::env; |
3 | 3 | ||
4 | use lsp_types::{ | 4 | use lsp_types::{ |
@@ -54,7 +54,7 @@ pub fn server_capabilities(client_caps: &ClientCapabilities) -> ServerCapabiliti | |||
54 | code_action_provider: Some(code_action_capabilities(client_caps)), | 54 | code_action_provider: Some(code_action_capabilities(client_caps)), |
55 | code_lens_provider: Some(CodeLensOptions { resolve_provider: Some(true) }), | 55 | code_lens_provider: Some(CodeLensOptions { resolve_provider: Some(true) }), |
56 | document_formatting_provider: Some(OneOf::Left(true)), | 56 | document_formatting_provider: Some(OneOf::Left(true)), |
57 | document_range_formatting_provider: None, | 57 | document_range_formatting_provider: Some(OneOf::Left(true)), |
58 | document_on_type_formatting_provider: Some(DocumentOnTypeFormattingOptions { | 58 | document_on_type_formatting_provider: Some(DocumentOnTypeFormattingOptions { |
59 | first_trigger_character: "=".to_string(), | 59 | first_trigger_character: "=".to_string(), |
60 | more_trigger_character: Some(vec![".".to_string(), ">".to_string(), "{".to_string()]), | 60 | more_trigger_character: Some(vec![".".to_string(), ">".to_string(), "{".to_string()]), |
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 7c02a507c..7620a2fe1 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs | |||
@@ -218,6 +218,10 @@ config_data! { | |||
218 | /// Advanced option, fully override the command rust-analyzer uses for | 218 | /// Advanced option, fully override the command rust-analyzer uses for |
219 | /// formatting. | 219 | /// formatting. |
220 | rustfmt_overrideCommand: Option<Vec<String>> = "null", | 220 | rustfmt_overrideCommand: Option<Vec<String>> = "null", |
221 | /// Enables the use of rustfmt's unstable range formatting command for the | ||
222 | /// `textDocument/rangeFormatting` request. The rustfmt option is unstable and only | ||
223 | /// available on a nightly build. | ||
224 | rustfmt_enableRangeFormatting: bool = "false", | ||
221 | 225 | ||
222 | /// Workspace symbol search scope. | 226 | /// Workspace symbol search scope. |
223 | workspace_symbol_search_scope: WorskpaceSymbolSearchScopeDef = "\"workspace\"", | 227 | workspace_symbol_search_scope: WorskpaceSymbolSearchScopeDef = "\"workspace\"", |
@@ -305,7 +309,7 @@ pub struct NotificationsConfig { | |||
305 | 309 | ||
306 | #[derive(Debug, Clone)] | 310 | #[derive(Debug, Clone)] |
307 | pub enum RustfmtConfig { | 311 | pub enum RustfmtConfig { |
308 | Rustfmt { extra_args: Vec<String> }, | 312 | Rustfmt { extra_args: Vec<String>, enable_range_formatting: bool }, |
309 | CustomCommand { command: String, args: Vec<String> }, | 313 | CustomCommand { command: String, args: Vec<String> }, |
310 | } | 314 | } |
311 | 315 | ||
@@ -584,9 +588,10 @@ impl Config { | |||
584 | let command = args.remove(0); | 588 | let command = args.remove(0); |
585 | RustfmtConfig::CustomCommand { command, args } | 589 | RustfmtConfig::CustomCommand { command, args } |
586 | } | 590 | } |
587 | Some(_) | None => { | 591 | Some(_) | None => RustfmtConfig::Rustfmt { |
588 | RustfmtConfig::Rustfmt { extra_args: self.data.rustfmt_extraArgs.clone() } | 592 | extra_args: self.data.rustfmt_extraArgs.clone(), |
589 | } | 593 | enable_range_formatting: self.data.rustfmt_enableRangeFormatting, |
594 | }, | ||
590 | } | 595 | } |
591 | } | 596 | } |
592 | pub fn flycheck(&self) -> Option<FlycheckConfig> { | 597 | pub fn flycheck(&self) -> Option<FlycheckConfig> { |
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index f48210424..456744603 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs | |||
@@ -27,7 +27,7 @@ use lsp_types::{ | |||
27 | }; | 27 | }; |
28 | use project_model::TargetKind; | 28 | use project_model::TargetKind; |
29 | use serde::{Deserialize, Serialize}; | 29 | use serde::{Deserialize, Serialize}; |
30 | use serde_json::to_value; | 30 | use serde_json::{json, to_value}; |
31 | use stdx::format_to; | 31 | use stdx::format_to; |
32 | use syntax::{algo, ast, AstNode, TextRange, TextSize}; | 32 | use syntax::{algo, ast, AstNode, TextRange, TextSize}; |
33 | 33 | ||
@@ -955,104 +955,17 @@ pub(crate) fn handle_formatting( | |||
955 | params: DocumentFormattingParams, | 955 | params: DocumentFormattingParams, |
956 | ) -> Result<Option<Vec<lsp_types::TextEdit>>> { | 956 | ) -> Result<Option<Vec<lsp_types::TextEdit>>> { |
957 | let _p = profile::span("handle_formatting"); | 957 | let _p = profile::span("handle_formatting"); |
958 | let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; | ||
959 | let file = snap.analysis.file_text(file_id)?; | ||
960 | let crate_ids = snap.analysis.crate_for(file_id)?; | ||
961 | |||
962 | let line_index = snap.file_line_index(file_id)?; | ||
963 | |||
964 | let mut rustfmt = match snap.config.rustfmt() { | ||
965 | RustfmtConfig::Rustfmt { extra_args } => { | ||
966 | let mut cmd = process::Command::new(toolchain::rustfmt()); | ||
967 | cmd.args(extra_args); | ||
968 | // try to chdir to the file so we can respect `rustfmt.toml` | ||
969 | // FIXME: use `rustfmt --config-path` once | ||
970 | // https://github.com/rust-lang/rustfmt/issues/4660 gets fixed | ||
971 | match params.text_document.uri.to_file_path() { | ||
972 | Ok(mut path) => { | ||
973 | // pop off file name | ||
974 | if path.pop() && path.is_dir() { | ||
975 | cmd.current_dir(path); | ||
976 | } | ||
977 | } | ||
978 | Err(_) => { | ||
979 | log::error!( | ||
980 | "Unable to get file path for {}, rustfmt.toml might be ignored", | ||
981 | params.text_document.uri | ||
982 | ); | ||
983 | } | ||
984 | } | ||
985 | if let Some(&crate_id) = crate_ids.first() { | ||
986 | // Assume all crates are in the same edition | ||
987 | let edition = snap.analysis.crate_edition(crate_id)?; | ||
988 | cmd.arg("--edition"); | ||
989 | cmd.arg(edition.to_string()); | ||
990 | } | ||
991 | cmd | ||
992 | } | ||
993 | RustfmtConfig::CustomCommand { command, args } => { | ||
994 | let mut cmd = process::Command::new(command); | ||
995 | cmd.args(args); | ||
996 | cmd | ||
997 | } | ||
998 | }; | ||
999 | 958 | ||
1000 | let mut rustfmt = | 959 | run_rustfmt(&snap, params.text_document, None) |
1001 | rustfmt.stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::piped()).spawn()?; | 960 | } |
1002 | |||
1003 | rustfmt.stdin.as_mut().unwrap().write_all(file.as_bytes())?; | ||
1004 | |||
1005 | let output = rustfmt.wait_with_output()?; | ||
1006 | let captured_stdout = String::from_utf8(output.stdout)?; | ||
1007 | let captured_stderr = String::from_utf8(output.stderr).unwrap_or_default(); | ||
1008 | |||
1009 | if !output.status.success() { | ||
1010 | let rustfmt_not_installed = | ||
1011 | captured_stderr.contains("not installed") || captured_stderr.contains("not available"); | ||
1012 | |||
1013 | return match output.status.code() { | ||
1014 | Some(1) if !rustfmt_not_installed => { | ||
1015 | // While `rustfmt` doesn't have a specific exit code for parse errors this is the | ||
1016 | // likely cause exiting with 1. Most Language Servers swallow parse errors on | ||
1017 | // formatting because otherwise an error is surfaced to the user on top of the | ||
1018 | // syntax error diagnostics they're already receiving. This is especially jarring | ||
1019 | // if they have format on save enabled. | ||
1020 | log::info!("rustfmt exited with status 1, assuming parse error and ignoring"); | ||
1021 | Ok(None) | ||
1022 | } | ||
1023 | _ => { | ||
1024 | // Something else happened - e.g. `rustfmt` is missing or caught a signal | ||
1025 | Err(LspError::new( | ||
1026 | -32900, | ||
1027 | format!( | ||
1028 | r#"rustfmt exited with: | ||
1029 | Status: {} | ||
1030 | stdout: {} | ||
1031 | stderr: {}"#, | ||
1032 | output.status, captured_stdout, captured_stderr, | ||
1033 | ), | ||
1034 | ) | ||
1035 | .into()) | ||
1036 | } | ||
1037 | }; | ||
1038 | } | ||
1039 | 961 | ||
1040 | let (new_text, new_line_endings) = LineEndings::normalize(captured_stdout); | 962 | pub(crate) fn handle_range_formatting( |
963 | snap: GlobalStateSnapshot, | ||
964 | params: lsp_types::DocumentRangeFormattingParams, | ||
965 | ) -> Result<Option<Vec<lsp_types::TextEdit>>> { | ||
966 | let _p = profile::span("handle_range_formatting"); | ||
1041 | 967 | ||
1042 | if line_index.endings != new_line_endings { | 968 | run_rustfmt(&snap, params.text_document, Some(params.range)) |
1043 | // If line endings are different, send the entire file. | ||
1044 | // Diffing would not work here, as the line endings might be the only | ||
1045 | // difference. | ||
1046 | Ok(Some(to_proto::text_edit_vec( | ||
1047 | &line_index, | ||
1048 | TextEdit::replace(TextRange::up_to(TextSize::of(&*file)), new_text), | ||
1049 | ))) | ||
1050 | } else if *file == new_text { | ||
1051 | // The document is already formatted correctly -- no edits needed. | ||
1052 | Ok(None) | ||
1053 | } else { | ||
1054 | Ok(Some(to_proto::text_edit_vec(&line_index, diff(&file, &new_text)))) | ||
1055 | } | ||
1056 | } | 969 | } |
1057 | 970 | ||
1058 | pub(crate) fn handle_code_action( | 971 | pub(crate) fn handle_code_action( |
@@ -1675,6 +1588,140 @@ fn should_skip_target(runnable: &Runnable, cargo_spec: Option<&CargoTargetSpec>) | |||
1675 | } | 1588 | } |
1676 | } | 1589 | } |
1677 | 1590 | ||
1591 | fn run_rustfmt( | ||
1592 | snap: &GlobalStateSnapshot, | ||
1593 | text_document: TextDocumentIdentifier, | ||
1594 | range: Option<lsp_types::Range>, | ||
1595 | ) -> Result<Option<Vec<lsp_types::TextEdit>>> { | ||
1596 | let file_id = from_proto::file_id(&snap, &text_document.uri)?; | ||
1597 | let file = snap.analysis.file_text(file_id)?; | ||
1598 | let crate_ids = snap.analysis.crate_for(file_id)?; | ||
1599 | |||
1600 | let line_index = snap.file_line_index(file_id)?; | ||
1601 | |||
1602 | let mut rustfmt = match snap.config.rustfmt() { | ||
1603 | RustfmtConfig::Rustfmt { extra_args, enable_range_formatting } => { | ||
1604 | let mut cmd = process::Command::new(toolchain::rustfmt()); | ||
1605 | cmd.args(extra_args); | ||
1606 | // try to chdir to the file so we can respect `rustfmt.toml` | ||
1607 | // FIXME: use `rustfmt --config-path` once | ||
1608 | // https://github.com/rust-lang/rustfmt/issues/4660 gets fixed | ||
1609 | match text_document.uri.to_file_path() { | ||
1610 | Ok(mut path) => { | ||
1611 | // pop off file name | ||
1612 | if path.pop() && path.is_dir() { | ||
1613 | cmd.current_dir(path); | ||
1614 | } | ||
1615 | } | ||
1616 | Err(_) => { | ||
1617 | log::error!( | ||
1618 | "Unable to get file path for {}, rustfmt.toml might be ignored", | ||
1619 | text_document.uri | ||
1620 | ); | ||
1621 | } | ||
1622 | } | ||
1623 | if let Some(&crate_id) = crate_ids.first() { | ||
1624 | // Assume all crates are in the same edition | ||
1625 | let edition = snap.analysis.crate_edition(crate_id)?; | ||
1626 | cmd.arg("--edition"); | ||
1627 | cmd.arg(edition.to_string()); | ||
1628 | } | ||
1629 | |||
1630 | if let Some(range) = range { | ||
1631 | if !enable_range_formatting { | ||
1632 | return Err(LspError::new( | ||
1633 | ErrorCode::InvalidRequest as i32, | ||
1634 | String::from( | ||
1635 | "rustfmt range formatting is unstable. \ | ||
1636 | Opt-in by using a nightly build of rustfmt and setting \ | ||
1637 | `rustfmt.enableRangeFormatting` to true in your LSP configuration", | ||
1638 | ), | ||
1639 | ) | ||
1640 | .into()); | ||
1641 | } | ||
1642 | |||
1643 | let frange = from_proto::file_range(&snap, text_document.clone(), range)?; | ||
1644 | let start_line = line_index.index.line_col(frange.range.start()).line; | ||
1645 | let end_line = line_index.index.line_col(frange.range.end()).line; | ||
1646 | |||
1647 | cmd.arg("--unstable-features"); | ||
1648 | cmd.arg("--file-lines"); | ||
1649 | cmd.arg( | ||
1650 | json!([{ | ||
1651 | "file": "stdin", | ||
1652 | "range": [start_line, end_line] | ||
1653 | }]) | ||
1654 | .to_string(), | ||
1655 | ); | ||
1656 | } | ||
1657 | |||
1658 | cmd | ||
1659 | } | ||
1660 | RustfmtConfig::CustomCommand { command, args } => { | ||
1661 | let mut cmd = process::Command::new(command); | ||
1662 | cmd.args(args); | ||
1663 | cmd | ||
1664 | } | ||
1665 | }; | ||
1666 | |||
1667 | let mut rustfmt = | ||
1668 | rustfmt.stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::piped()).spawn()?; | ||
1669 | |||
1670 | rustfmt.stdin.as_mut().unwrap().write_all(file.as_bytes())?; | ||
1671 | |||
1672 | let output = rustfmt.wait_with_output()?; | ||
1673 | let captured_stdout = String::from_utf8(output.stdout)?; | ||
1674 | let captured_stderr = String::from_utf8(output.stderr).unwrap_or_default(); | ||
1675 | |||
1676 | if !output.status.success() { | ||
1677 | let rustfmt_not_installed = | ||
1678 | captured_stderr.contains("not installed") || captured_stderr.contains("not available"); | ||
1679 | |||
1680 | return match output.status.code() { | ||
1681 | Some(1) if !rustfmt_not_installed => { | ||
1682 | // While `rustfmt` doesn't have a specific exit code for parse errors this is the | ||
1683 | // likely cause exiting with 1. Most Language Servers swallow parse errors on | ||
1684 | // formatting because otherwise an error is surfaced to the user on top of the | ||
1685 | // syntax error diagnostics they're already receiving. This is especially jarring | ||
1686 | // if they have format on save enabled. | ||
1687 | log::info!("rustfmt exited with status 1, assuming parse error and ignoring"); | ||
1688 | Ok(None) | ||
1689 | } | ||
1690 | _ => { | ||
1691 | // Something else happened - e.g. `rustfmt` is missing or caught a signal | ||
1692 | Err(LspError::new( | ||
1693 | -32900, | ||
1694 | format!( | ||
1695 | r#"rustfmt exited with: | ||
1696 | Status: {} | ||
1697 | stdout: {} | ||
1698 | stderr: {}"#, | ||
1699 | output.status, captured_stdout, captured_stderr, | ||
1700 | ), | ||
1701 | ) | ||
1702 | .into()) | ||
1703 | } | ||
1704 | }; | ||
1705 | } | ||
1706 | |||
1707 | let (new_text, new_line_endings) = LineEndings::normalize(captured_stdout); | ||
1708 | |||
1709 | if line_index.endings != new_line_endings { | ||
1710 | // If line endings are different, send the entire file. | ||
1711 | // Diffing would not work here, as the line endings might be the only | ||
1712 | // difference. | ||
1713 | Ok(Some(to_proto::text_edit_vec( | ||
1714 | &line_index, | ||
1715 | TextEdit::replace(TextRange::up_to(TextSize::of(&*file)), new_text), | ||
1716 | ))) | ||
1717 | } else if *file == new_text { | ||
1718 | // The document is already formatted correctly -- no edits needed. | ||
1719 | Ok(None) | ||
1720 | } else { | ||
1721 | Ok(Some(to_proto::text_edit_vec(&line_index, diff(&file, &new_text)))) | ||
1722 | } | ||
1723 | } | ||
1724 | |||
1678 | #[derive(Debug, Serialize, Deserialize)] | 1725 | #[derive(Debug, Serialize, Deserialize)] |
1679 | struct CompletionResolveData { | 1726 | struct CompletionResolveData { |
1680 | position: lsp_types::TextDocumentPositionParams, | 1727 | position: lsp_types::TextDocumentPositionParams, |
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index cb002f700..008758ea0 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs | |||
@@ -543,6 +543,7 @@ impl GlobalState { | |||
543 | .on::<lsp_types::request::Rename>(handlers::handle_rename) | 543 | .on::<lsp_types::request::Rename>(handlers::handle_rename) |
544 | .on::<lsp_types::request::References>(handlers::handle_references) | 544 | .on::<lsp_types::request::References>(handlers::handle_references) |
545 | .on::<lsp_types::request::Formatting>(handlers::handle_formatting) | 545 | .on::<lsp_types::request::Formatting>(handlers::handle_formatting) |
546 | .on::<lsp_types::request::RangeFormatting>(handlers::handle_range_formatting) | ||
546 | .on::<lsp_types::request::DocumentHighlightRequest>(handlers::handle_document_highlight) | 547 | .on::<lsp_types::request::DocumentHighlightRequest>(handlers::handle_document_highlight) |
547 | .on::<lsp_types::request::CallHierarchyPrepare>(handlers::handle_call_hierarchy_prepare) | 548 | .on::<lsp_types::request::CallHierarchyPrepare>(handlers::handle_call_hierarchy_prepare) |
548 | .on::<lsp_types::request::CallHierarchyIncomingCalls>( | 549 | .on::<lsp_types::request::CallHierarchyIncomingCalls>( |