diff options
72 files changed, 3335 insertions, 2088 deletions
diff --git a/.cargo/config b/.cargo/config index c319d33f2..b9db30c96 100644 --- a/.cargo/config +++ b/.cargo/config | |||
@@ -1,10 +1,16 @@ | |||
1 | [alias] | 1 | [alias] |
2 | # Automatically generates the ast and syntax kinds files | 2 | # Automatically generates the ast and syntax kinds files |
3 | gen-syntax = "run --package tools --bin tools -- gen-syntax" | 3 | gen-syntax = "run --package tools --bin tools -- gen-syntax" |
4 | gen-tests = "run --package tools --bin tools -- gen-tests" | 4 | # Extracts the tests from |
5 | gen-tests = "run --package tools --bin tools -- gen-tests" | ||
6 | # Installs the visual studio code extension | ||
5 | install-code = "run --package tools --bin tools -- install-code" | 7 | install-code = "run --package tools --bin tools -- install-code" |
6 | format = "run --package tools --bin tools -- format" | 8 | # Formats the full repository or installs the git hook to do it automatically. |
7 | format-hook = "run --package tools --bin tools -- format-hook" | 9 | format = "run --package tools --bin tools -- format" |
10 | format-hook = "run --package tools --bin tools -- format-hook" | ||
11 | # Runs the fuzzing test suite (currently only parser) | ||
12 | fuzz-tests = "run --package tools --bin tools -- fuzz-tests" | ||
8 | 13 | ||
9 | render-test = "run --package ra_cli -- render-test" | 14 | render-test = "run --package ra_cli -- render-test" |
10 | parse = "run --package ra_cli -- parse" | 15 | # Parse a file. This should be piped the file contents |
16 | parse = "run --package ra_cli -- parse" | ||
diff --git a/.travis.yml b/.travis.yml index 04bb3be37..86bce93d6 100644 --- a/.travis.yml +++ b/.travis.yml | |||
@@ -10,6 +10,7 @@ build: &rust_build | |||
10 | script: | 10 | script: |
11 | - cargo gen-tests --verify | 11 | - cargo gen-tests --verify |
12 | - cargo gen-syntax --verify | 12 | - cargo gen-syntax --verify |
13 | - cargo test --no-run # let's measure compile time separately | ||
13 | - cargo test | 14 | - cargo test |
14 | env: | 15 | env: |
15 | - RUSTFLAGS="-D warnings", CARGO_INCREMENTAL=0 | 16 | - RUSTFLAGS="-D warnings", CARGO_INCREMENTAL=0 |
@@ -25,7 +26,7 @@ matrix: | |||
25 | - cd editors/code && npm ci && npm run travis | 26 | - cd editors/code && npm ci && npm run travis |
26 | 27 | ||
27 | - os: windows | 28 | - os: windows |
28 | if: branch = master | 29 | if: branch = master AND type = push |
29 | before_script: | 30 | before_script: |
30 | - dos2unix ./crates/ra_syntax/tests/data/parser/**/*.txt | 31 | - dos2unix ./crates/ra_syntax/tests/data/parser/**/*.txt |
31 | - dos2unix ./crates/ra_syntax/tests/data/parser/**/*.rs | 32 | - dos2unix ./crates/ra_syntax/tests/data/parser/**/*.rs |
diff --git a/Cargo.lock b/Cargo.lock index f06da0050..5f35f2872 100644 --- a/Cargo.lock +++ b/Cargo.lock | |||
@@ -48,7 +48,7 @@ version = "0.3.13" | |||
48 | source = "registry+https://github.com/rust-lang/crates.io-index" | 48 | source = "registry+https://github.com/rust-lang/crates.io-index" |
49 | dependencies = [ | 49 | dependencies = [ |
50 | "autocfg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", | 50 | "autocfg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", |
51 | "backtrace-sys 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", | 51 | "backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", |
52 | "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", | 52 | "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", |
53 | "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", | 53 | "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", |
54 | "rustc-demangle 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", | 54 | "rustc-demangle 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", |
@@ -57,10 +57,10 @@ dependencies = [ | |||
57 | 57 | ||
58 | [[package]] | 58 | [[package]] |
59 | name = "backtrace-sys" | 59 | name = "backtrace-sys" |
60 | version = "0.1.26" | 60 | version = "0.1.28" |
61 | source = "registry+https://github.com/rust-lang/crates.io-index" | 61 | source = "registry+https://github.com/rust-lang/crates.io-index" |
62 | dependencies = [ | 62 | dependencies = [ |
63 | "cc 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)", | 63 | "cc 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)", |
64 | "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", | 64 | "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", |
65 | ] | 65 | ] |
66 | 66 | ||
@@ -117,14 +117,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
117 | dependencies = [ | 117 | dependencies = [ |
118 | "error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", | 118 | "error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", |
119 | "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", | 119 | "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", |
120 | "serde 1.0.83 (registry+https://github.com/rust-lang/crates.io-index)", | 120 | "serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", |
121 | "serde_derive 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)", | 121 | "serde_derive 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", |
122 | "serde_json 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)", | 122 | "serde_json 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)", |
123 | ] | 123 | ] |
124 | 124 | ||
125 | [[package]] | 125 | [[package]] |
126 | name = "cc" | 126 | name = "cc" |
127 | version = "1.0.27" | 127 | version = "1.0.28" |
128 | source = "registry+https://github.com/rust-lang/crates.io-index" | 128 | source = "registry+https://github.com/rust-lang/crates.io-index" |
129 | 129 | ||
130 | [[package]] | 130 | [[package]] |
@@ -340,8 +340,8 @@ dependencies = [ | |||
340 | "failure 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", | 340 | "failure 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", |
341 | "languageserver-types 0.53.1 (registry+https://github.com/rust-lang/crates.io-index)", | 341 | "languageserver-types 0.53.1 (registry+https://github.com/rust-lang/crates.io-index)", |
342 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", | 342 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", |
343 | "serde 1.0.83 (registry+https://github.com/rust-lang/crates.io-index)", | 343 | "serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", |
344 | "serde_json 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)", | 344 | "serde_json 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)", |
345 | ] | 345 | ] |
346 | 346 | ||
347 | [[package]] | 347 | [[package]] |
@@ -425,9 +425,9 @@ dependencies = [ | |||
425 | "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", | 425 | "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", |
426 | "num-derive 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", | 426 | "num-derive 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", |
427 | "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", | 427 | "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", |
428 | "serde 1.0.83 (registry+https://github.com/rust-lang/crates.io-index)", | 428 | "serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", |
429 | "serde_derive 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)", | 429 | "serde_derive 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", |
430 | "serde_json 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)", | 430 | "serde_json 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)", |
431 | "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", | 431 | "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", |
432 | "url_serde 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", | 432 | "url_serde 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", |
433 | ] | 433 | ] |
@@ -587,7 +587,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
587 | 587 | ||
588 | [[package]] | 588 | [[package]] |
589 | name = "pest" | 589 | name = "pest" |
590 | version = "2.0.2" | 590 | version = "2.1.0" |
591 | source = "registry+https://github.com/rust-lang/crates.io-index" | 591 | source = "registry+https://github.com/rust-lang/crates.io-index" |
592 | dependencies = [ | 592 | dependencies = [ |
593 | "ucd-trie 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", | 593 | "ucd-trie 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", |
@@ -595,32 +595,32 @@ dependencies = [ | |||
595 | 595 | ||
596 | [[package]] | 596 | [[package]] |
597 | name = "pest_derive" | 597 | name = "pest_derive" |
598 | version = "2.0.1" | 598 | version = "2.1.0" |
599 | source = "registry+https://github.com/rust-lang/crates.io-index" | 599 | source = "registry+https://github.com/rust-lang/crates.io-index" |
600 | dependencies = [ | 600 | dependencies = [ |
601 | "pest 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", | 601 | "pest 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", |
602 | "pest_generator 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", | 602 | "pest_generator 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", |
603 | ] | 603 | ] |
604 | 604 | ||
605 | [[package]] | 605 | [[package]] |
606 | name = "pest_generator" | 606 | name = "pest_generator" |
607 | version = "2.0.0" | 607 | version = "2.1.0" |
608 | source = "registry+https://github.com/rust-lang/crates.io-index" | 608 | source = "registry+https://github.com/rust-lang/crates.io-index" |
609 | dependencies = [ | 609 | dependencies = [ |
610 | "pest 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", | 610 | "pest 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", |
611 | "pest_meta 2.0.3 (registry+https://github.com/rust-lang/crates.io-index)", | 611 | "pest_meta 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", |
612 | "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", | 612 | "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", |
613 | "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", | 613 | "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", |
614 | "syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)", | 614 | "syn 0.15.23 (registry+https://github.com/rust-lang/crates.io-index)", |
615 | ] | 615 | ] |
616 | 616 | ||
617 | [[package]] | 617 | [[package]] |
618 | name = "pest_meta" | 618 | name = "pest_meta" |
619 | version = "2.0.3" | 619 | version = "2.1.0" |
620 | source = "registry+https://github.com/rust-lang/crates.io-index" | 620 | source = "registry+https://github.com/rust-lang/crates.io-index" |
621 | dependencies = [ | 621 | dependencies = [ |
622 | "maplit 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", | 622 | "maplit 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", |
623 | "pest 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", | 623 | "pest 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", |
624 | "sha-1 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", | 624 | "sha-1 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", |
625 | ] | 625 | ] |
626 | 626 | ||
@@ -667,6 +667,7 @@ name = "ra_analysis" | |||
667 | version = "0.1.0" | 667 | version = "0.1.0" |
668 | dependencies = [ | 668 | dependencies = [ |
669 | "fst 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", | 669 | "fst 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", |
670 | "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||
670 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", | 671 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", |
671 | "parking_lot 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", | 672 | "parking_lot 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", |
672 | "ra_db 0.1.0", | 673 | "ra_db 0.1.0", |
@@ -679,6 +680,7 @@ dependencies = [ | |||
679 | "rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", | 680 | "rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", |
680 | "salsa 0.9.0 (git+https://github.com/matklad/salsa.git?branch=transitive-untracked)", | 681 | "salsa 0.9.0 (git+https://github.com/matklad/salsa.git?branch=transitive-untracked)", |
681 | "test_utils 0.1.0", | 682 | "test_utils 0.1.0", |
683 | "unicase 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||
682 | ] | 684 | ] |
683 | 685 | ||
684 | [[package]] | 686 | [[package]] |
@@ -762,8 +764,8 @@ dependencies = [ | |||
762 | "rayon 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", | 764 | "rayon 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", |
763 | "relative-path 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", | 765 | "relative-path 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", |
764 | "rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", | 766 | "rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", |
765 | "serde 1.0.83 (registry+https://github.com/rust-lang/crates.io-index)", | 767 | "serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", |
766 | "serde_json 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)", | 768 | "serde_json 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)", |
767 | "smol_str 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", | 769 | "smol_str 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", |
768 | "tempfile 3.0.5 (registry+https://github.com/rust-lang/crates.io-index)", | 770 | "tempfile 3.0.5 (registry+https://github.com/rust-lang/crates.io-index)", |
769 | "test_utils 0.1.0", | 771 | "test_utils 0.1.0", |
@@ -921,7 +923,7 @@ dependencies = [ | |||
921 | 923 | ||
922 | [[package]] | 924 | [[package]] |
923 | name = "redox_syscall" | 925 | name = "redox_syscall" |
924 | version = "0.1.44" | 926 | version = "0.1.50" |
925 | source = "registry+https://github.com/rust-lang/crates.io-index" | 927 | source = "registry+https://github.com/rust-lang/crates.io-index" |
926 | 928 | ||
927 | [[package]] | 929 | [[package]] |
@@ -929,7 +931,7 @@ name = "redox_termios" | |||
929 | version = "0.1.1" | 931 | version = "0.1.1" |
930 | source = "registry+https://github.com/rust-lang/crates.io-index" | 932 | source = "registry+https://github.com/rust-lang/crates.io-index" |
931 | dependencies = [ | 933 | dependencies = [ |
932 | "redox_syscall 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)", | 934 | "redox_syscall 0.1.50 (registry+https://github.com/rust-lang/crates.io-index)", |
933 | ] | 935 | ] |
934 | 936 | ||
935 | [[package]] | 937 | [[package]] |
@@ -972,7 +974,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
972 | dependencies = [ | 974 | dependencies = [ |
973 | "base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", | 975 | "base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", |
974 | "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", | 976 | "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", |
975 | "serde 1.0.83 (registry+https://github.com/rust-lang/crates.io-index)", | 977 | "serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", |
976 | ] | 978 | ] |
977 | 979 | ||
978 | [[package]] | 980 | [[package]] |
@@ -1060,7 +1062,7 @@ version = "0.9.0" | |||
1060 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1062 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1061 | dependencies = [ | 1063 | dependencies = [ |
1062 | "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", | 1064 | "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1063 | "serde 1.0.83 (registry+https://github.com/rust-lang/crates.io-index)", | 1065 | "serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", |
1064 | ] | 1066 | ] |
1065 | 1067 | ||
1066 | [[package]] | 1068 | [[package]] |
@@ -1070,15 +1072,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
1070 | 1072 | ||
1071 | [[package]] | 1073 | [[package]] |
1072 | name = "serde" | 1074 | name = "serde" |
1073 | version = "1.0.83" | 1075 | version = "1.0.84" |
1074 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1076 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1075 | dependencies = [ | 1077 | dependencies = [ |
1076 | "serde_derive 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)", | 1078 | "serde_derive 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", |
1077 | ] | 1079 | ] |
1078 | 1080 | ||
1079 | [[package]] | 1081 | [[package]] |
1080 | name = "serde_derive" | 1082 | name = "serde_derive" |
1081 | version = "1.0.82" | 1083 | version = "1.0.84" |
1082 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1084 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1083 | dependencies = [ | 1085 | dependencies = [ |
1084 | "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", | 1086 | "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", |
@@ -1088,12 +1090,12 @@ dependencies = [ | |||
1088 | 1090 | ||
1089 | [[package]] | 1091 | [[package]] |
1090 | name = "serde_json" | 1092 | name = "serde_json" |
1091 | version = "1.0.33" | 1093 | version = "1.0.34" |
1092 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1094 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1093 | dependencies = [ | 1095 | dependencies = [ |
1094 | "itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", | 1096 | "itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", |
1095 | "ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", | 1097 | "ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", |
1096 | "serde 1.0.83 (registry+https://github.com/rust-lang/crates.io-index)", | 1098 | "serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", |
1097 | ] | 1099 | ] |
1098 | 1100 | ||
1099 | [[package]] | 1101 | [[package]] |
@@ -1128,7 +1130,7 @@ name = "smol_str" | |||
1128 | version = "0.1.8" | 1130 | version = "0.1.8" |
1129 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1131 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1130 | dependencies = [ | 1132 | dependencies = [ |
1131 | "serde 1.0.83 (registry+https://github.com/rust-lang/crates.io-index)", | 1133 | "serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", |
1132 | ] | 1134 | ] |
1133 | 1135 | ||
1134 | [[package]] | 1136 | [[package]] |
@@ -1148,16 +1150,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
1148 | 1150 | ||
1149 | [[package]] | 1151 | [[package]] |
1150 | name = "syn" | 1152 | name = "syn" |
1151 | version = "0.14.9" | ||
1152 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
1153 | dependencies = [ | ||
1154 | "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", | ||
1155 | "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", | ||
1156 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||
1157 | ] | ||
1158 | |||
1159 | [[package]] | ||
1160 | name = "syn" | ||
1161 | version = "0.15.23" | 1153 | version = "0.15.23" |
1162 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1154 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1163 | dependencies = [ | 1155 | dependencies = [ |
@@ -1185,7 +1177,7 @@ dependencies = [ | |||
1185 | "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", | 1177 | "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", |
1186 | "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", | 1178 | "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", |
1187 | "rand 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", | 1179 | "rand 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", |
1188 | "redox_syscall 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)", | 1180 | "redox_syscall 0.1.50 (registry+https://github.com/rust-lang/crates.io-index)", |
1189 | "remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", | 1181 | "remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", |
1190 | "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", | 1182 | "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", |
1191 | ] | 1183 | ] |
@@ -1200,11 +1192,11 @@ dependencies = [ | |||
1200 | "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", | 1192 | "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", |
1201 | "humansize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", | 1193 | "humansize 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1202 | "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", | 1194 | "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1203 | "pest 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", | 1195 | "pest 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1204 | "pest_derive 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", | 1196 | "pest_derive 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1205 | "regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", | 1197 | "regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1206 | "serde 1.0.83 (registry+https://github.com/rust-lang/crates.io-index)", | 1198 | "serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", |
1207 | "serde_json 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)", | 1199 | "serde_json 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)", |
1208 | "slug 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", | 1200 | "slug 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", |
1209 | "unic-segment 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", | 1201 | "unic-segment 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1210 | "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", | 1202 | "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", |
@@ -1227,7 +1219,7 @@ version = "1.5.1" | |||
1227 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1219 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1228 | dependencies = [ | 1220 | dependencies = [ |
1229 | "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", | 1221 | "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", |
1230 | "redox_syscall 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)", | 1222 | "redox_syscall 0.1.50 (registry+https://github.com/rust-lang/crates.io-index)", |
1231 | "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", | 1223 | "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", |
1232 | ] | 1224 | ] |
1233 | 1225 | ||
@@ -1237,7 +1229,7 @@ version = "0.1.0" | |||
1237 | dependencies = [ | 1229 | dependencies = [ |
1238 | "difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", | 1230 | "difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1239 | "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", | 1231 | "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1240 | "serde_json 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)", | 1232 | "serde_json 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)", |
1241 | "text_unit 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", | 1233 | "text_unit 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", |
1242 | ] | 1234 | ] |
1243 | 1235 | ||
@@ -1246,7 +1238,7 @@ name = "text_unit" | |||
1246 | version = "0.1.5" | 1238 | version = "0.1.5" |
1247 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1239 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1248 | dependencies = [ | 1240 | dependencies = [ |
1249 | "serde 1.0.83 (registry+https://github.com/rust-lang/crates.io-index)", | 1241 | "serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", |
1250 | ] | 1242 | ] |
1251 | 1243 | ||
1252 | [[package]] | 1244 | [[package]] |
@@ -1288,7 +1280,7 @@ version = "0.1.41" | |||
1288 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1280 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1289 | dependencies = [ | 1281 | dependencies = [ |
1290 | "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", | 1282 | "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", |
1291 | "redox_syscall 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)", | 1283 | "redox_syscall 0.1.50 (registry+https://github.com/rust-lang/crates.io-index)", |
1292 | "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", | 1284 | "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", |
1293 | ] | 1285 | ] |
1294 | 1286 | ||
@@ -1363,6 +1355,14 @@ dependencies = [ | |||
1363 | ] | 1355 | ] |
1364 | 1356 | ||
1365 | [[package]] | 1357 | [[package]] |
1358 | name = "unicase" | ||
1359 | version = "2.2.0" | ||
1360 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
1361 | dependencies = [ | ||
1362 | "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", | ||
1363 | ] | ||
1364 | |||
1365 | [[package]] | ||
1366 | name = "unicode-bidi" | 1366 | name = "unicode-bidi" |
1367 | version = "0.3.4" | 1367 | version = "0.3.4" |
1368 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1368 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -1413,7 +1413,7 @@ name = "url_serde" | |||
1413 | version = "0.2.0" | 1413 | version = "0.2.0" |
1414 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1414 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1415 | dependencies = [ | 1415 | dependencies = [ |
1416 | "serde 1.0.83 (registry+https://github.com/rust-lang/crates.io-index)", | 1416 | "serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)", |
1417 | "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", | 1417 | "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", |
1418 | ] | 1418 | ] |
1419 | 1419 | ||
@@ -1490,7 +1490,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
1490 | "checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" | 1490 | "checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" |
1491 | "checksum autocfg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4e5f34df7a019573fb8bdc7e24a2bfebe51a2a1d6bfdbaeccedb3c41fc574727" | 1491 | "checksum autocfg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4e5f34df7a019573fb8bdc7e24a2bfebe51a2a1d6bfdbaeccedb3c41fc574727" |
1492 | "checksum backtrace 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)" = "b5b493b66e03090ebc4343eb02f94ff944e0cbc9ac6571491d170ba026741eb5" | 1492 | "checksum backtrace 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)" = "b5b493b66e03090ebc4343eb02f94ff944e0cbc9ac6571491d170ba026741eb5" |
1493 | "checksum backtrace-sys 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)" = "3fcce89e5ad5c8949caa9434501f7b55415b3e7ad5270cb88c75a8d35e8f1279" | 1493 | "checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6" |
1494 | "checksum base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643" | 1494 | "checksum base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643" |
1495 | "checksum bit-set 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6f1efcc46c18245a69c38fcc5cc650f16d3a59d034f3106e9ed63748f695730a" | 1495 | "checksum bit-set 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6f1efcc46c18245a69c38fcc5cc650f16d3a59d034f3106e9ed63748f695730a" |
1496 | "checksum bit-vec 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4440d5cb623bb7390ae27fec0bb6c61111969860f8e3ae198bfa0663645e67cf" | 1496 | "checksum bit-vec 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4440d5cb623bb7390ae27fec0bb6c61111969860f8e3ae198bfa0663645e67cf" |
@@ -1499,7 +1499,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
1499 | "checksum byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40" | 1499 | "checksum byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40" |
1500 | "checksum byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "94f88df23a25417badc922ab0f5716cc1330e87f71ddd9203b3a3ccd9cedf75d" | 1500 | "checksum byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "94f88df23a25417badc922ab0f5716cc1330e87f71ddd9203b3a3ccd9cedf75d" |
1501 | "checksum cargo_metadata 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "e5d1b4d380e1bab994591a24c2bdd1b054f64b60bef483a8c598c7c345bc3bbe" | 1501 | "checksum cargo_metadata 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "e5d1b4d380e1bab994591a24c2bdd1b054f64b60bef483a8c598c7c345bc3bbe" |
1502 | "checksum cc 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)" = "155ed195f7bd722d1dfeb30365b9d0c1f6a078fa7ca4014497e5935d90993d6f" | 1502 | "checksum cc 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4a8b715cb4597106ea87c7c84b2f1d452c7492033765df7f32651e66fcf749" |
1503 | "checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4" | 1503 | "checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4" |
1504 | "checksum chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "45912881121cb26fad7c38c17ba7daa18764771836b34fab7d3fbd93ed633878" | 1504 | "checksum chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "45912881121cb26fad7c38c17ba7daa18764771836b34fab7d3fbd93ed633878" |
1505 | "checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e" | 1505 | "checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e" |
@@ -1557,10 +1557,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
1557 | "checksum parking_lot_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ad7f7e6ebdc79edff6fdcb87a55b620174f7a989e3eb31b65231f4af57f00b8c" | 1557 | "checksum parking_lot_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ad7f7e6ebdc79edff6fdcb87a55b620174f7a989e3eb31b65231f4af57f00b8c" |
1558 | "checksum parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94c8c7923936b28d546dfd14d4472eaf34c99b14e1c973a32b3e6d4eb04298c9" | 1558 | "checksum parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94c8c7923936b28d546dfd14d4472eaf34c99b14e1c973a32b3e6d4eb04298c9" |
1559 | "checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" | 1559 | "checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" |
1560 | "checksum pest 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a677051ad923732bb5c70f2d45f8985a96e3eee2e2bff86697e3b11b0c3fcfde" | 1560 | "checksum pest 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "54f0c72a98d8ab3c99560bfd16df8059cc10e1f9a8e83e6e3b97718dd766e9c3" |
1561 | "checksum pest_derive 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b76f477146419bc539a63f4ef40e902166cb43b3e51cecc71d9136fd12c567e7" | 1561 | "checksum pest_derive 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" |
1562 | "checksum pest_generator 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ebee4e9680be4fd162e6f3394ae4192a6b60b1e4d17d845e631f0c68d1a3386" | 1562 | "checksum pest_generator 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "63120576c4efd69615b5537d3d052257328a4ca82876771d6944424ccfd9f646" |
1563 | "checksum pest_meta 2.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1f6d5f6f0e6082578c86af197d780dc38328e3f768cec06aac9bc46d714e8221" | 1563 | "checksum pest_meta 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f5a3492a4ed208ffc247adcdcc7ba2a95be3104f58877d0d02f0df39bf3efb5e" |
1564 | "checksum proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)" = "77619697826f31a02ae974457af0b29b723e5619e113e9397b8b82c6bd253f09" | 1564 | "checksum proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)" = "77619697826f31a02ae974457af0b29b723e5619e113e9397b8b82c6bd253f09" |
1565 | "checksum proptest 0.8.7 (registry+https://github.com/rust-lang/crates.io-index)" = "926d0604475349f463fe44130aae73f2294b5309ab2ca0310b998bd334ef191f" | 1565 | "checksum proptest 0.8.7 (registry+https://github.com/rust-lang/crates.io-index)" = "926d0604475349f463fe44130aae73f2294b5309ab2ca0310b998bd334ef191f" |
1566 | "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" | 1566 | "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" |
@@ -1576,7 +1576,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
1576 | "checksum rand_xorshift 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "effa3fcaa47e18db002bdde6060944b6d2f9cfd8db471c30e873448ad9187be3" | 1576 | "checksum rand_xorshift 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "effa3fcaa47e18db002bdde6060944b6d2f9cfd8db471c30e873448ad9187be3" |
1577 | "checksum rayon 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "373814f27745b2686b350dd261bfd24576a6fb0e2c5919b3a2b6005f820b0473" | 1577 | "checksum rayon 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "373814f27745b2686b350dd261bfd24576a6fb0e2c5919b3a2b6005f820b0473" |
1578 | "checksum rayon-core 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b055d1e92aba6877574d8fe604a63c8b5df60f60e5982bf7ccbb1338ea527356" | 1578 | "checksum rayon-core 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b055d1e92aba6877574d8fe604a63c8b5df60f60e5982bf7ccbb1338ea527356" |
1579 | "checksum redox_syscall 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)" = "a84bcd297b87a545980a2d25a0beb72a1f490c31f0a9fde52fca35bfbb1ceb70" | 1579 | "checksum redox_syscall 0.1.50 (registry+https://github.com/rust-lang/crates.io-index)" = "52ee9a534dc1301776eff45b4fa92d2c39b1d8c3d3357e6eb593e0d795506fc2" |
1580 | "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" | 1580 | "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" |
1581 | "checksum regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "37e7cbbd370869ce2e8dff25c7018702d10b21a20ef7135316f8daecd6c25b7f" | 1581 | "checksum regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "37e7cbbd370869ce2e8dff25c7018702d10b21a20ef7135316f8daecd6c25b7f" |
1582 | "checksum regex-syntax 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4e47a2ed29da7a9e1960e1639e7a982e6edc6d49be308a3b02daf511504a16d1" | 1582 | "checksum regex-syntax 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4e47a2ed29da7a9e1960e1639e7a982e6edc6d49be308a3b02daf511504a16d1" |
@@ -1595,9 +1595,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
1595 | "checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" | 1595 | "checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" |
1596 | "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" | 1596 | "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" |
1597 | "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" | 1597 | "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" |
1598 | "checksum serde 1.0.83 (registry+https://github.com/rust-lang/crates.io-index)" = "157e12af46859e968da75dea9845530e13d03bcab2009a41b9b7bb3cf4eb3ec2" | 1598 | "checksum serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)" = "0e732ed5a5592c17d961555e3b552985baf98d50ce418b7b655f31f6ba7eb1b7" |
1599 | "checksum serde_derive 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)" = "96a7f9496ac65a2db5929afa087b54f8fc5008dcfbe48a8874ed20049b0d6154" | 1599 | "checksum serde_derive 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d6115a3ca25c224e409185325afc16a0d5aaaabc15c42b09587d6f1ba39a5b" |
1600 | "checksum serde_json 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)" = "c37ccd6be3ed1fdf419ee848f7c758eb31b054d7cd3ae3600e3bae0adf569811" | 1600 | "checksum serde_json 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)" = "bdf540260cfee6da923831f4776ddc495ada940c30117977c70f1313a6130545" |
1601 | "checksum sha-1 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "51b9d1f3b5de8a167ab06834a7c883bd197f2191e1dda1a22d9ccfeedbf9aded" | 1601 | "checksum sha-1 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "51b9d1f3b5de8a167ab06834a7c883bd197f2191e1dda1a22d9ccfeedbf9aded" |
1602 | "checksum slug 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b3bc762e6a4b6c6fcaade73e77f9ebc6991b676f88bb2358bddb56560f073373" | 1602 | "checksum slug 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b3bc762e6a4b6c6fcaade73e77f9ebc6991b676f88bb2358bddb56560f073373" |
1603 | "checksum smallvec 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "b73ea3738b47563803ef814925e69be00799a8c07420be8b996f8e98fb2336db" | 1603 | "checksum smallvec 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "b73ea3738b47563803ef814925e69be00799a8c07420be8b996f8e98fb2336db" |
@@ -1605,7 +1605,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
1605 | "checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" | 1605 | "checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" |
1606 | "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" | 1606 | "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" |
1607 | "checksum superslice 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b50b13d42370e0f5fc62eafdd5c2d20065eaf5458dab215ff3e20e63eea96b30" | 1607 | "checksum superslice 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b50b13d42370e0f5fc62eafdd5c2d20065eaf5458dab215ff3e20e63eea96b30" |
1608 | "checksum syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)" = "261ae9ecaa397c42b960649561949d69311f08eeaea86a65696e6e46517cf741" | ||
1609 | "checksum syn 0.15.23 (registry+https://github.com/rust-lang/crates.io-index)" = "9545a6a093a3f0bd59adb472700acc08cad3776f860f16a897dfce8c88721cbc" | 1608 | "checksum syn 0.15.23 (registry+https://github.com/rust-lang/crates.io-index)" = "9545a6a093a3f0bd59adb472700acc08cad3776f860f16a897dfce8c88721cbc" |
1610 | "checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015" | 1609 | "checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015" |
1611 | "checksum tempfile 3.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "7e91405c14320e5c79b3d148e1c86f40749a36e490642202a31689cb1a3452b2" | 1610 | "checksum tempfile 3.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "7e91405c14320e5c79b3d148e1c86f40749a36e490642202a31689cb1a3452b2" |
@@ -1626,6 +1625,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
1626 | "checksum unic-segment 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c9ca47cbb09fb5fcd066b5867d11dc528302fa465277882797d6a836e1ee6f9e" | 1625 | "checksum unic-segment 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c9ca47cbb09fb5fcd066b5867d11dc528302fa465277882797d6a836e1ee6f9e" |
1627 | "checksum unic-ucd-segment 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "48f1a08ce0409a9e391b88d1930118eec48af12742fc538bcec55f775865776e" | 1626 | "checksum unic-ucd-segment 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "48f1a08ce0409a9e391b88d1930118eec48af12742fc538bcec55f775865776e" |
1628 | "checksum unic-ucd-version 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1f5e6c6c53c2d0ece4a5964bc55fcff8602153063cb4fab20958ff32998ff6" | 1627 | "checksum unic-ucd-version 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1f5e6c6c53c2d0ece4a5964bc55fcff8602153063cb4fab20958ff32998ff6" |
1628 | "checksum unicase 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9d3218ea14b4edcaccfa0df0a64a3792a2c32cc706f1b336e48867f9d3147f90" | ||
1629 | "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" | 1629 | "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" |
1630 | "checksum unicode-normalization 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "6a0180bc61fc5a987082bfa111f4cc95c4caff7f9799f3e46df09163a937aa25" | 1630 | "checksum unicode-normalization 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "6a0180bc61fc5a987082bfa111f4cc95c4caff7f9799f3e46df09163a937aa25" |
1631 | "checksum unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aa6024fc12ddfd1c6dbc14a80fa2324d4568849869b779f6bd37e5e4c03344d1" | 1631 | "checksum unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aa6024fc12ddfd1c6dbc14a80fa2324d4568849869b779f6bd37e5e4c03344d1" |
diff --git a/crates/gen_lsp_server/Cargo.toml b/crates/gen_lsp_server/Cargo.toml index ff475f196..a421a3e0b 100644 --- a/crates/gen_lsp_server/Cargo.toml +++ b/crates/gen_lsp_server/Cargo.toml | |||
@@ -11,6 +11,6 @@ description = "Generic LSP server scaffold." | |||
11 | languageserver-types = "0.53.1" | 11 | languageserver-types = "0.53.1" |
12 | log = "0.4.3" | 12 | log = "0.4.3" |
13 | failure = "0.1.4" | 13 | failure = "0.1.4" |
14 | serde_json = "1.0.24" | 14 | serde_json = "1.0.34" |
15 | serde = { version = "1.0.83", features = ["derive"] } | 15 | serde = { version = "1.0.83", features = ["derive"] } |
16 | crossbeam-channel = "0.3.5" | 16 | crossbeam-channel = "0.3.5" |
diff --git a/crates/ra_analysis/Cargo.toml b/crates/ra_analysis/Cargo.toml index a5d4f65ab..7a4fdaed9 100644 --- a/crates/ra_analysis/Cargo.toml +++ b/crates/ra_analysis/Cargo.toml | |||
@@ -5,6 +5,7 @@ version = "0.1.0" | |||
5 | authors = ["Aleksey Kladov <[email protected]>"] | 5 | authors = ["Aleksey Kladov <[email protected]>"] |
6 | 6 | ||
7 | [dependencies] | 7 | [dependencies] |
8 | itertools = "0.8.0" | ||
8 | log = "0.4.5" | 9 | log = "0.4.5" |
9 | relative-path = "0.4.0" | 10 | relative-path = "0.4.0" |
10 | rayon = "1.0.2" | 11 | rayon = "1.0.2" |
@@ -12,6 +13,8 @@ fst = "0.3.1" | |||
12 | salsa = "0.9.0" | 13 | salsa = "0.9.0" |
13 | rustc-hash = "1.0" | 14 | rustc-hash = "1.0" |
14 | parking_lot = "0.7.0" | 15 | parking_lot = "0.7.0" |
16 | unicase = "2.2.0" | ||
17 | |||
15 | ra_syntax = { path = "../ra_syntax" } | 18 | ra_syntax = { path = "../ra_syntax" } |
16 | ra_editor = { path = "../ra_editor" } | 19 | ra_editor = { path = "../ra_editor" } |
17 | ra_text_edit = { path = "../ra_text_edit" } | 20 | ra_text_edit = { path = "../ra_text_edit" } |
diff --git a/crates/ra_analysis/src/completion.rs b/crates/ra_analysis/src/completion.rs index fe580700f..ce777a771 100644 --- a/crates/ra_analysis/src/completion.rs +++ b/crates/ra_analysis/src/completion.rs | |||
@@ -28,7 +28,21 @@ pub use crate::completion::completion_item::{CompletionItem, InsertText, Complet | |||
28 | /// incomplete and can look really weird. | 28 | /// incomplete and can look really weird. |
29 | /// | 29 | /// |
30 | /// Once the context is collected, we run a series of completion routines which | 30 | /// Once the context is collected, we run a series of completion routines which |
31 | /// look at the context and produce completion items. | 31 | /// look at the context and produce completion items. One subtelty about this |
32 | /// phase is that completion engine should not filter by the substring which is | ||
33 | /// already present, it should give all possible variants for the identifier at | ||
34 | /// the caret. In other words, for | ||
35 | /// | ||
36 | /// ```no-run | ||
37 | /// fn f() { | ||
38 | /// let foo = 92; | ||
39 | /// let _ = bar<|> | ||
40 | /// } | ||
41 | /// ``` | ||
42 | /// | ||
43 | /// `foo` *should* be present among the completion variants. Filtering by | ||
44 | /// identifier prefix/fuzzy match should be done higher in the stack, together | ||
45 | /// with ordering of completions (currently this is done by the client). | ||
32 | pub(crate) fn completions( | 46 | pub(crate) fn completions( |
33 | db: &db::RootDatabase, | 47 | db: &db::RootDatabase, |
34 | position: FilePosition, | 48 | position: FilePosition, |
@@ -40,6 +54,7 @@ pub(crate) fn completions( | |||
40 | 54 | ||
41 | complete_fn_param::complete_fn_param(&mut acc, &ctx); | 55 | complete_fn_param::complete_fn_param(&mut acc, &ctx); |
42 | complete_keyword::complete_expr_keyword(&mut acc, &ctx); | 56 | complete_keyword::complete_expr_keyword(&mut acc, &ctx); |
57 | complete_keyword::complete_use_tree_keyword(&mut acc, &ctx); | ||
43 | complete_snippet::complete_expr_snippet(&mut acc, &ctx); | 58 | complete_snippet::complete_expr_snippet(&mut acc, &ctx); |
44 | complete_snippet::complete_item_snippet(&mut acc, &ctx); | 59 | complete_snippet::complete_item_snippet(&mut acc, &ctx); |
45 | complete_path::complete_path(&mut acc, &ctx)?; | 60 | complete_path::complete_path(&mut acc, &ctx)?; |
@@ -57,6 +72,6 @@ fn check_completion(code: &str, expected_completions: &str, kind: CompletionKind | |||
57 | } else { | 72 | } else { |
58 | single_file_with_position(code) | 73 | single_file_with_position(code) |
59 | }; | 74 | }; |
60 | let completions = completions(&analysis.imp.db, position).unwrap().unwrap(); | 75 | let completions = completions(&analysis.db, position).unwrap().unwrap(); |
61 | completions.assert_match(expected_completions, kind); | 76 | completions.assert_match(expected_completions, kind); |
62 | } | 77 | } |
diff --git a/crates/ra_analysis/src/completion/complete_keyword.rs b/crates/ra_analysis/src/completion/complete_keyword.rs index d70fdaada..28194c908 100644 --- a/crates/ra_analysis/src/completion/complete_keyword.rs +++ b/crates/ra_analysis/src/completion/complete_keyword.rs | |||
@@ -7,6 +7,38 @@ use ra_syntax::{ | |||
7 | 7 | ||
8 | use crate::completion::{CompletionContext, CompletionItem, Completions, CompletionKind, CompletionItemKind}; | 8 | use crate::completion::{CompletionContext, CompletionItem, Completions, CompletionKind, CompletionItemKind}; |
9 | 9 | ||
10 | pub(super) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionContext) { | ||
11 | // complete keyword "crate" in use stmt | ||
12 | match (ctx.use_item_syntax.as_ref(), ctx.path_prefix.as_ref()) { | ||
13 | (Some(_), None) => { | ||
14 | CompletionItem::new(CompletionKind::Keyword, "crate") | ||
15 | .kind(CompletionItemKind::Keyword) | ||
16 | .lookup_by("crate") | ||
17 | .snippet("crate::") | ||
18 | .add_to(acc); | ||
19 | CompletionItem::new(CompletionKind::Keyword, "self") | ||
20 | .kind(CompletionItemKind::Keyword) | ||
21 | .lookup_by("self") | ||
22 | .add_to(acc); | ||
23 | CompletionItem::new(CompletionKind::Keyword, "super") | ||
24 | .kind(CompletionItemKind::Keyword) | ||
25 | .lookup_by("super") | ||
26 | .add_to(acc); | ||
27 | } | ||
28 | (Some(_), Some(_)) => { | ||
29 | CompletionItem::new(CompletionKind::Keyword, "self") | ||
30 | .kind(CompletionItemKind::Keyword) | ||
31 | .lookup_by("self") | ||
32 | .add_to(acc); | ||
33 | CompletionItem::new(CompletionKind::Keyword, "super") | ||
34 | .kind(CompletionItemKind::Keyword) | ||
35 | .lookup_by("super") | ||
36 | .add_to(acc); | ||
37 | } | ||
38 | _ => {} | ||
39 | } | ||
40 | } | ||
41 | |||
10 | fn keyword(kw: &str, snippet: &str) -> CompletionItem { | 42 | fn keyword(kw: &str, snippet: &str) -> CompletionItem { |
11 | CompletionItem::new(CompletionKind::Keyword, kw) | 43 | CompletionItem::new(CompletionKind::Keyword, kw) |
12 | .kind(CompletionItemKind::Keyword) | 44 | .kind(CompletionItemKind::Keyword) |
@@ -18,6 +50,7 @@ pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte | |||
18 | if !ctx.is_trivial_path { | 50 | if !ctx.is_trivial_path { |
19 | return; | 51 | return; |
20 | } | 52 | } |
53 | |||
21 | let fn_def = match ctx.function_syntax { | 54 | let fn_def = match ctx.function_syntax { |
22 | Some(it) => it, | 55 | Some(it) => it, |
23 | None => return, | 56 | None => return, |
@@ -80,6 +113,40 @@ mod tests { | |||
80 | } | 113 | } |
81 | 114 | ||
82 | #[test] | 115 | #[test] |
116 | fn completes_keywords_in_use_stmt() { | ||
117 | check_keyword_completion( | ||
118 | r" | ||
119 | use <|> | ||
120 | ", | ||
121 | r#" | ||
122 | crate "crate" "crate::" | ||
123 | self "self" | ||
124 | super "super" | ||
125 | "#, | ||
126 | ); | ||
127 | |||
128 | check_keyword_completion( | ||
129 | r" | ||
130 | use a::<|> | ||
131 | ", | ||
132 | r#" | ||
133 | self "self" | ||
134 | super "super" | ||
135 | "#, | ||
136 | ); | ||
137 | |||
138 | check_keyword_completion( | ||
139 | r" | ||
140 | use a::{b, <|>} | ||
141 | ", | ||
142 | r#" | ||
143 | self "self" | ||
144 | super "super" | ||
145 | "#, | ||
146 | ); | ||
147 | } | ||
148 | |||
149 | #[test] | ||
83 | fn completes_various_keywords_in_function() { | 150 | fn completes_various_keywords_in_function() { |
84 | check_keyword_completion( | 151 | check_keyword_completion( |
85 | r" | 152 | r" |
diff --git a/crates/ra_analysis/src/completion/complete_scope.rs b/crates/ra_analysis/src/completion/complete_scope.rs index daf666505..4dead3689 100644 --- a/crates/ra_analysis/src/completion/complete_scope.rs +++ b/crates/ra_analysis/src/completion/complete_scope.rs | |||
@@ -27,7 +27,7 @@ pub(super) fn complete_scope(acc: &mut Completions, ctx: &CompletionContext) -> | |||
27 | match res.import { | 27 | match res.import { |
28 | None => true, | 28 | None => true, |
29 | Some(import) => { | 29 | Some(import) => { |
30 | let range = import.range(ctx.db, module.source().file_id()); | 30 | let range = import.range(ctx.db, module.file_id()); |
31 | !range.is_subrange(&ctx.leaf.range()) | 31 | !range.is_subrange(&ctx.leaf.range()) |
32 | } | 32 | } |
33 | } | 33 | } |
diff --git a/crates/ra_analysis/src/db.rs b/crates/ra_analysis/src/db.rs index b072a5eba..d7740f0c4 100644 --- a/crates/ra_analysis/src/db.rs +++ b/crates/ra_analysis/src/db.rs | |||
@@ -1,7 +1,6 @@ | |||
1 | use std::{fmt, sync::Arc}; | 1 | use std::{fmt, sync::Arc}; |
2 | use salsa::{self, Database}; | 2 | use salsa::{self, Database}; |
3 | use ra_db::{LocationIntener, BaseDatabase}; | 3 | use ra_db::{LocationIntener, BaseDatabase}; |
4 | use hir::{self, DefId, DefLoc}; | ||
5 | 4 | ||
6 | use crate::{ | 5 | use crate::{ |
7 | symbol_index, | 6 | symbol_index, |
@@ -15,7 +14,8 @@ pub(crate) struct RootDatabase { | |||
15 | 14 | ||
16 | #[derive(Default)] | 15 | #[derive(Default)] |
17 | struct IdMaps { | 16 | struct IdMaps { |
18 | defs: LocationIntener<DefLoc, DefId>, | 17 | defs: LocationIntener<hir::DefLoc, hir::DefId>, |
18 | macros: LocationIntener<hir::MacroCallLoc, hir::MacroCallId>, | ||
19 | } | 19 | } |
20 | 20 | ||
21 | impl fmt::Debug for IdMaps { | 21 | impl fmt::Debug for IdMaps { |
@@ -59,12 +59,18 @@ impl salsa::ParallelDatabase for RootDatabase { | |||
59 | 59 | ||
60 | impl BaseDatabase for RootDatabase {} | 60 | impl BaseDatabase for RootDatabase {} |
61 | 61 | ||
62 | impl AsRef<LocationIntener<DefLoc, DefId>> for RootDatabase { | 62 | impl AsRef<LocationIntener<hir::DefLoc, hir::DefId>> for RootDatabase { |
63 | fn as_ref(&self) -> &LocationIntener<DefLoc, DefId> { | 63 | fn as_ref(&self) -> &LocationIntener<hir::DefLoc, hir::DefId> { |
64 | &self.id_maps.defs | 64 | &self.id_maps.defs |
65 | } | 65 | } |
66 | } | 66 | } |
67 | 67 | ||
68 | impl AsRef<LocationIntener<hir::MacroCallLoc, hir::MacroCallId>> for RootDatabase { | ||
69 | fn as_ref(&self) -> &LocationIntener<hir::MacroCallLoc, hir::MacroCallId> { | ||
70 | &self.id_maps.macros | ||
71 | } | ||
72 | } | ||
73 | |||
68 | salsa::database_storage! { | 74 | salsa::database_storage! { |
69 | pub(crate) struct RootDatabaseStorage for RootDatabase { | 75 | pub(crate) struct RootDatabaseStorage for RootDatabase { |
70 | impl ra_db::FilesDatabase { | 76 | impl ra_db::FilesDatabase { |
@@ -85,6 +91,8 @@ salsa::database_storage! { | |||
85 | fn library_symbols() for symbol_index::LibrarySymbolsQuery; | 91 | fn library_symbols() for symbol_index::LibrarySymbolsQuery; |
86 | } | 92 | } |
87 | impl hir::db::HirDatabase { | 93 | impl hir::db::HirDatabase { |
94 | fn hir_source_file() for hir::db::HirSourceFileQuery; | ||
95 | fn expand_macro_invocation() for hir::db::ExpandMacroCallQuery; | ||
88 | fn module_tree() for hir::db::ModuleTreeQuery; | 96 | fn module_tree() for hir::db::ModuleTreeQuery; |
89 | fn fn_scopes() for hir::db::FnScopesQuery; | 97 | fn fn_scopes() for hir::db::FnScopesQuery; |
90 | fn file_items() for hir::db::SourceFileItemsQuery; | 98 | fn file_items() for hir::db::SourceFileItemsQuery; |
diff --git a/crates/ra_analysis/src/extend_selection.rs b/crates/ra_analysis/src/extend_selection.rs index cde6ee101..f1b77f981 100644 --- a/crates/ra_analysis/src/extend_selection.rs +++ b/crates/ra_analysis/src/extend_selection.rs | |||
@@ -1,6 +1,6 @@ | |||
1 | use ra_db::SyntaxDatabase; | 1 | use ra_db::SyntaxDatabase; |
2 | use ra_syntax::{ | 2 | use ra_syntax::{ |
3 | SyntaxNodeRef, AstNode, | 3 | SyntaxNodeRef, AstNode, SourceFileNode, |
4 | ast, algo::find_covering_node, | 4 | ast, algo::find_covering_node, |
5 | }; | 5 | }; |
6 | 6 | ||
@@ -11,18 +11,23 @@ use crate::{ | |||
11 | 11 | ||
12 | pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange { | 12 | pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange { |
13 | let source_file = db.source_file(frange.file_id); | 13 | let source_file = db.source_file(frange.file_id); |
14 | if let Some(macro_call) = find_macro_call(source_file.syntax(), frange.range) { | 14 | if let Some(range) = extend_selection_in_macro(db, &source_file, frange) { |
15 | if let Some(exp) = crate::macros::expand(db, frange.file_id, macro_call) { | 15 | return range; |
16 | if let Some(dst_range) = exp.map_range_forward(frange.range) { | ||
17 | if let Some(dst_range) = ra_editor::extend_selection(exp.source_file(), dst_range) { | ||
18 | if let Some(src_range) = exp.map_range_back(dst_range) { | ||
19 | return src_range; | ||
20 | } | ||
21 | } | ||
22 | } | ||
23 | } | ||
24 | } | 16 | } |
25 | ra_editor::extend_selection(&source_file, frange.range).unwrap_or(frange.range) | 17 | ra_editor::extend_selection(source_file.syntax(), frange.range).unwrap_or(frange.range) |
18 | } | ||
19 | |||
20 | fn extend_selection_in_macro( | ||
21 | _db: &RootDatabase, | ||
22 | source_file: &SourceFileNode, | ||
23 | frange: FileRange, | ||
24 | ) -> Option<TextRange> { | ||
25 | let macro_call = find_macro_call(source_file.syntax(), frange.range)?; | ||
26 | let (off, exp) = hir::MacroDef::ast_expand(macro_call)?; | ||
27 | let dst_range = exp.map_range_forward(frange.range - off)?; | ||
28 | let dst_range = ra_editor::extend_selection(exp.syntax().borrowed(), dst_range)?; | ||
29 | let src_range = exp.map_range_back(dst_range)? + off; | ||
30 | Some(src_range) | ||
26 | } | 31 | } |
27 | 32 | ||
28 | fn find_macro_call(node: SyntaxNodeRef, range: TextRange) -> Option<ast::MacroCall> { | 33 | fn find_macro_call(node: SyntaxNodeRef, range: TextRange) -> Option<ast::MacroCall> { |
diff --git a/crates/ra_analysis/src/imp.rs b/crates/ra_analysis/src/imp.rs index 5ed374c79..eae73c2c4 100644 --- a/crates/ra_analysis/src/imp.rs +++ b/crates/ra_analysis/src/imp.rs | |||
@@ -1,59 +1,42 @@ | |||
1 | use std::{ | 1 | use std::sync::Arc; |
2 | fmt, | ||
3 | sync::Arc, | ||
4 | }; | ||
5 | 2 | ||
6 | use rayon::prelude::*; | 3 | use salsa::Database; |
7 | use salsa::{Database, ParallelDatabase}; | ||
8 | 4 | ||
9 | use hir::{ | 5 | use hir::{ |
10 | self, FnSignatureInfo, Problem, source_binder, | 6 | self, FnSignatureInfo, Problem, source_binder, |
11 | }; | 7 | }; |
12 | use ra_db::{FilesDatabase, SourceRoot, SourceRootId, SyntaxDatabase}; | 8 | use ra_db::{FilesDatabase, SourceRoot, SourceRootId, SyntaxDatabase}; |
13 | use ra_editor::{self, FileSymbol, find_node_at_offset, LineIndex, LocalEdit, Severity}; | 9 | use ra_editor::{self, find_node_at_offset, assists, LocalEdit, Severity}; |
14 | use ra_syntax::{ | 10 | use ra_syntax::{ |
15 | algo::find_covering_node, | 11 | algo::{find_covering_node, visit::{visitor, Visitor}}, |
16 | ast::{self, ArgListOwner, Expr, FnDef, NameOwner}, | 12 | ast::{self, ArgListOwner, Expr, FnDef, NameOwner}, |
17 | AstNode, SourceFileNode, | 13 | AstNode, SourceFileNode, |
18 | SyntaxKind::*, | 14 | SyntaxKind::*, |
19 | SyntaxNodeRef, TextRange, TextUnit, | 15 | SyntaxNode, SyntaxNodeRef, TextRange, TextUnit, |
20 | }; | 16 | }; |
21 | 17 | ||
22 | use crate::{ | 18 | use crate::{ |
23 | AnalysisChange, | 19 | AnalysisChange, |
24 | Cancelable, | 20 | Cancelable, NavigationTarget, |
25 | completion::{CompletionItem, completions}, | ||
26 | CrateId, db, Diagnostic, FileId, FilePosition, FileRange, FileSystemEdit, | 21 | CrateId, db, Diagnostic, FileId, FilePosition, FileRange, FileSystemEdit, |
27 | Query, ReferenceResolution, RootChange, SourceChange, SourceFileEdit, | 22 | Query, ReferenceResolution, RootChange, SourceChange, SourceFileEdit, |
28 | symbol_index::{LibrarySymbolsQuery, SymbolIndex, SymbolsDatabase}, | 23 | symbol_index::{LibrarySymbolsQuery, FileSymbol}, |
29 | }; | 24 | }; |
30 | 25 | ||
31 | #[derive(Debug, Default)] | 26 | impl db::RootDatabase { |
32 | pub(crate) struct AnalysisHostImpl { | 27 | pub(crate) fn apply_change(&mut self, change: AnalysisChange) { |
33 | db: db::RootDatabase, | ||
34 | } | ||
35 | |||
36 | impl AnalysisHostImpl { | ||
37 | pub fn analysis(&self) -> AnalysisImpl { | ||
38 | AnalysisImpl { | ||
39 | db: self.db.snapshot(), | ||
40 | } | ||
41 | } | ||
42 | pub fn apply_change(&mut self, change: AnalysisChange) { | ||
43 | log::info!("apply_change {:?}", change); | 28 | log::info!("apply_change {:?}", change); |
44 | // self.gc_syntax_trees(); | 29 | // self.gc_syntax_trees(); |
45 | if !change.new_roots.is_empty() { | 30 | if !change.new_roots.is_empty() { |
46 | let mut local_roots = Vec::clone(&self.db.local_roots()); | 31 | let mut local_roots = Vec::clone(&self.local_roots()); |
47 | for (root_id, is_local) in change.new_roots { | 32 | for (root_id, is_local) in change.new_roots { |
48 | self.db | 33 | self.query_mut(ra_db::SourceRootQuery) |
49 | .query_mut(ra_db::SourceRootQuery) | ||
50 | .set(root_id, Default::default()); | 34 | .set(root_id, Default::default()); |
51 | if is_local { | 35 | if is_local { |
52 | local_roots.push(root_id); | 36 | local_roots.push(root_id); |
53 | } | 37 | } |
54 | } | 38 | } |
55 | self.db | 39 | self.query_mut(ra_db::LocalRootsQuery) |
56 | .query_mut(ra_db::LocalRootsQuery) | ||
57 | .set((), Arc::new(local_roots)); | 40 | .set((), Arc::new(local_roots)); |
58 | } | 41 | } |
59 | 42 | ||
@@ -61,53 +44,44 @@ impl AnalysisHostImpl { | |||
61 | self.apply_root_change(root_id, root_change); | 44 | self.apply_root_change(root_id, root_change); |
62 | } | 45 | } |
63 | for (file_id, text) in change.files_changed { | 46 | for (file_id, text) in change.files_changed { |
64 | self.db.query_mut(ra_db::FileTextQuery).set(file_id, text) | 47 | self.query_mut(ra_db::FileTextQuery).set(file_id, text) |
65 | } | 48 | } |
66 | if !change.libraries_added.is_empty() { | 49 | if !change.libraries_added.is_empty() { |
67 | let mut libraries = Vec::clone(&self.db.library_roots()); | 50 | let mut libraries = Vec::clone(&self.library_roots()); |
68 | for library in change.libraries_added { | 51 | for library in change.libraries_added { |
69 | libraries.push(library.root_id); | 52 | libraries.push(library.root_id); |
70 | self.db | 53 | self.query_mut(ra_db::SourceRootQuery) |
71 | .query_mut(ra_db::SourceRootQuery) | ||
72 | .set(library.root_id, Default::default()); | 54 | .set(library.root_id, Default::default()); |
73 | self.db | 55 | self.query_mut(LibrarySymbolsQuery) |
74 | .query_mut(LibrarySymbolsQuery) | ||
75 | .set_constant(library.root_id, Arc::new(library.symbol_index)); | 56 | .set_constant(library.root_id, Arc::new(library.symbol_index)); |
76 | self.apply_root_change(library.root_id, library.root_change); | 57 | self.apply_root_change(library.root_id, library.root_change); |
77 | } | 58 | } |
78 | self.db | 59 | self.query_mut(ra_db::LibraryRootsQuery) |
79 | .query_mut(ra_db::LibraryRootsQuery) | ||
80 | .set((), Arc::new(libraries)); | 60 | .set((), Arc::new(libraries)); |
81 | } | 61 | } |
82 | if let Some(crate_graph) = change.crate_graph { | 62 | if let Some(crate_graph) = change.crate_graph { |
83 | self.db | 63 | self.query_mut(ra_db::CrateGraphQuery) |
84 | .query_mut(ra_db::CrateGraphQuery) | ||
85 | .set((), Arc::new(crate_graph)) | 64 | .set((), Arc::new(crate_graph)) |
86 | } | 65 | } |
87 | } | 66 | } |
88 | 67 | ||
89 | fn apply_root_change(&mut self, root_id: SourceRootId, root_change: RootChange) { | 68 | fn apply_root_change(&mut self, root_id: SourceRootId, root_change: RootChange) { |
90 | let mut source_root = SourceRoot::clone(&self.db.source_root(root_id)); | 69 | let mut source_root = SourceRoot::clone(&self.source_root(root_id)); |
91 | for add_file in root_change.added { | 70 | for add_file in root_change.added { |
92 | self.db | 71 | self.query_mut(ra_db::FileTextQuery) |
93 | .query_mut(ra_db::FileTextQuery) | ||
94 | .set(add_file.file_id, add_file.text); | 72 | .set(add_file.file_id, add_file.text); |
95 | self.db | 73 | self.query_mut(ra_db::FileRelativePathQuery) |
96 | .query_mut(ra_db::FileRelativePathQuery) | ||
97 | .set(add_file.file_id, add_file.path.clone()); | 74 | .set(add_file.file_id, add_file.path.clone()); |
98 | self.db | 75 | self.query_mut(ra_db::FileSourceRootQuery) |
99 | .query_mut(ra_db::FileSourceRootQuery) | ||
100 | .set(add_file.file_id, root_id); | 76 | .set(add_file.file_id, root_id); |
101 | source_root.files.insert(add_file.path, add_file.file_id); | 77 | source_root.files.insert(add_file.path, add_file.file_id); |
102 | } | 78 | } |
103 | for remove_file in root_change.removed { | 79 | for remove_file in root_change.removed { |
104 | self.db | 80 | self.query_mut(ra_db::FileTextQuery) |
105 | .query_mut(ra_db::FileTextQuery) | ||
106 | .set(remove_file.file_id, Default::default()); | 81 | .set(remove_file.file_id, Default::default()); |
107 | source_root.files.remove(&remove_file.path); | 82 | source_root.files.remove(&remove_file.path); |
108 | } | 83 | } |
109 | self.db | 84 | self.query_mut(ra_db::SourceRootQuery) |
110 | .query_mut(ra_db::SourceRootQuery) | ||
111 | .set(root_id, Arc::new(source_root)); | 85 | .set(root_id, Arc::new(source_root)); |
112 | } | 86 | } |
113 | 87 | ||
@@ -116,142 +90,86 @@ impl AnalysisHostImpl { | |||
116 | /// syntax trees. However, if we actually do that, everything is recomputed | 90 | /// syntax trees. However, if we actually do that, everything is recomputed |
117 | /// for some reason. Needs investigation. | 91 | /// for some reason. Needs investigation. |
118 | fn gc_syntax_trees(&mut self) { | 92 | fn gc_syntax_trees(&mut self) { |
119 | self.db | 93 | self.query(ra_db::SourceFileQuery) |
120 | .query(ra_db::SourceFileQuery) | ||
121 | .sweep(salsa::SweepStrategy::default().discard_values()); | 94 | .sweep(salsa::SweepStrategy::default().discard_values()); |
122 | self.db | 95 | self.query(hir::db::SourceFileItemsQuery) |
123 | .query(hir::db::SourceFileItemsQuery) | ||
124 | .sweep(salsa::SweepStrategy::default().discard_values()); | 96 | .sweep(salsa::SweepStrategy::default().discard_values()); |
125 | self.db | 97 | self.query(hir::db::FileItemQuery) |
126 | .query(hir::db::FileItemQuery) | ||
127 | .sweep(salsa::SweepStrategy::default().discard_values()); | 98 | .sweep(salsa::SweepStrategy::default().discard_values()); |
128 | } | 99 | } |
129 | } | 100 | } |
130 | 101 | ||
131 | pub(crate) struct AnalysisImpl { | 102 | impl db::RootDatabase { |
132 | pub(crate) db: salsa::Snapshot<db::RootDatabase>, | ||
133 | } | ||
134 | |||
135 | impl fmt::Debug for AnalysisImpl { | ||
136 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { | ||
137 | let db: &db::RootDatabase = &self.db; | ||
138 | fmt.debug_struct("AnalysisImpl").field("db", db).finish() | ||
139 | } | ||
140 | } | ||
141 | |||
142 | impl AnalysisImpl { | ||
143 | pub fn file_text(&self, file_id: FileId) -> Arc<String> { | ||
144 | self.db.file_text(file_id) | ||
145 | } | ||
146 | pub fn file_syntax(&self, file_id: FileId) -> SourceFileNode { | ||
147 | self.db.source_file(file_id) | ||
148 | } | ||
149 | pub fn file_line_index(&self, file_id: FileId) -> Arc<LineIndex> { | ||
150 | self.db.file_lines(file_id) | ||
151 | } | ||
152 | pub fn world_symbols(&self, query: Query) -> Cancelable<Vec<(FileId, FileSymbol)>> { | ||
153 | /// Need to wrap Snapshot to provide `Clone` impl for `map_with` | ||
154 | struct Snap(salsa::Snapshot<db::RootDatabase>); | ||
155 | impl Clone for Snap { | ||
156 | fn clone(&self) -> Snap { | ||
157 | Snap(self.0.snapshot()) | ||
158 | } | ||
159 | } | ||
160 | |||
161 | let buf: Vec<Arc<SymbolIndex>> = if query.libs { | ||
162 | let snap = Snap(self.db.snapshot()); | ||
163 | self.db | ||
164 | .library_roots() | ||
165 | .par_iter() | ||
166 | .map_with(snap, |db, &lib_id| db.0.library_symbols(lib_id)) | ||
167 | .collect() | ||
168 | } else { | ||
169 | let mut files = Vec::new(); | ||
170 | for &root in self.db.local_roots().iter() { | ||
171 | let sr = self.db.source_root(root); | ||
172 | files.extend(sr.files.values().map(|&it| it)) | ||
173 | } | ||
174 | |||
175 | let snap = Snap(self.db.snapshot()); | ||
176 | files | ||
177 | .par_iter() | ||
178 | .map_with(snap, |db, &file_id| db.0.file_symbols(file_id)) | ||
179 | .filter_map(|it| it.ok()) | ||
180 | .collect() | ||
181 | }; | ||
182 | Ok(query.search(&buf)) | ||
183 | } | ||
184 | /// This returns `Vec` because a module may be included from several places. We | 103 | /// This returns `Vec` because a module may be included from several places. We |
185 | /// don't handle this case yet though, so the Vec has length at most one. | 104 | /// don't handle this case yet though, so the Vec has length at most one. |
186 | pub fn parent_module(&self, position: FilePosition) -> Cancelable<Vec<(FileId, FileSymbol)>> { | 105 | pub(crate) fn parent_module( |
187 | let descr = match source_binder::module_from_position(&*self.db, position)? { | 106 | &self, |
107 | position: FilePosition, | ||
108 | ) -> Cancelable<Vec<NavigationTarget>> { | ||
109 | let descr = match source_binder::module_from_position(self, position)? { | ||
188 | None => return Ok(Vec::new()), | 110 | None => return Ok(Vec::new()), |
189 | Some(it) => it, | 111 | Some(it) => it, |
190 | }; | 112 | }; |
191 | let (file_id, decl) = match descr.parent_link_source(&*self.db) { | 113 | let (file_id, decl) = match descr.parent_link_source(self) { |
192 | None => return Ok(Vec::new()), | 114 | None => return Ok(Vec::new()), |
193 | Some(it) => it, | 115 | Some(it) => it, |
194 | }; | 116 | }; |
195 | let decl = decl.borrowed(); | 117 | let decl = decl.borrowed(); |
196 | let decl_name = decl.name().unwrap(); | 118 | let decl_name = decl.name().unwrap(); |
197 | let sym = FileSymbol { | 119 | Ok(vec![NavigationTarget { |
120 | file_id, | ||
198 | name: decl_name.text(), | 121 | name: decl_name.text(), |
199 | node_range: decl_name.syntax().range(), | 122 | range: decl_name.syntax().range(), |
200 | kind: MODULE, | 123 | kind: MODULE, |
201 | }; | 124 | ptr: None, |
202 | Ok(vec![(file_id, sym)]) | 125 | }]) |
203 | } | 126 | } |
204 | /// Returns `Vec` for the same reason as `parent_module` | 127 | /// Returns `Vec` for the same reason as `parent_module` |
205 | pub fn crate_for(&self, file_id: FileId) -> Cancelable<Vec<CrateId>> { | 128 | pub(crate) fn crate_for(&self, file_id: FileId) -> Cancelable<Vec<CrateId>> { |
206 | let descr = match source_binder::module_from_file_id(&*self.db, file_id)? { | 129 | let descr = match source_binder::module_from_file_id(self, file_id)? { |
207 | None => return Ok(Vec::new()), | 130 | None => return Ok(Vec::new()), |
208 | Some(it) => it, | 131 | Some(it) => it, |
209 | }; | 132 | }; |
210 | let root = descr.crate_root(); | 133 | let root = descr.crate_root(); |
211 | let file_id = root.source().file_id(); | 134 | let file_id = root.file_id(); |
212 | 135 | ||
213 | let crate_graph = self.db.crate_graph(); | 136 | let crate_graph = self.crate_graph(); |
214 | let crate_id = crate_graph.crate_id_for_crate_root(file_id); | 137 | let crate_id = crate_graph.crate_id_for_crate_root(file_id); |
215 | Ok(crate_id.into_iter().collect()) | 138 | Ok(crate_id.into_iter().collect()) |
216 | } | 139 | } |
217 | pub fn crate_root(&self, crate_id: CrateId) -> FileId { | 140 | pub(crate) fn crate_root(&self, crate_id: CrateId) -> FileId { |
218 | self.db.crate_graph().crate_root(crate_id) | 141 | self.crate_graph().crate_root(crate_id) |
219 | } | 142 | } |
220 | pub fn completions(&self, position: FilePosition) -> Cancelable<Option<Vec<CompletionItem>>> { | 143 | pub(crate) fn approximately_resolve_symbol( |
221 | let completions = completions(&self.db, position)?; | ||
222 | Ok(completions.map(|it| it.into())) | ||
223 | } | ||
224 | pub fn approximately_resolve_symbol( | ||
225 | &self, | 144 | &self, |
226 | position: FilePosition, | 145 | position: FilePosition, |
227 | ) -> Cancelable<Option<ReferenceResolution>> { | 146 | ) -> Cancelable<Option<ReferenceResolution>> { |
228 | let file = self.db.source_file(position.file_id); | 147 | let file = self.source_file(position.file_id); |
229 | let syntax = file.syntax(); | 148 | let syntax = file.syntax(); |
230 | if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(syntax, position.offset) { | 149 | if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(syntax, position.offset) { |
231 | let mut rr = ReferenceResolution::new(name_ref.syntax().range()); | 150 | let mut rr = ReferenceResolution::new(name_ref.syntax().range()); |
232 | if let Some(fn_descr) = source_binder::function_from_child_node( | 151 | if let Some(fn_descr) = |
233 | &*self.db, | 152 | source_binder::function_from_child_node(self, position.file_id, name_ref.syntax())? |
234 | position.file_id, | 153 | { |
235 | name_ref.syntax(), | 154 | let scope = fn_descr.scopes(self); |
236 | )? { | ||
237 | let scope = fn_descr.scopes(&*self.db); | ||
238 | // First try to resolve the symbol locally | 155 | // First try to resolve the symbol locally |
239 | if let Some(entry) = scope.resolve_local_name(name_ref) { | 156 | if let Some(entry) = scope.resolve_local_name(name_ref) { |
240 | rr.add_resolution( | 157 | rr.resolves_to.push(NavigationTarget { |
241 | position.file_id, | 158 | file_id: position.file_id, |
242 | FileSymbol { | 159 | name: entry.name().to_string().into(), |
243 | name: entry.name().to_string().into(), | 160 | range: entry.ptr().range(), |
244 | node_range: entry.ptr().range(), | 161 | kind: NAME, |
245 | kind: NAME, | 162 | ptr: None, |
246 | }, | 163 | }); |
247 | ); | ||
248 | return Ok(Some(rr)); | 164 | return Ok(Some(rr)); |
249 | }; | 165 | }; |
250 | } | 166 | } |
251 | // If that fails try the index based approach. | 167 | // If that fails try the index based approach. |
252 | for (file_id, symbol) in self.index_resolve(name_ref)? { | 168 | rr.resolves_to.extend( |
253 | rr.add_resolution(file_id, symbol); | 169 | self.index_resolve(name_ref)? |
254 | } | 170 | .into_iter() |
171 | .map(NavigationTarget::from_symbol), | ||
172 | ); | ||
255 | return Ok(Some(rr)); | 173 | return Ok(Some(rr)); |
256 | } | 174 | } |
257 | if let Some(name) = find_node_at_offset::<ast::Name>(syntax, position.offset) { | 175 | if let Some(name) = find_node_at_offset::<ast::Name>(syntax, position.offset) { |
@@ -259,19 +177,21 @@ impl AnalysisImpl { | |||
259 | if let Some(module) = name.syntax().parent().and_then(ast::Module::cast) { | 177 | if let Some(module) = name.syntax().parent().and_then(ast::Module::cast) { |
260 | if module.has_semi() { | 178 | if module.has_semi() { |
261 | if let Some(child_module) = | 179 | if let Some(child_module) = |
262 | source_binder::module_from_declaration(&*self.db, position.file_id, module)? | 180 | source_binder::module_from_declaration(self, position.file_id, module)? |
263 | { | 181 | { |
264 | let file_id = child_module.source().file_id(); | 182 | let file_id = child_module.file_id(); |
265 | let name = match child_module.name() { | 183 | let name = match child_module.name() { |
266 | Some(name) => name.to_string().into(), | 184 | Some(name) => name.to_string().into(), |
267 | None => "".into(), | 185 | None => "".into(), |
268 | }; | 186 | }; |
269 | let symbol = FileSymbol { | 187 | let symbol = NavigationTarget { |
188 | file_id, | ||
270 | name, | 189 | name, |
271 | node_range: TextRange::offset_len(0.into(), 0.into()), | 190 | range: TextRange::offset_len(0.into(), 0.into()), |
272 | kind: MODULE, | 191 | kind: MODULE, |
192 | ptr: None, | ||
273 | }; | 193 | }; |
274 | rr.add_resolution(file_id, symbol); | 194 | rr.resolves_to.push(symbol); |
275 | return Ok(Some(rr)); | 195 | return Ok(Some(rr)); |
276 | } | 196 | } |
277 | } | 197 | } |
@@ -280,10 +200,13 @@ impl AnalysisImpl { | |||
280 | Ok(None) | 200 | Ok(None) |
281 | } | 201 | } |
282 | 202 | ||
283 | pub fn find_all_refs(&self, position: FilePosition) -> Cancelable<Vec<(FileId, TextRange)>> { | 203 | pub(crate) fn find_all_refs( |
284 | let file = self.db.source_file(position.file_id); | 204 | &self, |
205 | position: FilePosition, | ||
206 | ) -> Cancelable<Vec<(FileId, TextRange)>> { | ||
207 | let file = self.source_file(position.file_id); | ||
285 | // Find the binding associated with the offset | 208 | // Find the binding associated with the offset |
286 | let (binding, descr) = match find_binding(&self.db, &file, position)? { | 209 | let (binding, descr) = match find_binding(self, &file, position)? { |
287 | None => return Ok(Vec::new()), | 210 | None => return Ok(Vec::new()), |
288 | Some(it) => it, | 211 | Some(it) => it, |
289 | }; | 212 | }; |
@@ -295,7 +218,7 @@ impl AnalysisImpl { | |||
295 | .collect::<Vec<_>>(); | 218 | .collect::<Vec<_>>(); |
296 | ret.extend( | 219 | ret.extend( |
297 | descr | 220 | descr |
298 | .scopes(&*self.db) | 221 | .scopes(self) |
299 | .find_all_refs(binding) | 222 | .find_all_refs(binding) |
300 | .into_iter() | 223 | .into_iter() |
301 | .map(|ref_desc| (position.file_id, ref_desc.range)), | 224 | .map(|ref_desc| (position.file_id, ref_desc.range)), |
@@ -333,9 +256,8 @@ impl AnalysisImpl { | |||
333 | Ok(Some((binding, descr))) | 256 | Ok(Some((binding, descr))) |
334 | } | 257 | } |
335 | } | 258 | } |
336 | pub fn doc_text_for(&self, file_id: FileId, symbol: FileSymbol) -> Cancelable<Option<String>> { | 259 | pub(crate) fn doc_text_for(&self, nav: NavigationTarget) -> Cancelable<Option<String>> { |
337 | let file = self.db.source_file(file_id); | 260 | let result = match (nav.description(self), nav.docs(self)) { |
338 | let result = match (symbol.description(&file), symbol.docs(&file)) { | ||
339 | (Some(desc), Some(docs)) => { | 261 | (Some(desc), Some(docs)) => { |
340 | Some("```rust\n".to_string() + &*desc + "\n```\n\n" + &*docs) | 262 | Some("```rust\n".to_string() + &*desc + "\n```\n\n" + &*docs) |
341 | } | 263 | } |
@@ -347,8 +269,8 @@ impl AnalysisImpl { | |||
347 | Ok(result) | 269 | Ok(result) |
348 | } | 270 | } |
349 | 271 | ||
350 | pub fn diagnostics(&self, file_id: FileId) -> Cancelable<Vec<Diagnostic>> { | 272 | pub(crate) fn diagnostics(&self, file_id: FileId) -> Cancelable<Vec<Diagnostic>> { |
351 | let syntax = self.db.source_file(file_id); | 273 | let syntax = self.source_file(file_id); |
352 | 274 | ||
353 | let mut res = ra_editor::diagnostics(&syntax) | 275 | let mut res = ra_editor::diagnostics(&syntax) |
354 | .into_iter() | 276 | .into_iter() |
@@ -359,9 +281,9 @@ impl AnalysisImpl { | |||
359 | fix: d.fix.map(|fix| SourceChange::from_local_edit(file_id, fix)), | 281 | fix: d.fix.map(|fix| SourceChange::from_local_edit(file_id, fix)), |
360 | }) | 282 | }) |
361 | .collect::<Vec<_>>(); | 283 | .collect::<Vec<_>>(); |
362 | if let Some(m) = source_binder::module_from_file_id(&*self.db, file_id)? { | 284 | if let Some(m) = source_binder::module_from_file_id(self, file_id)? { |
363 | for (name_node, problem) in m.problems(&*self.db) { | 285 | for (name_node, problem) in m.problems(self) { |
364 | let source_root = self.db.file_source_root(file_id); | 286 | let source_root = self.file_source_root(file_id); |
365 | let diag = match problem { | 287 | let diag = match problem { |
366 | Problem::UnresolvedModule { candidate } => { | 288 | Problem::UnresolvedModule { candidate } => { |
367 | let create_file = FileSystemEdit::CreateFile { | 289 | let create_file = FileSystemEdit::CreateFile { |
@@ -411,29 +333,19 @@ impl AnalysisImpl { | |||
411 | Ok(res) | 333 | Ok(res) |
412 | } | 334 | } |
413 | 335 | ||
414 | pub fn assists(&self, frange: FileRange) -> Vec<SourceChange> { | 336 | pub(crate) fn assists(&self, frange: FileRange) -> Vec<SourceChange> { |
415 | let file = self.file_syntax(frange.file_id); | 337 | let file = self.source_file(frange.file_id); |
416 | let offset = frange.range.start(); | 338 | assists::assists(&file, frange.range) |
417 | let actions = vec![ | ||
418 | ra_editor::flip_comma(&file, offset).map(|f| f()), | ||
419 | ra_editor::add_derive(&file, offset).map(|f| f()), | ||
420 | ra_editor::add_impl(&file, offset).map(|f| f()), | ||
421 | ra_editor::make_pub_crate(&file, offset).map(|f| f()), | ||
422 | ra_editor::introduce_variable(&file, frange.range).map(|f| f()), | ||
423 | ]; | ||
424 | actions | ||
425 | .into_iter() | 339 | .into_iter() |
426 | .filter_map(|local_edit| { | 340 | .map(|local_edit| SourceChange::from_local_edit(frange.file_id, local_edit)) |
427 | Some(SourceChange::from_local_edit(frange.file_id, local_edit?)) | ||
428 | }) | ||
429 | .collect() | 341 | .collect() |
430 | } | 342 | } |
431 | 343 | ||
432 | pub fn resolve_callable( | 344 | pub(crate) fn resolve_callable( |
433 | &self, | 345 | &self, |
434 | position: FilePosition, | 346 | position: FilePosition, |
435 | ) -> Cancelable<Option<(FnSignatureInfo, Option<usize>)>> { | 347 | ) -> Cancelable<Option<(FnSignatureInfo, Option<usize>)>> { |
436 | let file = self.db.source_file(position.file_id); | 348 | let file = self.source_file(position.file_id); |
437 | let syntax = file.syntax(); | 349 | let syntax = file.syntax(); |
438 | 350 | ||
439 | // Find the calling expression and it's NameRef | 351 | // Find the calling expression and it's NameRef |
@@ -442,53 +354,55 @@ impl AnalysisImpl { | |||
442 | 354 | ||
443 | // Resolve the function's NameRef (NOTE: this isn't entirely accurate). | 355 | // Resolve the function's NameRef (NOTE: this isn't entirely accurate). |
444 | let file_symbols = self.index_resolve(name_ref)?; | 356 | let file_symbols = self.index_resolve(name_ref)?; |
445 | for (fn_file_id, fs) in file_symbols { | 357 | for symbol in file_symbols { |
446 | if fs.kind == FN_DEF { | 358 | if symbol.ptr.kind() == FN_DEF { |
447 | let fn_file = self.db.source_file(fn_file_id); | 359 | let fn_file = self.source_file(symbol.file_id); |
448 | if let Some(fn_def) = find_node_at_offset(fn_file.syntax(), fs.node_range.start()) { | 360 | let fn_def = symbol.ptr.resolve(&fn_file); |
449 | let descr = ctry!(source_binder::function_from_source( | 361 | let fn_def = ast::FnDef::cast(fn_def.borrowed()).unwrap(); |
450 | &*self.db, fn_file_id, fn_def | 362 | let descr = ctry!(source_binder::function_from_source( |
451 | )?); | 363 | self, |
452 | if let Some(descriptor) = descr.signature_info(&*self.db) { | 364 | symbol.file_id, |
453 | // If we have a calling expression let's find which argument we are on | 365 | fn_def |
454 | let mut current_parameter = None; | 366 | )?); |
455 | 367 | if let Some(descriptor) = descr.signature_info(self) { | |
456 | let num_params = descriptor.params.len(); | 368 | // If we have a calling expression let's find which argument we are on |
457 | let has_self = fn_def.param_list().and_then(|l| l.self_param()).is_some(); | 369 | let mut current_parameter = None; |
458 | 370 | ||
459 | if num_params == 1 { | 371 | let num_params = descriptor.params.len(); |
460 | if !has_self { | 372 | let has_self = fn_def.param_list().and_then(|l| l.self_param()).is_some(); |
461 | current_parameter = Some(0); | 373 | |
462 | } | 374 | if num_params == 1 { |
463 | } else if num_params > 1 { | 375 | if !has_self { |
464 | // Count how many parameters into the call we are. | 376 | current_parameter = Some(0); |
465 | // TODO: This is best effort for now and should be fixed at some point. | ||
466 | // It may be better to see where we are in the arg_list and then check | ||
467 | // where offset is in that list (or beyond). | ||
468 | // Revisit this after we get documentation comments in. | ||
469 | if let Some(ref arg_list) = calling_node.arg_list() { | ||
470 | let start = arg_list.syntax().range().start(); | ||
471 | |||
472 | let range_search = TextRange::from_to(start, position.offset); | ||
473 | let mut commas: usize = arg_list | ||
474 | .syntax() | ||
475 | .text() | ||
476 | .slice(range_search) | ||
477 | .to_string() | ||
478 | .matches(',') | ||
479 | .count(); | ||
480 | |||
481 | // If we have a method call eat the first param since it's just self. | ||
482 | if has_self { | ||
483 | commas += 1; | ||
484 | } | ||
485 | |||
486 | current_parameter = Some(commas); | ||
487 | } | ||
488 | } | 377 | } |
378 | } else if num_params > 1 { | ||
379 | // Count how many parameters into the call we are. | ||
380 | // TODO: This is best effort for now and should be fixed at some point. | ||
381 | // It may be better to see where we are in the arg_list and then check | ||
382 | // where offset is in that list (or beyond). | ||
383 | // Revisit this after we get documentation comments in. | ||
384 | if let Some(ref arg_list) = calling_node.arg_list() { | ||
385 | let start = arg_list.syntax().range().start(); | ||
386 | |||
387 | let range_search = TextRange::from_to(start, position.offset); | ||
388 | let mut commas: usize = arg_list | ||
389 | .syntax() | ||
390 | .text() | ||
391 | .slice(range_search) | ||
392 | .to_string() | ||
393 | .matches(',') | ||
394 | .count(); | ||
395 | |||
396 | // If we have a method call eat the first param since it's just self. | ||
397 | if has_self { | ||
398 | commas += 1; | ||
399 | } | ||
489 | 400 | ||
490 | return Ok(Some((descriptor, current_parameter))); | 401 | current_parameter = Some(commas); |
402 | } | ||
491 | } | 403 | } |
404 | |||
405 | return Ok(Some((descriptor, current_parameter))); | ||
492 | } | 406 | } |
493 | } | 407 | } |
494 | } | 408 | } |
@@ -496,20 +410,20 @@ impl AnalysisImpl { | |||
496 | Ok(None) | 410 | Ok(None) |
497 | } | 411 | } |
498 | 412 | ||
499 | pub fn type_of(&self, frange: FileRange) -> Cancelable<Option<String>> { | 413 | pub(crate) fn type_of(&self, frange: FileRange) -> Cancelable<Option<String>> { |
500 | let file = self.db.source_file(frange.file_id); | 414 | let file = self.source_file(frange.file_id); |
501 | let syntax = file.syntax(); | 415 | let syntax = file.syntax(); |
502 | let node = find_covering_node(syntax, frange.range); | 416 | let node = find_covering_node(syntax, frange.range); |
503 | let parent_fn = ctry!(node.ancestors().find_map(FnDef::cast)); | 417 | let parent_fn = ctry!(node.ancestors().find_map(FnDef::cast)); |
504 | let function = ctry!(source_binder::function_from_source( | 418 | let function = ctry!(source_binder::function_from_source( |
505 | &*self.db, | 419 | self, |
506 | frange.file_id, | 420 | frange.file_id, |
507 | parent_fn | 421 | parent_fn |
508 | )?); | 422 | )?); |
509 | let infer = function.infer(&*self.db)?; | 423 | let infer = function.infer(self)?; |
510 | Ok(infer.type_of_node(node).map(|t| t.to_string())) | 424 | Ok(infer.type_of_node(node).map(|t| t.to_string())) |
511 | } | 425 | } |
512 | pub fn rename( | 426 | pub(crate) fn rename( |
513 | &self, | 427 | &self, |
514 | position: FilePosition, | 428 | position: FilePosition, |
515 | new_name: &str, | 429 | new_name: &str, |
@@ -520,7 +434,7 @@ impl AnalysisImpl { | |||
520 | .map(|(file_id, text_range)| SourceFileEdit { | 434 | .map(|(file_id, text_range)| SourceFileEdit { |
521 | file_id: *file_id, | 435 | file_id: *file_id, |
522 | edit: { | 436 | edit: { |
523 | let mut builder = ra_text_edit::TextEditBuilder::new(); | 437 | let mut builder = ra_text_edit::TextEditBuilder::default(); |
524 | builder.replace(*text_range, new_name.into()); | 438 | builder.replace(*text_range, new_name.into()); |
525 | builder.finish() | 439 | builder.finish() |
526 | }, | 440 | }, |
@@ -528,12 +442,12 @@ impl AnalysisImpl { | |||
528 | .collect::<Vec<_>>(); | 442 | .collect::<Vec<_>>(); |
529 | Ok(res) | 443 | Ok(res) |
530 | } | 444 | } |
531 | fn index_resolve(&self, name_ref: ast::NameRef) -> Cancelable<Vec<(FileId, FileSymbol)>> { | 445 | fn index_resolve(&self, name_ref: ast::NameRef) -> Cancelable<Vec<FileSymbol>> { |
532 | let name = name_ref.text(); | 446 | let name = name_ref.text(); |
533 | let mut query = Query::new(name.to_string()); | 447 | let mut query = Query::new(name.to_string()); |
534 | query.exact(); | 448 | query.exact(); |
535 | query.limit(4); | 449 | query.limit(4); |
536 | self.world_symbols(query) | 450 | crate::symbol_index::world_symbols(self, query) |
537 | } | 451 | } |
538 | } | 452 | } |
539 | 453 | ||
@@ -592,3 +506,91 @@ impl<'a> FnCallNode<'a> { | |||
592 | } | 506 | } |
593 | } | 507 | } |
594 | } | 508 | } |
509 | |||
510 | impl NavigationTarget { | ||
511 | fn node(&self, db: &db::RootDatabase) -> Option<SyntaxNode> { | ||
512 | let source_file = db.source_file(self.file_id); | ||
513 | let source_file = source_file.syntax(); | ||
514 | let node = source_file | ||
515 | .descendants() | ||
516 | .find(|node| node.kind() == self.kind && node.range() == self.range)? | ||
517 | .owned(); | ||
518 | Some(node) | ||
519 | } | ||
520 | |||
521 | fn docs(&self, db: &db::RootDatabase) -> Option<String> { | ||
522 | let node = self.node(db)?; | ||
523 | let node = node.borrowed(); | ||
524 | fn doc_comments<'a, N: ast::DocCommentsOwner<'a>>(node: N) -> Option<String> { | ||
525 | let comments = node.doc_comment_text(); | ||
526 | if comments.is_empty() { | ||
527 | None | ||
528 | } else { | ||
529 | Some(comments) | ||
530 | } | ||
531 | } | ||
532 | |||
533 | visitor() | ||
534 | .visit(doc_comments::<ast::FnDef>) | ||
535 | .visit(doc_comments::<ast::StructDef>) | ||
536 | .visit(doc_comments::<ast::EnumDef>) | ||
537 | .visit(doc_comments::<ast::TraitDef>) | ||
538 | .visit(doc_comments::<ast::Module>) | ||
539 | .visit(doc_comments::<ast::TypeDef>) | ||
540 | .visit(doc_comments::<ast::ConstDef>) | ||
541 | .visit(doc_comments::<ast::StaticDef>) | ||
542 | .accept(node)? | ||
543 | } | ||
544 | |||
545 | /// Get a description of this node. | ||
546 | /// | ||
547 | /// e.g. `struct Name`, `enum Name`, `fn Name` | ||
548 | fn description(&self, db: &db::RootDatabase) -> Option<String> { | ||
549 | // TODO: After type inference is done, add type information to improve the output | ||
550 | let node = self.node(db)?; | ||
551 | let node = node.borrowed(); | ||
552 | // TODO: Refactor to be have less repetition | ||
553 | visitor() | ||
554 | .visit(|node: ast::FnDef| { | ||
555 | let mut string = "fn ".to_string(); | ||
556 | node.name()?.syntax().text().push_to(&mut string); | ||
557 | Some(string) | ||
558 | }) | ||
559 | .visit(|node: ast::StructDef| { | ||
560 | let mut string = "struct ".to_string(); | ||
561 | node.name()?.syntax().text().push_to(&mut string); | ||
562 | Some(string) | ||
563 | }) | ||
564 | .visit(|node: ast::EnumDef| { | ||
565 | let mut string = "enum ".to_string(); | ||
566 | node.name()?.syntax().text().push_to(&mut string); | ||
567 | Some(string) | ||
568 | }) | ||
569 | .visit(|node: ast::TraitDef| { | ||
570 | let mut string = "trait ".to_string(); | ||
571 | node.name()?.syntax().text().push_to(&mut string); | ||
572 | Some(string) | ||
573 | }) | ||
574 | .visit(|node: ast::Module| { | ||
575 | let mut string = "mod ".to_string(); | ||
576 | node.name()?.syntax().text().push_to(&mut string); | ||
577 | Some(string) | ||
578 | }) | ||
579 | .visit(|node: ast::TypeDef| { | ||
580 | let mut string = "type ".to_string(); | ||
581 | node.name()?.syntax().text().push_to(&mut string); | ||
582 | Some(string) | ||
583 | }) | ||
584 | .visit(|node: ast::ConstDef| { | ||
585 | let mut string = "const ".to_string(); | ||
586 | node.name()?.syntax().text().push_to(&mut string); | ||
587 | Some(string) | ||
588 | }) | ||
589 | .visit(|node: ast::StaticDef| { | ||
590 | let mut string = "static ".to_string(); | ||
591 | node.name()?.syntax().text().push_to(&mut string); | ||
592 | Some(string) | ||
593 | }) | ||
594 | .accept(node)? | ||
595 | } | ||
596 | } | ||
diff --git a/crates/ra_analysis/src/lib.rs b/crates/ra_analysis/src/lib.rs index e56168510..ab935954c 100644 --- a/crates/ra_analysis/src/lib.rs +++ b/crates/ra_analysis/src/lib.rs | |||
@@ -1,6 +1,8 @@ | |||
1 | //! ra_analyzer crate is the brain of Rust analyzer. It relies on the `salsa` | 1 | //! ra_analyzer crate provides "ide-centric" APIs for the rust-analyzer. What |
2 | //! crate, which provides and incremental on-demand database of facts. | 2 | //! powers this API are the `RootDatabase` struct, which defines a `salsa` |
3 | 3 | //! database, and the `ra_hir` crate, where majority of the analysis happens. | |
4 | //! However, IDE specific bits of the analysis (most notably completion) happen | ||
5 | //! in this crate. | ||
4 | macro_rules! ctry { | 6 | macro_rules! ctry { |
5 | ($expr:expr) => { | 7 | ($expr:expr) => { |
6 | match $expr { | 8 | match $expr { |
@@ -15,34 +17,34 @@ mod imp; | |||
15 | mod completion; | 17 | mod completion; |
16 | mod symbol_index; | 18 | mod symbol_index; |
17 | pub mod mock_analysis; | 19 | pub mod mock_analysis; |
20 | mod runnables; | ||
18 | 21 | ||
19 | mod extend_selection; | 22 | mod extend_selection; |
20 | mod syntax_highlighting; | 23 | mod syntax_highlighting; |
21 | mod macros; | ||
22 | 24 | ||
23 | use std::{fmt, sync::Arc}; | 25 | use std::{fmt, sync::Arc}; |
24 | 26 | ||
25 | use rustc_hash::FxHashMap; | 27 | use rustc_hash::FxHashMap; |
26 | use ra_syntax::{SourceFileNode, TextRange, TextUnit}; | 28 | use ra_syntax::{SourceFileNode, TextRange, TextUnit, SmolStr, SyntaxKind}; |
27 | use ra_text_edit::TextEdit; | 29 | use ra_text_edit::TextEdit; |
28 | use rayon::prelude::*; | 30 | use rayon::prelude::*; |
29 | use relative_path::RelativePathBuf; | 31 | use relative_path::RelativePathBuf; |
32 | use salsa::ParallelDatabase; | ||
30 | 33 | ||
31 | use crate::{ | 34 | use crate::symbol_index::{SymbolIndex, FileSymbol}; |
32 | imp::{AnalysisHostImpl, AnalysisImpl}, | ||
33 | symbol_index::SymbolIndex, | ||
34 | }; | ||
35 | 35 | ||
36 | pub use crate::completion::{CompletionItem, CompletionItemKind, InsertText}; | 36 | pub use crate::{ |
37 | completion::{CompletionItem, CompletionItemKind, InsertText}, | ||
38 | runnables::{Runnable, RunnableKind}, | ||
39 | }; | ||
37 | pub use ra_editor::{ | 40 | pub use ra_editor::{ |
38 | FileSymbol, Fold, FoldKind, HighlightedRange, LineIndex, Runnable, RunnableKind, StructureNode, | 41 | Fold, FoldKind, HighlightedRange, LineIndex, StructureNode, Severity |
39 | Severity | ||
40 | }; | 42 | }; |
41 | pub use hir::FnSignatureInfo; | 43 | pub use hir::FnSignatureInfo; |
42 | 44 | ||
43 | pub use ra_db::{ | 45 | pub use ra_db::{ |
44 | Canceled, Cancelable, FilePosition, FileRange, | 46 | Canceled, Cancelable, FilePosition, FileRange, LocalSyntaxPtr, |
45 | CrateGraph, CrateId, SourceRootId, FileId | 47 | CrateGraph, CrateId, SourceRootId, FileId, SyntaxDatabase, FilesDatabase |
46 | }; | 48 | }; |
47 | 49 | ||
48 | #[derive(Default)] | 50 | #[derive(Default)] |
@@ -148,27 +150,6 @@ impl AnalysisChange { | |||
148 | } | 150 | } |
149 | } | 151 | } |
150 | 152 | ||
151 | /// `AnalysisHost` stores the current state of the world. | ||
152 | #[derive(Debug, Default)] | ||
153 | pub struct AnalysisHost { | ||
154 | imp: AnalysisHostImpl, | ||
155 | } | ||
156 | |||
157 | impl AnalysisHost { | ||
158 | /// Returns a snapshot of the current state, which you can query for | ||
159 | /// semantic information. | ||
160 | pub fn analysis(&self) -> Analysis { | ||
161 | Analysis { | ||
162 | imp: self.imp.analysis(), | ||
163 | } | ||
164 | } | ||
165 | /// Applies changes to the current state of the world. If there are | ||
166 | /// outstanding snapshots, they will be canceled. | ||
167 | pub fn apply_change(&mut self, change: AnalysisChange) { | ||
168 | self.imp.apply_change(change) | ||
169 | } | ||
170 | } | ||
171 | |||
172 | #[derive(Debug)] | 153 | #[derive(Debug)] |
173 | pub struct SourceChange { | 154 | pub struct SourceChange { |
174 | pub label: String, | 155 | pub label: String, |
@@ -240,6 +221,45 @@ impl Query { | |||
240 | } | 221 | } |
241 | } | 222 | } |
242 | 223 | ||
224 | /// `NavigationTarget` represents and element in the editor's UI whihc you can | ||
225 | /// click on to navigate to a particular piece of code. | ||
226 | /// | ||
227 | /// Typically, a `NavigationTarget` corresponds to some element in the source | ||
228 | /// code, like a function or a struct, but this is not strictly required. | ||
229 | #[derive(Debug)] | ||
230 | pub struct NavigationTarget { | ||
231 | file_id: FileId, | ||
232 | name: SmolStr, | ||
233 | kind: SyntaxKind, | ||
234 | range: TextRange, | ||
235 | // Should be DefId ideally | ||
236 | ptr: Option<LocalSyntaxPtr>, | ||
237 | } | ||
238 | |||
239 | impl NavigationTarget { | ||
240 | fn from_symbol(symbol: FileSymbol) -> NavigationTarget { | ||
241 | NavigationTarget { | ||
242 | file_id: symbol.file_id, | ||
243 | name: symbol.name.clone(), | ||
244 | kind: symbol.ptr.kind(), | ||
245 | range: symbol.ptr.range(), | ||
246 | ptr: Some(symbol.ptr.clone()), | ||
247 | } | ||
248 | } | ||
249 | pub fn name(&self) -> &SmolStr { | ||
250 | &self.name | ||
251 | } | ||
252 | pub fn kind(&self) -> SyntaxKind { | ||
253 | self.kind | ||
254 | } | ||
255 | pub fn file_id(&self) -> FileId { | ||
256 | self.file_id | ||
257 | } | ||
258 | pub fn range(&self) -> TextRange { | ||
259 | self.range | ||
260 | } | ||
261 | } | ||
262 | |||
243 | /// Result of "goto def" query. | 263 | /// Result of "goto def" query. |
244 | #[derive(Debug)] | 264 | #[derive(Debug)] |
245 | pub struct ReferenceResolution { | 265 | pub struct ReferenceResolution { |
@@ -248,7 +268,7 @@ pub struct ReferenceResolution { | |||
248 | /// client where the reference was. | 268 | /// client where the reference was. |
249 | pub reference_range: TextRange, | 269 | pub reference_range: TextRange, |
250 | /// What this reference resolves to. | 270 | /// What this reference resolves to. |
251 | pub resolves_to: Vec<(FileId, FileSymbol)>, | 271 | pub resolves_to: Vec<NavigationTarget>, |
252 | } | 272 | } |
253 | 273 | ||
254 | impl ReferenceResolution { | 274 | impl ReferenceResolution { |
@@ -258,9 +278,26 @@ impl ReferenceResolution { | |||
258 | resolves_to: Vec::new(), | 278 | resolves_to: Vec::new(), |
259 | } | 279 | } |
260 | } | 280 | } |
281 | } | ||
282 | |||
283 | /// `AnalysisHost` stores the current state of the world. | ||
284 | #[derive(Debug, Default)] | ||
285 | pub struct AnalysisHost { | ||
286 | db: db::RootDatabase, | ||
287 | } | ||
261 | 288 | ||
262 | fn add_resolution(&mut self, file_id: FileId, symbol: FileSymbol) { | 289 | impl AnalysisHost { |
263 | self.resolves_to.push((file_id, symbol)) | 290 | /// Returns a snapshot of the current state, which you can query for |
291 | /// semantic information. | ||
292 | pub fn analysis(&self) -> Analysis { | ||
293 | Analysis { | ||
294 | db: self.db.snapshot(), | ||
295 | } | ||
296 | } | ||
297 | /// Applies changes to the current state of the world. If there are | ||
298 | /// outstanding snapshots, they will be canceled. | ||
299 | pub fn apply_change(&mut self, change: AnalysisChange) { | ||
300 | self.db.apply_change(change) | ||
264 | } | 301 | } |
265 | } | 302 | } |
266 | 303 | ||
@@ -270,109 +307,146 @@ impl ReferenceResolution { | |||
270 | /// `Analysis` are canceled (most method return `Err(Canceled)`). | 307 | /// `Analysis` are canceled (most method return `Err(Canceled)`). |
271 | #[derive(Debug)] | 308 | #[derive(Debug)] |
272 | pub struct Analysis { | 309 | pub struct Analysis { |
273 | pub(crate) imp: AnalysisImpl, | 310 | db: salsa::Snapshot<db::RootDatabase>, |
274 | } | 311 | } |
275 | 312 | ||
276 | impl Analysis { | 313 | impl Analysis { |
314 | /// Gets the text of the source file. | ||
277 | pub fn file_text(&self, file_id: FileId) -> Arc<String> { | 315 | pub fn file_text(&self, file_id: FileId) -> Arc<String> { |
278 | self.imp.file_text(file_id) | 316 | self.db.file_text(file_id) |
279 | } | 317 | } |
318 | /// Gets the syntax tree of the file. | ||
280 | pub fn file_syntax(&self, file_id: FileId) -> SourceFileNode { | 319 | pub fn file_syntax(&self, file_id: FileId) -> SourceFileNode { |
281 | self.imp.file_syntax(file_id).clone() | 320 | self.db.source_file(file_id).clone() |
282 | } | 321 | } |
322 | /// Gets the file's `LineIndex`: data structure to convert between absolute | ||
323 | /// offsets and line/column representation. | ||
283 | pub fn file_line_index(&self, file_id: FileId) -> Arc<LineIndex> { | 324 | pub fn file_line_index(&self, file_id: FileId) -> Arc<LineIndex> { |
284 | self.imp.file_line_index(file_id) | 325 | self.db.file_lines(file_id) |
285 | } | 326 | } |
327 | /// Selects the next syntactic nodes encopasing the range. | ||
286 | pub fn extend_selection(&self, frange: FileRange) -> TextRange { | 328 | pub fn extend_selection(&self, frange: FileRange) -> TextRange { |
287 | extend_selection::extend_selection(&self.imp.db, frange) | 329 | extend_selection::extend_selection(&self.db, frange) |
288 | } | 330 | } |
331 | /// Returns position of the mathcing brace (all types of braces are | ||
332 | /// supported). | ||
289 | pub fn matching_brace(&self, file: &SourceFileNode, offset: TextUnit) -> Option<TextUnit> { | 333 | pub fn matching_brace(&self, file: &SourceFileNode, offset: TextUnit) -> Option<TextUnit> { |
290 | ra_editor::matching_brace(file, offset) | 334 | ra_editor::matching_brace(file, offset) |
291 | } | 335 | } |
336 | /// Returns a syntax tree represented as `String`, for debug purposes. | ||
337 | // FIXME: use a better name here. | ||
292 | pub fn syntax_tree(&self, file_id: FileId) -> String { | 338 | pub fn syntax_tree(&self, file_id: FileId) -> String { |
293 | let file = self.imp.file_syntax(file_id); | 339 | let file = self.db.source_file(file_id); |
294 | ra_editor::syntax_tree(&file) | 340 | ra_editor::syntax_tree(&file) |
295 | } | 341 | } |
342 | /// Returns an edit to remove all newlines in the range, cleaning up minor | ||
343 | /// stuff like trailing commas. | ||
296 | pub fn join_lines(&self, frange: FileRange) -> SourceChange { | 344 | pub fn join_lines(&self, frange: FileRange) -> SourceChange { |
297 | let file = self.imp.file_syntax(frange.file_id); | 345 | let file = self.db.source_file(frange.file_id); |
298 | SourceChange::from_local_edit(frange.file_id, ra_editor::join_lines(&file, frange.range)) | 346 | SourceChange::from_local_edit(frange.file_id, ra_editor::join_lines(&file, frange.range)) |
299 | } | 347 | } |
348 | /// Returns an edit which should be applied when opening a new line, fixing | ||
349 | /// up minor stuff like continuing the comment. | ||
300 | pub fn on_enter(&self, position: FilePosition) -> Option<SourceChange> { | 350 | pub fn on_enter(&self, position: FilePosition) -> Option<SourceChange> { |
301 | let file = self.imp.file_syntax(position.file_id); | 351 | let file = self.db.source_file(position.file_id); |
302 | let edit = ra_editor::on_enter(&file, position.offset)?; | 352 | let edit = ra_editor::on_enter(&file, position.offset)?; |
303 | let res = SourceChange::from_local_edit(position.file_id, edit); | 353 | Some(SourceChange::from_local_edit(position.file_id, edit)) |
304 | Some(res) | ||
305 | } | 354 | } |
355 | /// Returns an edit which should be applied after `=` was typed. Primaraly, | ||
356 | /// this works when adding `let =`. | ||
357 | // FIXME: use a snippet completion instead of this hack here. | ||
306 | pub fn on_eq_typed(&self, position: FilePosition) -> Option<SourceChange> { | 358 | pub fn on_eq_typed(&self, position: FilePosition) -> Option<SourceChange> { |
307 | let file = self.imp.file_syntax(position.file_id); | 359 | let file = self.db.source_file(position.file_id); |
308 | Some(SourceChange::from_local_edit( | 360 | let edit = ra_editor::on_eq_typed(&file, position.offset)?; |
309 | position.file_id, | 361 | Some(SourceChange::from_local_edit(position.file_id, edit)) |
310 | ra_editor::on_eq_typed(&file, position.offset)?, | ||
311 | )) | ||
312 | } | 362 | } |
363 | /// Returns a tree representation of symbols in the file. Useful to draw a | ||
364 | /// file outline. | ||
313 | pub fn file_structure(&self, file_id: FileId) -> Vec<StructureNode> { | 365 | pub fn file_structure(&self, file_id: FileId) -> Vec<StructureNode> { |
314 | let file = self.imp.file_syntax(file_id); | 366 | let file = self.db.source_file(file_id); |
315 | ra_editor::file_structure(&file) | 367 | ra_editor::file_structure(&file) |
316 | } | 368 | } |
369 | /// Returns the set of folding ranges. | ||
317 | pub fn folding_ranges(&self, file_id: FileId) -> Vec<Fold> { | 370 | pub fn folding_ranges(&self, file_id: FileId) -> Vec<Fold> { |
318 | let file = self.imp.file_syntax(file_id); | 371 | let file = self.db.source_file(file_id); |
319 | ra_editor::folding_ranges(&file) | 372 | ra_editor::folding_ranges(&file) |
320 | } | 373 | } |
321 | pub fn symbol_search(&self, query: Query) -> Cancelable<Vec<(FileId, FileSymbol)>> { | 374 | /// Fuzzy searches for a symbol. |
322 | self.imp.world_symbols(query) | 375 | pub fn symbol_search(&self, query: Query) -> Cancelable<Vec<NavigationTarget>> { |
376 | let res = symbol_index::world_symbols(&*self.db, query)? | ||
377 | .into_iter() | ||
378 | .map(NavigationTarget::from_symbol) | ||
379 | .collect(); | ||
380 | Ok(res) | ||
323 | } | 381 | } |
382 | /// Resolves reference to definition, but does not gurantee correctness. | ||
324 | pub fn approximately_resolve_symbol( | 383 | pub fn approximately_resolve_symbol( |
325 | &self, | 384 | &self, |
326 | position: FilePosition, | 385 | position: FilePosition, |
327 | ) -> Cancelable<Option<ReferenceResolution>> { | 386 | ) -> Cancelable<Option<ReferenceResolution>> { |
328 | self.imp.approximately_resolve_symbol(position) | 387 | self.db.approximately_resolve_symbol(position) |
329 | } | 388 | } |
389 | /// Finds all usages of the reference at point. | ||
330 | pub fn find_all_refs(&self, position: FilePosition) -> Cancelable<Vec<(FileId, TextRange)>> { | 390 | pub fn find_all_refs(&self, position: FilePosition) -> Cancelable<Vec<(FileId, TextRange)>> { |
331 | self.imp.find_all_refs(position) | 391 | self.db.find_all_refs(position) |
332 | } | 392 | } |
333 | pub fn doc_text_for(&self, file_id: FileId, symbol: FileSymbol) -> Cancelable<Option<String>> { | 393 | /// Returns documentation string for a given target. |
334 | self.imp.doc_text_for(file_id, symbol) | 394 | pub fn doc_text_for(&self, nav: NavigationTarget) -> Cancelable<Option<String>> { |
395 | self.db.doc_text_for(nav) | ||
335 | } | 396 | } |
336 | pub fn parent_module(&self, position: FilePosition) -> Cancelable<Vec<(FileId, FileSymbol)>> { | 397 | /// Returns a `mod name;` declaration whihc created the current module. |
337 | self.imp.parent_module(position) | 398 | pub fn parent_module(&self, position: FilePosition) -> Cancelable<Vec<NavigationTarget>> { |
399 | self.db.parent_module(position) | ||
338 | } | 400 | } |
401 | /// Returns crates this file belongs too. | ||
339 | pub fn crate_for(&self, file_id: FileId) -> Cancelable<Vec<CrateId>> { | 402 | pub fn crate_for(&self, file_id: FileId) -> Cancelable<Vec<CrateId>> { |
340 | self.imp.crate_for(file_id) | 403 | self.db.crate_for(file_id) |
341 | } | 404 | } |
405 | /// Returns the root file of the given crate. | ||
342 | pub fn crate_root(&self, crate_id: CrateId) -> Cancelable<FileId> { | 406 | pub fn crate_root(&self, crate_id: CrateId) -> Cancelable<FileId> { |
343 | Ok(self.imp.crate_root(crate_id)) | 407 | Ok(self.db.crate_root(crate_id)) |
344 | } | 408 | } |
409 | /// Returns the set of possible targets to run for the current file. | ||
345 | pub fn runnables(&self, file_id: FileId) -> Cancelable<Vec<Runnable>> { | 410 | pub fn runnables(&self, file_id: FileId) -> Cancelable<Vec<Runnable>> { |
346 | let file = self.imp.file_syntax(file_id); | 411 | runnables::runnables(&*self.db, file_id) |
347 | Ok(ra_editor::runnables(&file)) | ||
348 | } | 412 | } |
413 | /// Computes syntax highlighting for the given file. | ||
349 | pub fn highlight(&self, file_id: FileId) -> Cancelable<Vec<HighlightedRange>> { | 414 | pub fn highlight(&self, file_id: FileId) -> Cancelable<Vec<HighlightedRange>> { |
350 | syntax_highlighting::highlight(&*self.imp.db, file_id) | 415 | syntax_highlighting::highlight(&*self.db, file_id) |
351 | } | 416 | } |
417 | /// Computes completions at the given position. | ||
352 | pub fn completions(&self, position: FilePosition) -> Cancelable<Option<Vec<CompletionItem>>> { | 418 | pub fn completions(&self, position: FilePosition) -> Cancelable<Option<Vec<CompletionItem>>> { |
353 | self.imp.completions(position) | 419 | let completions = completion::completions(&self.db, position)?; |
420 | Ok(completions.map(|it| it.into())) | ||
354 | } | 421 | } |
422 | /// Computes assists (aks code actons aka intentions) for the given | ||
423 | /// position. | ||
355 | pub fn assists(&self, frange: FileRange) -> Cancelable<Vec<SourceChange>> { | 424 | pub fn assists(&self, frange: FileRange) -> Cancelable<Vec<SourceChange>> { |
356 | Ok(self.imp.assists(frange)) | 425 | Ok(self.db.assists(frange)) |
357 | } | 426 | } |
427 | /// Computes the set of diagnostics for the given file. | ||
358 | pub fn diagnostics(&self, file_id: FileId) -> Cancelable<Vec<Diagnostic>> { | 428 | pub fn diagnostics(&self, file_id: FileId) -> Cancelable<Vec<Diagnostic>> { |
359 | self.imp.diagnostics(file_id) | 429 | self.db.diagnostics(file_id) |
360 | } | 430 | } |
431 | /// Computes parameter information for the given call expression. | ||
361 | pub fn resolve_callable( | 432 | pub fn resolve_callable( |
362 | &self, | 433 | &self, |
363 | position: FilePosition, | 434 | position: FilePosition, |
364 | ) -> Cancelable<Option<(FnSignatureInfo, Option<usize>)>> { | 435 | ) -> Cancelable<Option<(FnSignatureInfo, Option<usize>)>> { |
365 | self.imp.resolve_callable(position) | 436 | self.db.resolve_callable(position) |
366 | } | 437 | } |
438 | /// Computes the type of the expression at the given position. | ||
367 | pub fn type_of(&self, frange: FileRange) -> Cancelable<Option<String>> { | 439 | pub fn type_of(&self, frange: FileRange) -> Cancelable<Option<String>> { |
368 | self.imp.type_of(frange) | 440 | self.db.type_of(frange) |
369 | } | 441 | } |
442 | /// Returns the edit required to rename reference at the position to the new | ||
443 | /// name. | ||
370 | pub fn rename( | 444 | pub fn rename( |
371 | &self, | 445 | &self, |
372 | position: FilePosition, | 446 | position: FilePosition, |
373 | new_name: &str, | 447 | new_name: &str, |
374 | ) -> Cancelable<Vec<SourceFileEdit>> { | 448 | ) -> Cancelable<Vec<SourceFileEdit>> { |
375 | self.imp.rename(position, new_name) | 449 | self.db.rename(position, new_name) |
376 | } | 450 | } |
377 | } | 451 | } |
378 | 452 | ||
diff --git a/crates/ra_analysis/src/macros.rs b/crates/ra_analysis/src/macros.rs deleted file mode 100644 index b9feb7fad..000000000 --- a/crates/ra_analysis/src/macros.rs +++ /dev/null | |||
@@ -1,75 +0,0 @@ | |||
1 | /// Begining of macro expansion. | ||
2 | /// | ||
3 | /// This code should be moved out of ra_analysis into hir (?) ideally. | ||
4 | use ra_syntax::{ast, AstNode, SourceFileNode, TextRange}; | ||
5 | |||
6 | use crate::{db::RootDatabase, FileId}; | ||
7 | |||
8 | pub(crate) fn expand( | ||
9 | _db: &RootDatabase, | ||
10 | _file_id: FileId, | ||
11 | macro_call: ast::MacroCall, | ||
12 | ) -> Option<MacroExpansion> { | ||
13 | let path = macro_call.path()?; | ||
14 | if path.qualifier().is_some() { | ||
15 | return None; | ||
16 | } | ||
17 | let name_ref = path.segment()?.name_ref()?; | ||
18 | if name_ref.text() != "ctry" { | ||
19 | return None; | ||
20 | } | ||
21 | |||
22 | let arg = macro_call.token_tree()?; | ||
23 | let text = format!( | ||
24 | r" | ||
25 | fn dummy() {{ | ||
26 | match {} {{ | ||
27 | None => return Ok(None), | ||
28 | Some(it) => it, | ||
29 | }} | ||
30 | }}", | ||
31 | arg.syntax().text() | ||
32 | ); | ||
33 | let file = SourceFileNode::parse(&text); | ||
34 | let match_expr = file.syntax().descendants().find_map(ast::MatchExpr::cast)?; | ||
35 | let match_arg = match_expr.expr()?; | ||
36 | let ranges_map = vec![(arg.syntax().range(), match_arg.syntax().range())]; | ||
37 | let res = MacroExpansion { | ||
38 | source_file: file, | ||
39 | ranges_map, | ||
40 | }; | ||
41 | Some(res) | ||
42 | } | ||
43 | |||
44 | pub(crate) struct MacroExpansion { | ||
45 | pub(crate) source_file: SourceFileNode, | ||
46 | pub(crate) ranges_map: Vec<(TextRange, TextRange)>, | ||
47 | } | ||
48 | |||
49 | impl MacroExpansion { | ||
50 | pub(crate) fn source_file(&self) -> &SourceFileNode { | ||
51 | &self.source_file | ||
52 | } | ||
53 | pub(crate) fn map_range_back(&self, tgt_range: TextRange) -> Option<TextRange> { | ||
54 | for (s_range, t_range) in self.ranges_map.iter() { | ||
55 | if tgt_range.is_subrange(&t_range) { | ||
56 | let tgt_at_zero_range = tgt_range - tgt_range.start(); | ||
57 | let tgt_range_offset = tgt_range.start() - t_range.start(); | ||
58 | let src_range = tgt_at_zero_range + tgt_range_offset + s_range.start(); | ||
59 | return Some(src_range); | ||
60 | } | ||
61 | } | ||
62 | None | ||
63 | } | ||
64 | pub(crate) fn map_range_forward(&self, src_range: TextRange) -> Option<TextRange> { | ||
65 | for (s_range, t_range) in self.ranges_map.iter() { | ||
66 | if src_range.is_subrange(&s_range) { | ||
67 | let src_at_zero_range = src_range - src_range.start(); | ||
68 | let src_range_offset = src_range.start() - s_range.start(); | ||
69 | let src_range = src_at_zero_range + src_range_offset + t_range.start(); | ||
70 | return Some(src_range); | ||
71 | } | ||
72 | } | ||
73 | None | ||
74 | } | ||
75 | } | ||
diff --git a/crates/ra_analysis/src/runnables.rs b/crates/ra_analysis/src/runnables.rs new file mode 100644 index 000000000..474267605 --- /dev/null +++ b/crates/ra_analysis/src/runnables.rs | |||
@@ -0,0 +1,86 @@ | |||
1 | use itertools::Itertools; | ||
2 | use ra_syntax::{ | ||
3 | ast::{self, AstNode, NameOwner, ModuleItemOwner}, | ||
4 | TextRange, SyntaxNodeRef, | ||
5 | }; | ||
6 | use ra_db::{Cancelable, SyntaxDatabase}; | ||
7 | |||
8 | use crate::{db::RootDatabase, FileId}; | ||
9 | |||
10 | #[derive(Debug)] | ||
11 | pub struct Runnable { | ||
12 | pub range: TextRange, | ||
13 | pub kind: RunnableKind, | ||
14 | } | ||
15 | |||
16 | #[derive(Debug)] | ||
17 | pub enum RunnableKind { | ||
18 | Test { name: String }, | ||
19 | TestMod { path: String }, | ||
20 | Bin, | ||
21 | } | ||
22 | |||
23 | pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Cancelable<Vec<Runnable>> { | ||
24 | let source_file = db.source_file(file_id); | ||
25 | let res = source_file | ||
26 | .syntax() | ||
27 | .descendants() | ||
28 | .filter_map(|i| runnable(db, file_id, i)) | ||
29 | .collect(); | ||
30 | Ok(res) | ||
31 | } | ||
32 | |||
33 | fn runnable(db: &RootDatabase, file_id: FileId, item: SyntaxNodeRef) -> Option<Runnable> { | ||
34 | if let Some(fn_def) = ast::FnDef::cast(item) { | ||
35 | runnable_fn(fn_def) | ||
36 | } else if let Some(m) = ast::Module::cast(item) { | ||
37 | runnable_mod(db, file_id, m) | ||
38 | } else { | ||
39 | None | ||
40 | } | ||
41 | } | ||
42 | |||
43 | fn runnable_fn(fn_def: ast::FnDef) -> Option<Runnable> { | ||
44 | let name = fn_def.name()?.text(); | ||
45 | let kind = if name == "main" { | ||
46 | RunnableKind::Bin | ||
47 | } else if fn_def.has_atom_attr("test") { | ||
48 | RunnableKind::Test { | ||
49 | name: name.to_string(), | ||
50 | } | ||
51 | } else { | ||
52 | return None; | ||
53 | }; | ||
54 | Some(Runnable { | ||
55 | range: fn_def.syntax().range(), | ||
56 | kind, | ||
57 | }) | ||
58 | } | ||
59 | |||
60 | fn runnable_mod(db: &RootDatabase, file_id: FileId, module: ast::Module) -> Option<Runnable> { | ||
61 | let has_test_function = module | ||
62 | .item_list()? | ||
63 | .items() | ||
64 | .filter_map(|it| match it { | ||
65 | ast::ModuleItem::FnDef(it) => Some(it), | ||
66 | _ => None, | ||
67 | }) | ||
68 | .any(|f| f.has_atom_attr("test")); | ||
69 | if !has_test_function { | ||
70 | return None; | ||
71 | } | ||
72 | let range = module.syntax().range(); | ||
73 | let module = | ||
74 | hir::source_binder::module_from_child_node(db, file_id, module.syntax()).ok()??; | ||
75 | let path = module | ||
76 | .path_to_root() | ||
77 | .into_iter() | ||
78 | .rev() | ||
79 | .into_iter() | ||
80 | .filter_map(|it| it.name().map(Clone::clone)) | ||
81 | .join("::"); | ||
82 | Some(Runnable { | ||
83 | range, | ||
84 | kind: RunnableKind::TestMod { path }, | ||
85 | }) | ||
86 | } | ||
diff --git a/crates/ra_analysis/src/symbol_index.rs b/crates/ra_analysis/src/symbol_index.rs index e5bdf0aa1..e2b1c88fe 100644 --- a/crates/ra_analysis/src/symbol_index.rs +++ b/crates/ra_analysis/src/symbol_index.rs | |||
@@ -1,24 +1,48 @@ | |||
1 | //! This module handles fuzzy-searching of functions, structs and other symbols | ||
2 | //! by name across the whole workspace and dependencies. | ||
3 | //! | ||
4 | //! It works by building an incrementally-updated text-search index of all | ||
5 | //! symbols. The backbone of the index is the **awesome** `fst` crate by | ||
6 | //! @BurntSushi. | ||
7 | //! | ||
8 | //! In a nutshell, you give a set of strings to the `fst`, and it builds a | ||
9 | //! finite state machine describing this set of strtings. The strings which | ||
10 | //! could fuzzy-match a pattern can also be described by a finite state machine. | ||
11 | //! What is freakingly cool is that you can now traverse both state machines in | ||
12 | //! lock-step to enumerate the strings which are both in the input set and | ||
13 | //! fuzz-match the query. Or, more formally, given two langauges described by | ||
14 | //! fsts, one can build an product fst which describes the intersection of the | ||
15 | //! languages. | ||
16 | //! | ||
17 | //! `fst` does not support cheap updating of the index, but it supports unioning | ||
18 | //! of state machines. So, to account for changing source code, we build an fst | ||
19 | //! for each library (which is assumed to never change) and an fst for each rust | ||
20 | //! file in the current workspace, and run a query aginst the union of all | ||
21 | //! thouse fsts. | ||
1 | use std::{ | 22 | use std::{ |
23 | cmp::Ordering, | ||
2 | hash::{Hash, Hasher}, | 24 | hash::{Hash, Hasher}, |
3 | sync::Arc, | 25 | sync::Arc, |
4 | }; | 26 | }; |
5 | 27 | ||
6 | use fst::{self, Streamer}; | 28 | use fst::{self, Streamer}; |
7 | use ra_editor::{self, FileSymbol}; | ||
8 | use ra_syntax::{ | 29 | use ra_syntax::{ |
9 | SourceFileNode, | 30 | SyntaxNodeRef, SourceFileNode, SmolStr, |
31 | algo::{visit::{visitor, Visitor}, find_covering_node}, | ||
10 | SyntaxKind::{self, *}, | 32 | SyntaxKind::{self, *}, |
33 | ast::{self, NameOwner}, | ||
11 | }; | 34 | }; |
12 | use ra_db::{SyntaxDatabase, SourceRootId}; | 35 | use ra_db::{SourceRootId, FilesDatabase, LocalSyntaxPtr}; |
36 | use salsa::ParallelDatabase; | ||
13 | use rayon::prelude::*; | 37 | use rayon::prelude::*; |
14 | 38 | ||
15 | use crate::{ | 39 | use crate::{ |
16 | Cancelable, | 40 | Cancelable, FileId, Query, |
17 | FileId, Query, | 41 | db::RootDatabase, |
18 | }; | 42 | }; |
19 | 43 | ||
20 | salsa::query_group! { | 44 | salsa::query_group! { |
21 | pub(crate) trait SymbolsDatabase: SyntaxDatabase { | 45 | pub(crate) trait SymbolsDatabase: hir::db::HirDatabase { |
22 | fn file_symbols(file_id: FileId) -> Cancelable<Arc<SymbolIndex>> { | 46 | fn file_symbols(file_id: FileId) -> Cancelable<Arc<SymbolIndex>> { |
23 | type FileSymbolsQuery; | 47 | type FileSymbolsQuery; |
24 | } | 48 | } |
@@ -29,15 +53,60 @@ salsa::query_group! { | |||
29 | } | 53 | } |
30 | } | 54 | } |
31 | 55 | ||
32 | fn file_symbols(db: &impl SyntaxDatabase, file_id: FileId) -> Cancelable<Arc<SymbolIndex>> { | 56 | fn file_symbols(db: &impl SymbolsDatabase, file_id: FileId) -> Cancelable<Arc<SymbolIndex>> { |
33 | db.check_canceled()?; | 57 | db.check_canceled()?; |
34 | let syntax = db.source_file(file_id); | 58 | let source_file = db.source_file(file_id); |
35 | Ok(Arc::new(SymbolIndex::for_file(file_id, syntax))) | 59 | let mut symbols = source_file |
60 | .syntax() | ||
61 | .descendants() | ||
62 | .filter_map(to_symbol) | ||
63 | .map(move |(name, ptr)| FileSymbol { name, ptr, file_id }) | ||
64 | .collect::<Vec<_>>(); | ||
65 | |||
66 | for (name, text_range) in hir::source_binder::macro_symbols(db, file_id)? { | ||
67 | let node = find_covering_node(source_file.syntax(), text_range); | ||
68 | let ptr = LocalSyntaxPtr::new(node); | ||
69 | symbols.push(FileSymbol { file_id, name, ptr }) | ||
70 | } | ||
71 | |||
72 | Ok(Arc::new(SymbolIndex::new(symbols))) | ||
73 | } | ||
74 | |||
75 | pub(crate) fn world_symbols(db: &RootDatabase, query: Query) -> Cancelable<Vec<FileSymbol>> { | ||
76 | /// Need to wrap Snapshot to provide `Clone` impl for `map_with` | ||
77 | struct Snap(salsa::Snapshot<RootDatabase>); | ||
78 | impl Clone for Snap { | ||
79 | fn clone(&self) -> Snap { | ||
80 | Snap(self.0.snapshot()) | ||
81 | } | ||
82 | } | ||
83 | |||
84 | let buf: Vec<Arc<SymbolIndex>> = if query.libs { | ||
85 | let snap = Snap(db.snapshot()); | ||
86 | db.library_roots() | ||
87 | .par_iter() | ||
88 | .map_with(snap, |db, &lib_id| db.0.library_symbols(lib_id)) | ||
89 | .collect() | ||
90 | } else { | ||
91 | let mut files = Vec::new(); | ||
92 | for &root in db.local_roots().iter() { | ||
93 | let sr = db.source_root(root); | ||
94 | files.extend(sr.files.values().map(|&it| it)) | ||
95 | } | ||
96 | |||
97 | let snap = Snap(db.snapshot()); | ||
98 | files | ||
99 | .par_iter() | ||
100 | .map_with(snap, |db, &file_id| db.0.file_symbols(file_id)) | ||
101 | .filter_map(|it| it.ok()) | ||
102 | .collect() | ||
103 | }; | ||
104 | Ok(query.search(&buf)) | ||
36 | } | 105 | } |
37 | 106 | ||
38 | #[derive(Default, Debug)] | 107 | #[derive(Default, Debug)] |
39 | pub(crate) struct SymbolIndex { | 108 | pub(crate) struct SymbolIndex { |
40 | symbols: Vec<(FileId, FileSymbol)>, | 109 | symbols: Vec<FileSymbol>, |
41 | map: fst::Map, | 110 | map: fst::Map, |
42 | } | 111 | } |
43 | 112 | ||
@@ -56,6 +125,17 @@ impl Hash for SymbolIndex { | |||
56 | } | 125 | } |
57 | 126 | ||
58 | impl SymbolIndex { | 127 | impl SymbolIndex { |
128 | fn new(mut symbols: Vec<FileSymbol>) -> SymbolIndex { | ||
129 | fn cmp(s1: &FileSymbol, s2: &FileSymbol) -> Ordering { | ||
130 | unicase::Ascii::new(s1.name.as_str()).cmp(&unicase::Ascii::new(s2.name.as_str())) | ||
131 | } | ||
132 | symbols.par_sort_by(cmp); | ||
133 | symbols.dedup_by(|s1, s2| cmp(s1, s2) == Ordering::Equal); | ||
134 | let names = symbols.iter().map(|it| it.name.as_str().to_lowercase()); | ||
135 | let map = fst::Map::from_iter(names.into_iter().zip(0u64..)).unwrap(); | ||
136 | SymbolIndex { symbols, map } | ||
137 | } | ||
138 | |||
59 | pub(crate) fn len(&self) -> usize { | 139 | pub(crate) fn len(&self) -> usize { |
60 | self.symbols.len() | 140 | self.symbols.len() |
61 | } | 141 | } |
@@ -63,29 +143,21 @@ impl SymbolIndex { | |||
63 | pub(crate) fn for_files( | 143 | pub(crate) fn for_files( |
64 | files: impl ParallelIterator<Item = (FileId, SourceFileNode)>, | 144 | files: impl ParallelIterator<Item = (FileId, SourceFileNode)>, |
65 | ) -> SymbolIndex { | 145 | ) -> SymbolIndex { |
66 | let mut symbols = files | 146 | let symbols = files |
67 | .flat_map(|(file_id, file)| { | 147 | .flat_map(|(file_id, file)| { |
68 | ra_editor::file_symbols(&file) | 148 | file.syntax() |
69 | .into_iter() | 149 | .descendants() |
70 | .map(move |symbol| (symbol.name.as_str().to_lowercase(), (file_id, symbol))) | 150 | .filter_map(to_symbol) |
151 | .map(move |(name, ptr)| FileSymbol { name, ptr, file_id }) | ||
71 | .collect::<Vec<_>>() | 152 | .collect::<Vec<_>>() |
72 | }) | 153 | }) |
73 | .collect::<Vec<_>>(); | 154 | .collect::<Vec<_>>(); |
74 | symbols.par_sort_by(|s1, s2| s1.0.cmp(&s2.0)); | 155 | SymbolIndex::new(symbols) |
75 | symbols.dedup_by(|s1, s2| s1.0 == s2.0); | ||
76 | let (names, symbols): (Vec<String>, Vec<(FileId, FileSymbol)>) = | ||
77 | symbols.into_iter().unzip(); | ||
78 | let map = fst::Map::from_iter(names.into_iter().zip(0u64..)).unwrap(); | ||
79 | SymbolIndex { symbols, map } | ||
80 | } | ||
81 | |||
82 | pub(crate) fn for_file(file_id: FileId, file: SourceFileNode) -> SymbolIndex { | ||
83 | SymbolIndex::for_files(rayon::iter::once((file_id, file))) | ||
84 | } | 156 | } |
85 | } | 157 | } |
86 | 158 | ||
87 | impl Query { | 159 | impl Query { |
88 | pub(crate) fn search(self, indices: &[Arc<SymbolIndex>]) -> Vec<(FileId, FileSymbol)> { | 160 | pub(crate) fn search(self, indices: &[Arc<SymbolIndex>]) -> Vec<FileSymbol> { |
89 | let mut op = fst::map::OpBuilder::new(); | 161 | let mut op = fst::map::OpBuilder::new(); |
90 | for file_symbols in indices.iter() { | 162 | for file_symbols in indices.iter() { |
91 | let automaton = fst::automaton::Subsequence::new(&self.lowercased); | 163 | let automaton = fst::automaton::Subsequence::new(&self.lowercased); |
@@ -101,14 +173,14 @@ impl Query { | |||
101 | let file_symbols = &indices[indexed_value.index]; | 173 | let file_symbols = &indices[indexed_value.index]; |
102 | let idx = indexed_value.value as usize; | 174 | let idx = indexed_value.value as usize; |
103 | 175 | ||
104 | let (file_id, symbol) = &file_symbols.symbols[idx]; | 176 | let symbol = &file_symbols.symbols[idx]; |
105 | if self.only_types && !is_type(symbol.kind) { | 177 | if self.only_types && !is_type(symbol.ptr.kind()) { |
106 | continue; | 178 | continue; |
107 | } | 179 | } |
108 | if self.exact && symbol.name != self.query { | 180 | if self.exact && symbol.name != self.query { |
109 | continue; | 181 | continue; |
110 | } | 182 | } |
111 | res.push((*file_id, symbol.clone())); | 183 | res.push(symbol.clone()); |
112 | } | 184 | } |
113 | } | 185 | } |
114 | res | 186 | res |
@@ -121,3 +193,30 @@ fn is_type(kind: SyntaxKind) -> bool { | |||
121 | _ => false, | 193 | _ => false, |
122 | } | 194 | } |
123 | } | 195 | } |
196 | |||
197 | /// The actual data that is stored in the index. It should be as compact as | ||
198 | /// possible. | ||
199 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] | ||
200 | pub(crate) struct FileSymbol { | ||
201 | pub(crate) file_id: FileId, | ||
202 | pub(crate) name: SmolStr, | ||
203 | pub(crate) ptr: LocalSyntaxPtr, | ||
204 | } | ||
205 | |||
206 | fn to_symbol(node: SyntaxNodeRef) -> Option<(SmolStr, LocalSyntaxPtr)> { | ||
207 | fn decl<'a, N: NameOwner<'a>>(node: N) -> Option<(SmolStr, LocalSyntaxPtr)> { | ||
208 | let name = node.name()?.text(); | ||
209 | let ptr = LocalSyntaxPtr::new(node.syntax()); | ||
210 | Some((name, ptr)) | ||
211 | } | ||
212 | visitor() | ||
213 | .visit(decl::<ast::FnDef>) | ||
214 | .visit(decl::<ast::StructDef>) | ||
215 | .visit(decl::<ast::EnumDef>) | ||
216 | .visit(decl::<ast::TraitDef>) | ||
217 | .visit(decl::<ast::Module>) | ||
218 | .visit(decl::<ast::TypeDef>) | ||
219 | .visit(decl::<ast::ConstDef>) | ||
220 | .visit(decl::<ast::StaticDef>) | ||
221 | .accept(node)? | ||
222 | } | ||
diff --git a/crates/ra_analysis/src/syntax_highlighting.rs b/crates/ra_analysis/src/syntax_highlighting.rs index 38219da71..35e153ca0 100644 --- a/crates/ra_analysis/src/syntax_highlighting.rs +++ b/crates/ra_analysis/src/syntax_highlighting.rs | |||
@@ -9,19 +9,19 @@ use crate::{ | |||
9 | 9 | ||
10 | pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Cancelable<Vec<HighlightedRange>> { | 10 | pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Cancelable<Vec<HighlightedRange>> { |
11 | let source_file = db.source_file(file_id); | 11 | let source_file = db.source_file(file_id); |
12 | let mut res = ra_editor::highlight(&source_file); | 12 | let mut res = ra_editor::highlight(source_file.syntax()); |
13 | for macro_call in source_file | 13 | for macro_call in source_file |
14 | .syntax() | 14 | .syntax() |
15 | .descendants() | 15 | .descendants() |
16 | .filter_map(ast::MacroCall::cast) | 16 | .filter_map(ast::MacroCall::cast) |
17 | { | 17 | { |
18 | if let Some(exp) = crate::macros::expand(db, file_id, macro_call) { | 18 | if let Some((off, exp)) = hir::MacroDef::ast_expand(macro_call) { |
19 | let mapped_ranges = ra_editor::highlight(exp.source_file()) | 19 | let mapped_ranges = ra_editor::highlight(exp.syntax().borrowed()) |
20 | .into_iter() | 20 | .into_iter() |
21 | .filter_map(|r| { | 21 | .filter_map(|r| { |
22 | let mapped_range = exp.map_range_back(r.range)?; | 22 | let mapped_range = exp.map_range_back(r.range)?; |
23 | let res = HighlightedRange { | 23 | let res = HighlightedRange { |
24 | range: mapped_range, | 24 | range: mapped_range + off, |
25 | tag: r.tag, | 25 | tag: r.tag, |
26 | }; | 26 | }; |
27 | Some(res) | 27 | Some(res) |
@@ -43,8 +43,9 @@ mod tests { | |||
43 | " | 43 | " |
44 | fn main() { | 44 | fn main() { |
45 | ctry!({ let x = 92; x}); | 45 | ctry!({ let x = 92; x}); |
46 | vec![{ let x = 92; x}]; | ||
46 | } | 47 | } |
47 | ", | 48 | ", |
48 | ); | 49 | ); |
49 | let highlights = analysis.highlight(file_id).unwrap(); | 50 | let highlights = analysis.highlight(file_id).unwrap(); |
50 | assert_eq_dbg( | 51 | assert_eq_dbg( |
@@ -53,10 +54,39 @@ mod tests { | |||
53 | HighlightedRange { range: [41; 46), tag: "macro" }, | 54 | HighlightedRange { range: [41; 46), tag: "macro" }, |
54 | HighlightedRange { range: [49; 52), tag: "keyword" }, | 55 | HighlightedRange { range: [49; 52), tag: "keyword" }, |
55 | HighlightedRange { range: [57; 59), tag: "literal" }, | 56 | HighlightedRange { range: [57; 59), tag: "literal" }, |
57 | HighlightedRange { range: [82; 86), tag: "macro" }, | ||
58 | HighlightedRange { range: [89; 92), tag: "keyword" }, | ||
59 | HighlightedRange { range: [97; 99), tag: "literal" }, | ||
56 | HighlightedRange { range: [49; 52), tag: "keyword" }, | 60 | HighlightedRange { range: [49; 52), tag: "keyword" }, |
57 | HighlightedRange { range: [53; 54), tag: "function" }, | 61 | HighlightedRange { range: [53; 54), tag: "function" }, |
58 | HighlightedRange { range: [57; 59), tag: "literal" }, | 62 | HighlightedRange { range: [57; 59), tag: "literal" }, |
59 | HighlightedRange { range: [61; 62), tag: "text" }]"#, | 63 | HighlightedRange { range: [61; 62), tag: "text" }, |
64 | HighlightedRange { range: [89; 92), tag: "keyword" }, | ||
65 | HighlightedRange { range: [93; 94), tag: "function" }, | ||
66 | HighlightedRange { range: [97; 99), tag: "literal" }, | ||
67 | HighlightedRange { range: [101; 102), tag: "text" }]"#, | ||
68 | &highlights, | ||
69 | ) | ||
70 | } | ||
71 | |||
72 | // FIXME: this test is not really necessary: artifact of the inital hacky | ||
73 | // macros implementation. | ||
74 | #[test] | ||
75 | fn highlight_query_group_macro() { | ||
76 | let (analysis, file_id) = single_file( | ||
77 | " | ||
78 | salsa::query_group! { | ||
79 | pub trait HirDatabase: SyntaxDatabase {} | ||
80 | } | ||
81 | ", | ||
82 | ); | ||
83 | let highlights = analysis.highlight(file_id).unwrap(); | ||
84 | assert_eq_dbg( | ||
85 | r#"[HighlightedRange { range: [20; 32), tag: "macro" }, | ||
86 | HighlightedRange { range: [13; 18), tag: "text" }, | ||
87 | HighlightedRange { range: [51; 54), tag: "keyword" }, | ||
88 | HighlightedRange { range: [55; 60), tag: "keyword" }, | ||
89 | HighlightedRange { range: [61; 72), tag: "function" }]"#, | ||
60 | &highlights, | 90 | &highlights, |
61 | ) | 91 | ) |
62 | } | 92 | } |
diff --git a/crates/ra_analysis/tests/tests.rs b/crates/ra_analysis/tests/test/main.rs index a314fbc40..bfdf8aef2 100644 --- a/crates/ra_analysis/tests/tests.rs +++ b/crates/ra_analysis/tests/test/main.rs | |||
@@ -1,9 +1,12 @@ | |||
1 | mod runnables; | ||
2 | mod type_of; | ||
3 | |||
1 | use ra_syntax::TextRange; | 4 | use ra_syntax::TextRange; |
2 | use test_utils::{assert_eq_dbg, assert_eq_text}; | 5 | use test_utils::{assert_eq_dbg, assert_eq_text}; |
3 | 6 | ||
4 | use ra_analysis::{ | 7 | use ra_analysis::{ |
5 | mock_analysis::{analysis_and_position, single_file, single_file_with_position, MockAnalysis}, | 8 | mock_analysis::{analysis_and_position, single_file, single_file_with_position, MockAnalysis}, |
6 | AnalysisChange, CrateGraph, FileId, FnSignatureInfo, | 9 | AnalysisChange, CrateGraph, FileId, FnSignatureInfo, Query |
7 | }; | 10 | }; |
8 | 11 | ||
9 | fn get_signature(text: &str) -> (FnSignatureInfo, Option<usize>) { | 12 | fn get_signature(text: &str) -> (FnSignatureInfo, Option<usize>) { |
@@ -25,7 +28,7 @@ fn approximate_resolve_works_in_items() { | |||
25 | assert_eq_dbg( | 28 | assert_eq_dbg( |
26 | r#"ReferenceResolution { | 29 | r#"ReferenceResolution { |
27 | reference_range: [23; 26), | 30 | reference_range: [23; 26), |
28 | resolves_to: [(FileId(1), FileSymbol { name: "Foo", node_range: [0; 11), kind: STRUCT_DEF })] | 31 | resolves_to: [NavigationTarget { file_id: FileId(1), name: "Foo", kind: STRUCT_DEF, range: [0; 11), ptr: Some(LocalSyntaxPtr { range: [0; 11), kind: STRUCT_DEF }) }] |
29 | }"#, | 32 | }"#, |
30 | &symbols, | 33 | &symbols, |
31 | ); | 34 | ); |
@@ -46,7 +49,7 @@ fn test_resolve_module() { | |||
46 | assert_eq_dbg( | 49 | assert_eq_dbg( |
47 | r#"ReferenceResolution { | 50 | r#"ReferenceResolution { |
48 | reference_range: [4; 7), | 51 | reference_range: [4; 7), |
49 | resolves_to: [(FileId(2), FileSymbol { name: "foo", node_range: [0; 0), kind: MODULE })] | 52 | resolves_to: [NavigationTarget { file_id: FileId(2), name: "foo", kind: MODULE, range: [0; 0), ptr: None }] |
50 | }"#, | 53 | }"#, |
51 | &symbols, | 54 | &symbols, |
52 | ); | 55 | ); |
@@ -64,7 +67,7 @@ fn test_resolve_module() { | |||
64 | assert_eq_dbg( | 67 | assert_eq_dbg( |
65 | r#"ReferenceResolution { | 68 | r#"ReferenceResolution { |
66 | reference_range: [4; 7), | 69 | reference_range: [4; 7), |
67 | resolves_to: [(FileId(2), FileSymbol { name: "foo", node_range: [0; 0), kind: MODULE })] | 70 | resolves_to: [NavigationTarget { file_id: FileId(2), name: "foo", kind: MODULE, range: [0; 0), ptr: None }] |
68 | }"#, | 71 | }"#, |
69 | &symbols, | 72 | &symbols, |
70 | ); | 73 | ); |
@@ -107,7 +110,7 @@ fn test_resolve_parent_module() { | |||
107 | ); | 110 | ); |
108 | let symbols = analysis.parent_module(pos).unwrap(); | 111 | let symbols = analysis.parent_module(pos).unwrap(); |
109 | assert_eq_dbg( | 112 | assert_eq_dbg( |
110 | r#"[(FileId(1), FileSymbol { name: "foo", node_range: [4; 7), kind: MODULE })]"#, | 113 | r#"[NavigationTarget { file_id: FileId(1), name: "foo", kind: MODULE, range: [4; 7), ptr: None }]"#, |
111 | &symbols, | 114 | &symbols, |
112 | ); | 115 | ); |
113 | } | 116 | } |
@@ -126,7 +129,7 @@ fn test_resolve_parent_module_for_inline() { | |||
126 | ); | 129 | ); |
127 | let symbols = analysis.parent_module(pos).unwrap(); | 130 | let symbols = analysis.parent_module(pos).unwrap(); |
128 | assert_eq_dbg( | 131 | assert_eq_dbg( |
129 | r#"[(FileId(1), FileSymbol { name: "bar", node_range: [18; 21), kind: MODULE })]"#, | 132 | r#"[NavigationTarget { file_id: FileId(1), name: "bar", kind: MODULE, range: [18; 21), ptr: None }]"#, |
130 | &symbols, | 133 | &symbols, |
131 | ); | 134 | ); |
132 | } | 135 | } |
@@ -528,10 +531,11 @@ fn test_rename_for_mut_param() { | |||
528 | }"#, | 531 | }"#, |
529 | ); | 532 | ); |
530 | } | 533 | } |
534 | |||
531 | fn test_rename(text: &str, new_name: &str, expected: &str) { | 535 | fn test_rename(text: &str, new_name: &str, expected: &str) { |
532 | let (analysis, position) = single_file_with_position(text); | 536 | let (analysis, position) = single_file_with_position(text); |
533 | let edits = analysis.rename(position, new_name).unwrap(); | 537 | let edits = analysis.rename(position, new_name).unwrap(); |
534 | let mut text_edit_bulder = ra_text_edit::TextEditBuilder::new(); | 538 | let mut text_edit_bulder = ra_text_edit::TextEditBuilder::default(); |
535 | let mut file_id: Option<FileId> = None; | 539 | let mut file_id: Option<FileId> = None; |
536 | for edit in edits { | 540 | for edit in edits { |
537 | file_id = Some(edit.file_id); | 541 | file_id = Some(edit.file_id); |
@@ -544,3 +548,19 @@ fn test_rename(text: &str, new_name: &str, expected: &str) { | |||
544 | .apply(&*analysis.file_text(file_id.unwrap())); | 548 | .apply(&*analysis.file_text(file_id.unwrap())); |
545 | assert_eq_text!(expected, &*result); | 549 | assert_eq_text!(expected, &*result); |
546 | } | 550 | } |
551 | |||
552 | #[test] | ||
553 | fn world_symbols_include_stuff_from_macros() { | ||
554 | let (analysis, _) = single_file( | ||
555 | " | ||
556 | salsa::query_group! { | ||
557 | pub trait HirDatabase: SyntaxDatabase {} | ||
558 | } | ||
559 | ", | ||
560 | ); | ||
561 | |||
562 | let mut symbols = analysis.symbol_search(Query::new("Hir".into())).unwrap(); | ||
563 | let s = symbols.pop().unwrap(); | ||
564 | assert_eq!(s.name(), "HirDatabase"); | ||
565 | assert_eq!(s.range(), TextRange::from_to(33.into(), 44.into())); | ||
566 | } | ||
diff --git a/crates/ra_analysis/tests/test/runnables.rs b/crates/ra_analysis/tests/test/runnables.rs new file mode 100644 index 000000000..e6e0afbc3 --- /dev/null +++ b/crates/ra_analysis/tests/test/runnables.rs | |||
@@ -0,0 +1,109 @@ | |||
1 | use test_utils::assert_eq_dbg; | ||
2 | |||
3 | use ra_analysis::mock_analysis::analysis_and_position; | ||
4 | |||
5 | #[test] | ||
6 | fn test_runnables() { | ||
7 | let (analysis, pos) = analysis_and_position( | ||
8 | r#" | ||
9 | //- /lib.rs | ||
10 | <|> //empty | ||
11 | fn main() {} | ||
12 | |||
13 | #[test] | ||
14 | fn test_foo() {} | ||
15 | |||
16 | #[test] | ||
17 | #[ignore] | ||
18 | fn test_foo() {} | ||
19 | "#, | ||
20 | ); | ||
21 | let runnables = analysis.runnables(pos.file_id).unwrap(); | ||
22 | assert_eq_dbg( | ||
23 | r#"[Runnable { range: [1; 21), kind: Bin }, | ||
24 | Runnable { range: [22; 46), kind: Test { name: "test_foo" } }, | ||
25 | Runnable { range: [47; 81), kind: Test { name: "test_foo" } }]"#, | ||
26 | &runnables, | ||
27 | ) | ||
28 | } | ||
29 | |||
30 | #[test] | ||
31 | fn test_runnables_module() { | ||
32 | let (analysis, pos) = analysis_and_position( | ||
33 | r#" | ||
34 | //- /lib.rs | ||
35 | <|> //empty | ||
36 | mod test_mod { | ||
37 | #[test] | ||
38 | fn test_foo1() {} | ||
39 | } | ||
40 | "#, | ||
41 | ); | ||
42 | let runnables = analysis.runnables(pos.file_id).unwrap(); | ||
43 | assert_eq_dbg( | ||
44 | r#"[Runnable { range: [1; 59), kind: TestMod { path: "test_mod" } }, | ||
45 | Runnable { range: [28; 57), kind: Test { name: "test_foo1" } }]"#, | ||
46 | &runnables, | ||
47 | ) | ||
48 | } | ||
49 | |||
50 | #[test] | ||
51 | fn test_runnables_one_depth_layer_module() { | ||
52 | let (analysis, pos) = analysis_and_position( | ||
53 | r#" | ||
54 | //- /lib.rs | ||
55 | <|> //empty | ||
56 | mod foo { | ||
57 | mod test_mod { | ||
58 | #[test] | ||
59 | fn test_foo1() {} | ||
60 | } | ||
61 | } | ||
62 | "#, | ||
63 | ); | ||
64 | let runnables = analysis.runnables(pos.file_id).unwrap(); | ||
65 | assert_eq_dbg( | ||
66 | r#"[Runnable { range: [23; 85), kind: TestMod { path: "foo::test_mod" } }, | ||
67 | Runnable { range: [46; 79), kind: Test { name: "test_foo1" } }]"#, | ||
68 | &runnables, | ||
69 | ) | ||
70 | } | ||
71 | |||
72 | #[test] | ||
73 | fn test_runnables_multiple_depth_module() { | ||
74 | let (analysis, pos) = analysis_and_position( | ||
75 | r#" | ||
76 | //- /lib.rs | ||
77 | <|> //empty | ||
78 | mod foo { | ||
79 | mod bar { | ||
80 | mod test_mod { | ||
81 | #[test] | ||
82 | fn test_foo1() {} | ||
83 | } | ||
84 | } | ||
85 | } | ||
86 | "#, | ||
87 | ); | ||
88 | let runnables = analysis.runnables(pos.file_id).unwrap(); | ||
89 | assert_eq_dbg( | ||
90 | r#"[Runnable { range: [41; 115), kind: TestMod { path: "foo::bar::test_mod" } }, | ||
91 | Runnable { range: [68; 105), kind: Test { name: "test_foo1" } }]"#, | ||
92 | &runnables, | ||
93 | ) | ||
94 | } | ||
95 | |||
96 | #[test] | ||
97 | fn test_runnables_no_test_function_in_module() { | ||
98 | let (analysis, pos) = analysis_and_position( | ||
99 | r#" | ||
100 | //- /lib.rs | ||
101 | <|> //empty | ||
102 | mod test_mod { | ||
103 | fn foo1() {} | ||
104 | } | ||
105 | "#, | ||
106 | ); | ||
107 | let runnables = analysis.runnables(pos.file_id).unwrap(); | ||
108 | assert_eq_dbg(r#"[]"#, &runnables) | ||
109 | } | ||
diff --git a/crates/ra_analysis/tests/test/type_of.rs b/crates/ra_analysis/tests/test/type_of.rs new file mode 100644 index 000000000..9d15b52a8 --- /dev/null +++ b/crates/ra_analysis/tests/test/type_of.rs | |||
@@ -0,0 +1,77 @@ | |||
1 | use ra_analysis::mock_analysis::single_file_with_range; | ||
2 | |||
3 | #[test] | ||
4 | fn test_type_of_for_function() { | ||
5 | let (analysis, range) = single_file_with_range( | ||
6 | " | ||
7 | pub fn foo() -> u32 { 1 }; | ||
8 | |||
9 | fn main() { | ||
10 | let foo_test = <|>foo()<|>; | ||
11 | } | ||
12 | ", | ||
13 | ); | ||
14 | |||
15 | let type_name = analysis.type_of(range).unwrap().unwrap(); | ||
16 | assert_eq!("u32", &type_name); | ||
17 | } | ||
18 | |||
19 | // FIXME: improve type_of to make this work | ||
20 | #[test] | ||
21 | fn test_type_of_for_num() { | ||
22 | let (analysis, range) = single_file_with_range( | ||
23 | r#" | ||
24 | fn main() { | ||
25 | let foo_test = <|>"foo"<|>; | ||
26 | } | ||
27 | "#, | ||
28 | ); | ||
29 | |||
30 | assert!(analysis.type_of(range).unwrap().is_none()); | ||
31 | } | ||
32 | // FIXME: improve type_of to make this work | ||
33 | #[test] | ||
34 | fn test_type_of_for_binding() { | ||
35 | let (analysis, range) = single_file_with_range( | ||
36 | " | ||
37 | pub fn foo() -> u32 { 1 }; | ||
38 | |||
39 | fn main() { | ||
40 | let <|>foo_test<|> = foo(); | ||
41 | } | ||
42 | ", | ||
43 | ); | ||
44 | |||
45 | assert!(analysis.type_of(range).unwrap().is_none()); | ||
46 | } | ||
47 | |||
48 | // FIXME: improve type_of to make this work | ||
49 | #[test] | ||
50 | fn test_type_of_for_expr_1() { | ||
51 | let (analysis, range) = single_file_with_range( | ||
52 | " | ||
53 | fn main() { | ||
54 | let foo = <|>1 + foo_test<|>; | ||
55 | } | ||
56 | ", | ||
57 | ); | ||
58 | |||
59 | let type_name = analysis.type_of(range).unwrap().unwrap(); | ||
60 | assert_eq!("[unknown]", &type_name); | ||
61 | } | ||
62 | |||
63 | // FIXME: improve type_of to make this work | ||
64 | #[test] | ||
65 | fn test_type_of_for_expr_2() { | ||
66 | let (analysis, range) = single_file_with_range( | ||
67 | " | ||
68 | fn main() { | ||
69 | let foo: usize = 1; | ||
70 | let bar = <|>1 + foo_test<|>; | ||
71 | } | ||
72 | ", | ||
73 | ); | ||
74 | |||
75 | let type_name = analysis.type_of(range).unwrap().unwrap(); | ||
76 | assert_eq!("[unknown]", &type_name); | ||
77 | } | ||
diff --git a/crates/ra_cli/src/main.rs b/crates/ra_cli/src/main.rs index 939f7fe77..a3b856aa9 100644 --- a/crates/ra_cli/src/main.rs +++ b/crates/ra_cli/src/main.rs | |||
@@ -102,7 +102,7 @@ fn selections(file: &SourceFileNode, start: u32, end: u32) -> String { | |||
102 | let mut cur = Some(TextRange::from_to((start - 1).into(), (end - 1).into())); | 102 | let mut cur = Some(TextRange::from_to((start - 1).into(), (end - 1).into())); |
103 | while let Some(r) = cur { | 103 | while let Some(r) = cur { |
104 | ranges.push(r); | 104 | ranges.push(r); |
105 | cur = extend_selection(&file, r); | 105 | cur = extend_selection(file.syntax(), r); |
106 | } | 106 | } |
107 | let ranges = ranges | 107 | let ranges = ranges |
108 | .iter() | 108 | .iter() |
diff --git a/crates/ra_db/src/syntax_ptr.rs b/crates/ra_db/src/syntax_ptr.rs index dac94dd36..744cb2352 100644 --- a/crates/ra_db/src/syntax_ptr.rs +++ b/crates/ra_db/src/syntax_ptr.rs | |||
@@ -31,6 +31,10 @@ impl LocalSyntaxPtr { | |||
31 | pub fn range(self) -> TextRange { | 31 | pub fn range(self) -> TextRange { |
32 | self.range | 32 | self.range |
33 | } | 33 | } |
34 | |||
35 | pub fn kind(self) -> SyntaxKind { | ||
36 | self.kind | ||
37 | } | ||
34 | } | 38 | } |
35 | 39 | ||
36 | #[test] | 40 | #[test] |
diff --git a/crates/ra_editor/src/assists.rs b/crates/ra_editor/src/assists.rs new file mode 100644 index 000000000..cc40ee4c8 --- /dev/null +++ b/crates/ra_editor/src/assists.rs | |||
@@ -0,0 +1,185 @@ | |||
1 | //! This modules contains various "assits": suggestions for source code edits | ||
2 | //! which are likely to occur at a given cursor positon. For example, if the | ||
3 | //! cursor is on the `,`, a possible assist is swapping the elments around the | ||
4 | //! comma. | ||
5 | |||
6 | mod flip_comma; | ||
7 | mod add_derive; | ||
8 | mod add_impl; | ||
9 | mod introduce_variable; | ||
10 | mod change_visibility; | ||
11 | |||
12 | use ra_text_edit::{TextEdit, TextEditBuilder}; | ||
13 | use ra_syntax::{ | ||
14 | Direction, SyntaxNodeRef, TextUnit, TextRange,SourceFileNode, AstNode, | ||
15 | algo::{find_leaf_at_offset, find_covering_node, LeafAtOffset}, | ||
16 | }; | ||
17 | |||
18 | use crate::find_node_at_offset; | ||
19 | |||
20 | pub use self::{ | ||
21 | flip_comma::flip_comma, | ||
22 | add_derive::add_derive, | ||
23 | add_impl::add_impl, | ||
24 | introduce_variable::introduce_variable, | ||
25 | change_visibility::change_visibility, | ||
26 | }; | ||
27 | |||
28 | /// Return all the assists applicable at the given position. | ||
29 | pub fn assists(file: &SourceFileNode, range: TextRange) -> Vec<LocalEdit> { | ||
30 | let ctx = AssistCtx::new(file, range); | ||
31 | [ | ||
32 | flip_comma, | ||
33 | add_derive, | ||
34 | add_impl, | ||
35 | introduce_variable, | ||
36 | change_visibility, | ||
37 | ] | ||
38 | .iter() | ||
39 | .filter_map(|&assist| ctx.clone().apply(assist)) | ||
40 | .collect() | ||
41 | } | ||
42 | |||
43 | #[derive(Debug)] | ||
44 | pub struct LocalEdit { | ||
45 | pub label: String, | ||
46 | pub edit: TextEdit, | ||
47 | pub cursor_position: Option<TextUnit>, | ||
48 | } | ||
49 | |||
50 | fn non_trivia_sibling(node: SyntaxNodeRef, direction: Direction) -> Option<SyntaxNodeRef> { | ||
51 | node.siblings(direction) | ||
52 | .skip(1) | ||
53 | .find(|node| !node.kind().is_trivia()) | ||
54 | } | ||
55 | |||
56 | /// `AssistCtx` allows to apply an assist or check if it could be applied. | ||
57 | /// | ||
58 | /// Assists use a somewhat overengeneered approach, given the current needs. The | ||
59 | /// assists workflow consists of two phases. In the first phase, a user asks for | ||
60 | /// the list of available assists. In the second phase, the user picks a | ||
61 | /// particular assist and it gets applied. | ||
62 | /// | ||
63 | /// There are two peculiarities here: | ||
64 | /// | ||
65 | /// * first, we ideally avoid computing more things then neccessary to answer | ||
66 | /// "is assist applicable" in the first phase. | ||
67 | /// * second, when we are appling assist, we don't have a gurantee that there | ||
68 | /// weren't any changes between the point when user asked for assists and when | ||
69 | /// they applied a particular assist. So, when applying assist, we need to do | ||
70 | /// all the checks from scratch. | ||
71 | /// | ||
72 | /// To avoid repeating the same code twice for both "check" and "apply" | ||
73 | /// functions, we use an approach remeniscent of that of Django's function based | ||
74 | /// views dealing with forms. Each assist receives a runtime parameter, | ||
75 | /// `should_compute_edit`. It first check if an edit is applicable (potentially | ||
76 | /// computing info required to compute the actual edit). If it is applicable, | ||
77 | /// and `should_compute_edit` is `true`, it then computes the actual edit. | ||
78 | /// | ||
79 | /// So, to implement the original assists workflow, we can first apply each edit | ||
80 | /// with `should_compute_edit = false`, and then applying the selected edit | ||
81 | /// again, with `should_compute_edit = true` this time. | ||
82 | /// | ||
83 | /// Note, however, that we don't actually use such two-phase logic at the | ||
84 | /// moment, because the LSP API is pretty awkward in this place, and it's much | ||
85 | /// easier to just compute the edit eagarly :-) | ||
86 | #[derive(Debug, Clone)] | ||
87 | pub struct AssistCtx<'a> { | ||
88 | source_file: &'a SourceFileNode, | ||
89 | range: TextRange, | ||
90 | should_compute_edit: bool, | ||
91 | } | ||
92 | |||
93 | #[derive(Debug)] | ||
94 | pub enum Assist { | ||
95 | Applicable, | ||
96 | Edit(LocalEdit), | ||
97 | } | ||
98 | |||
99 | #[derive(Default)] | ||
100 | struct AssistBuilder { | ||
101 | edit: TextEditBuilder, | ||
102 | cursor_position: Option<TextUnit>, | ||
103 | } | ||
104 | |||
105 | impl<'a> AssistCtx<'a> { | ||
106 | pub fn new(source_file: &'a SourceFileNode, range: TextRange) -> AssistCtx { | ||
107 | AssistCtx { | ||
108 | source_file, | ||
109 | range, | ||
110 | should_compute_edit: false, | ||
111 | } | ||
112 | } | ||
113 | |||
114 | pub fn apply(mut self, assist: fn(AssistCtx) -> Option<Assist>) -> Option<LocalEdit> { | ||
115 | self.should_compute_edit = true; | ||
116 | match assist(self) { | ||
117 | None => None, | ||
118 | Some(Assist::Edit(e)) => Some(e), | ||
119 | Some(Assist::Applicable) => unreachable!(), | ||
120 | } | ||
121 | } | ||
122 | |||
123 | pub fn check(mut self, assist: fn(AssistCtx) -> Option<Assist>) -> bool { | ||
124 | self.should_compute_edit = false; | ||
125 | match assist(self) { | ||
126 | None => false, | ||
127 | Some(Assist::Edit(_)) => unreachable!(), | ||
128 | Some(Assist::Applicable) => true, | ||
129 | } | ||
130 | } | ||
131 | |||
132 | fn build(self, label: impl Into<String>, f: impl FnOnce(&mut AssistBuilder)) -> Option<Assist> { | ||
133 | if !self.should_compute_edit { | ||
134 | return Some(Assist::Applicable); | ||
135 | } | ||
136 | let mut edit = AssistBuilder::default(); | ||
137 | f(&mut edit); | ||
138 | Some(Assist::Edit(LocalEdit { | ||
139 | label: label.into(), | ||
140 | edit: edit.edit.finish(), | ||
141 | cursor_position: edit.cursor_position, | ||
142 | })) | ||
143 | } | ||
144 | |||
145 | pub(crate) fn leaf_at_offset(&self) -> LeafAtOffset<SyntaxNodeRef<'a>> { | ||
146 | find_leaf_at_offset(self.source_file.syntax(), self.range.start()) | ||
147 | } | ||
148 | pub(crate) fn node_at_offset<N: AstNode<'a>>(&self) -> Option<N> { | ||
149 | find_node_at_offset(self.source_file.syntax(), self.range.start()) | ||
150 | } | ||
151 | pub(crate) fn covering_node(&self) -> SyntaxNodeRef<'a> { | ||
152 | find_covering_node(self.source_file.syntax(), self.range) | ||
153 | } | ||
154 | } | ||
155 | |||
156 | impl AssistBuilder { | ||
157 | fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) { | ||
158 | self.edit.replace(range, replace_with.into()) | ||
159 | } | ||
160 | #[allow(unused)] | ||
161 | fn delete(&mut self, range: TextRange) { | ||
162 | self.edit.delete(range) | ||
163 | } | ||
164 | fn insert(&mut self, offset: TextUnit, text: impl Into<String>) { | ||
165 | self.edit.insert(offset, text.into()) | ||
166 | } | ||
167 | fn set_cursor(&mut self, offset: TextUnit) { | ||
168 | self.cursor_position = Some(offset) | ||
169 | } | ||
170 | } | ||
171 | |||
172 | #[cfg(test)] | ||
173 | fn check_assist(assist: fn(AssistCtx) -> Option<Assist>, before: &str, after: &str) { | ||
174 | crate::test_utils::check_action(before, after, |file, off| { | ||
175 | let range = TextRange::offset_len(off, 0.into()); | ||
176 | AssistCtx::new(file, range).apply(assist) | ||
177 | }) | ||
178 | } | ||
179 | |||
180 | #[cfg(test)] | ||
181 | fn check_assist_range(assist: fn(AssistCtx) -> Option<Assist>, before: &str, after: &str) { | ||
182 | crate::test_utils::check_action_range(before, after, |file, range| { | ||
183 | AssistCtx::new(file, range).apply(assist) | ||
184 | }) | ||
185 | } | ||
diff --git a/crates/ra_editor/src/assists/add_derive.rs b/crates/ra_editor/src/assists/add_derive.rs new file mode 100644 index 000000000..1e2cd4f30 --- /dev/null +++ b/crates/ra_editor/src/assists/add_derive.rs | |||
@@ -0,0 +1,84 @@ | |||
1 | use ra_syntax::{ | ||
2 | ast::{self, AstNode, AttrsOwner}, | ||
3 | SyntaxKind::{WHITESPACE, COMMENT}, | ||
4 | TextUnit, | ||
5 | }; | ||
6 | |||
7 | use crate::assists::{AssistCtx, Assist}; | ||
8 | |||
9 | pub fn add_derive(ctx: AssistCtx) -> Option<Assist> { | ||
10 | let nominal = ctx.node_at_offset::<ast::NominalDef>()?; | ||
11 | let node_start = derive_insertion_offset(nominal)?; | ||
12 | ctx.build("add `#[derive]`", |edit| { | ||
13 | let derive_attr = nominal | ||
14 | .attrs() | ||
15 | .filter_map(|x| x.as_call()) | ||
16 | .filter(|(name, _arg)| name == "derive") | ||
17 | .map(|(_name, arg)| arg) | ||
18 | .next(); | ||
19 | let offset = match derive_attr { | ||
20 | None => { | ||
21 | edit.insert(node_start, "#[derive()]\n"); | ||
22 | node_start + TextUnit::of_str("#[derive(") | ||
23 | } | ||
24 | Some(tt) => tt.syntax().range().end() - TextUnit::of_char(')'), | ||
25 | }; | ||
26 | edit.set_cursor(offset) | ||
27 | }) | ||
28 | } | ||
29 | |||
30 | // Insert `derive` after doc comments. | ||
31 | fn derive_insertion_offset(nominal: ast::NominalDef) -> Option<TextUnit> { | ||
32 | let non_ws_child = nominal | ||
33 | .syntax() | ||
34 | .children() | ||
35 | .find(|it| it.kind() != COMMENT && it.kind() != WHITESPACE)?; | ||
36 | Some(non_ws_child.range().start()) | ||
37 | } | ||
38 | |||
39 | #[cfg(test)] | ||
40 | mod tests { | ||
41 | use super::*; | ||
42 | use crate::assists::check_assist; | ||
43 | |||
44 | #[test] | ||
45 | fn add_derive_new() { | ||
46 | check_assist( | ||
47 | add_derive, | ||
48 | "struct Foo { a: i32, <|>}", | ||
49 | "#[derive(<|>)]\nstruct Foo { a: i32, }", | ||
50 | ); | ||
51 | check_assist( | ||
52 | add_derive, | ||
53 | "struct Foo { <|> a: i32, }", | ||
54 | "#[derive(<|>)]\nstruct Foo { a: i32, }", | ||
55 | ); | ||
56 | } | ||
57 | |||
58 | #[test] | ||
59 | fn add_derive_existing() { | ||
60 | check_assist( | ||
61 | add_derive, | ||
62 | "#[derive(Clone)]\nstruct Foo { a: i32<|>, }", | ||
63 | "#[derive(Clone<|>)]\nstruct Foo { a: i32, }", | ||
64 | ); | ||
65 | } | ||
66 | |||
67 | #[test] | ||
68 | fn add_derive_new_with_doc_comment() { | ||
69 | check_assist( | ||
70 | add_derive, | ||
71 | " | ||
72 | /// `Foo` is a pretty important struct. | ||
73 | /// It does stuff. | ||
74 | struct Foo { a: i32<|>, } | ||
75 | ", | ||
76 | " | ||
77 | /// `Foo` is a pretty important struct. | ||
78 | /// It does stuff. | ||
79 | #[derive(<|>)] | ||
80 | struct Foo { a: i32, } | ||
81 | ", | ||
82 | ); | ||
83 | } | ||
84 | } | ||
diff --git a/crates/ra_editor/src/assists/add_impl.rs b/crates/ra_editor/src/assists/add_impl.rs new file mode 100644 index 000000000..9353e2717 --- /dev/null +++ b/crates/ra_editor/src/assists/add_impl.rs | |||
@@ -0,0 +1,66 @@ | |||
1 | use join_to_string::join; | ||
2 | use ra_syntax::{ | ||
3 | ast::{self, AstNode, NameOwner, TypeParamsOwner}, | ||
4 | TextUnit, | ||
5 | }; | ||
6 | |||
7 | use crate::assists::{AssistCtx, Assist}; | ||
8 | |||
9 | pub fn add_impl(ctx: AssistCtx) -> Option<Assist> { | ||
10 | let nominal = ctx.node_at_offset::<ast::NominalDef>()?; | ||
11 | let name = nominal.name()?; | ||
12 | ctx.build("add impl", |edit| { | ||
13 | let type_params = nominal.type_param_list(); | ||
14 | let start_offset = nominal.syntax().range().end(); | ||
15 | let mut buf = String::new(); | ||
16 | buf.push_str("\n\nimpl"); | ||
17 | if let Some(type_params) = type_params { | ||
18 | type_params.syntax().text().push_to(&mut buf); | ||
19 | } | ||
20 | buf.push_str(" "); | ||
21 | buf.push_str(name.text().as_str()); | ||
22 | if let Some(type_params) = type_params { | ||
23 | let lifetime_params = type_params | ||
24 | .lifetime_params() | ||
25 | .filter_map(|it| it.lifetime()) | ||
26 | .map(|it| it.text()); | ||
27 | let type_params = type_params | ||
28 | .type_params() | ||
29 | .filter_map(|it| it.name()) | ||
30 | .map(|it| it.text()); | ||
31 | join(lifetime_params.chain(type_params)) | ||
32 | .surround_with("<", ">") | ||
33 | .to_buf(&mut buf); | ||
34 | } | ||
35 | buf.push_str(" {\n"); | ||
36 | edit.set_cursor(start_offset + TextUnit::of_str(&buf)); | ||
37 | buf.push_str("\n}"); | ||
38 | edit.insert(start_offset, buf); | ||
39 | }) | ||
40 | } | ||
41 | |||
42 | #[cfg(test)] | ||
43 | mod tests { | ||
44 | use super::*; | ||
45 | use crate::assists::check_assist; | ||
46 | |||
47 | #[test] | ||
48 | fn test_add_impl() { | ||
49 | check_assist( | ||
50 | add_impl, | ||
51 | "struct Foo {<|>}\n", | ||
52 | "struct Foo {}\n\nimpl Foo {\n<|>\n}\n", | ||
53 | ); | ||
54 | check_assist( | ||
55 | add_impl, | ||
56 | "struct Foo<T: Clone> {<|>}", | ||
57 | "struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n<|>\n}", | ||
58 | ); | ||
59 | check_assist( | ||
60 | add_impl, | ||
61 | "struct Foo<'a, T: Foo<'a>> {<|>}", | ||
62 | "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n<|>\n}", | ||
63 | ); | ||
64 | } | ||
65 | |||
66 | } | ||
diff --git a/crates/ra_editor/src/assists/change_visibility.rs b/crates/ra_editor/src/assists/change_visibility.rs new file mode 100644 index 000000000..ac75f635e --- /dev/null +++ b/crates/ra_editor/src/assists/change_visibility.rs | |||
@@ -0,0 +1,88 @@ | |||
1 | use ra_syntax::{ | ||
2 | AstNode, | ||
3 | ast::{self, VisibilityOwner, NameOwner}, | ||
4 | SyntaxKind::{VISIBILITY, FN_KW, MOD_KW, STRUCT_KW, ENUM_KW, TRAIT_KW, FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF, IDENT}, | ||
5 | }; | ||
6 | |||
7 | use crate::assists::{AssistCtx, Assist}; | ||
8 | |||
9 | pub fn change_visibility(ctx: AssistCtx) -> Option<Assist> { | ||
10 | let offset = if let Some(keyword) = ctx.leaf_at_offset().find(|leaf| match leaf.kind() { | ||
11 | FN_KW | MOD_KW | STRUCT_KW | ENUM_KW | TRAIT_KW => true, | ||
12 | _ => false, | ||
13 | }) { | ||
14 | let parent = keyword.parent()?; | ||
15 | let def_kws = vec![FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF]; | ||
16 | // Parent is not a definition, can't add visibility | ||
17 | if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) { | ||
18 | return None; | ||
19 | } | ||
20 | // Already have visibility, do nothing | ||
21 | if parent.children().any(|child| child.kind() == VISIBILITY) { | ||
22 | return None; | ||
23 | } | ||
24 | parent.range().start() | ||
25 | } else { | ||
26 | let ident = ctx.leaf_at_offset().find(|leaf| leaf.kind() == IDENT)?; | ||
27 | let field = ident.ancestors().find_map(ast::NamedFieldDef::cast)?; | ||
28 | if field.name()?.syntax().range() != ident.range() && field.visibility().is_some() { | ||
29 | return None; | ||
30 | } | ||
31 | field.syntax().range().start() | ||
32 | }; | ||
33 | |||
34 | ctx.build("make pub(crate)", |edit| { | ||
35 | edit.insert(offset, "pub(crate) "); | ||
36 | edit.set_cursor(offset); | ||
37 | }) | ||
38 | } | ||
39 | |||
40 | #[cfg(test)] | ||
41 | mod tests { | ||
42 | use super::*; | ||
43 | use crate::assists::check_assist; | ||
44 | |||
45 | #[test] | ||
46 | fn change_visibility_adds_pub_crate_to_items() { | ||
47 | check_assist( | ||
48 | change_visibility, | ||
49 | "<|>fn foo() {}", | ||
50 | "<|>pub(crate) fn foo() {}", | ||
51 | ); | ||
52 | check_assist( | ||
53 | change_visibility, | ||
54 | "f<|>n foo() {}", | ||
55 | "<|>pub(crate) fn foo() {}", | ||
56 | ); | ||
57 | check_assist( | ||
58 | change_visibility, | ||
59 | "<|>struct Foo {}", | ||
60 | "<|>pub(crate) struct Foo {}", | ||
61 | ); | ||
62 | check_assist( | ||
63 | change_visibility, | ||
64 | "<|>mod foo {}", | ||
65 | "<|>pub(crate) mod foo {}", | ||
66 | ); | ||
67 | check_assist( | ||
68 | change_visibility, | ||
69 | "<|>trait Foo {}", | ||
70 | "<|>pub(crate) trait Foo {}", | ||
71 | ); | ||
72 | check_assist(change_visibility, "m<|>od {}", "<|>pub(crate) mod {}"); | ||
73 | check_assist( | ||
74 | change_visibility, | ||
75 | "unsafe f<|>n foo() {}", | ||
76 | "<|>pub(crate) unsafe fn foo() {}", | ||
77 | ); | ||
78 | } | ||
79 | |||
80 | #[test] | ||
81 | fn change_visibility_works_with_struct_fields() { | ||
82 | check_assist( | ||
83 | change_visibility, | ||
84 | "struct S { <|>field: u32 }", | ||
85 | "struct S { <|>pub(crate) field: u32 }", | ||
86 | ) | ||
87 | } | ||
88 | } | ||
diff --git a/crates/ra_editor/src/assists/flip_comma.rs b/crates/ra_editor/src/assists/flip_comma.rs new file mode 100644 index 000000000..a343413cc --- /dev/null +++ b/crates/ra_editor/src/assists/flip_comma.rs | |||
@@ -0,0 +1,31 @@ | |||
1 | use ra_syntax::{ | ||
2 | Direction, | ||
3 | SyntaxKind::COMMA, | ||
4 | }; | ||
5 | |||
6 | use crate::assists::{non_trivia_sibling, AssistCtx, Assist}; | ||
7 | |||
8 | pub fn flip_comma(ctx: AssistCtx) -> Option<Assist> { | ||
9 | let comma = ctx.leaf_at_offset().find(|leaf| leaf.kind() == COMMA)?; | ||
10 | let prev = non_trivia_sibling(comma, Direction::Prev)?; | ||
11 | let next = non_trivia_sibling(comma, Direction::Next)?; | ||
12 | ctx.build("flip comma", |edit| { | ||
13 | edit.replace(prev.range(), next.text()); | ||
14 | edit.replace(next.range(), prev.text()); | ||
15 | }) | ||
16 | } | ||
17 | |||
18 | #[cfg(test)] | ||
19 | mod tests { | ||
20 | use super::*; | ||
21 | use crate::assists::check_assist; | ||
22 | |||
23 | #[test] | ||
24 | fn flip_comma_works_for_function_parameters() { | ||
25 | check_assist( | ||
26 | flip_comma, | ||
27 | "fn foo(x: i32,<|> y: Result<(), ()>) {}", | ||
28 | "fn foo(y: Result<(), ()>,<|> x: i32) {}", | ||
29 | ) | ||
30 | } | ||
31 | } | ||
diff --git a/crates/ra_editor/src/assists/introduce_variable.rs b/crates/ra_editor/src/assists/introduce_variable.rs new file mode 100644 index 000000000..782861023 --- /dev/null +++ b/crates/ra_editor/src/assists/introduce_variable.rs | |||
@@ -0,0 +1,144 @@ | |||
1 | use ra_syntax::{ | ||
2 | ast::{self, AstNode}, | ||
3 | SyntaxKind::WHITESPACE, | ||
4 | SyntaxNodeRef, TextUnit, | ||
5 | }; | ||
6 | |||
7 | use crate::assists::{AssistCtx, Assist}; | ||
8 | |||
9 | pub fn introduce_variable<'a>(ctx: AssistCtx) -> Option<Assist> { | ||
10 | let node = ctx.covering_node(); | ||
11 | let expr = node.ancestors().filter_map(ast::Expr::cast).next()?; | ||
12 | |||
13 | let anchor_stmt = anchor_stmt(expr)?; | ||
14 | let indent = anchor_stmt.prev_sibling()?; | ||
15 | if indent.kind() != WHITESPACE { | ||
16 | return None; | ||
17 | } | ||
18 | ctx.build("introduce variable", move |edit| { | ||
19 | let mut buf = String::new(); | ||
20 | |||
21 | buf.push_str("let var_name = "); | ||
22 | expr.syntax().text().push_to(&mut buf); | ||
23 | let is_full_stmt = if let Some(expr_stmt) = ast::ExprStmt::cast(anchor_stmt) { | ||
24 | Some(expr.syntax()) == expr_stmt.expr().map(|e| e.syntax()) | ||
25 | } else { | ||
26 | false | ||
27 | }; | ||
28 | if is_full_stmt { | ||
29 | edit.replace(expr.syntax().range(), buf); | ||
30 | } else { | ||
31 | buf.push_str(";"); | ||
32 | indent.text().push_to(&mut buf); | ||
33 | edit.replace(expr.syntax().range(), "var_name".to_string()); | ||
34 | edit.insert(anchor_stmt.range().start(), buf); | ||
35 | } | ||
36 | edit.set_cursor(anchor_stmt.range().start() + TextUnit::of_str("let ")); | ||
37 | }) | ||
38 | } | ||
39 | |||
40 | /// Statement or last in the block expression, which will follow | ||
41 | /// the freshly introduced var. | ||
42 | fn anchor_stmt(expr: ast::Expr) -> Option<SyntaxNodeRef> { | ||
43 | expr.syntax().ancestors().find(|&node| { | ||
44 | if ast::Stmt::cast(node).is_some() { | ||
45 | return true; | ||
46 | } | ||
47 | if let Some(expr) = node | ||
48 | .parent() | ||
49 | .and_then(ast::Block::cast) | ||
50 | .and_then(|it| it.expr()) | ||
51 | { | ||
52 | if expr.syntax() == node { | ||
53 | return true; | ||
54 | } | ||
55 | } | ||
56 | false | ||
57 | }) | ||
58 | } | ||
59 | |||
60 | #[cfg(test)] | ||
61 | mod tests { | ||
62 | use super::*; | ||
63 | use crate::assists::check_assist_range; | ||
64 | |||
65 | #[test] | ||
66 | fn test_introduce_var_simple() { | ||
67 | check_assist_range( | ||
68 | introduce_variable, | ||
69 | " | ||
70 | fn foo() { | ||
71 | foo(<|>1 + 1<|>); | ||
72 | }", | ||
73 | " | ||
74 | fn foo() { | ||
75 | let <|>var_name = 1 + 1; | ||
76 | foo(var_name); | ||
77 | }", | ||
78 | ); | ||
79 | } | ||
80 | |||
81 | #[test] | ||
82 | fn test_introduce_var_expr_stmt() { | ||
83 | check_assist_range( | ||
84 | introduce_variable, | ||
85 | " | ||
86 | fn foo() { | ||
87 | <|>1 + 1<|>; | ||
88 | }", | ||
89 | " | ||
90 | fn foo() { | ||
91 | let <|>var_name = 1 + 1; | ||
92 | }", | ||
93 | ); | ||
94 | } | ||
95 | |||
96 | #[test] | ||
97 | fn test_introduce_var_part_of_expr_stmt() { | ||
98 | check_assist_range( | ||
99 | introduce_variable, | ||
100 | " | ||
101 | fn foo() { | ||
102 | <|>1<|> + 1; | ||
103 | }", | ||
104 | " | ||
105 | fn foo() { | ||
106 | let <|>var_name = 1; | ||
107 | var_name + 1; | ||
108 | }", | ||
109 | ); | ||
110 | } | ||
111 | |||
112 | #[test] | ||
113 | fn test_introduce_var_last_expr() { | ||
114 | check_assist_range( | ||
115 | introduce_variable, | ||
116 | " | ||
117 | fn foo() { | ||
118 | bar(<|>1 + 1<|>) | ||
119 | }", | ||
120 | " | ||
121 | fn foo() { | ||
122 | let <|>var_name = 1 + 1; | ||
123 | bar(var_name) | ||
124 | }", | ||
125 | ); | ||
126 | } | ||
127 | |||
128 | #[test] | ||
129 | fn test_introduce_var_last_full_expr() { | ||
130 | check_assist_range( | ||
131 | introduce_variable, | ||
132 | " | ||
133 | fn foo() { | ||
134 | <|>bar(1 + 1)<|> | ||
135 | }", | ||
136 | " | ||
137 | fn foo() { | ||
138 | let <|>var_name = bar(1 + 1); | ||
139 | var_name | ||
140 | }", | ||
141 | ); | ||
142 | } | ||
143 | |||
144 | } | ||
diff --git a/crates/ra_editor/src/code_actions.rs b/crates/ra_editor/src/code_actions.rs deleted file mode 100644 index 7615f37a6..000000000 --- a/crates/ra_editor/src/code_actions.rs +++ /dev/null | |||
@@ -1,415 +0,0 @@ | |||
1 | use join_to_string::join; | ||
2 | |||
3 | use ra_syntax::{ | ||
4 | algo::{find_covering_node, find_leaf_at_offset}, | ||
5 | ast::{self, AstNode, AttrsOwner, NameOwner, TypeParamsOwner}, | ||
6 | Direction, SourceFileNode, | ||
7 | SyntaxKind::{COMMA, WHITESPACE, COMMENT, VISIBILITY, FN_KW, MOD_KW, STRUCT_KW, ENUM_KW, TRAIT_KW, FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF}, | ||
8 | SyntaxNodeRef, TextRange, TextUnit, | ||
9 | }; | ||
10 | |||
11 | use crate::{find_node_at_offset, TextEdit, TextEditBuilder}; | ||
12 | |||
13 | #[derive(Debug)] | ||
14 | pub struct LocalEdit { | ||
15 | pub label: String, | ||
16 | pub edit: TextEdit, | ||
17 | pub cursor_position: Option<TextUnit>, | ||
18 | } | ||
19 | |||
20 | pub fn flip_comma<'a>( | ||
21 | file: &'a SourceFileNode, | ||
22 | offset: TextUnit, | ||
23 | ) -> Option<impl FnOnce() -> LocalEdit + 'a> { | ||
24 | let syntax = file.syntax(); | ||
25 | |||
26 | let comma = find_leaf_at_offset(syntax, offset).find(|leaf| leaf.kind() == COMMA)?; | ||
27 | let prev = non_trivia_sibling(comma, Direction::Prev)?; | ||
28 | let next = non_trivia_sibling(comma, Direction::Next)?; | ||
29 | Some(move || { | ||
30 | let mut edit = TextEditBuilder::new(); | ||
31 | edit.replace(prev.range(), next.text().to_string()); | ||
32 | edit.replace(next.range(), prev.text().to_string()); | ||
33 | LocalEdit { | ||
34 | label: "flip comma".to_string(), | ||
35 | edit: edit.finish(), | ||
36 | cursor_position: None, | ||
37 | } | ||
38 | }) | ||
39 | } | ||
40 | |||
41 | pub fn add_derive<'a>( | ||
42 | file: &'a SourceFileNode, | ||
43 | offset: TextUnit, | ||
44 | ) -> Option<impl FnOnce() -> LocalEdit + 'a> { | ||
45 | let nominal = find_node_at_offset::<ast::NominalDef>(file.syntax(), offset)?; | ||
46 | let node_start = derive_insertion_offset(nominal)?; | ||
47 | return Some(move || { | ||
48 | let derive_attr = nominal | ||
49 | .attrs() | ||
50 | .filter_map(|x| x.as_call()) | ||
51 | .filter(|(name, _arg)| name == "derive") | ||
52 | .map(|(_name, arg)| arg) | ||
53 | .next(); | ||
54 | let mut edit = TextEditBuilder::new(); | ||
55 | let offset = match derive_attr { | ||
56 | None => { | ||
57 | edit.insert(node_start, "#[derive()]\n".to_string()); | ||
58 | node_start + TextUnit::of_str("#[derive(") | ||
59 | } | ||
60 | Some(tt) => tt.syntax().range().end() - TextUnit::of_char(')'), | ||
61 | }; | ||
62 | LocalEdit { | ||
63 | label: "add `#[derive]`".to_string(), | ||
64 | edit: edit.finish(), | ||
65 | cursor_position: Some(offset), | ||
66 | } | ||
67 | }); | ||
68 | |||
69 | // Insert `derive` after doc comments. | ||
70 | fn derive_insertion_offset(nominal: ast::NominalDef) -> Option<TextUnit> { | ||
71 | let non_ws_child = nominal | ||
72 | .syntax() | ||
73 | .children() | ||
74 | .find(|it| it.kind() != COMMENT && it.kind() != WHITESPACE)?; | ||
75 | Some(non_ws_child.range().start()) | ||
76 | } | ||
77 | } | ||
78 | |||
79 | pub fn add_impl<'a>( | ||
80 | file: &'a SourceFileNode, | ||
81 | offset: TextUnit, | ||
82 | ) -> Option<impl FnOnce() -> LocalEdit + 'a> { | ||
83 | let nominal = find_node_at_offset::<ast::NominalDef>(file.syntax(), offset)?; | ||
84 | let name = nominal.name()?; | ||
85 | |||
86 | Some(move || { | ||
87 | let type_params = nominal.type_param_list(); | ||
88 | let mut edit = TextEditBuilder::new(); | ||
89 | let start_offset = nominal.syntax().range().end(); | ||
90 | let mut buf = String::new(); | ||
91 | buf.push_str("\n\nimpl"); | ||
92 | if let Some(type_params) = type_params { | ||
93 | type_params.syntax().text().push_to(&mut buf); | ||
94 | } | ||
95 | buf.push_str(" "); | ||
96 | buf.push_str(name.text().as_str()); | ||
97 | if let Some(type_params) = type_params { | ||
98 | let lifetime_params = type_params | ||
99 | .lifetime_params() | ||
100 | .filter_map(|it| it.lifetime()) | ||
101 | .map(|it| it.text()); | ||
102 | let type_params = type_params | ||
103 | .type_params() | ||
104 | .filter_map(|it| it.name()) | ||
105 | .map(|it| it.text()); | ||
106 | join(lifetime_params.chain(type_params)) | ||
107 | .surround_with("<", ">") | ||
108 | .to_buf(&mut buf); | ||
109 | } | ||
110 | buf.push_str(" {\n"); | ||
111 | let offset = start_offset + TextUnit::of_str(&buf); | ||
112 | buf.push_str("\n}"); | ||
113 | edit.insert(start_offset, buf); | ||
114 | LocalEdit { | ||
115 | label: "add impl".to_string(), | ||
116 | edit: edit.finish(), | ||
117 | cursor_position: Some(offset), | ||
118 | } | ||
119 | }) | ||
120 | } | ||
121 | |||
122 | pub fn introduce_variable<'a>( | ||
123 | file: &'a SourceFileNode, | ||
124 | range: TextRange, | ||
125 | ) -> Option<impl FnOnce() -> LocalEdit + 'a> { | ||
126 | let node = find_covering_node(file.syntax(), range); | ||
127 | let expr = node.ancestors().filter_map(ast::Expr::cast).next()?; | ||
128 | |||
129 | let anchor_stmt = anchor_stmt(expr)?; | ||
130 | let indent = anchor_stmt.prev_sibling()?; | ||
131 | if indent.kind() != WHITESPACE { | ||
132 | return None; | ||
133 | } | ||
134 | return Some(move || { | ||
135 | let mut buf = String::new(); | ||
136 | let mut edit = TextEditBuilder::new(); | ||
137 | |||
138 | buf.push_str("let var_name = "); | ||
139 | expr.syntax().text().push_to(&mut buf); | ||
140 | let is_full_stmt = if let Some(expr_stmt) = ast::ExprStmt::cast(anchor_stmt) { | ||
141 | Some(expr.syntax()) == expr_stmt.expr().map(|e| e.syntax()) | ||
142 | } else { | ||
143 | false | ||
144 | }; | ||
145 | if is_full_stmt { | ||
146 | edit.replace(expr.syntax().range(), buf); | ||
147 | } else { | ||
148 | buf.push_str(";"); | ||
149 | indent.text().push_to(&mut buf); | ||
150 | edit.replace(expr.syntax().range(), "var_name".to_string()); | ||
151 | edit.insert(anchor_stmt.range().start(), buf); | ||
152 | } | ||
153 | let cursor_position = anchor_stmt.range().start() + TextUnit::of_str("let "); | ||
154 | LocalEdit { | ||
155 | label: "introduce variable".to_string(), | ||
156 | edit: edit.finish(), | ||
157 | cursor_position: Some(cursor_position), | ||
158 | } | ||
159 | }); | ||
160 | |||
161 | /// Statement or last in the block expression, which will follow | ||
162 | /// the freshly introduced var. | ||
163 | fn anchor_stmt(expr: ast::Expr) -> Option<SyntaxNodeRef> { | ||
164 | expr.syntax().ancestors().find(|&node| { | ||
165 | if ast::Stmt::cast(node).is_some() { | ||
166 | return true; | ||
167 | } | ||
168 | if let Some(expr) = node | ||
169 | .parent() | ||
170 | .and_then(ast::Block::cast) | ||
171 | .and_then(|it| it.expr()) | ||
172 | { | ||
173 | if expr.syntax() == node { | ||
174 | return true; | ||
175 | } | ||
176 | } | ||
177 | false | ||
178 | }) | ||
179 | } | ||
180 | } | ||
181 | |||
182 | pub fn make_pub_crate<'a>( | ||
183 | file: &'a SourceFileNode, | ||
184 | offset: TextUnit, | ||
185 | ) -> Option<impl FnOnce() -> LocalEdit + 'a> { | ||
186 | let syntax = file.syntax(); | ||
187 | |||
188 | let keyword = find_leaf_at_offset(syntax, offset).find(|leaf| match leaf.kind() { | ||
189 | FN_KW | MOD_KW | STRUCT_KW | ENUM_KW | TRAIT_KW => true, | ||
190 | _ => false, | ||
191 | })?; | ||
192 | let parent = keyword.parent()?; | ||
193 | let def_kws = vec![FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF]; | ||
194 | let node_start = parent.range().start(); | ||
195 | Some(move || { | ||
196 | let mut edit = TextEditBuilder::new(); | ||
197 | |||
198 | if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) | ||
199 | || parent.children().any(|child| child.kind() == VISIBILITY) | ||
200 | { | ||
201 | return LocalEdit { | ||
202 | label: "make pub crate".to_string(), | ||
203 | edit: edit.finish(), | ||
204 | cursor_position: Some(offset), | ||
205 | }; | ||
206 | } | ||
207 | |||
208 | edit.insert(node_start, "pub(crate) ".to_string()); | ||
209 | LocalEdit { | ||
210 | label: "make pub crate".to_string(), | ||
211 | edit: edit.finish(), | ||
212 | cursor_position: Some(node_start), | ||
213 | } | ||
214 | }) | ||
215 | } | ||
216 | |||
217 | fn non_trivia_sibling(node: SyntaxNodeRef, direction: Direction) -> Option<SyntaxNodeRef> { | ||
218 | node.siblings(direction) | ||
219 | .skip(1) | ||
220 | .find(|node| !node.kind().is_trivia()) | ||
221 | } | ||
222 | |||
223 | #[cfg(test)] | ||
224 | mod tests { | ||
225 | use super::*; | ||
226 | use crate::test_utils::{check_action, check_action_range}; | ||
227 | |||
228 | #[test] | ||
229 | fn test_swap_comma() { | ||
230 | check_action( | ||
231 | "fn foo(x: i32,<|> y: Result<(), ()>) {}", | ||
232 | "fn foo(y: Result<(), ()>,<|> x: i32) {}", | ||
233 | |file, off| flip_comma(file, off).map(|f| f()), | ||
234 | ) | ||
235 | } | ||
236 | |||
237 | #[test] | ||
238 | fn add_derive_new() { | ||
239 | check_action( | ||
240 | "struct Foo { a: i32, <|>}", | ||
241 | "#[derive(<|>)]\nstruct Foo { a: i32, }", | ||
242 | |file, off| add_derive(file, off).map(|f| f()), | ||
243 | ); | ||
244 | check_action( | ||
245 | "struct Foo { <|> a: i32, }", | ||
246 | "#[derive(<|>)]\nstruct Foo { a: i32, }", | ||
247 | |file, off| add_derive(file, off).map(|f| f()), | ||
248 | ); | ||
249 | } | ||
250 | |||
251 | #[test] | ||
252 | fn add_derive_existing() { | ||
253 | check_action( | ||
254 | "#[derive(Clone)]\nstruct Foo { a: i32<|>, }", | ||
255 | "#[derive(Clone<|>)]\nstruct Foo { a: i32, }", | ||
256 | |file, off| add_derive(file, off).map(|f| f()), | ||
257 | ); | ||
258 | } | ||
259 | |||
260 | #[test] | ||
261 | fn add_derive_new_with_doc_comment() { | ||
262 | check_action( | ||
263 | " | ||
264 | /// `Foo` is a pretty important struct. | ||
265 | /// It does stuff. | ||
266 | struct Foo { a: i32<|>, } | ||
267 | ", | ||
268 | " | ||
269 | /// `Foo` is a pretty important struct. | ||
270 | /// It does stuff. | ||
271 | #[derive(<|>)] | ||
272 | struct Foo { a: i32, } | ||
273 | ", | ||
274 | |file, off| add_derive(file, off).map(|f| f()), | ||
275 | ); | ||
276 | } | ||
277 | |||
278 | #[test] | ||
279 | fn test_add_impl() { | ||
280 | check_action( | ||
281 | "struct Foo {<|>}\n", | ||
282 | "struct Foo {}\n\nimpl Foo {\n<|>\n}\n", | ||
283 | |file, off| add_impl(file, off).map(|f| f()), | ||
284 | ); | ||
285 | check_action( | ||
286 | "struct Foo<T: Clone> {<|>}", | ||
287 | "struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n<|>\n}", | ||
288 | |file, off| add_impl(file, off).map(|f| f()), | ||
289 | ); | ||
290 | check_action( | ||
291 | "struct Foo<'a, T: Foo<'a>> {<|>}", | ||
292 | "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n<|>\n}", | ||
293 | |file, off| add_impl(file, off).map(|f| f()), | ||
294 | ); | ||
295 | } | ||
296 | |||
297 | #[test] | ||
298 | fn test_introduce_var_simple() { | ||
299 | check_action_range( | ||
300 | " | ||
301 | fn foo() { | ||
302 | foo(<|>1 + 1<|>); | ||
303 | }", | ||
304 | " | ||
305 | fn foo() { | ||
306 | let <|>var_name = 1 + 1; | ||
307 | foo(var_name); | ||
308 | }", | ||
309 | |file, range| introduce_variable(file, range).map(|f| f()), | ||
310 | ); | ||
311 | } | ||
312 | |||
313 | #[test] | ||
314 | fn test_introduce_var_expr_stmt() { | ||
315 | check_action_range( | ||
316 | " | ||
317 | fn foo() { | ||
318 | <|>1 + 1<|>; | ||
319 | }", | ||
320 | " | ||
321 | fn foo() { | ||
322 | let <|>var_name = 1 + 1; | ||
323 | }", | ||
324 | |file, range| introduce_variable(file, range).map(|f| f()), | ||
325 | ); | ||
326 | } | ||
327 | |||
328 | #[test] | ||
329 | fn test_introduce_var_part_of_expr_stmt() { | ||
330 | check_action_range( | ||
331 | " | ||
332 | fn foo() { | ||
333 | <|>1<|> + 1; | ||
334 | }", | ||
335 | " | ||
336 | fn foo() { | ||
337 | let <|>var_name = 1; | ||
338 | var_name + 1; | ||
339 | }", | ||
340 | |file, range| introduce_variable(file, range).map(|f| f()), | ||
341 | ); | ||
342 | } | ||
343 | |||
344 | #[test] | ||
345 | fn test_introduce_var_last_expr() { | ||
346 | check_action_range( | ||
347 | " | ||
348 | fn foo() { | ||
349 | bar(<|>1 + 1<|>) | ||
350 | }", | ||
351 | " | ||
352 | fn foo() { | ||
353 | let <|>var_name = 1 + 1; | ||
354 | bar(var_name) | ||
355 | }", | ||
356 | |file, range| introduce_variable(file, range).map(|f| f()), | ||
357 | ); | ||
358 | } | ||
359 | |||
360 | #[test] | ||
361 | fn test_introduce_var_last_full_expr() { | ||
362 | check_action_range( | ||
363 | " | ||
364 | fn foo() { | ||
365 | <|>bar(1 + 1)<|> | ||
366 | }", | ||
367 | " | ||
368 | fn foo() { | ||
369 | let <|>var_name = bar(1 + 1); | ||
370 | var_name | ||
371 | }", | ||
372 | |file, range| introduce_variable(file, range).map(|f| f()), | ||
373 | ); | ||
374 | } | ||
375 | |||
376 | #[test] | ||
377 | fn test_make_pub_crate() { | ||
378 | check_action( | ||
379 | "<|>fn foo() {}", | ||
380 | "<|>pub(crate) fn foo() {}", | ||
381 | |file, off| make_pub_crate(file, off).map(|f| f()), | ||
382 | ); | ||
383 | check_action( | ||
384 | "f<|>n foo() {}", | ||
385 | "<|>pub(crate) fn foo() {}", | ||
386 | |file, off| make_pub_crate(file, off).map(|f| f()), | ||
387 | ); | ||
388 | check_action( | ||
389 | "<|>struct Foo {}", | ||
390 | "<|>pub(crate) struct Foo {}", | ||
391 | |file, off| make_pub_crate(file, off).map(|f| f()), | ||
392 | ); | ||
393 | check_action("<|>mod foo {}", "<|>pub(crate) mod foo {}", |file, off| { | ||
394 | make_pub_crate(file, off).map(|f| f()) | ||
395 | }); | ||
396 | check_action( | ||
397 | "<|>trait Foo {}", | ||
398 | "<|>pub(crate) trait Foo {}", | ||
399 | |file, off| make_pub_crate(file, off).map(|f| f()), | ||
400 | ); | ||
401 | check_action("m<|>od {}", "<|>pub(crate) mod {}", |file, off| { | ||
402 | make_pub_crate(file, off).map(|f| f()) | ||
403 | }); | ||
404 | check_action( | ||
405 | "pub(crate) f<|>n foo() {}", | ||
406 | "pub(crate) f<|>n foo() {}", | ||
407 | |file, off| make_pub_crate(file, off).map(|f| f()), | ||
408 | ); | ||
409 | check_action( | ||
410 | "unsafe f<|>n foo() {}", | ||
411 | "<|>pub(crate) unsafe fn foo() {}", | ||
412 | |file, off| make_pub_crate(file, off).map(|f| f()), | ||
413 | ); | ||
414 | } | ||
415 | } | ||
diff --git a/crates/ra_editor/src/diagnostics.rs b/crates/ra_editor/src/diagnostics.rs index 1b336cfe2..199b0e502 100644 --- a/crates/ra_editor/src/diagnostics.rs +++ b/crates/ra_editor/src/diagnostics.rs | |||
@@ -57,7 +57,7 @@ fn check_unnecessary_braces_in_use_statement( | |||
57 | text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(single_use_tree) | 57 | text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(single_use_tree) |
58 | .unwrap_or_else(|| { | 58 | .unwrap_or_else(|| { |
59 | let to_replace = single_use_tree.syntax().text().to_string(); | 59 | let to_replace = single_use_tree.syntax().text().to_string(); |
60 | let mut edit_builder = TextEditBuilder::new(); | 60 | let mut edit_builder = TextEditBuilder::default(); |
61 | edit_builder.delete(range); | 61 | edit_builder.delete(range); |
62 | edit_builder.insert(range.start(), to_replace); | 62 | edit_builder.insert(range.start(), to_replace); |
63 | edit_builder.finish() | 63 | edit_builder.finish() |
@@ -93,7 +93,7 @@ fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement( | |||
93 | let start = use_tree_list_node.prev_sibling()?.range().start(); | 93 | let start = use_tree_list_node.prev_sibling()?.range().start(); |
94 | let end = use_tree_list_node.range().end(); | 94 | let end = use_tree_list_node.range().end(); |
95 | let range = TextRange::from_to(start, end); | 95 | let range = TextRange::from_to(start, end); |
96 | let mut edit_builder = TextEditBuilder::new(); | 96 | let mut edit_builder = TextEditBuilder::default(); |
97 | edit_builder.delete(range); | 97 | edit_builder.delete(range); |
98 | return Some(edit_builder.finish()); | 98 | return Some(edit_builder.finish()); |
99 | } | 99 | } |
@@ -111,7 +111,7 @@ fn check_struct_shorthand_initialization( | |||
111 | let field_name = name_ref.syntax().text().to_string(); | 111 | let field_name = name_ref.syntax().text().to_string(); |
112 | let field_expr = expr.syntax().text().to_string(); | 112 | let field_expr = expr.syntax().text().to_string(); |
113 | if field_name == field_expr { | 113 | if field_name == field_expr { |
114 | let mut edit_builder = TextEditBuilder::new(); | 114 | let mut edit_builder = TextEditBuilder::default(); |
115 | edit_builder.delete(named_field.syntax().range()); | 115 | edit_builder.delete(named_field.syntax().range()); |
116 | edit_builder.insert(named_field.syntax().range().start(), field_name); | 116 | edit_builder.insert(named_field.syntax().range().start(), field_name); |
117 | let edit = edit_builder.finish(); | 117 | let edit = edit_builder.finish(); |
diff --git a/crates/ra_editor/src/extend_selection.rs b/crates/ra_editor/src/extend_selection.rs index 4665a336a..7a423852b 100644 --- a/crates/ra_editor/src/extend_selection.rs +++ b/crates/ra_editor/src/extend_selection.rs | |||
@@ -1,16 +1,12 @@ | |||
1 | use ra_syntax::{ | 1 | use ra_syntax::{ |
2 | algo::{find_covering_node, find_leaf_at_offset, LeafAtOffset}, | 2 | algo::{find_covering_node, find_leaf_at_offset, LeafAtOffset}, |
3 | Direction, SourceFileNode, | 3 | Direction, |
4 | SyntaxKind::*, | 4 | SyntaxKind::*, |
5 | SyntaxNodeRef, TextRange, TextUnit, | 5 | SyntaxNodeRef, TextRange, TextUnit, |
6 | }; | 6 | }; |
7 | 7 | ||
8 | pub fn extend_selection(file: &SourceFileNode, range: TextRange) -> Option<TextRange> { | 8 | pub fn extend_selection(root: SyntaxNodeRef, range: TextRange) -> Option<TextRange> { |
9 | let syntax = file.syntax(); | 9 | let string_kinds = [COMMENT, STRING, RAW_STRING, BYTE_STRING, RAW_BYTE_STRING]; |
10 | extend(syntax.borrowed(), range) | ||
11 | } | ||
12 | |||
13 | pub(crate) fn extend(root: SyntaxNodeRef, range: TextRange) -> Option<TextRange> { | ||
14 | if range.is_empty() { | 10 | if range.is_empty() { |
15 | let offset = range.start(); | 11 | let offset = range.start(); |
16 | let mut leaves = find_leaf_at_offset(root, offset); | 12 | let mut leaves = find_leaf_at_offset(root, offset); |
@@ -20,8 +16,8 @@ pub(crate) fn extend(root: SyntaxNodeRef, range: TextRange) -> Option<TextRange> | |||
20 | let leaf_range = match leaves { | 16 | let leaf_range = match leaves { |
21 | LeafAtOffset::None => return None, | 17 | LeafAtOffset::None => return None, |
22 | LeafAtOffset::Single(l) => { | 18 | LeafAtOffset::Single(l) => { |
23 | if l.kind() == COMMENT { | 19 | if string_kinds.contains(&l.kind()) { |
24 | extend_single_word_in_comment(l, offset).unwrap_or_else(|| l.range()) | 20 | extend_single_word_in_comment_or_string(l, offset).unwrap_or_else(|| l.range()) |
25 | } else { | 21 | } else { |
26 | l.range() | 22 | l.range() |
27 | } | 23 | } |
@@ -31,7 +27,7 @@ pub(crate) fn extend(root: SyntaxNodeRef, range: TextRange) -> Option<TextRange> | |||
31 | return Some(leaf_range); | 27 | return Some(leaf_range); |
32 | }; | 28 | }; |
33 | let node = find_covering_node(root, range); | 29 | let node = find_covering_node(root, range); |
34 | if node.kind() == COMMENT && range == node.range() { | 30 | if string_kinds.contains(&node.kind()) && range == node.range() { |
35 | if let Some(range) = extend_comments(node) { | 31 | if let Some(range) = extend_comments(node) { |
36 | return Some(range); | 32 | return Some(range); |
37 | } | 33 | } |
@@ -43,7 +39,10 @@ pub(crate) fn extend(root: SyntaxNodeRef, range: TextRange) -> Option<TextRange> | |||
43 | } | 39 | } |
44 | } | 40 | } |
45 | 41 | ||
46 | fn extend_single_word_in_comment(leaf: SyntaxNodeRef, offset: TextUnit) -> Option<TextRange> { | 42 | fn extend_single_word_in_comment_or_string( |
43 | leaf: SyntaxNodeRef, | ||
44 | offset: TextUnit, | ||
45 | ) -> Option<TextRange> { | ||
47 | let text: &str = leaf.leaf_text()?; | 46 | let text: &str = leaf.leaf_text()?; |
48 | let cursor_position: u32 = (offset - leaf.range().start()).into(); | 47 | let cursor_position: u32 = (offset - leaf.range().start()).into(); |
49 | 48 | ||
@@ -126,6 +125,7 @@ fn adj_comments(node: SyntaxNodeRef, dir: Direction) -> SyntaxNodeRef { | |||
126 | #[cfg(test)] | 125 | #[cfg(test)] |
127 | mod tests { | 126 | mod tests { |
128 | use super::*; | 127 | use super::*; |
128 | use ra_syntax::SourceFileNode; | ||
129 | use test_utils::extract_offset; | 129 | use test_utils::extract_offset; |
130 | 130 | ||
131 | fn do_check(before: &str, afters: &[&str]) { | 131 | fn do_check(before: &str, afters: &[&str]) { |
@@ -133,7 +133,7 @@ mod tests { | |||
133 | let file = SourceFileNode::parse(&before); | 133 | let file = SourceFileNode::parse(&before); |
134 | let mut range = TextRange::offset_len(cursor, 0.into()); | 134 | let mut range = TextRange::offset_len(cursor, 0.into()); |
135 | for &after in afters { | 135 | for &after in afters { |
136 | range = extend_selection(&file, range).unwrap(); | 136 | range = extend_selection(file.syntax(), range).unwrap(); |
137 | let actual = &before[range]; | 137 | let actual = &before[range]; |
138 | assert_eq!(after, actual); | 138 | assert_eq!(after, actual); |
139 | } | 139 | } |
@@ -266,4 +266,16 @@ impl S { | |||
266 | &["hello", "// hello world"], | 266 | &["hello", "// hello world"], |
267 | ); | 267 | ); |
268 | } | 268 | } |
269 | |||
270 | #[test] | ||
271 | fn test_extend_selection_string() { | ||
272 | do_check( | ||
273 | r#" | ||
274 | fn bar(){} | ||
275 | |||
276 | " fn f<|>oo() {" | ||
277 | "#, | ||
278 | &["foo", "\" fn foo() {\""], | ||
279 | ); | ||
280 | } | ||
269 | } | 281 | } |
diff --git a/crates/ra_editor/src/lib.rs b/crates/ra_editor/src/lib.rs index a65637d52..ac283e2e0 100644 --- a/crates/ra_editor/src/lib.rs +++ b/crates/ra_editor/src/lib.rs | |||
@@ -1,28 +1,28 @@ | |||
1 | mod code_actions; | 1 | pub mod assists; |
2 | mod extend_selection; | 2 | mod extend_selection; |
3 | mod folding_ranges; | 3 | mod folding_ranges; |
4 | mod line_index; | 4 | mod line_index; |
5 | mod line_index_utils; | 5 | mod line_index_utils; |
6 | mod symbols; | 6 | mod structure; |
7 | #[cfg(test)] | 7 | #[cfg(test)] |
8 | mod test_utils; | 8 | mod test_utils; |
9 | mod typing; | 9 | mod typing; |
10 | mod diagnostics; | 10 | mod diagnostics; |
11 | 11 | ||
12 | pub use self::{ | 12 | pub use self::{ |
13 | code_actions::{add_derive, add_impl, flip_comma, introduce_variable, make_pub_crate, LocalEdit}, | 13 | assists::LocalEdit, |
14 | extend_selection::extend_selection, | 14 | extend_selection::extend_selection, |
15 | folding_ranges::{folding_ranges, Fold, FoldKind}, | 15 | folding_ranges::{folding_ranges, Fold, FoldKind}, |
16 | line_index::{LineCol, LineIndex}, | 16 | line_index::{LineCol, LineIndex}, |
17 | line_index_utils::translate_offset_with_edit, | 17 | line_index_utils::translate_offset_with_edit, |
18 | symbols::{file_structure, file_symbols, FileSymbol, StructureNode}, | 18 | structure::{file_structure, StructureNode}, |
19 | typing::{join_lines, on_enter, on_eq_typed}, | 19 | typing::{join_lines, on_enter, on_eq_typed}, |
20 | diagnostics::diagnostics | 20 | diagnostics::diagnostics |
21 | }; | 21 | }; |
22 | use ra_text_edit::{TextEdit, TextEditBuilder}; | 22 | use ra_text_edit::TextEditBuilder; |
23 | use ra_syntax::{ | 23 | use ra_syntax::{ |
24 | algo::find_leaf_at_offset, | 24 | algo::find_leaf_at_offset, |
25 | ast::{self, AstNode, NameOwner}, | 25 | ast::{self, AstNode}, |
26 | SourceFileNode, | 26 | SourceFileNode, |
27 | SyntaxKind::{self, *}, | 27 | SyntaxKind::{self, *}, |
28 | SyntaxNodeRef, TextRange, TextUnit, Direction, | 28 | SyntaxNodeRef, TextRange, TextUnit, Direction, |
@@ -49,18 +49,6 @@ pub struct Diagnostic { | |||
49 | pub fix: Option<LocalEdit>, | 49 | pub fix: Option<LocalEdit>, |
50 | } | 50 | } |
51 | 51 | ||
52 | #[derive(Debug)] | ||
53 | pub struct Runnable { | ||
54 | pub range: TextRange, | ||
55 | pub kind: RunnableKind, | ||
56 | } | ||
57 | |||
58 | #[derive(Debug)] | ||
59 | pub enum RunnableKind { | ||
60 | Test { name: String }, | ||
61 | Bin, | ||
62 | } | ||
63 | |||
64 | pub fn matching_brace(file: &SourceFileNode, offset: TextUnit) -> Option<TextUnit> { | 52 | pub fn matching_brace(file: &SourceFileNode, offset: TextUnit) -> Option<TextUnit> { |
65 | const BRACES: &[SyntaxKind] = &[ | 53 | const BRACES: &[SyntaxKind] = &[ |
66 | L_CURLY, R_CURLY, L_BRACK, R_BRACK, L_PAREN, R_PAREN, L_ANGLE, R_ANGLE, | 54 | L_CURLY, R_CURLY, L_BRACK, R_BRACK, L_PAREN, R_PAREN, L_ANGLE, R_ANGLE, |
@@ -79,11 +67,11 @@ pub fn matching_brace(file: &SourceFileNode, offset: TextUnit) -> Option<TextUni | |||
79 | Some(matching_node.range().start()) | 67 | Some(matching_node.range().start()) |
80 | } | 68 | } |
81 | 69 | ||
82 | pub fn highlight(file: &SourceFileNode) -> Vec<HighlightedRange> { | 70 | pub fn highlight(root: SyntaxNodeRef) -> Vec<HighlightedRange> { |
83 | // Visited nodes to handle highlighting priorities | 71 | // Visited nodes to handle highlighting priorities |
84 | let mut highlighted = FxHashSet::default(); | 72 | let mut highlighted = FxHashSet::default(); |
85 | let mut res = Vec::new(); | 73 | let mut res = Vec::new(); |
86 | for node in file.syntax().descendants() { | 74 | for node in root.descendants() { |
87 | if highlighted.contains(&node) { | 75 | if highlighted.contains(&node) { |
88 | continue; | 76 | continue; |
89 | } | 77 | } |
@@ -133,29 +121,6 @@ pub fn syntax_tree(file: &SourceFileNode) -> String { | |||
133 | ::ra_syntax::utils::dump_tree(file.syntax()) | 121 | ::ra_syntax::utils::dump_tree(file.syntax()) |
134 | } | 122 | } |
135 | 123 | ||
136 | pub fn runnables(file: &SourceFileNode) -> Vec<Runnable> { | ||
137 | file.syntax() | ||
138 | .descendants() | ||
139 | .filter_map(ast::FnDef::cast) | ||
140 | .filter_map(|f| { | ||
141 | let name = f.name()?.text(); | ||
142 | let kind = if name == "main" { | ||
143 | RunnableKind::Bin | ||
144 | } else if f.has_atom_attr("test") { | ||
145 | RunnableKind::Test { | ||
146 | name: name.to_string(), | ||
147 | } | ||
148 | } else { | ||
149 | return None; | ||
150 | }; | ||
151 | Some(Runnable { | ||
152 | range: f.syntax().range(), | ||
153 | kind, | ||
154 | }) | ||
155 | }) | ||
156 | .collect() | ||
157 | } | ||
158 | |||
159 | pub fn find_node_at_offset<'a, N: AstNode<'a>>( | 124 | pub fn find_node_at_offset<'a, N: AstNode<'a>>( |
160 | syntax: SyntaxNodeRef<'a>, | 125 | syntax: SyntaxNodeRef<'a>, |
161 | offset: TextUnit, | 126 | offset: TextUnit, |
@@ -178,7 +143,7 @@ fn main() {} | |||
178 | println!("Hello, {}!", 92); | 143 | println!("Hello, {}!", 92); |
179 | "#, | 144 | "#, |
180 | ); | 145 | ); |
181 | let hls = highlight(&file); | 146 | let hls = highlight(file.syntax()); |
182 | assert_eq_dbg( | 147 | assert_eq_dbg( |
183 | r#"[HighlightedRange { range: [1; 11), tag: "comment" }, | 148 | r#"[HighlightedRange { range: [1; 11), tag: "comment" }, |
184 | HighlightedRange { range: [12; 14), tag: "keyword" }, | 149 | HighlightedRange { range: [12; 14), tag: "keyword" }, |
@@ -191,29 +156,6 @@ fn main() {} | |||
191 | } | 156 | } |
192 | 157 | ||
193 | #[test] | 158 | #[test] |
194 | fn test_runnables() { | ||
195 | let file = SourceFileNode::parse( | ||
196 | r#" | ||
197 | fn main() {} | ||
198 | |||
199 | #[test] | ||
200 | fn test_foo() {} | ||
201 | |||
202 | #[test] | ||
203 | #[ignore] | ||
204 | fn test_foo() {} | ||
205 | "#, | ||
206 | ); | ||
207 | let runnables = runnables(&file); | ||
208 | assert_eq_dbg( | ||
209 | r#"[Runnable { range: [1; 13), kind: Bin }, | ||
210 | Runnable { range: [15; 39), kind: Test { name: "test_foo" } }, | ||
211 | Runnable { range: [41; 75), kind: Test { name: "test_foo" } }]"#, | ||
212 | &runnables, | ||
213 | ) | ||
214 | } | ||
215 | |||
216 | #[test] | ||
217 | fn test_matching_brace() { | 159 | fn test_matching_brace() { |
218 | fn do_check(before: &str, after: &str) { | 160 | fn do_check(before: &str, after: &str) { |
219 | let (pos, before) = extract_offset(before); | 161 | let (pos, before) = extract_offset(before); |
diff --git a/crates/ra_editor/src/structure.rs b/crates/ra_editor/src/structure.rs new file mode 100644 index 000000000..2292b1ddf --- /dev/null +++ b/crates/ra_editor/src/structure.rs | |||
@@ -0,0 +1,129 @@ | |||
1 | use crate::TextRange; | ||
2 | |||
3 | use ra_syntax::{ | ||
4 | algo::visit::{visitor, Visitor}, | ||
5 | ast::{self, NameOwner}, | ||
6 | AstNode, SourceFileNode, SyntaxKind, SyntaxNodeRef, WalkEvent, | ||
7 | }; | ||
8 | |||
9 | #[derive(Debug, Clone)] | ||
10 | pub struct StructureNode { | ||
11 | pub parent: Option<usize>, | ||
12 | pub label: String, | ||
13 | pub navigation_range: TextRange, | ||
14 | pub node_range: TextRange, | ||
15 | pub kind: SyntaxKind, | ||
16 | } | ||
17 | |||
18 | pub fn file_structure(file: &SourceFileNode) -> Vec<StructureNode> { | ||
19 | let mut res = Vec::new(); | ||
20 | let mut stack = Vec::new(); | ||
21 | |||
22 | for event in file.syntax().preorder() { | ||
23 | match event { | ||
24 | WalkEvent::Enter(node) => { | ||
25 | if let Some(mut symbol) = structure_node(node) { | ||
26 | symbol.parent = stack.last().map(|&n| n); | ||
27 | stack.push(res.len()); | ||
28 | res.push(symbol); | ||
29 | } | ||
30 | } | ||
31 | WalkEvent::Leave(node) => { | ||
32 | if structure_node(node).is_some() { | ||
33 | stack.pop().unwrap(); | ||
34 | } | ||
35 | } | ||
36 | } | ||
37 | } | ||
38 | res | ||
39 | } | ||
40 | |||
41 | fn structure_node(node: SyntaxNodeRef) -> Option<StructureNode> { | ||
42 | fn decl<'a, N: NameOwner<'a>>(node: N) -> Option<StructureNode> { | ||
43 | let name = node.name()?; | ||
44 | Some(StructureNode { | ||
45 | parent: None, | ||
46 | label: name.text().to_string(), | ||
47 | navigation_range: name.syntax().range(), | ||
48 | node_range: node.syntax().range(), | ||
49 | kind: node.syntax().kind(), | ||
50 | }) | ||
51 | } | ||
52 | |||
53 | visitor() | ||
54 | .visit(decl::<ast::FnDef>) | ||
55 | .visit(decl::<ast::StructDef>) | ||
56 | .visit(decl::<ast::NamedFieldDef>) | ||
57 | .visit(decl::<ast::EnumDef>) | ||
58 | .visit(decl::<ast::TraitDef>) | ||
59 | .visit(decl::<ast::Module>) | ||
60 | .visit(decl::<ast::TypeDef>) | ||
61 | .visit(decl::<ast::ConstDef>) | ||
62 | .visit(decl::<ast::StaticDef>) | ||
63 | .visit(|im: ast::ImplItem| { | ||
64 | let target_type = im.target_type()?; | ||
65 | let target_trait = im.target_trait(); | ||
66 | let label = match target_trait { | ||
67 | None => format!("impl {}", target_type.syntax().text()), | ||
68 | Some(t) => format!( | ||
69 | "impl {} for {}", | ||
70 | t.syntax().text(), | ||
71 | target_type.syntax().text(), | ||
72 | ), | ||
73 | }; | ||
74 | |||
75 | let node = StructureNode { | ||
76 | parent: None, | ||
77 | label, | ||
78 | navigation_range: target_type.syntax().range(), | ||
79 | node_range: im.syntax().range(), | ||
80 | kind: im.syntax().kind(), | ||
81 | }; | ||
82 | Some(node) | ||
83 | }) | ||
84 | .accept(node)? | ||
85 | } | ||
86 | |||
87 | #[cfg(test)] | ||
88 | mod tests { | ||
89 | use super::*; | ||
90 | use test_utils::assert_eq_dbg; | ||
91 | |||
92 | #[test] | ||
93 | fn test_file_structure() { | ||
94 | let file = SourceFileNode::parse( | ||
95 | r#" | ||
96 | struct Foo { | ||
97 | x: i32 | ||
98 | } | ||
99 | |||
100 | mod m { | ||
101 | fn bar() {} | ||
102 | } | ||
103 | |||
104 | enum E { X, Y(i32) } | ||
105 | type T = (); | ||
106 | static S: i32 = 92; | ||
107 | const C: i32 = 92; | ||
108 | |||
109 | impl E {} | ||
110 | |||
111 | impl fmt::Debug for E {} | ||
112 | "#, | ||
113 | ); | ||
114 | let structure = file_structure(&file); | ||
115 | assert_eq_dbg( | ||
116 | r#"[StructureNode { parent: None, label: "Foo", navigation_range: [8; 11), node_range: [1; 26), kind: STRUCT_DEF }, | ||
117 | StructureNode { parent: Some(0), label: "x", navigation_range: [18; 19), node_range: [18; 24), kind: NAMED_FIELD_DEF }, | ||
118 | StructureNode { parent: None, label: "m", navigation_range: [32; 33), node_range: [28; 53), kind: MODULE }, | ||
119 | StructureNode { parent: Some(2), label: "bar", navigation_range: [43; 46), node_range: [40; 51), kind: FN_DEF }, | ||
120 | StructureNode { parent: None, label: "E", navigation_range: [60; 61), node_range: [55; 75), kind: ENUM_DEF }, | ||
121 | StructureNode { parent: None, label: "T", navigation_range: [81; 82), node_range: [76; 88), kind: TYPE_DEF }, | ||
122 | StructureNode { parent: None, label: "S", navigation_range: [96; 97), node_range: [89; 108), kind: STATIC_DEF }, | ||
123 | StructureNode { parent: None, label: "C", navigation_range: [115; 116), node_range: [109; 127), kind: CONST_DEF }, | ||
124 | StructureNode { parent: None, label: "impl E", navigation_range: [134; 135), node_range: [129; 138), kind: IMPL_ITEM }, | ||
125 | StructureNode { parent: None, label: "impl fmt::Debug for E", navigation_range: [160; 161), node_range: [140; 164), kind: IMPL_ITEM }]"#, | ||
126 | &structure, | ||
127 | ) | ||
128 | } | ||
129 | } | ||
diff --git a/crates/ra_editor/src/symbols.rs b/crates/ra_editor/src/symbols.rs deleted file mode 100644 index 9e25decfb..000000000 --- a/crates/ra_editor/src/symbols.rs +++ /dev/null | |||
@@ -1,246 +0,0 @@ | |||
1 | use crate::TextRange; | ||
2 | |||
3 | use ra_syntax::{ | ||
4 | algo::visit::{visitor, Visitor}, | ||
5 | ast::{self, DocCommentsOwner, NameOwner}, | ||
6 | AstNode, SourceFileNode, SmolStr, SyntaxKind, SyntaxNodeRef, WalkEvent, | ||
7 | }; | ||
8 | |||
9 | #[derive(Debug, Clone)] | ||
10 | pub struct StructureNode { | ||
11 | pub parent: Option<usize>, | ||
12 | pub label: String, | ||
13 | pub navigation_range: TextRange, | ||
14 | pub node_range: TextRange, | ||
15 | pub kind: SyntaxKind, | ||
16 | } | ||
17 | |||
18 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] | ||
19 | pub struct FileSymbol { | ||
20 | pub name: SmolStr, | ||
21 | pub node_range: TextRange, | ||
22 | pub kind: SyntaxKind, | ||
23 | } | ||
24 | |||
25 | impl FileSymbol { | ||
26 | pub fn docs(&self, file: &SourceFileNode) -> Option<String> { | ||
27 | file.syntax() | ||
28 | .descendants() | ||
29 | .filter(|node| node.kind() == self.kind && node.range() == self.node_range) | ||
30 | .filter_map(|node: SyntaxNodeRef| { | ||
31 | fn doc_comments<'a, N: DocCommentsOwner<'a>>(node: N) -> Option<String> { | ||
32 | let comments = node.doc_comment_text(); | ||
33 | if comments.is_empty() { | ||
34 | None | ||
35 | } else { | ||
36 | Some(comments) | ||
37 | } | ||
38 | } | ||
39 | |||
40 | visitor() | ||
41 | .visit(doc_comments::<ast::FnDef>) | ||
42 | .visit(doc_comments::<ast::StructDef>) | ||
43 | .visit(doc_comments::<ast::EnumDef>) | ||
44 | .visit(doc_comments::<ast::TraitDef>) | ||
45 | .visit(doc_comments::<ast::Module>) | ||
46 | .visit(doc_comments::<ast::TypeDef>) | ||
47 | .visit(doc_comments::<ast::ConstDef>) | ||
48 | .visit(doc_comments::<ast::StaticDef>) | ||
49 | .accept(node)? | ||
50 | }) | ||
51 | .nth(0) | ||
52 | } | ||
53 | /// Get a description of this node. | ||
54 | /// | ||
55 | /// e.g. `struct Name`, `enum Name`, `fn Name` | ||
56 | pub fn description(&self, file: &SourceFileNode) -> Option<String> { | ||
57 | // TODO: After type inference is done, add type information to improve the output | ||
58 | file.syntax() | ||
59 | .descendants() | ||
60 | .filter(|node| node.kind() == self.kind && node.range() == self.node_range) | ||
61 | .filter_map(|node: SyntaxNodeRef| { | ||
62 | // TODO: Refactor to be have less repetition | ||
63 | visitor() | ||
64 | .visit(|node: ast::FnDef| { | ||
65 | let mut string = "fn ".to_string(); | ||
66 | node.name()?.syntax().text().push_to(&mut string); | ||
67 | Some(string) | ||
68 | }) | ||
69 | .visit(|node: ast::StructDef| { | ||
70 | let mut string = "struct ".to_string(); | ||
71 | node.name()?.syntax().text().push_to(&mut string); | ||
72 | Some(string) | ||
73 | }) | ||
74 | .visit(|node: ast::EnumDef| { | ||
75 | let mut string = "enum ".to_string(); | ||
76 | node.name()?.syntax().text().push_to(&mut string); | ||
77 | Some(string) | ||
78 | }) | ||
79 | .visit(|node: ast::TraitDef| { | ||
80 | let mut string = "trait ".to_string(); | ||
81 | node.name()?.syntax().text().push_to(&mut string); | ||
82 | Some(string) | ||
83 | }) | ||
84 | .visit(|node: ast::Module| { | ||
85 | let mut string = "mod ".to_string(); | ||
86 | node.name()?.syntax().text().push_to(&mut string); | ||
87 | Some(string) | ||
88 | }) | ||
89 | .visit(|node: ast::TypeDef| { | ||
90 | let mut string = "type ".to_string(); | ||
91 | node.name()?.syntax().text().push_to(&mut string); | ||
92 | Some(string) | ||
93 | }) | ||
94 | .visit(|node: ast::ConstDef| { | ||
95 | let mut string = "const ".to_string(); | ||
96 | node.name()?.syntax().text().push_to(&mut string); | ||
97 | Some(string) | ||
98 | }) | ||
99 | .visit(|node: ast::StaticDef| { | ||
100 | let mut string = "static ".to_string(); | ||
101 | node.name()?.syntax().text().push_to(&mut string); | ||
102 | Some(string) | ||
103 | }) | ||
104 | .accept(node)? | ||
105 | }) | ||
106 | .nth(0) | ||
107 | } | ||
108 | } | ||
109 | |||
110 | pub fn file_symbols(file: &SourceFileNode) -> Vec<FileSymbol> { | ||
111 | file.syntax().descendants().filter_map(to_symbol).collect() | ||
112 | } | ||
113 | |||
114 | fn to_symbol(node: SyntaxNodeRef) -> Option<FileSymbol> { | ||
115 | fn decl<'a, N: NameOwner<'a>>(node: N) -> Option<FileSymbol> { | ||
116 | let name = node.name()?; | ||
117 | Some(FileSymbol { | ||
118 | name: name.text(), | ||
119 | node_range: node.syntax().range(), | ||
120 | kind: node.syntax().kind(), | ||
121 | }) | ||
122 | } | ||
123 | visitor() | ||
124 | .visit(decl::<ast::FnDef>) | ||
125 | .visit(decl::<ast::StructDef>) | ||
126 | .visit(decl::<ast::EnumDef>) | ||
127 | .visit(decl::<ast::TraitDef>) | ||
128 | .visit(decl::<ast::Module>) | ||
129 | .visit(decl::<ast::TypeDef>) | ||
130 | .visit(decl::<ast::ConstDef>) | ||
131 | .visit(decl::<ast::StaticDef>) | ||
132 | .accept(node)? | ||
133 | } | ||
134 | |||
135 | pub fn file_structure(file: &SourceFileNode) -> Vec<StructureNode> { | ||
136 | let mut res = Vec::new(); | ||
137 | let mut stack = Vec::new(); | ||
138 | |||
139 | for event in file.syntax().preorder() { | ||
140 | match event { | ||
141 | WalkEvent::Enter(node) => { | ||
142 | if let Some(mut symbol) = structure_node(node) { | ||
143 | symbol.parent = stack.last().map(|&n| n); | ||
144 | stack.push(res.len()); | ||
145 | res.push(symbol); | ||
146 | } | ||
147 | } | ||
148 | WalkEvent::Leave(node) => { | ||
149 | if structure_node(node).is_some() { | ||
150 | stack.pop().unwrap(); | ||
151 | } | ||
152 | } | ||
153 | } | ||
154 | } | ||
155 | res | ||
156 | } | ||
157 | |||
158 | fn structure_node(node: SyntaxNodeRef) -> Option<StructureNode> { | ||
159 | fn decl<'a, N: NameOwner<'a>>(node: N) -> Option<StructureNode> { | ||
160 | let name = node.name()?; | ||
161 | Some(StructureNode { | ||
162 | parent: None, | ||
163 | label: name.text().to_string(), | ||
164 | navigation_range: name.syntax().range(), | ||
165 | node_range: node.syntax().range(), | ||
166 | kind: node.syntax().kind(), | ||
167 | }) | ||
168 | } | ||
169 | |||
170 | visitor() | ||
171 | .visit(decl::<ast::FnDef>) | ||
172 | .visit(decl::<ast::StructDef>) | ||
173 | .visit(decl::<ast::NamedFieldDef>) | ||
174 | .visit(decl::<ast::EnumDef>) | ||
175 | .visit(decl::<ast::TraitDef>) | ||
176 | .visit(decl::<ast::Module>) | ||
177 | .visit(decl::<ast::TypeDef>) | ||
178 | .visit(decl::<ast::ConstDef>) | ||
179 | .visit(decl::<ast::StaticDef>) | ||
180 | .visit(|im: ast::ImplItem| { | ||
181 | let target_type = im.target_type()?; | ||
182 | let target_trait = im.target_trait(); | ||
183 | let label = match target_trait { | ||
184 | None => format!("impl {}", target_type.syntax().text()), | ||
185 | Some(t) => format!( | ||
186 | "impl {} for {}", | ||
187 | t.syntax().text(), | ||
188 | target_type.syntax().text(), | ||
189 | ), | ||
190 | }; | ||
191 | |||
192 | let node = StructureNode { | ||
193 | parent: None, | ||
194 | label, | ||
195 | navigation_range: target_type.syntax().range(), | ||
196 | node_range: im.syntax().range(), | ||
197 | kind: im.syntax().kind(), | ||
198 | }; | ||
199 | Some(node) | ||
200 | }) | ||
201 | .accept(node)? | ||
202 | } | ||
203 | |||
204 | #[cfg(test)] | ||
205 | mod tests { | ||
206 | use super::*; | ||
207 | use test_utils::assert_eq_dbg; | ||
208 | |||
209 | #[test] | ||
210 | fn test_file_structure() { | ||
211 | let file = SourceFileNode::parse( | ||
212 | r#" | ||
213 | struct Foo { | ||
214 | x: i32 | ||
215 | } | ||
216 | |||
217 | mod m { | ||
218 | fn bar() {} | ||
219 | } | ||
220 | |||
221 | enum E { X, Y(i32) } | ||
222 | type T = (); | ||
223 | static S: i32 = 92; | ||
224 | const C: i32 = 92; | ||
225 | |||
226 | impl E {} | ||
227 | |||
228 | impl fmt::Debug for E {} | ||
229 | "#, | ||
230 | ); | ||
231 | let symbols = file_structure(&file); | ||
232 | assert_eq_dbg( | ||
233 | r#"[StructureNode { parent: None, label: "Foo", navigation_range: [8; 11), node_range: [1; 26), kind: STRUCT_DEF }, | ||
234 | StructureNode { parent: Some(0), label: "x", navigation_range: [18; 19), node_range: [18; 24), kind: NAMED_FIELD_DEF }, | ||
235 | StructureNode { parent: None, label: "m", navigation_range: [32; 33), node_range: [28; 53), kind: MODULE }, | ||
236 | StructureNode { parent: Some(2), label: "bar", navigation_range: [43; 46), node_range: [40; 51), kind: FN_DEF }, | ||
237 | StructureNode { parent: None, label: "E", navigation_range: [60; 61), node_range: [55; 75), kind: ENUM_DEF }, | ||
238 | StructureNode { parent: None, label: "T", navigation_range: [81; 82), node_range: [76; 88), kind: TYPE_DEF }, | ||
239 | StructureNode { parent: None, label: "S", navigation_range: [96; 97), node_range: [89; 108), kind: STATIC_DEF }, | ||
240 | StructureNode { parent: None, label: "C", navigation_range: [115; 116), node_range: [109; 127), kind: CONST_DEF }, | ||
241 | StructureNode { parent: None, label: "impl E", navigation_range: [134; 135), node_range: [129; 138), kind: IMPL_ITEM }, | ||
242 | StructureNode { parent: None, label: "impl fmt::Debug for E", navigation_range: [160; 161), node_range: [140; 164), kind: IMPL_ITEM }]"#, | ||
243 | &symbols, | ||
244 | ) | ||
245 | } | ||
246 | } | ||
diff --git a/crates/ra_editor/src/typing.rs b/crates/ra_editor/src/typing.rs index 21d068a7b..dd3d0f260 100644 --- a/crates/ra_editor/src/typing.rs +++ b/crates/ra_editor/src/typing.rs | |||
@@ -21,7 +21,7 @@ pub fn join_lines(file: &SourceFileNode, range: TextRange) -> LocalEdit { | |||
21 | None => { | 21 | None => { |
22 | return LocalEdit { | 22 | return LocalEdit { |
23 | label: "join lines".to_string(), | 23 | label: "join lines".to_string(), |
24 | edit: TextEditBuilder::new().finish(), | 24 | edit: TextEditBuilder::default().finish(), |
25 | cursor_position: None, | 25 | cursor_position: None, |
26 | }; | 26 | }; |
27 | } | 27 | } |
@@ -33,7 +33,7 @@ pub fn join_lines(file: &SourceFileNode, range: TextRange) -> LocalEdit { | |||
33 | }; | 33 | }; |
34 | 34 | ||
35 | let node = find_covering_node(file.syntax(), range); | 35 | let node = find_covering_node(file.syntax(), range); |
36 | let mut edit = TextEditBuilder::new(); | 36 | let mut edit = TextEditBuilder::default(); |
37 | for node in node.descendants() { | 37 | for node in node.descendants() { |
38 | let text = match node.leaf_text() { | 38 | let text = match node.leaf_text() { |
39 | Some(text) => text, | 39 | Some(text) => text, |
@@ -76,7 +76,7 @@ pub fn on_enter(file: &SourceFileNode, offset: TextUnit) -> Option<LocalEdit> { | |||
76 | let indent = node_indent(file, comment.syntax())?; | 76 | let indent = node_indent(file, comment.syntax())?; |
77 | let inserted = format!("\n{}{} ", indent, prefix); | 77 | let inserted = format!("\n{}{} ", indent, prefix); |
78 | let cursor_position = offset + TextUnit::of_str(&inserted); | 78 | let cursor_position = offset + TextUnit::of_str(&inserted); |
79 | let mut edit = TextEditBuilder::new(); | 79 | let mut edit = TextEditBuilder::default(); |
80 | edit.insert(offset, inserted); | 80 | edit.insert(offset, inserted); |
81 | Some(LocalEdit { | 81 | Some(LocalEdit { |
82 | label: "on enter".to_string(), | 82 | label: "on enter".to_string(), |
@@ -127,7 +127,7 @@ pub fn on_eq_typed(file: &SourceFileNode, offset: TextUnit) -> Option<LocalEdit> | |||
127 | return None; | 127 | return None; |
128 | } | 128 | } |
129 | let offset = let_stmt.syntax().range().end(); | 129 | let offset = let_stmt.syntax().range().end(); |
130 | let mut edit = TextEditBuilder::new(); | 130 | let mut edit = TextEditBuilder::default(); |
131 | edit.insert(offset, ";".to_string()); | 131 | edit.insert(offset, ";".to_string()); |
132 | Some(LocalEdit { | 132 | Some(LocalEdit { |
133 | label: "add semicolon".to_string(), | 133 | label: "add semicolon".to_string(), |
diff --git a/crates/ra_hir/src/adt.rs b/crates/ra_hir/src/adt.rs index e839a5a90..c6463235c 100644 --- a/crates/ra_hir/src/adt.rs +++ b/crates/ra_hir/src/adt.rs | |||
@@ -1,9 +1,10 @@ | |||
1 | use std::sync::Arc; | 1 | use std::sync::Arc; |
2 | 2 | ||
3 | use ra_db::Cancelable; | ||
3 | use ra_syntax::ast::{self, NameOwner, StructFlavor}; | 4 | use ra_syntax::ast::{self, NameOwner, StructFlavor}; |
4 | 5 | ||
5 | use crate::{ | 6 | use crate::{ |
6 | DefId, Cancelable, Name, AsName, | 7 | DefId, Name, AsName, |
7 | db::HirDatabase, | 8 | db::HirDatabase, |
8 | type_ref::TypeRef, | 9 | type_ref::TypeRef, |
9 | }; | 10 | }; |
diff --git a/crates/ra_hir/src/db.rs b/crates/ra_hir/src/db.rs index 5a8ca3b47..73a4cdc5c 100644 --- a/crates/ra_hir/src/db.rs +++ b/crates/ra_hir/src/db.rs | |||
@@ -1,13 +1,14 @@ | |||
1 | use std::sync::Arc; | 1 | use std::sync::Arc; |
2 | 2 | ||
3 | use ra_syntax::SyntaxNode; | 3 | use ra_syntax::{SyntaxNode, SourceFileNode}; |
4 | use ra_db::{SourceRootId, LocationIntener, SyntaxDatabase, FileId, Cancelable}; | 4 | use ra_db::{SourceRootId, LocationIntener, SyntaxDatabase, Cancelable}; |
5 | 5 | ||
6 | use crate::{ | 6 | use crate::{ |
7 | DefLoc, DefId, Name, | 7 | DefLoc, DefId, MacroCallLoc, MacroCallId, Name, HirFileId, |
8 | SourceFileItems, SourceItemId, | 8 | SourceFileItems, SourceItemId, |
9 | query_definitions, | 9 | query_definitions, |
10 | FnScopes, | 10 | FnScopes, |
11 | macros::MacroExpansion, | ||
11 | module::{ModuleId, ModuleTree, ModuleSource, | 12 | module::{ModuleId, ModuleTree, ModuleSource, |
12 | nameres::{ItemMap, InputModuleItems}}, | 13 | nameres::{ItemMap, InputModuleItems}}, |
13 | ty::{InferenceResult, Ty}, | 14 | ty::{InferenceResult, Ty}, |
@@ -18,7 +19,17 @@ salsa::query_group! { | |||
18 | 19 | ||
19 | pub trait HirDatabase: SyntaxDatabase | 20 | pub trait HirDatabase: SyntaxDatabase |
20 | + AsRef<LocationIntener<DefLoc, DefId>> | 21 | + AsRef<LocationIntener<DefLoc, DefId>> |
22 | + AsRef<LocationIntener<MacroCallLoc, MacroCallId>> | ||
21 | { | 23 | { |
24 | fn hir_source_file(file_id: HirFileId) -> SourceFileNode { | ||
25 | type HirSourceFileQuery; | ||
26 | use fn HirFileId::hir_source_file; | ||
27 | } | ||
28 | fn expand_macro_invocation(invoc: MacroCallId) -> Option<Arc<MacroExpansion>> { | ||
29 | type |