aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock7
-rw-r--r--crates/completion/src/completions/attribute.rs2
-rw-r--r--crates/hir_def/src/nameres.rs2
-rw-r--r--crates/hir_expand/src/builtin_macro.rs3
-rw-r--r--crates/proc_macro_srv/src/rustc_server.rs43
-rw-r--r--crates/project_model/src/cargo_workspace.rs23
-rw-r--r--crates/rust-analyzer/Cargo.toml1
-rw-r--r--crates/rust-analyzer/src/diff.rs53
-rw-r--r--crates/rust-analyzer/src/handlers.rs23
-rw-r--r--crates/rust-analyzer/src/lib.rs1
-rw-r--r--crates/rust-analyzer/tests/rust-analyzer/main.rs31
-rw-r--r--crates/ssr/src/matching.rs4
-rw-r--r--xtask/tests/tidy.rs2
13 files changed, 155 insertions, 40 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 9ddbeac47..1aa0c072d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -349,6 +349,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
349checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" 349checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
350 350
351[[package]] 351[[package]]
352name = "dissimilar"
353version = "1.0.2"
354source = "registry+https://github.com/rust-lang/crates.io-index"
355checksum = "fc4b29f4b9bb94bf267d57269fd0706d343a160937108e9619fe380645428abb"
356
357[[package]]
352name = "drop_bomb" 358name = "drop_bomb"
353version = "0.1.5" 359version = "0.1.5"
354source = "registry+https://github.com/rust-lang/crates.io-index" 360source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1343,6 +1349,7 @@ dependencies = [
1343 "anyhow", 1349 "anyhow",
1344 "cfg", 1350 "cfg",
1345 "crossbeam-channel 0.5.0", 1351 "crossbeam-channel 0.5.0",
1352 "dissimilar",
1346 "env_logger", 1353 "env_logger",
1347 "expect-test", 1354 "expect-test",
1348 "flycheck", 1355 "flycheck",
diff --git a/crates/completion/src/completions/attribute.rs b/crates/completion/src/completions/attribute.rs
index 19ce2482f..8695eed39 100644
--- a/crates/completion/src/completions/attribute.rs
+++ b/crates/completion/src/completions/attribute.rs
@@ -234,7 +234,7 @@ fn parse_comma_sep_input(derive_input: ast::TokenTree) -> Result<FxHashSet<Strin
234 current_derive = String::new(); 234 current_derive = String::new();
235 } 235 }
236 } else { 236 } else {
237 current_derive.push_str(token.to_string().trim()); 237 current_derive.push_str(token.text().trim());
238 } 238 }
239 } 239 }
240 240
diff --git a/crates/hir_def/src/nameres.rs b/crates/hir_def/src/nameres.rs
index 9bf358775..5682e122d 100644
--- a/crates/hir_def/src/nameres.rs
+++ b/crates/hir_def/src/nameres.rs
@@ -454,7 +454,7 @@ mod diagnostics {
454 }); 454 });
455 for token in tokens { 455 for token in tokens {
456 if token.kind() == SyntaxKind::IDENT 456 if token.kind() == SyntaxKind::IDENT
457 && token.to_string() == *name 457 && token.text() == name.as_str()
458 { 458 {
459 precise_location = Some(token.text_range()); 459 precise_location = Some(token.text_range());
460 break 'outer; 460 break 'outer;
diff --git a/crates/hir_expand/src/builtin_macro.rs b/crates/hir_expand/src/builtin_macro.rs
index 6382521fb..80b60d59f 100644
--- a/crates/hir_expand/src/builtin_macro.rs
+++ b/crates/hir_expand/src/builtin_macro.rs
@@ -259,7 +259,8 @@ fn format_args_expand(
259 } 259 }
260 for arg in &mut args { 260 for arg in &mut args {
261 // Remove `key =`. 261 // Remove `key =`.
262 if matches!(arg.get(1), Some(tt::TokenTree::Leaf(tt::Leaf::Punct(p))) if p.char == '=') { 262 if matches!(arg.get(1), Some(tt::TokenTree::Leaf(tt::Leaf::Punct(p))) if p.char == '=' && p.spacing != tt::Spacing::Joint)
263 {
263 arg.drain(..2); 264 arg.drain(..2);
264 } 265 }
265 } 266 }
diff --git a/crates/proc_macro_srv/src/rustc_server.rs b/crates/proc_macro_srv/src/rustc_server.rs
index 503f4c101..b54aa1f3b 100644
--- a/crates/proc_macro_srv/src/rustc_server.rs
+++ b/crates/proc_macro_srv/src/rustc_server.rs
@@ -204,17 +204,18 @@ pub mod token_stream {
204 let content = subtree 204 let content = subtree
205 .token_trees 205 .token_trees
206 .iter() 206 .iter()
207 .map(|tkn| { 207 .fold((String::new(), true), |(last, last_to_joint), tkn| {
208 let s = to_text(tkn); 208 let s = [last, to_text(tkn)].join(if last_to_joint { "" } else { " " });
209 let mut is_joint = false;
209 if let tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) = tkn { 210 if let tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) = tkn {
210 if punct.spacing == tt::Spacing::Alone { 211 if punct.spacing == tt::Spacing::Joint {
211 return s + " "; 212 is_joint = true;
212 } 213 }
213 } 214 }
214 s 215 (s, is_joint)
215 }) 216 })
216 .collect::<Vec<_>>() 217 .0;
217 .concat(); 218
218 let (open, close) = match subtree.delimiter.map(|it| it.kind) { 219 let (open, close) = match subtree.delimiter.map(|it| it.kind) {
219 None => ("", ""), 220 None => ("", ""),
220 Some(tt::DelimiterKind::Brace) => ("{", "}"), 221 Some(tt::DelimiterKind::Brace) => ("{", "}"),
@@ -710,4 +711,32 @@ mod tests {
710 assert_eq!(srv.character('c').text, "'c'"); 711 assert_eq!(srv.character('c').text, "'c'");
711 assert_eq!(srv.byte_string(b"1234586\x88").text, "b\"1234586\\x88\""); 712 assert_eq!(srv.byte_string(b"1234586\x88").text, "b\"1234586\\x88\"");
712 } 713 }
714
715 #[test]
716 fn test_rustc_server_to_string() {
717 let s = TokenStream {
718 subtree: tt::Subtree {
719 delimiter: None,
720 token_trees: vec![
721 tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident {
722 text: "struct".into(),
723 id: tt::TokenId::unspecified(),
724 })),
725 tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident {
726 text: "T".into(),
727 id: tt::TokenId::unspecified(),
728 })),
729 tt::TokenTree::Subtree(tt::Subtree {
730 delimiter: Some(tt::Delimiter {
731 id: tt::TokenId::unspecified(),
732 kind: tt::DelimiterKind::Brace,
733 }),
734 token_trees: vec![],
735 }),
736 ],
737 },
738 };
739
740 assert_eq!(s.to_string(), "struct T {}");
741 }
713} 742}
diff --git a/crates/project_model/src/cargo_workspace.rs b/crates/project_model/src/cargo_workspace.rs
index bb3b6f2ef..1700cb8a7 100644
--- a/crates/project_model/src/cargo_workspace.rs
+++ b/crates/project_model/src/cargo_workspace.rs
@@ -1,6 +1,7 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use std::{ 3use std::{
4 convert::TryInto,
4 ffi::OsStr, 5 ffi::OsStr,
5 ops, 6 ops,
6 path::{Path, PathBuf}, 7 path::{Path, PathBuf},
@@ -196,8 +197,23 @@ impl CargoWorkspace {
196 if let Some(target) = target { 197 if let Some(target) = target {
197 meta.other_options(vec![String::from("--filter-platform"), target]); 198 meta.other_options(vec![String::from("--filter-platform"), target]);
198 } 199 }
200
199 let mut meta = meta.exec().with_context(|| { 201 let mut meta = meta.exec().with_context(|| {
200 format!("Failed to run `cargo metadata --manifest-path {}`", cargo_toml.display()) 202 let cwd: Option<AbsPathBuf> =
203 std::env::current_dir().ok().and_then(|p| p.try_into().ok());
204
205 let workdir = cargo_toml
206 .parent()
207 .map(|p| p.to_path_buf())
208 .or(cwd)
209 .map(|dir| dir.to_string_lossy().to_string())
210 .unwrap_or_else(|| "<failed to get path>".into());
211
212 format!(
213 "Failed to run `cargo metadata --manifest-path {}` in `{}`",
214 cargo_toml.display(),
215 workdir
216 )
201 })?; 217 })?;
202 218
203 let mut out_dir_by_id = FxHashMap::default(); 219 let mut out_dir_by_id = FxHashMap::default();
@@ -334,6 +350,11 @@ pub(crate) fn load_extern_resources(
334 let mut cmd = Command::new(toolchain::cargo()); 350 let mut cmd = Command::new(toolchain::cargo());
335 cmd.args(&["check", "--message-format=json", "--manifest-path"]).arg(cargo_toml); 351 cmd.args(&["check", "--message-format=json", "--manifest-path"]).arg(cargo_toml);
336 352
353 // --all-targets includes tests, benches and examples in addition to the
354 // default lib and bins. This is an independent concept from the --targets
355 // flag below.
356 cmd.arg("--all-targets");
357
337 if let Some(target) = &cargo_features.target { 358 if let Some(target) = &cargo_features.target {
338 cmd.args(&["--target", target]); 359 cmd.args(&["--target", target]);
339 } 360 }
diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml
index 0a002337b..0a63593fb 100644
--- a/crates/rust-analyzer/Cargo.toml
+++ b/crates/rust-analyzer/Cargo.toml
@@ -17,6 +17,7 @@ path = "src/bin/main.rs"
17[dependencies] 17[dependencies]
18anyhow = "1.0.26" 18anyhow = "1.0.26"
19crossbeam-channel = "0.5.0" 19crossbeam-channel = "0.5.0"
20dissimilar = "1.0.2"
20env_logger = { version = "0.8.1", default-features = false } 21env_logger = { version = "0.8.1", default-features = false }
21itertools = "0.10.0" 22itertools = "0.10.0"
22jod-thread = "0.1.0" 23jod-thread = "0.1.0"
diff --git a/crates/rust-analyzer/src/diff.rs b/crates/rust-analyzer/src/diff.rs
new file mode 100644
index 000000000..231be5807
--- /dev/null
+++ b/crates/rust-analyzer/src/diff.rs
@@ -0,0 +1,53 @@
1//! Generate minimal `TextEdit`s from different text versions
2use dissimilar::Chunk;
3use ide::{TextEdit, TextRange, TextSize};
4
5pub(crate) fn diff(left: &str, right: &str) -> TextEdit {
6 let chunks = dissimilar::diff(left, right);
7 textedit_from_chunks(chunks)
8}
9
10fn textedit_from_chunks(chunks: Vec<dissimilar::Chunk>) -> TextEdit {
11 let mut builder = TextEdit::builder();
12 let mut pos = TextSize::default();
13
14 let mut chunks = chunks.into_iter().peekable();
15 while let Some(chunk) = chunks.next() {
16 if let (Chunk::Delete(deleted), Some(&Chunk::Insert(inserted))) = (chunk, chunks.peek()) {
17 chunks.next().unwrap();
18 let deleted_len = TextSize::of(deleted);
19 builder.replace(TextRange::at(pos, deleted_len), inserted.into());
20 pos += deleted_len;
21 continue;
22 }
23
24 match chunk {
25 Chunk::Equal(text) => {
26 pos += TextSize::of(text);
27 }
28 Chunk::Delete(deleted) => {
29 let deleted_len = TextSize::of(deleted);
30 builder.delete(TextRange::at(pos, deleted_len));
31 pos += deleted_len;
32 }
33 Chunk::Insert(inserted) => {
34 builder.insert(pos, inserted.into());
35 }
36 }
37 }
38 builder.finish()
39}
40
41#[cfg(test)]
42mod tests {
43 use super::*;
44
45 #[test]
46 fn diff_applies() {
47 let mut original = String::from("fn foo(a:u32){\n}");
48 let result = "fn foo(a: u32) {}";
49 let edit = diff(&original, result);
50 edit.apply(&mut original);
51 assert_eq!(original, result);
52 }
53}
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index 23f323f55..948cfc17c 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -31,6 +31,7 @@ use serde_json::to_value;
31use stdx::{format_to, split_once}; 31use stdx::{format_to, split_once};
32use syntax::{algo, ast, AstNode, TextRange, TextSize}; 32use syntax::{algo, ast, AstNode, TextRange, TextSize};
33 33
34use crate::diff::diff;
34use crate::{ 35use crate::{
35 cargo_target_spec::CargoTargetSpec, 36 cargo_target_spec::CargoTargetSpec,
36 config::RustfmtConfig, 37 config::RustfmtConfig,
@@ -840,7 +841,7 @@ pub(crate) fn handle_formatting(
840 let crate_ids = snap.analysis.crate_for(file_id)?; 841 let crate_ids = snap.analysis.crate_for(file_id)?;
841 842
842 let file_line_index = snap.analysis.file_line_index(file_id)?; 843 let file_line_index = snap.analysis.file_line_index(file_id)?;
843 let end_position = to_proto::position(&file_line_index, TextSize::of(file.as_str())); 844 let file_line_endings = snap.file_line_endings(file_id);
844 845
845 let mut rustfmt = match &snap.config.rustfmt { 846 let mut rustfmt = match &snap.config.rustfmt {
846 RustfmtConfig::Rustfmt { extra_args } => { 847 RustfmtConfig::Rustfmt { extra_args } => {
@@ -861,16 +862,18 @@ pub(crate) fn handle_formatting(
861 } 862 }
862 }; 863 };
863 864
864 let mut rustfmt = rustfmt.stdin(Stdio::piped()).stdout(Stdio::piped()).spawn()?; 865 let mut rustfmt =
866 rustfmt.stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::piped()).spawn()?;
865 867
866 rustfmt.stdin.as_mut().unwrap().write_all(file.as_bytes())?; 868 rustfmt.stdin.as_mut().unwrap().write_all(file.as_bytes())?;
867 869
868 let output = rustfmt.wait_with_output()?; 870 let output = rustfmt.wait_with_output()?;
869 let captured_stdout = String::from_utf8(output.stdout)?; 871 let captured_stdout = String::from_utf8(output.stdout)?;
872 let captured_stderr = String::from_utf8(output.stderr).unwrap_or_default();
870 873
871 if !output.status.success() { 874 if !output.status.success() {
872 match output.status.code() { 875 match output.status.code() {
873 Some(1) => { 876 Some(1) if !captured_stderr.contains("not installed") => {
874 // While `rustfmt` doesn't have a specific exit code for parse errors this is the 877 // While `rustfmt` doesn't have a specific exit code for parse errors this is the
875 // likely cause exiting with 1. Most Language Servers swallow parse errors on 878 // likely cause exiting with 1. Most Language Servers swallow parse errors on
876 // formatting because otherwise an error is surfaced to the user on top of the 879 // formatting because otherwise an error is surfaced to the user on top of the
@@ -886,8 +889,9 @@ pub(crate) fn handle_formatting(
886 format!( 889 format!(
887 r#"rustfmt exited with: 890 r#"rustfmt exited with:
888 Status: {} 891 Status: {}
889 stdout: {}"#, 892 stdout: {}
890 output.status, captured_stdout, 893 stderr: {}"#,
894 output.status, captured_stdout, captured_stderr,
891 ), 895 ),
892 ) 896 )
893 .into()); 897 .into());
@@ -899,10 +903,11 @@ pub(crate) fn handle_formatting(
899 // The document is already formatted correctly -- no edits needed. 903 // The document is already formatted correctly -- no edits needed.
900 Ok(None) 904 Ok(None)
901 } else { 905 } else {
902 Ok(Some(vec![lsp_types::TextEdit { 906 Ok(Some(to_proto::text_edit_vec(
903 range: Range::new(Position::new(0, 0), end_position), 907 &file_line_index,
904 new_text: captured_stdout, 908 file_line_endings,
905 }])) 909 diff(&file, &captured_stdout),
910 )))
906 } 911 }
907} 912}
908 913
diff --git a/crates/rust-analyzer/src/lib.rs b/crates/rust-analyzer/src/lib.rs
index d538ad69a..c9494e300 100644
--- a/crates/rust-analyzer/src/lib.rs
+++ b/crates/rust-analyzer/src/lib.rs
@@ -34,6 +34,7 @@ mod request_metrics;
34mod lsp_utils; 34mod lsp_utils;
35mod thread_pool; 35mod thread_pool;
36mod document; 36mod document;
37mod diff;
37pub mod lsp_ext; 38pub mod lsp_ext;
38pub mod config; 39pub mod config;
39 40
diff --git a/crates/rust-analyzer/tests/rust-analyzer/main.rs b/crates/rust-analyzer/tests/rust-analyzer/main.rs
index e51eb2626..84db0856d 100644
--- a/crates/rust-analyzer/tests/rust-analyzer/main.rs
+++ b/crates/rust-analyzer/tests/rust-analyzer/main.rs
@@ -190,15 +190,10 @@ pub use std::collections::HashMap;
190 }, 190 },
191 json!([ 191 json!([
192 { 192 {
193 "newText": r#"mod bar; 193 "newText": "",
194
195fn main() {}
196
197pub use std::collections::HashMap;
198"#,
199 "range": { 194 "range": {
200 "end": { "character": 0, "line": 6 }, 195 "end": { "character": 0, "line": 3 },
201 "start": { "character": 0, "line": 0 } 196 "start": { "character": 11, "line": 2 }
202 } 197 }
203 } 198 }
204 ]), 199 ]),
@@ -248,17 +243,17 @@ pub use std::collections::HashMap;
248 }, 243 },
249 json!([ 244 json!([
250 { 245 {
251 "newText": r#"mod bar; 246 "newText": "",
252 247 "range": {
253async fn test() {} 248 "end": { "character": 0, "line": 3 },
254 249 "start": { "character": 17, "line": 2 }
255fn main() {} 250 }
256 251 },
257pub use std::collections::HashMap; 252 {
258"#, 253 "newText": "",
259 "range": { 254 "range": {
260 "end": { "character": 0, "line": 9 }, 255 "end": { "character": 0, "line": 6 },
261 "start": { "character": 0, "line": 0 } 256 "start": { "character": 11, "line": 5 }
262 } 257 }
263 } 258 }
264 ]), 259 ]),
diff --git a/crates/ssr/src/matching.rs b/crates/ssr/src/matching.rs
index 99b187311..6cf831431 100644
--- a/crates/ssr/src/matching.rs
+++ b/crates/ssr/src/matching.rs
@@ -473,7 +473,9 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
473 } 473 }
474 SyntaxElement::Node(n) => { 474 SyntaxElement::Node(n) => {
475 if let Some(first_token) = n.first_token() { 475 if let Some(first_token) = n.first_token() {
476 if Some(first_token.to_string()) == next_pattern_token { 476 if Some(first_token.text().as_str())
477 == next_pattern_token.as_deref()
478 {
477 if let Some(SyntaxElement::Node(p)) = pattern.next() { 479 if let Some(SyntaxElement::Node(p)) = pattern.next() {
478 // We have a subtree that starts with the next token in our pattern. 480 // We have a subtree that starts with the next token in our pattern.
479 self.attempt_match_token_tree(phase, &p, &n)?; 481 self.attempt_match_token_tree(phase, &p, &n)?;
diff --git a/xtask/tests/tidy.rs b/xtask/tests/tidy.rs
index 6bfa922e6..d1ffb70ad 100644
--- a/xtask/tests/tidy.rs
+++ b/xtask/tests/tidy.rs
@@ -139,7 +139,7 @@ fn deny_clippy(path: &PathBuf, text: &String) {
139 return; 139 return;
140 } 140 }
141 141
142 if text.contains("[\u{61}llow(clippy") { 142 if text.contains("\u{61}llow(clippy") {
143 panic!( 143 panic!(
144 "\n\nallowing lints is forbidden: {}. 144 "\n\nallowing lints is forbidden: {}.
145rust-analyzer intentionally doesn't check clippy on CI. 145rust-analyzer intentionally doesn't check clippy on CI.