├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .rustfmt.toml ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE-MIT ├── README.md ├── benches ├── bench_extern.rs ├── bench_globs.rs └── bench_stdlibs.rs ├── fixtures ├── .cargo │ └── config └── arst │ ├── Cargo.toml │ └── src │ ├── lib.rs │ └── submodule │ └── mod.rs ├── images ├── racer_completion.png └── racer_eldoc.png ├── interner ├── Cargo.toml └── src │ └── lib.rs ├── metadata ├── Cargo.toml ├── src │ ├── lib.rs │ ├── mapping.rs │ └── metadata.rs ├── test-data-full.json ├── test-data-no-deps.json └── tests │ ├── from_json.rs │ ├── manifest.rs │ └── self_test.rs ├── rust-toolchain.toml ├── src ├── bin │ └── main.rs └── racer │ ├── ast.rs │ ├── ast_types.rs │ ├── benches.rs │ ├── codecleaner.rs │ ├── codeiter.rs │ ├── core.rs │ ├── fileres.rs │ ├── lib.rs │ ├── matchers.rs │ ├── metadata.rs │ ├── nameres.rs │ ├── primitive.rs │ ├── project_model.rs │ ├── scopes.rs │ ├── snippets.rs │ ├── testutils.rs │ ├── typeinf.rs │ └── util.rs ├── test_project ├── Cargo.lock ├── Cargo.toml ├── src │ ├── lib.rs │ ├── submod │ │ ├── foo.rs │ │ └── mod.rs │ ├── submod2018.rs │ └── submod2018 │ │ └── foo2018.rs ├── test-crate2 │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── test-crate3 │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── test-crate4 │ ├── Cargo.toml │ └── src │ │ └── lib.rs └── test_fixtures │ ├── Cargo.toml │ └── src │ ├── foo.rs │ ├── lib.rs │ └── submod │ └── bar.rs ├── tests ├── alias.rs ├── async.rs ├── binary.rs ├── consts.rs ├── external.rs ├── follow_self.rs ├── for_arg.rs ├── if_let.rs ├── index.rs ├── module_structure.rs ├── primitive.rs ├── struct_field.rs ├── system.rs ├── trait_bounds.rs ├── try.rs └── union.rs └── testutils ├── Cargo.toml └── src └── lib.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened] 6 | push: 7 | branches: 8 | - master 9 | - '*' 10 | schedule: 11 | - cron: '0 0 * * *' # Nightly at 00:00 UTC 12 | 13 | jobs: 14 | build_and_test: 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | toolchain: 19 | - x86_64-unknown-linux-gnu 20 | - x86_64-apple-darwin 21 | - x86_64-pc-windows-msvc 22 | - i686-pc-windows-msvc 23 | include: 24 | - toolchain: x86_64-unknown-linux-gnu 25 | builder: ubuntu-latest 26 | os: linux 27 | - toolchain: x86_64-apple-darwin 28 | builder: macos-latest 29 | os: macos 30 | - toolchain: x86_64-pc-windows-msvc 31 | builder: windows-latest 32 | os: windows 33 | - toolchain: i686-pc-windows-msvc 34 | builder: windows-latest 35 | os: windows 36 | 37 | name: nightly - ${{ matrix.toolchain }} 38 | runs-on: ${{ matrix.builder }} 39 | 40 | steps: 41 | - uses: actions/checkout@v2 42 | - name: Use latest nightly on scheduled builds 43 | if: github.event_name == 'schedule' 44 | run: echo "nightly" > rust-toolchain 45 | - run: rustup set default-host ${{ matrix.toolchain }} 46 | - run: rustup component add rust-src 47 | - run: rustc -vV 48 | - run: cargo build --verbose --all 49 | - run: cargo test --all 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | \#* 2 | src/scopes 3 | !.travis.yml 4 | *tmpfile* 5 | *.racertmp 6 | target/ 7 | *.py[cod] 8 | .vscode/** 9 | *.log -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/racer-rust/racer/a457c235b545c5251570cda8fec1edeab05ab1d4/.rustfmt.toml -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Change Log 2 | ========== 3 | 4 | All notable changes to this project will be documented in this file. This 5 | project adheres to [Semantic Versioning](https://semver.org/). 6 | 7 | # 2.1.37 8 | - Bump rustc-ap-* version to 677.0 9 | - Account for new standard library source directory layout 10 | 11 | # 2.1.37 12 | - Bump rustc-ap-* version to 671.0 13 | 14 | # 2.1.36 15 | - Bump rustc-ap-* version to 669.0 16 | 17 | # 2.1.35 18 | - Bump rustc-ap-* version to 664.0 19 | 20 | # 2.1.34 21 | - Bump rustc-ap-* version to 659.0 22 | - Fix submodule search (#1107) 23 | 24 | # 2.1.33 25 | - Bump rustc-ap-* version to 654.0 26 | 27 | # 2.1.32 28 | - Bump rustc-ap-* version to 651.0 29 | 30 | # 2.1.31 31 | - Bump rustc-ap-* version to 642.0 32 | 33 | # 2.1.30 34 | - Support for union(#1086) 35 | 36 | # 2.1.29 37 | - Support async/await syntax(#1083, #1085) 38 | 39 | # 2.1.28 40 | - Update the version of rustc-ap-syntax 41 | 42 | # 2.1.27 43 | - Update the version of rustc-ap-syntax 44 | 45 | # 2.1.26 46 | - Update the version of rustc-ap-syntax 47 | 48 | # 2.1.25 49 | - Update the version of rustc-ap-syntax 50 | 51 | # 2.1.24 52 | - Rust 2018 (#1051) 53 | - Update the version of rustc-ap-syntax 54 | 55 | # 2.1.22 56 | - Fix completion for `super::super::...`(#1053) 57 | 58 | # 2.1.20, 2.1.21 59 | - Fix completion in testdir for Rust 2018(#1022) 60 | - Fix enum variant completion for pub(crate) enum(#1025) 61 | 62 | # 2.1.18, 2.1.19 63 | - Update rustc-ap-syntax 64 | 65 | # 2.1.17, 2.1.18 66 | - Fix doc comment parsing(#1010) 67 | 68 | # 2.1.15. 2.1.16 69 | - Handle CRLF correctly(#1007) 70 | 71 | # 2.1.14 72 | - Completion for binary operation(#976) 73 | 74 | # 2.1.10, 2.1.11, 2.1.12, 2.1.13 75 | - Completion for impl trait(#985, #986) 76 | - Completion for use as(#988) 77 | 78 | # 2.1.8, 2.1.9 79 | - Completion for trait objects(#972) 80 | - Completion for simple closure return types(#973) 81 | 82 | # 2.1.7 83 | - Lots of refactoring(#961, #963, #965) 84 | - Add `is_use_statement` for RLS(#965) 85 | 86 | # 2.1.6 87 | - Completion based on impl #948 88 | - Fix for argument completion #943 89 | - Trait bound in where clause #937 90 | 91 | # 2.1.5 92 | - migrate to cargo metadata #930 93 | 94 | # 2.1.3 95 | - Make cargo optional for RLS #910 96 | 97 | ## 2.1.2 98 | - Fix bug around getting `use` context #906 99 | - Update rustc-ap-syntax to fix build in current nightly #911 100 | 101 | ## 2.1.1 102 | - Fix coordinate bug 103 | - Get doc string for macro #905 104 | 105 | ## 2.1.0 106 | - Support completions for stdlib macros #902 107 | - Support extern "~" block #895 108 | - Support `crate_in_paths` #891 109 | - Fix bug of getting completion context from `use` statement #886 110 | - Handle const unsafe fn #879 111 | - Limit recursion depth through glob imports #875 112 | - Enable completion based on trait bound for function args #871 113 | - Fix bug in search_closure_args #862 114 | - Replace cargo.rs with cargo crate #855 115 | - Migrate over to rustc_ap_syntax #854 116 | - Make RUST_SRC_PATH optional #808 117 | - Refactor based on clippy #860 118 | 119 | ## 2.0.14 120 | - Cache generic impls #839 121 | - Cache parsed TOML file and cargo crate roots #838 122 | - Skip `pub` keyword as a temporary fix for #624 #850 123 | - Remove complex generic type by impl trait #848 124 | - Fix bug for array expression #841 125 | - Support completion for enum variants without type annotation #825 126 | - Fix bug for raw string #822 127 | 128 | ## 2.0.13 129 | - Fix bug for finding the start of match statement #819 130 | 131 | ## 2.0.12 132 | - Fix bug that broke completions in previous release #807 133 | 134 | ## 2.0.11 135 | 136 | - Use `rustup` to find libstd path even when used as library #799 137 | 138 | ## 2.0.10 139 | 140 | - Support resolving `use as` aliases declared in multi-element `use` statements #753 141 | - Provide suggestions for global paths in more cases #765 142 | - Suggestions imported via `use as` statements now return their in-scope alias as the match string #767 143 | - Add new commands for converting between points and coordinates in files #776 144 | - Return fewer duplicate suggestions #778 145 | - Handle cases where mod names and trait methods collide, such as `fmt` #781 146 | 147 | ## 2.0.9 148 | 149 | - Support completion after using try operator `?` #726 150 | - Find methods on cooked string literals #728 151 | - Fix bug caused by closure completions feature #734 152 | - Find static methods on enums #737 153 | - Find doc comments on named and indexed struct fields #739 154 | - Find `pub(restricted)` items #748 155 | 156 | ## 2.0.8 157 | 158 | - Fix bug finding definitions where impl contains bang #717 159 | - Find definition for closures #697 160 | - Resolve types for tuple struct fields #722 161 | - Resolve types for let patterns #724 162 | - Fix completions for reference fields #723 163 | 164 | ## 2.0.7 165 | 166 | - Fix panic with macros called `impl*` #701 167 | - Relax semver specs 168 | 169 | ## 2.0.6 170 | 171 | - resolve Self (e.g. in-impl function calls like Self::myfunction()) 172 | - Fix stack overflow issue on unresolvable imports :tada: #698 173 | 174 | ## 2.0.5 175 | 176 | - Chained completions on separate lines now work #686 177 | 178 | ## 2.0.4 179 | 180 | - Fix for find-doc not always returning full doc string #675 181 | 182 | ## 2.0.3 183 | 184 | - Fix for recursion in certain `use foo::{self, ..}` cases #669 185 | 186 | ## 2.0.2 187 | 188 | - Internal fixes so we can publish on crates.io 189 | 190 | ## 2.0.1 191 | 192 | - Syntex 0.52 #643 193 | 194 | - Fix `racer --help` bug from 2.0 refactor #662 195 | 196 | - Support short revision identifiers for git checkout paths #664 197 | 198 | - Handle self resolution when using `use mymod::{self, Thing}` #665 199 | 200 | - Fix type alias resolution #666 201 | 202 | ## 2.0 203 | 204 | - Rework public API to hide many implementation details and allow the project to 205 | move forward without breaking changes. 206 | 207 | - Many fixes that didn't make it into the changelog, but we're going to work on 208 | that in the future! 209 | 210 | ## 1.2 211 | 212 | - Added basic 'daemon' mode, racer process can be kept running between 213 | invocations 214 | 215 | - now uses clap to parse command line options 216 | 217 | - Adds caching of file source and code indices 218 | 219 | - Adds an alternative 'tabbed' mode where inputs and outputs can be tab 220 | separated for easier parsing 221 | 222 | - emacs and vim support split out into their own git projects [emacs-racer] and 223 | [vim-racer], respectively. 224 | 225 | - Fix issue resolving some `std::*` modules in latest rust source: (rust std lib 226 | implicitly imports core with `#![no_std]`) 227 | 228 | - Searches multirust overrides when locating cargo src directories 229 | 230 | ## 1.0.0 2015-07-29 231 | 232 | - First release 233 | 234 | [vim-racer]: https://github.com/racer-rust/vim-racer 235 | [emacs-racer]: https://github.com/racer-rust/emacs-racer 236 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.18" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "ansi_term" 16 | version = "0.12.1" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 19 | dependencies = [ 20 | "winapi", 21 | ] 22 | 23 | [[package]] 24 | name = "atty" 25 | version = "0.2.14" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 28 | dependencies = [ 29 | "hermit-abi", 30 | "libc", 31 | "winapi", 32 | ] 33 | 34 | [[package]] 35 | name = "bitflags" 36 | version = "1.3.2" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 39 | 40 | [[package]] 41 | name = "cfg-if" 42 | version = "1.0.0" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 45 | 46 | [[package]] 47 | name = "clap" 48 | version = "2.34.0" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" 51 | dependencies = [ 52 | "ansi_term", 53 | "atty", 54 | "bitflags", 55 | "strsim", 56 | "textwrap", 57 | "unicode-width", 58 | "vec_map", 59 | ] 60 | 61 | [[package]] 62 | name = "convert_case" 63 | version = "0.4.0" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" 66 | 67 | [[package]] 68 | name = "derive_more" 69 | version = "0.99.17" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" 72 | dependencies = [ 73 | "convert_case", 74 | "proc-macro2", 75 | "quote", 76 | "rustc_version", 77 | "syn", 78 | ] 79 | 80 | [[package]] 81 | name = "env_logger" 82 | version = "0.7.1" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" 85 | dependencies = [ 86 | "atty", 87 | "humantime 1.3.0", 88 | "log", 89 | "regex", 90 | "termcolor", 91 | ] 92 | 93 | [[package]] 94 | name = "fastrand" 95 | version = "1.6.0" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "779d043b6a0b90cc4c0ed7ee380a6504394cee7efd7db050e3774eee387324b2" 98 | dependencies = [ 99 | "instant", 100 | ] 101 | 102 | [[package]] 103 | name = "hermit-abi" 104 | version = "0.1.19" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 107 | dependencies = [ 108 | "libc", 109 | ] 110 | 111 | [[package]] 112 | name = "humantime" 113 | version = "1.3.0" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" 116 | dependencies = [ 117 | "quick-error", 118 | ] 119 | 120 | [[package]] 121 | name = "humantime" 122 | version = "2.1.0" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 125 | 126 | [[package]] 127 | name = "instant" 128 | version = "0.1.12" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 131 | dependencies = [ 132 | "cfg-if", 133 | ] 134 | 135 | [[package]] 136 | name = "itoa" 137 | version = "1.0.1" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" 140 | 141 | [[package]] 142 | name = "lazy_static" 143 | version = "1.4.0" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 146 | 147 | [[package]] 148 | name = "lazycell" 149 | version = "1.3.0" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" 152 | 153 | [[package]] 154 | name = "libc" 155 | version = "0.2.112" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" 158 | 159 | [[package]] 160 | name = "log" 161 | version = "0.4.14" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 164 | dependencies = [ 165 | "cfg-if", 166 | ] 167 | 168 | [[package]] 169 | name = "memchr" 170 | version = "2.4.1" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 173 | 174 | [[package]] 175 | name = "proc-macro2" 176 | version = "1.0.36" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" 179 | dependencies = [ 180 | "unicode-xid", 181 | ] 182 | 183 | [[package]] 184 | name = "quick-error" 185 | version = "1.2.3" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" 188 | 189 | [[package]] 190 | name = "quote" 191 | version = "1.0.14" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d" 194 | dependencies = [ 195 | "proc-macro2", 196 | ] 197 | 198 | [[package]] 199 | name = "racer" 200 | version = "2.2.2" 201 | dependencies = [ 202 | "bitflags", 203 | "clap", 204 | "derive_more", 205 | "env_logger", 206 | "humantime 2.1.0", 207 | "lazy_static", 208 | "lazycell", 209 | "log", 210 | "racer-cargo-metadata", 211 | "racer-testutils", 212 | "rls-span", 213 | ] 214 | 215 | [[package]] 216 | name = "racer-cargo-metadata" 217 | version = "0.1.2" 218 | dependencies = [ 219 | "racer-interner", 220 | "serde", 221 | "serde_json", 222 | ] 223 | 224 | [[package]] 225 | name = "racer-interner" 226 | version = "0.1.0" 227 | dependencies = [ 228 | "serde", 229 | ] 230 | 231 | [[package]] 232 | name = "racer-testutils" 233 | version = "0.1.0" 234 | dependencies = [ 235 | "racer", 236 | "tempfile", 237 | ] 238 | 239 | [[package]] 240 | name = "redox_syscall" 241 | version = "0.2.10" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" 244 | dependencies = [ 245 | "bitflags", 246 | ] 247 | 248 | [[package]] 249 | name = "regex" 250 | version = "1.5.4" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" 253 | dependencies = [ 254 | "aho-corasick", 255 | "memchr", 256 | "regex-syntax", 257 | ] 258 | 259 | [[package]] 260 | name = "regex-syntax" 261 | version = "0.6.25" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 264 | 265 | [[package]] 266 | name = "remove_dir_all" 267 | version = "0.5.3" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" 270 | dependencies = [ 271 | "winapi", 272 | ] 273 | 274 | [[package]] 275 | name = "rls-span" 276 | version = "0.5.4" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | checksum = "b6e80f614ad4b37910bfe9b029af19c6f92612bb8e1af66e37d35829bf4ef6d1" 279 | dependencies = [ 280 | "serde", 281 | ] 282 | 283 | [[package]] 284 | name = "rustc_version" 285 | version = "0.4.0" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" 288 | dependencies = [ 289 | "semver", 290 | ] 291 | 292 | [[package]] 293 | name = "ryu" 294 | version = "1.0.9" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" 297 | 298 | [[package]] 299 | name = "semver" 300 | version = "1.0.4" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012" 303 | 304 | [[package]] 305 | name = "serde" 306 | version = "1.0.133" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "97565067517b60e2d1ea8b268e59ce036de907ac523ad83a0475da04e818989a" 309 | dependencies = [ 310 | "serde_derive", 311 | ] 312 | 313 | [[package]] 314 | name = "serde_derive" 315 | version = "1.0.133" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "ed201699328568d8d08208fdd080e3ff594e6c422e438b6705905da01005d537" 318 | dependencies = [ 319 | "proc-macro2", 320 | "quote", 321 | "syn", 322 | ] 323 | 324 | [[package]] 325 | name = "serde_json" 326 | version = "1.0.74" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "ee2bb9cd061c5865d345bb02ca49fcef1391741b672b54a0bf7b679badec3142" 329 | dependencies = [ 330 | "itoa", 331 | "ryu", 332 | "serde", 333 | ] 334 | 335 | [[package]] 336 | name = "strsim" 337 | version = "0.8.0" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 340 | 341 | [[package]] 342 | name = "syn" 343 | version = "1.0.85" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "a684ac3dcd8913827e18cd09a68384ee66c1de24157e3c556c9ab16d85695fb7" 346 | dependencies = [ 347 | "proc-macro2", 348 | "quote", 349 | "unicode-xid", 350 | ] 351 | 352 | [[package]] 353 | name = "tempfile" 354 | version = "3.3.0" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" 357 | dependencies = [ 358 | "cfg-if", 359 | "fastrand", 360 | "libc", 361 | "redox_syscall", 362 | "remove_dir_all", 363 | "winapi", 364 | ] 365 | 366 | [[package]] 367 | name = "termcolor" 368 | version = "1.1.2" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" 371 | dependencies = [ 372 | "winapi-util", 373 | ] 374 | 375 | [[package]] 376 | name = "textwrap" 377 | version = "0.11.0" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 380 | dependencies = [ 381 | "unicode-width", 382 | ] 383 | 384 | [[package]] 385 | name = "unicode-width" 386 | version = "0.1.9" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" 389 | 390 | [[package]] 391 | name = "unicode-xid" 392 | version = "0.2.2" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 395 | 396 | [[package]] 397 | name = "vec_map" 398 | version = "0.8.2" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 401 | 402 | [[package]] 403 | name = "winapi" 404 | version = "0.3.9" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 407 | dependencies = [ 408 | "winapi-i686-pc-windows-gnu", 409 | "winapi-x86_64-pc-windows-gnu", 410 | ] 411 | 412 | [[package]] 413 | name = "winapi-i686-pc-windows-gnu" 414 | version = "0.4.0" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 417 | 418 | [[package]] 419 | name = "winapi-util" 420 | version = "0.1.5" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 423 | dependencies = [ 424 | "winapi", 425 | ] 426 | 427 | [[package]] 428 | name = "winapi-x86_64-pc-windows-gnu" 429 | version = "0.4.0" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 432 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "racer" 3 | version = "2.2.2" 4 | license = "MIT" 5 | description = "Code completion for Rust" 6 | authors = ["Phil Dawes ", "The Racer developers"] 7 | homepage = "https://github.com/racer-rust/racer" 8 | repository = "https://github.com/racer-rust/racer" 9 | edition = "2018" 10 | 11 | [lib] 12 | name = "racer" 13 | path = "src/racer/lib.rs" 14 | 15 | [[bin]] 16 | name = "racer" 17 | path = "src/bin/main.rs" 18 | doc = false 19 | required-features = ["metadata"] 20 | 21 | [profile.release] 22 | debug = false # because of #1005 23 | 24 | [dependencies] 25 | bitflags = "1.0" 26 | log = "0.4" 27 | env_logger = "0.7.1" 28 | clap = "2.32" 29 | lazy_static = "1.2" 30 | humantime = "2.0.0" 31 | derive_more = "0.99.2" 32 | rls-span = "0.5.1" 33 | lazycell = { version = "1.2", optional = true } 34 | 35 | [dependencies.racer-cargo-metadata] 36 | version = "0.1" 37 | optional = true 38 | path = "metadata" 39 | 40 | [dev-dependencies.racer-testutils] 41 | version = "0.1" 42 | path = "testutils" 43 | 44 | 45 | [features] 46 | default = ["metadata"] 47 | metadata = ["lazycell", "racer-cargo-metadata"] 48 | nightly = [] 49 | 50 | [workspace] 51 | members = ["interner", "metadata", "testutils"] 52 | 53 | [package.metadata.rust-analyzer] 54 | # This package uses #[feature(rustc_private)] 55 | rustc_private = true 56 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Phil Dawes 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # *Racer* - code completion for [Rust](https://www.rust-lang.org/) 2 | 3 | [![Build Status](https://github.com/racer-rust/racer/workflows/CI/badge.svg?branch=master)](https://github.com/racer-rust/racer/actions?query=workflow%3ACI+branch%3Amaster) 4 | 5 | 6 | ![racer completion screenshot](images/racer_completion.png) 7 | 8 | ![racer eldoc screenshot](images/racer_eldoc.png) 9 | 10 | *RACER* = *R*ust *A*uto-*C*omplete-*er*. A utility intended to provide Rust code completion for editors and IDEs. Maybe one day the 'er' bit will be exploring + refactoring or something. 11 | 12 | ## **DISCLAIMER** 13 | Racer is **not** actively developped now. 14 | Please consider using newer software such as 15 | [rust-analyzer](https://rust-analyzer.github.io/). 16 | 17 | ## Installation 18 | 19 | **NOTE** 20 | From 2.1, racer needs **nightly rust** 21 | 22 | ### Requirements 23 | 24 | #### Current nightly Rust 25 | 26 | If you're using rustup, run 27 | ``` 28 | rustup toolchain install nightly 29 | rustup component add rustc-dev --toolchain=nightly 30 | ``` 31 | 32 | _Note: The second command adds the `rustc-dev` component to the nightly 33 | toolchain, which is necessary to compile Racer._ 34 | 35 | #### Cargo 36 | Internally, racer calls cargo as a CLI tool, so please make sure cargo is installed 37 | 38 | ### With `cargo install` 39 | 40 | Simply run: 41 | 42 | ```cargo +nightly install racer``` 43 | 44 | As mentioned in the command output, don't forget to add the installation directory to your `PATH`. 45 | 46 | ### From sources 47 | 48 | 1. Clone the repository: ```git clone https://github.com/racer-rust/racer.git``` 49 | 50 | 2. ```cd racer; cargo +nightly build --release```. The binary will now be in ```./target/release/racer``` 51 | 52 | 3. Add the binary to your `PATH`. This can be done by moving it to a directory already in your `PATH` (i.e. `/usr/local/bin`) or by adding the `./target/release/` directory to your `PATH` 53 | 54 | ## Configuration 55 | 56 | 1. Fetch the Rust sourcecode 57 | 58 | 1. automatically via [rustup](https://www.rustup.rs/) and run `rustup component add rust-src` in order to install the source to `$(rustc --print sysroot)/lib/rustlib/src/rust/library` (or `$(rustc --print sysroot)/lib/rustlib/src/rust/src` in older toolchains). Rustup will keep the sources in sync with the toolchain if you run `rustup update`. 59 | 60 | 2. manually from git: https://github.com/rust-lang/rust 61 | 62 | **Note** 63 | 64 | If you want to use `racer` with multiple release channels (Rust has 3 release channels: `stable`, `beta` and `nightly`), you have to also download Rust source code for each release channel you install. 65 | 66 | e.g. (rustup case) Add a nightly toolchain build and install nightly sources too 67 | 68 | `rustup toolchain add nightly` 69 | 70 | `rustup component add rust-src` 71 | 72 | 2. (Optional) Set `RUST_SRC_PATH` environment variable to point to the 'src' dir in the Rust source installation 73 | e.g. `% export RUST_SRC_PATH=$(rustc --print sysroot)/lib/rustlib/src/rust/library` or `% export RUST_SRC_PATH="$(rustc --print sysroot)/lib/rustlib/src/rust/src"` (older) 74 | 75 | It's recommended to set `RUST_SRC_PATH` for speed up, but racer detects it automatically if you don't set it. 76 | 77 | 3. Test on the command line: 78 | 79 | `racer complete std::io::B ` (should show some completions) 80 | 81 | **Note** 82 | 83 | To complete names in external crates, Racer needs `Cargo.lock`. 84 | So, when you add a dependency in your `Cargo.toml`, you have to run a build command 85 | such as `cargo build` or `cargo test`, to get completions. 86 | 87 | ## Editors/IDEs Supported 88 | 89 | ### RLS 90 | 91 | Racer is used as a static library in [RLS](https://github.com/rust-lang-nursery/rls) 92 | 93 | ### Eclipse integration 94 | 95 | Racer can be used with Eclipse through the use of [RustDT](https://github.com/RustDT/RustDT). (User guide is [linked](https://rustdt.github.io/) in repo description) 96 | 97 | ### Emacs integration 98 | 99 | Emacs integration has been moved to a separate project: [emacs-racer](https://github.com/racer-rust/emacs-racer). 100 | 101 | ### Gedit integration 102 | 103 | Gedit integration can be found [here](https://github.com/isamert/gracer). 104 | 105 | ### Builder integration 106 | 107 | Gnome Builder integration can be found [here](https://github.com/deikatsuo/bracer) 108 | 109 | ### Kate integration 110 | 111 | The Kate community maintains a [plugin](https://cgit.kde.org/kate.git/tree/addons/rustcompletion). It is bundled with recent releases of Kate (tested with 16.08 - read more [here](https://blogs.kde.org/2015/05/22/updates-kates-rust-plugin-syntax-highlighting-and-rust-source-mime-type)). 112 | 113 | 1. Enable 'Rust code completion' in the plugin list in the Kate config dialog; 114 | 115 | 2. On the new 'Rust code completion' dialog page, make sure 'Racer command' and 'Rust source tree location' are set correctly. 116 | 117 | ### Sublime Text integration 118 | 119 | The Sublime Text community maintains some packages that integrates Racer 120 | * [RustAutoComplete](https://github.com/defuz/RustAutoComplete) that offers auto completion and goto definition. 121 | * [AnacondaRUST](https://github.com/DamnWidget/anaconda_rust) from the [anaconda](https://github.com/DamnWidget/anaconda) plugins family that offers auto completion, goto definition and show documentation 122 | 123 | ### Vim integration 124 | 125 | Vim integration has been moved to a separate project: [vim-racer](https://github.com/racer-rust/vim-racer). 126 | 127 | ### Visual Studio Code extension 128 | 129 | Racer recommends the official [`Rust (rls)` extension](https://github.com/rust-lang-nursery/rls-vscode) based on RLS, which uses Racer for completion. 130 | 131 | ### Atom integration 132 | 133 | You can find the racer package for Atom [here](https://atom.io/packages/autocomplete-racer) 134 | 135 | ### Kakoune integration 136 | 137 | [Kakoune](https://github.com/mawww/kakoune) comes with a builtin integration for racer auto completion. 138 | -------------------------------------------------------------------------------- /benches/bench_extern.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | extern crate test; 4 | use test::Bencher; 5 | 6 | use racer_testutils::*; 7 | 8 | #[bench] 9 | fn completes_rand(b: &mut Bencher) { 10 | let src = " 11 | extern crate rand; 12 | use rand::{Rng, thread_rng}; 13 | fn main() { 14 | let mut rng: Box = Box::new(thread_rng()); 15 | rng.gen_rang~ 16 | } 17 | "; 18 | let mut match_ = None; 19 | b.iter(|| { 20 | with_test_project(|dir| { 21 | let src_dir = dir.nested_dir("test-crate3").nested_dir("src"); 22 | match_ = Some(get_only_completion(src, Some(src_dir))); 23 | }); 24 | }) 25 | } 26 | 27 | #[bench] 28 | fn completes_rayon(b: &mut Bencher) { 29 | let src = " 30 | extern crate rayon; 31 | extern crate rand; 32 | use rand::{Rng, thread_rng}; 33 | fn main() { 34 | rayon::colle~ 35 | } 36 | "; 37 | let mut match_ = None; 38 | b.iter(|| { 39 | with_test_project(|dir| { 40 | let src_dir = dir.nested_dir("test-crate3").nested_dir("src"); 41 | match_ = Some(get_only_completion(src, Some(src_dir))); 42 | }); 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /benches/bench_globs.rs: -------------------------------------------------------------------------------- 1 | //! for #844 2 | #![feature(test)] 3 | 4 | extern crate test; 5 | use test::Bencher; 6 | 7 | use racer_testutils::*; 8 | 9 | #[bench] 10 | fn glob_imports5(b: &mut Bencher) { 11 | let src = r" 12 | use a::*; 13 | use b::*; 14 | use c::*; 15 | use d::*; 16 | use e::*; 17 | pub fn foo() -> () { 18 | Whatever::~ 19 | } 20 | "; 21 | let mut var = vec![]; 22 | b.iter(|| { 23 | var = get_all_completions(src, None); 24 | }) 25 | } 26 | 27 | #[bench] 28 | fn glob_imports6(b: &mut Bencher) { 29 | let src = r" 30 | use a::*; 31 | use b::*; 32 | use c::*; 33 | use d::*; 34 | use e::*; 35 | use f::*; 36 | pub fn foo() -> () { 37 | Whatever::~ 38 | } 39 | "; 40 | let mut var = vec![]; 41 | b.iter(|| { 42 | var = get_all_completions(src, None); 43 | }) 44 | } 45 | 46 | #[bench] 47 | fn glob_imports7(b: &mut Bencher) { 48 | let src = r" 49 | use a::*; 50 | use b::*; 51 | use c::*; 52 | use d::*; 53 | use e::*; 54 | use f::*; 55 | use g::*; 56 | pub fn foo() -> () { 57 | Whatever::~ 58 | } 59 | "; 60 | let mut var = vec![]; 61 | b.iter(|| { 62 | var = get_all_completions(src, None); 63 | }) 64 | } 65 | -------------------------------------------------------------------------------- /benches/bench_stdlibs.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | extern crate test; 4 | use test::Bencher; 5 | 6 | use racer_testutils::*; 7 | 8 | #[bench] 9 | fn completes_hashmap(b: &mut Bencher) { 10 | let src = r" 11 | use std::collections::HashM~ 12 | "; 13 | let mut var = vec![]; 14 | b.iter(|| { 15 | var = get_all_completions(src, None); 16 | }) 17 | } 18 | 19 | #[bench] 20 | fn completes_methods_for_vec(b: &mut Bencher) { 21 | let src = r" 22 | let vec = Vec::new(); 23 | let a = vec.~ 24 | "; 25 | let mut var = vec![]; 26 | b.iter(|| { 27 | var = get_all_completions(src, None); 28 | }) 29 | } 30 | 31 | #[bench] 32 | fn completes_methods_for_file_open(b: &mut Bencher) { 33 | let src = r#" 34 | use std::io; 35 | use std::io::prelude::*; 36 | use std::fs::File; 37 | let mut f = File::open("no-file-here"): 38 | f.~ 39 | "#; 40 | let mut var = vec![]; 41 | b.iter(|| { 42 | var = get_all_completions(src, None); 43 | }) 44 | } 45 | 46 | #[bench] 47 | fn follows_std(b: &mut Bencher) { 48 | let src = r#" 49 | use std::~ 50 | "#; 51 | let mut var = vec![]; 52 | b.iter(|| { 53 | var = get_all_completions(src, None); 54 | }) 55 | } 56 | 57 | #[bench] 58 | fn follows_collections(b: &mut Bencher) { 59 | let src = r#" 60 | use std::collections::~ 61 | "#; 62 | let mut var = vec![]; 63 | b.iter(|| { 64 | var = get_all_completions(src, None); 65 | }) 66 | } 67 | -------------------------------------------------------------------------------- /fixtures/.cargo/config: -------------------------------------------------------------------------------- 1 | paths = ["./arst"] 2 | -------------------------------------------------------------------------------- /fixtures/arst/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "arst" 3 | version = "0.1.0" 4 | authors = ["Joe Wilm "] 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /fixtures/arst/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | #[test] 4 | fn it_works() { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /fixtures/arst/src/submodule/mod.rs: -------------------------------------------------------------------------------- 1 | pub fn hello_submodule() { 2 | println!("Hello from submodule."); 3 | } -------------------------------------------------------------------------------- /images/racer_completion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/racer-rust/racer/a457c235b545c5251570cda8fec1edeab05ab1d4/images/racer_completion.png -------------------------------------------------------------------------------- /images/racer_eldoc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/racer-rust/racer/a457c235b545c5251570cda8fec1edeab05ab1d4/images/racer_eldoc.png -------------------------------------------------------------------------------- /interner/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "racer-interner" 3 | version = "0.1.0" 4 | authors = ["Yuji Kanagawa "] 5 | license = "MIT" 6 | description = "thread-local string interner for racer-rust" 7 | homepage = "https://github.com/racer-rust/racer" 8 | repository = "https://github.com/racer-rust/racer" 9 | edition = "2018" 10 | workspace = ".." 11 | 12 | [dependencies] 13 | serde = "1.0" 14 | -------------------------------------------------------------------------------- /interner/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! string interner 2 | //! same as cargo::core::interning.rs, but thread local and Deserializable 3 | 4 | use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; 5 | 6 | use std::cell::RefCell; 7 | use std::collections::HashSet; 8 | use std::error::Error; 9 | use std::fmt; 10 | use std::ops::Deref; 11 | use std::ptr; 12 | use std::str; 13 | 14 | fn leak(s: String) -> &'static str { 15 | Box::leak(s.into_boxed_str()) 16 | } 17 | 18 | thread_local! { 19 | static STRING_CACHE: RefCell> = Default::default(); 20 | } 21 | 22 | #[derive(Clone, Copy, PartialOrd, Ord, Eq, Hash)] 23 | pub struct InternedString { 24 | inner: &'static str, 25 | } 26 | 27 | impl PartialEq for InternedString { 28 | fn eq(&self, other: &InternedString) -> bool { 29 | ptr::eq(self.as_str(), other.as_str()) 30 | } 31 | } 32 | 33 | impl InternedString { 34 | pub fn new(st: &str) -> InternedString { 35 | STRING_CACHE.with(|cache| { 36 | let mut cache = cache.borrow_mut(); 37 | let s = cache.get(st).map(|&s| s).unwrap_or_else(|| { 38 | let s = leak(st.to_string()); 39 | cache.insert(s); 40 | s 41 | }); 42 | InternedString { inner: s } 43 | }) 44 | } 45 | 46 | pub fn new_if_exists(st: &str) -> Option { 47 | STRING_CACHE.with(|cache| cache.borrow().get(st).map(|&s| InternedString { inner: s })) 48 | } 49 | 50 | pub fn as_str(&self) -> &'static str { 51 | self.inner 52 | } 53 | } 54 | 55 | impl Deref for InternedString { 56 | type Target = str; 57 | fn deref(&self) -> &'static str { 58 | self.as_str() 59 | } 60 | } 61 | 62 | impl fmt::Debug for InternedString { 63 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 64 | fmt::Debug::fmt(self.as_str(), f) 65 | } 66 | } 67 | 68 | impl fmt::Display for InternedString { 69 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 70 | fmt::Display::fmt(self.as_str(), f) 71 | } 72 | } 73 | 74 | impl Serialize for InternedString { 75 | fn serialize(&self, serializer: S) -> Result 76 | where 77 | S: Serializer, 78 | { 79 | serializer.serialize_str(self.inner) 80 | } 81 | } 82 | 83 | impl<'de> Deserialize<'de> for InternedString { 84 | fn deserialize(deserializer: D) -> Result 85 | where 86 | D: Deserializer<'de>, 87 | { 88 | struct VisStr; 89 | impl<'de> Visitor<'de> for VisStr { 90 | type Value = InternedString; 91 | fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 92 | write!(f, "expecting string") 93 | } 94 | fn visit_borrowed_str(self, v: &'de str) -> Result { 95 | Ok(InternedString::new(v)) 96 | } 97 | } 98 | deserializer.deserialize_str(VisStr {}) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /metadata/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "racer-cargo-metadata" 3 | version = "0.1.2" 4 | authors = ["Yuji Kanagawa "] 5 | license = "MIT" 6 | description = "light-weight cargo metadata parser for racer" 7 | homepage = "https://github.com/racer-rust/racer" 8 | repository = "https://github.com/racer-rust/racer" 9 | edition = "2018" 10 | workspace = ".." 11 | 12 | [dependencies] 13 | serde_json = "1.0" 14 | 15 | [dependencies.serde] 16 | version = "1.0" 17 | features = ["derive"] 18 | 19 | [dependencies.racer-interner] 20 | version = "0.1" 21 | path = "../interner" 22 | 23 | -------------------------------------------------------------------------------- /metadata/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate racer_interner; 2 | #[macro_use] 3 | extern crate serde; 4 | extern crate serde_json; 5 | 6 | pub mod mapping; 7 | pub mod metadata; 8 | 9 | use crate::metadata::Metadata; 10 | use std::env; 11 | use std::error::Error; 12 | use std::fmt; 13 | use std::io; 14 | use std::path::{Path, PathBuf}; 15 | use std::process::Command; 16 | use std::str::Utf8Error; 17 | 18 | #[derive(Debug)] 19 | pub enum ErrorKind { 20 | Encode(Utf8Error), 21 | Json(serde_json::Error), 22 | Io(io::Error), 23 | Subprocess(String), 24 | } 25 | 26 | impl fmt::Display for ErrorKind { 27 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 28 | match self { 29 | ErrorKind::Encode(e) => fmt::Display::fmt(e, f), 30 | ErrorKind::Json(e) => fmt::Display::fmt(e, f), 31 | ErrorKind::Io(e) => fmt::Display::fmt(e, f), 32 | ErrorKind::Subprocess(s) => write!(f, "stderr: {}", s), 33 | } 34 | } 35 | } 36 | 37 | impl Error for ErrorKind {} 38 | 39 | impl From for ErrorKind { 40 | fn from(e: Utf8Error) -> ErrorKind { 41 | ErrorKind::Encode(e) 42 | } 43 | } 44 | 45 | impl From for ErrorKind { 46 | fn from(e: serde_json::Error) -> ErrorKind { 47 | ErrorKind::Json(e) 48 | } 49 | } 50 | 51 | impl From for ErrorKind { 52 | fn from(e: io::Error) -> ErrorKind { 53 | ErrorKind::Io(e) 54 | } 55 | } 56 | 57 | pub fn find_manifest(mut current: &Path) -> Option { 58 | let file = "Cargo.toml"; 59 | if current.is_dir() { 60 | let manifest = current.join(file); 61 | if manifest.exists() { 62 | return Some(manifest); 63 | } 64 | } 65 | while let Some(parent) = current.parent() { 66 | let manifest = parent.join(file); 67 | if manifest.exists() { 68 | return Some(manifest); 69 | } 70 | current = parent; 71 | } 72 | None 73 | } 74 | 75 | pub fn run(manifest_path: &Path, frozen: bool) -> Result { 76 | let cargo = env::var("CARGO").unwrap_or_else(|_| "cargo".to_owned()); 77 | let mut cmd = Command::new(cargo); 78 | cmd.arg("metadata"); 79 | cmd.arg("--all-features"); 80 | cmd.args(&["--format-version", "1"]); 81 | cmd.args(&["--color", "never"]); 82 | cmd.arg("--manifest-path"); 83 | cmd.arg(manifest_path.as_os_str()); 84 | if frozen { 85 | cmd.arg("--frozen"); 86 | } 87 | let op = cmd.output()?; 88 | if !op.status.success() { 89 | let stderr = String::from_utf8(op.stderr).map_err(|e| e.utf8_error())?; 90 | return Err(ErrorKind::Subprocess(stderr)); 91 | } 92 | serde_json::from_slice(&op.stdout).map_err(From::from) 93 | } 94 | -------------------------------------------------------------------------------- /metadata/src/mapping.rs: -------------------------------------------------------------------------------- 1 | use crate::metadata::{Metadata, Package, PackageId, Resolve, ResolveNode, Target}; 2 | use racer_interner::InternedString; 3 | use std::collections::HashMap; 4 | use std::path::{Path, PathBuf}; 5 | 6 | /// Cached dependencies for racer 7 | #[derive(Clone, Debug)] 8 | pub struct PackageMap { 9 | manifest_to_idx: HashMap, 10 | id_to_idx: HashMap, 11 | packages: Vec, 12 | } 13 | 14 | #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)] 15 | pub enum Edition { 16 | Ed2015, 17 | Ed2018, 18 | Ed2021, 19 | } 20 | 21 | impl Edition { 22 | pub fn from_str(s: &str) -> Self { 23 | match s { 24 | "2015" => Edition::Ed2015, 25 | "2018" => Edition::Ed2018, 26 | "2021" => Edition::Ed2021, 27 | _ => unreachable!("got unexpected edition {}", s), 28 | } 29 | } 30 | } 31 | 32 | #[derive(Clone, Debug)] 33 | struct PackageInner { 34 | edition: Edition, 35 | deps: Vec<(InternedString, PathBuf)>, 36 | lib: Option, 37 | id: PackageId, 38 | } 39 | 40 | impl PackageInner { 41 | fn new(ed: InternedString, id: PackageId, lib: Option) -> Self { 42 | PackageInner { 43 | edition: Edition::from_str(ed.as_str()), 44 | deps: Vec::new(), 45 | id, 46 | lib, 47 | } 48 | } 49 | } 50 | 51 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 52 | pub struct PackageIdx(usize); 53 | 54 | impl PackageMap { 55 | pub fn from_metadata(meta: Metadata) -> Self { 56 | let Metadata { 57 | packages, resolve, .. 58 | } = meta; 59 | PackageMap::new(packages, resolve) 60 | } 61 | pub fn new(packages: Vec, resolve: Option) -> Self { 62 | let mut manifest_to_idx = HashMap::new(); 63 | let mut id_to_idx = HashMap::new(); 64 | let mut inner = Vec::new(); 65 | for (i, package) in packages.into_iter().enumerate() { 66 | let Package { 67 | id, 68 | targets, 69 | manifest_path, 70 | edition, 71 | .. 72 | } = package; 73 | id_to_idx.insert(id, PackageIdx(i)); 74 | manifest_to_idx.insert(manifest_path, PackageIdx(i)); 75 | let lib = targets.into_iter().find(|t| t.is_lib()).to_owned(); 76 | inner.push(PackageInner::new(edition, id, lib)); 77 | } 78 | if let Some(res) = resolve { 79 | construct_deps(res.nodes, &id_to_idx, &mut inner); 80 | } 81 | PackageMap { 82 | manifest_to_idx, 83 | id_to_idx, 84 | packages: inner, 85 | } 86 | } 87 | pub fn ids<'a>(&'a self) -> impl 'a + Iterator { 88 | self.packages.iter().map(|p| p.id) 89 | } 90 | pub fn id_to_idx(&self, id: PackageId) -> Option { 91 | self.id_to_idx.get(&id).map(|&x| x) 92 | } 93 | pub fn get_idx(&self, path: &Path) -> Option { 94 | self.manifest_to_idx.get(path).map(|&id| id) 95 | } 96 | pub fn get_id(&self, idx: PackageIdx) -> PackageId { 97 | self.packages[idx.0].id 98 | } 99 | pub fn get_edition(&self, idx: PackageIdx) -> Edition { 100 | self.packages[idx.0].edition 101 | } 102 | pub fn get_lib(&self, idx: PackageIdx) -> Option<&Target> { 103 | self.packages[idx.0].lib.as_ref() 104 | } 105 | pub fn get_lib_src_path(&self, idx: PackageIdx) -> Option<&Path> { 106 | self.get_lib(idx).map(|t| t.src_path.as_ref()) 107 | } 108 | pub fn get_dependencies(&self, idx: PackageIdx) -> &[(InternedString, PathBuf)] { 109 | self.packages[idx.0].deps.as_ref() 110 | } 111 | pub fn get_src_path_from_libname(&self, id: PackageIdx, s: &str) -> Option<&Path> { 112 | let deps = self.get_dependencies(id); 113 | let query_str = InternedString::new_if_exists(s)?; 114 | deps.iter().find(|t| t.0 == query_str).map(|t| t.1.as_ref()) 115 | } 116 | } 117 | 118 | fn construct_deps( 119 | nodes: Vec, 120 | id_to_idx: &HashMap, 121 | res: &mut [PackageInner], 122 | ) -> Option<()> { 123 | for node in nodes { 124 | let idx = id_to_idx.get(&node.id)?; 125 | let deps: Vec<_> = node 126 | .dependencies 127 | .into_iter() 128 | .filter_map(|id| { 129 | let idx = id_to_idx.get(&id)?; 130 | res[idx.0] 131 | .lib 132 | .as_ref() 133 | .map(|l| (l.name, l.src_path.clone())) 134 | }) 135 | .collect(); 136 | res[idx.0].deps.extend(deps); 137 | } 138 | Some(()) 139 | } 140 | -------------------------------------------------------------------------------- /metadata/src/metadata.rs: -------------------------------------------------------------------------------- 1 | //! Data structures for metadata 2 | use racer_interner::InternedString; 3 | use std::path::PathBuf; 4 | 5 | #[derive(Clone, Debug, Serialize, Deserialize)] 6 | pub struct Metadata { 7 | pub packages: Vec, 8 | pub workspace_members: Vec, 9 | pub resolve: Option, 10 | #[serde(default)] 11 | pub workspace_root: PathBuf, 12 | pub target_directory: PathBuf, 13 | version: usize, 14 | #[serde(skip)] 15 | __guard: (), 16 | } 17 | 18 | #[derive(Clone, Debug, Serialize, Deserialize)] 19 | pub struct Package { 20 | pub id: PackageId, 21 | pub targets: Vec, 22 | pub manifest_path: PathBuf, 23 | #[serde(default = "edition_default")] 24 | pub edition: InternedString, 25 | #[serde(skip)] 26 | __guard: (), 27 | } 28 | 29 | #[derive(Clone, Debug, Serialize, Deserialize)] 30 | pub struct Resolve { 31 | pub nodes: Vec, 32 | #[serde(skip)] 33 | __guard: (), 34 | } 35 | 36 | #[derive(Clone, Debug, Serialize, Deserialize)] 37 | pub struct ResolveNode { 38 | pub id: PackageId, 39 | pub dependencies: Vec, 40 | } 41 | 42 | #[derive(Clone, Debug, Serialize, Deserialize)] 43 | pub struct Target { 44 | pub name: InternedString, 45 | pub kind: Vec, 46 | pub src_path: PathBuf, 47 | #[serde(default = "edition_default")] 48 | pub edition: InternedString, 49 | #[serde(skip)] 50 | __guard: (), 51 | } 52 | 53 | const LIB_KINDS: [&'static str; 4] = ["lib", "rlib", "dylib", "proc-macro"]; 54 | 55 | impl Target { 56 | pub fn is_lib(&self) -> bool { 57 | self.kind.iter().any(|k| LIB_KINDS.contains(&k.as_str())) 58 | } 59 | pub fn is_2015(&self) -> bool { 60 | self.edition.as_str() == "2015" 61 | } 62 | } 63 | 64 | #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] 65 | pub struct PackageId(InternedString); 66 | 67 | impl PackageId { 68 | pub fn name(&self) -> &str { 69 | let idx = self.0.find(' ').expect("Whitespace not found"); 70 | &self.0[..idx] 71 | } 72 | } 73 | 74 | #[inline(always)] 75 | fn edition_default() -> InternedString { 76 | InternedString::new("2015") 77 | } 78 | -------------------------------------------------------------------------------- /metadata/test-data-no-deps.json: -------------------------------------------------------------------------------- 1 | {"packages":[{"name":"test-crate2","version":"0.1.0","id":"test-crate2 0.1.0 (path+file:///home/yu/Programs/no-rustfmt/racer-nightly/test_project/test-crate2)","license":null,"license_file":null,"description":null,"source":null,"dependencies":[],"targets":[{"kind":["lib"],"crate_types":["lib"],"name":"test-crate2","src_path":"/home/yu/Programs/no-rustfmt/racer-nightly/test_project/test-crate2/src/lib.rs","edition":"2015"}],"features":{},"manifest_path":"/home/yu/Programs/no-rustfmt/racer-nightly/test_project/test-crate2/Cargo.toml","metadata":null,"authors":["kngwyu "],"categories":[],"keywords":[],"readme":null,"repository":null,"edition":"2015"},{"name":"test-crate3","version":"0.1.0","id":"test-crate3 0.1.0 (path+file:///home/yu/Programs/no-rustfmt/racer-nightly/test_project/test-crate3)","license":null,"license_file":null,"description":"test crate for extern crate~","source":null,"dependencies":[{"name":"rand","source":"registry+https://github.com/rust-lang/crates.io-index","req":"^0.5","kind":null,"rename":null,"optional":false,"uses_default_features":true,"features":[],"target":null},{"name":"regex","source":"registry+https://github.com/rust-lang/crates.io-index","req":"^1.0","kind":null,"rename":null,"optional":false,"uses_default_features":true,"features":[],"target":null},{"name":"serde","source":"registry+https://github.com/rust-lang/crates.io-index","req":"^1.0","kind":null,"rename":null,"optional":false,"uses_default_features":true,"features":[],"target":null}],"targets":[{"kind":["bin"],"crate_types":["bin"],"name":"test-crate3","src_path":"/home/yu/Programs/no-rustfmt/racer-nightly/test_project/test-crate3/src/main.rs","edition":"2015"}],"features":{},"manifest_path":"/home/yu/Programs/no-rustfmt/racer-nightly/test_project/test-crate3/Cargo.toml","metadata":null,"authors":["kngwyu "],"categories":[],"keywords":[],"readme":null,"repository":null,"edition":"2015"},{"name":"test_fixtures","version":"0.1.0","id":"test_fixtures 0.1.0 (path+file:///home/yu/Programs/no-rustfmt/racer-nightly/test_project/test_fixtures)","license":null,"license_file":null,"description":null,"source":null,"dependencies":[{"name":"test-crate2","source":null,"req":"*","kind":null,"rename":null,"optional":false,"uses_default_features":true,"features":[],"target":null}],"targets":[{"kind":["lib"],"crate_types":["lib"],"name":"fixtures","src_path":"/home/yu/Programs/no-rustfmt/racer-nightly/test_project/test_fixtures/src/lib.rs","edition":"2015"}],"features":{},"manifest_path":"/home/yu/Programs/no-rustfmt/racer-nightly/test_project/test_fixtures/Cargo.toml","metadata":null,"authors":["jaxx"],"categories":[],"keywords":[],"readme":null,"repository":null,"edition":"2015"},{"name":"test_project","version":"0.1.0","id":"test_project 0.1.0 (path+file:///home/yu/Programs/no-rustfmt/racer-nightly/test_project)","license":null,"license_file":null,"description":"crate for testing racer's functionalities","source":null,"dependencies":[{"name":"test-crate2","source":null,"req":"*","kind":null,"rename":null,"optional":false,"uses_default_features":true,"features":[],"target":null},{"name":"test_fixtures","source":null,"req":"*","kind":null,"rename":null,"optional":false,"uses_default_features":true,"features":[],"target":null}],"targets":[{"kind":["lib"],"crate_types":["lib"],"name":"test_project","src_path":"/home/yu/Programs/no-rustfmt/racer-nightly/test_project/src/lib.rs","edition":"2015"}],"features":{},"manifest_path":"/home/yu/Programs/no-rustfmt/racer-nightly/test_project/Cargo.toml","metadata":null,"authors":["Joe Wilm "],"categories":[],"keywords":[],"readme":null,"repository":null,"edition":"2015"}],"workspace_members":["test-crate2 0.1.0 (path+file:///home/yu/Programs/no-rustfmt/racer-nightly/test_project/test-crate2)","test-crate3 0.1.0 (path+file:///home/yu/Programs/no-rustfmt/racer-nightly/test_project/test-crate3)","test_fixtures 0.1.0 (path+file:///home/yu/Programs/no-rustfmt/racer-nightly/test_project/test_fixtures)","test_project 0.1.0 (path+file:///home/yu/Programs/no-rustfmt/racer-nightly/test_project)"],"resolve":null,"target_directory":"/home/yu/Programs/no-rustfmt/racer-nightly/test_project/target","version":1,"workspace_root":"/home/yu/Programs/no-rustfmt/racer-nightly/test_project"} 2 | -------------------------------------------------------------------------------- /metadata/tests/from_json.rs: -------------------------------------------------------------------------------- 1 | extern crate racer_cargo_metadata; 2 | extern crate serde_json; 3 | use racer_cargo_metadata::{mapping::PackageMap, metadata::Metadata}; 4 | use std::collections::BTreeSet; 5 | use std::fs::File; 6 | use std::io::prelude::*; 7 | 8 | #[test] 9 | fn full() { 10 | let mut file = File::open("test-data-full.json").unwrap(); 11 | let mut buf = String::new(); 12 | file.read_to_string(&mut buf).unwrap(); 13 | let meta: Metadata = serde_json::from_str(&buf).unwrap(); 14 | assert!(meta.resolve.is_some()); 15 | let pkg_map = PackageMap::from_metadata(meta); 16 | let regex = pkg_map.ids().find(|id| id.name() == "regex").unwrap(); 17 | assert!(pkg_map 18 | .get_src_path_from_libname(pkg_map.id_to_idx(regex).unwrap(), "memchr") 19 | .is_some()); 20 | } 21 | 22 | #[test] 23 | fn no_deps() { 24 | let mut file = File::open("test-data-no-deps.json").unwrap(); 25 | let mut buf = String::new(); 26 | file.read_to_string(&mut buf).unwrap(); 27 | let meta: Metadata = serde_json::from_str(&buf).unwrap(); 28 | let packages: BTreeSet<_> = meta 29 | .packages 30 | .iter() 31 | .map(|p| p.id.name().to_owned()) 32 | .collect(); 33 | assert_eq!( 34 | packages, 35 | meta.workspace_members 36 | .iter() 37 | .map(|p| p.name().to_string()) 38 | .collect() 39 | ); 40 | assert!(meta.resolve.is_none()); 41 | } 42 | -------------------------------------------------------------------------------- /metadata/tests/manifest.rs: -------------------------------------------------------------------------------- 1 | extern crate racer_cargo_metadata; 2 | 3 | use racer_cargo_metadata::find_manifest; 4 | use std::path::PathBuf; 5 | 6 | fn manifest_dir() -> PathBuf { 7 | PathBuf::from(env!("CARGO_MANIFEST_DIR")) 8 | } 9 | 10 | #[test] 11 | fn exact() { 12 | let path = manifest_dir().join("Cargo.toml"); 13 | assert_eq!(find_manifest(&path).unwrap(), path); 14 | } 15 | 16 | #[test] 17 | fn from_src() { 18 | let path = manifest_dir().join("src").join("lib.rs"); 19 | assert_eq!( 20 | find_manifest(&path).unwrap(), 21 | manifest_dir().join("Cargo.toml") 22 | ); 23 | } 24 | 25 | #[test] 26 | fn from_dir() { 27 | let path = manifest_dir().join("src"); 28 | assert_eq!( 29 | find_manifest(&path).unwrap(), 30 | manifest_dir().join("Cargo.toml") 31 | ); 32 | } 33 | 34 | #[test] 35 | fn same_dir() { 36 | let path = manifest_dir().join("foo.txt"); 37 | assert_eq!( 38 | find_manifest(&path).unwrap(), 39 | manifest_dir().join("Cargo.toml") 40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /metadata/tests/self_test.rs: -------------------------------------------------------------------------------- 1 | extern crate racer_cargo_metadata; 2 | use racer_cargo_metadata::mapping::PackageMap; 3 | 4 | use std::path::Path; 5 | 6 | #[test] 7 | fn get_self_metadata() { 8 | let manifest = 9 | racer_cargo_metadata::find_manifest(Path::new(env!("CARGO_MANIFEST_DIR"))).unwrap(); 10 | let meta = racer_cargo_metadata::run(&manifest, false).unwrap(); 11 | let pkg_map = PackageMap::from_metadata(meta); 12 | let racer_manifest = manifest 13 | .parent() 14 | .unwrap() 15 | .parent() 16 | .unwrap() 17 | .join("Cargo.toml"); 18 | let racer = pkg_map.get_idx(&racer_manifest).unwrap(); 19 | assert!(pkg_map.get_id(racer).name() == "racer"); 20 | assert!(pkg_map 21 | .get_src_path_from_libname(racer, "lazy_static") 22 | .is_some()); 23 | assert!(pkg_map 24 | .get_src_path_from_libname(racer, "im-not-a-crate") 25 | .is_none()); 26 | } 27 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly-2022-04-06" 3 | components = ["rust-src", "rustc-dev"] 4 | -------------------------------------------------------------------------------- /src/bin/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate log; 3 | use env_logger; 4 | #[macro_use] 5 | extern crate clap; 6 | 7 | use humantime; 8 | use racer; 9 | 10 | use clap::{App, AppSettings, Arg, ArgMatches, SubCommand}; 11 | use racer::{BytePos, Coordinate, FileCache, Match, MatchType, Session}; 12 | use std::fs::File; 13 | use std::io::{self, BufRead, Read}; 14 | use std::path::{Path, PathBuf}; 15 | use std::time::SystemTime; 16 | 17 | fn point(cfg: &Config) { 18 | let cache = FileCache::default(); 19 | let session = Session::new(&cache, None); 20 | cfg.interface.emit(Message::Coords(cfg.coords())); 21 | if let Some(point) = racer::to_point(cfg.coords(), cfg.expect_file(), &session) { 22 | cfg.interface.emit(Message::Point(point)); 23 | } 24 | cfg.interface.emit(Message::End); 25 | } 26 | 27 | fn coord(cfg: &Config) { 28 | let cache = FileCache::default(); 29 | let session = Session::new(&cache, None); 30 | cfg.interface.emit(Message::Point(cfg.point)); 31 | if let Some(coords) = racer::to_coords(cfg.point, cfg.expect_file(), &session) { 32 | cfg.interface.emit(Message::Coords(coords)); 33 | } 34 | cfg.interface.emit(Message::End); 35 | } 36 | 37 | fn match_with_snippet_fn(m: Match, session: &Session<'_>, interface: Interface) { 38 | let cd = m 39 | .coords 40 | .expect("[match_with_snipper_fn] failed to get coordinate"); 41 | if m.matchstr == "" { 42 | panic!("MATCHSTR is empty - waddup?"); 43 | } 44 | 45 | let snippet = racer::snippet_for_match(&m, session); 46 | interface.emit(Message::MatchWithSnippet( 47 | m.matchstr, 48 | snippet, 49 | cd, 50 | m.filepath.as_path(), 51 | m.mtype, 52 | m.contextstr, 53 | m.docs, 54 | )); 55 | } 56 | 57 | fn match_fn(m: Match, interface: Interface) { 58 | if let Some(coords) = m.coords { 59 | interface.emit(Message::Match( 60 | m.matchstr, 61 | coords, 62 | m.filepath.as_path(), 63 | m.mtype, 64 | m.contextstr, 65 | )); 66 | } else { 67 | error!("Could not resolve file coords for match {:?}", m); 68 | } 69 | } 70 | 71 | fn complete(cfg: Config, print_type: CompletePrinter) { 72 | if cfg.fqn.is_some() { 73 | return external_complete(&cfg, print_type); 74 | } 75 | complete_by_line_coords(cfg, print_type); 76 | } 77 | 78 | fn complete_by_line_coords(cfg: Config, print_type: CompletePrinter) { 79 | // input: linenum, colnum, fname 80 | let tb = std::thread::Builder::new().name("searcher".to_owned()); 81 | let interface = cfg.interface; 82 | 83 | // PD: this probably sucks for performance, but lots of plugins 84 | // end up failing and leaving tmp files around if racer crashes, 85 | // so catch the crash. 86 | let res = tb 87 | .spawn(move || { 88 | run_the_complete_fn(&cfg, print_type); 89 | }) 90 | .unwrap(); 91 | if let Err(e) = res.join() { 92 | error!("Search thread panicked: {:?}", e); 93 | } 94 | 95 | interface.emit(Message::End); 96 | } 97 | 98 | #[derive(Debug, Clone, Copy)] 99 | enum CompletePrinter { 100 | Normal, 101 | WithSnippets, 102 | } 103 | 104 | fn read_file_from_stdin() -> String { 105 | let mut rawbytes = Vec::new(); 106 | 107 | let stdin = io::stdin(); 108 | stdin 109 | .lock() 110 | .read_until(0x04, &mut rawbytes) 111 | .expect("read until EOT"); 112 | 113 | String::from_utf8(rawbytes).expect("utf8 from stdin") 114 | } 115 | 116 | fn read_file

(path: P) -> io::Result 117 | where 118 | P: AsRef, 119 | { 120 | let mut res = String::new(); 121 | let mut f = File::open(path)?; 122 | 123 | f.read_to_string(&mut res)?; 124 | Ok(res) 125 | } 126 | 127 | fn load_query_file(path: P, sub: S, session: &Session<'_>) 128 | where 129 | P: Into, 130 | S: AsRef, 131 | { 132 | let path = path.into(); 133 | let sub = sub.as_ref(); 134 | 135 | if sub.to_str() == Some("-") { 136 | let contents = read_file_from_stdin(); 137 | session.cache_file_contents(path, contents); 138 | } else if sub != path { 139 | let contents = read_file(sub).unwrap(); 140 | session.cache_file_contents(path, contents); 141 | } 142 | } 143 | 144 | fn run_the_complete_fn(cfg: &Config, print_type: CompletePrinter) { 145 | let fn_path = cfg.fn_name.as_ref().unwrap(); 146 | let substitute_file = cfg.substitute_file.as_ref().unwrap_or(fn_path); 147 | 148 | let cache = FileCache::default(); 149 | let session = Session::new(&cache, Some(fn_path)); 150 | 151 | load_query_file(fn_path, &substitute_file, &session); 152 | 153 | if let Some(expanded) = racer::expand_ident(&fn_path, cfg.coords(), &session) { 154 | cfg.interface.emit(Message::Prefix( 155 | expanded.start(), 156 | expanded.pos(), 157 | expanded.ident(), 158 | )); 159 | 160 | for m in racer::complete_from_file(&fn_path, cfg.coords(), &session) { 161 | match print_type { 162 | CompletePrinter::Normal => match_fn(m, cfg.interface), 163 | CompletePrinter::WithSnippets => match_with_snippet_fn(m, &session, cfg.interface), 164 | }; 165 | } 166 | } 167 | } 168 | 169 | /// Completes a fully qualified name specified on command line 170 | fn external_complete(cfg: &Config, print_type: CompletePrinter) { 171 | let cwd = Path::new("."); 172 | let cache = FileCache::default(); 173 | let session = Session::new(&cache, Some(cwd)); 174 | 175 | for m in racer::complete_fully_qualified_name(cfg.fqn.as_ref().unwrap(), &cwd, &session) { 176 | match print_type { 177 | CompletePrinter::Normal => match_fn(m, cfg.interface), 178 | CompletePrinter::WithSnippets => match_with_snippet_fn(m, &session, cfg.interface), 179 | } 180 | } 181 | } 182 | 183 | fn prefix(cfg: &Config) { 184 | let fn_path = cfg.fn_name.as_ref().unwrap(); 185 | let substitute_file = cfg.substitute_file.as_ref().unwrap_or(fn_path); 186 | let cache = FileCache::default(); 187 | let session = Session::new(&cache, Some(fn_path)); 188 | 189 | // Cache query file in session 190 | load_query_file(fn_path, &substitute_file, &session); 191 | 192 | // print the start, end, and the identifier prefix being matched 193 | let expanded = racer::expand_ident(fn_path, cfg.coords(), &session).unwrap(); 194 | cfg.interface.emit(Message::Prefix( 195 | expanded.start(), 196 | expanded.pos(), 197 | expanded.ident(), 198 | )); 199 | } 200 | 201 | fn find_definition(cfg: &Config) { 202 | let fn_path = cfg.fn_name.as_ref().unwrap(); 203 | let substitute_file = cfg.substitute_file.as_ref().unwrap_or(fn_path); 204 | let cache = FileCache::default(); 205 | let session = Session::new(&cache, Some(fn_path)); 206 | 207 | // Cache query file in session 208 | load_query_file(fn_path, &substitute_file, &session); 209 | 210 | if let Some(m) = racer::find_definition(fn_path, cfg.coords(), &session) { 211 | match_fn(m, cfg.interface); 212 | } 213 | cfg.interface.emit(Message::End); 214 | } 215 | 216 | fn validate_rust_src_path_env_var() { 217 | match racer::get_rust_src_path() { 218 | Ok(_) => (), 219 | Err(err) => { 220 | println!("{}", err); 221 | std::process::exit(1); 222 | } 223 | } 224 | } 225 | 226 | fn daemon(cfg: &Config) { 227 | let mut input = String::new(); 228 | while let Ok(n) = io::stdin().read_line(&mut input) { 229 | // '\n' == 1 230 | if n == 1 { 231 | break; 232 | } 233 | // We add the setting NoBinaryName because in daemon mode we won't be passed the preceeding 234 | // binary name 235 | let cli = build_cli().setting(AppSettings::NoBinaryName); 236 | let matches = match cfg.interface { 237 | Interface::Text => cli.get_matches_from(input.trim_end().split_whitespace()), 238 | Interface::TabText => cli.get_matches_from(input.trim_end().split('\t')), 239 | }; 240 | run(&matches, cfg.interface); 241 | 242 | input.clear(); 243 | } 244 | } 245 | 246 | enum Message<'a> { 247 | End, 248 | Prefix(BytePos, BytePos, &'a str), 249 | Match(String, Coordinate, &'a Path, MatchType, String), 250 | MatchWithSnippet( 251 | String, 252 | String, 253 | Coordinate, 254 | &'a Path, 255 | MatchType, 256 | String, 257 | String, 258 | ), 259 | Point(BytePos), 260 | Coords(Coordinate), 261 | } 262 | 263 | #[derive(Copy, Clone)] 264 | enum Interface { 265 | Text, // The original human-readable format. 266 | TabText, // Machine-readable format. This is basically the same as Text, except that all field 267 | // separators are replaced with tabs. 268 | // In `daemon` mode tabs are also used to delimit command arguments. 269 | } 270 | 271 | impl Default for Interface { 272 | fn default() -> Self { 273 | Interface::Text 274 | } 275 | } 276 | 277 | impl Interface { 278 | fn leading_space(&self) -> &str { 279 | match *self { 280 | Interface::Text => " ", 281 | Interface::TabText => "\t", 282 | } 283 | } 284 | 285 | fn field_separator(&self) -> &str { 286 | match *self { 287 | Interface::Text => ",", 288 | Interface::TabText => "\t", 289 | } 290 | } 291 | 292 | fn emit(&self, message: Message<'_>) { 293 | match message { 294 | Message::End => println!("END"), 295 | Message::Prefix(start, pos, text) => match *self { 296 | Interface::Text => println!("PREFIX {},{},{}", start, pos, text), 297 | Interface::TabText => println!("PREFIX\t{}\t{}\t{}", start, pos, text), 298 | }, 299 | Message::Point(point) => println!("POINT{}{}", self.leading_space(), point), 300 | Message::Coords(coord) => { 301 | println!( 302 | "COORD{lead}{}{field}{}", 303 | coord.row.0, 304 | coord.col.0, 305 | lead = self.leading_space(), 306 | field = self.field_separator() 307 | ); 308 | } 309 | Message::Match(mstr, cd, path, mtype, context) => match *self { 310 | Interface::Text => { 311 | let context = context.split_whitespace().collect::>().join(" "); 312 | println!( 313 | "MATCH {},{},{},{},{},{}", 314 | mstr, 315 | cd.row.0, 316 | cd.col.0, 317 | path.display(), 318 | mtype, 319 | context 320 | ); 321 | } 322 | Interface::TabText => { 323 | let context = context.split_whitespace().collect::>().join(" "); 324 | println!( 325 | "MATCH\t{}\t{}\t{}\t{}\t{}\t{}", 326 | mstr, 327 | cd.row.0, 328 | cd.col.0, 329 | path.display(), 330 | mtype, 331 | context 332 | ); 333 | } 334 | }, 335 | Message::MatchWithSnippet(mstr, snippet, cd, path, mtype, context, docs) => match *self 336 | { 337 | Interface::Text => { 338 | let context = context 339 | .replace(";", "\\;") 340 | .split_whitespace() 341 | .collect::>() 342 | .join(" "); 343 | let docs = format!("{:?}", docs).replace(";", "\\;"); 344 | println!( 345 | "MATCH {};{};{};{};{};{};{};{}", 346 | mstr, 347 | snippet, 348 | cd.row.0, 349 | cd.col.0, 350 | path.display(), 351 | mtype, 352 | context, 353 | docs 354 | ); 355 | } 356 | Interface::TabText => { 357 | let context = context 358 | .replace("\t", "\\t") 359 | .split_whitespace() 360 | .collect::>() 361 | .join(" "); 362 | println!( 363 | "MATCH\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{:?}", 364 | mstr, 365 | snippet, 366 | cd.row.0, 367 | cd.col.0, 368 | path.display(), 369 | mtype, 370 | context, 371 | docs 372 | ); 373 | } 374 | }, 375 | } 376 | } 377 | } 378 | 379 | #[derive(Default)] 380 | struct Config { 381 | fqn: Option, 382 | linenum: usize, 383 | charnum: usize, 384 | fn_name: Option, 385 | substitute_file: Option, 386 | interface: Interface, 387 | point: BytePos, 388 | } 389 | 390 | impl Config { 391 | fn coords(&self) -> Coordinate { 392 | Coordinate::new(self.linenum as u32, self.charnum as u32) 393 | } 394 | 395 | fn expect_file(&self) -> &PathBuf { 396 | self.fn_name.as_ref().expect("File path required") 397 | } 398 | } 399 | 400 | impl<'a> From<&'a ArgMatches<'a>> for Config { 401 | fn from(m: &'a ArgMatches<'_>) -> Self { 402 | // Check for the presence of the `point` argument that indicates we're 403 | // being asked to convert from point to coordinates 404 | if m.is_present("point") && m.is_present("path") { 405 | return Config { 406 | point: value_t_or_exit!(m.value_of("point"), usize).into(), 407 | fn_name: m.value_of("path").map(PathBuf::from), 408 | ..Default::default() 409 | }; 410 | } 411 | 412 | // We check for charnum because it's the second argument, which means more than just 413 | // an FQN was used (i.e. racer complete [substitute_file]) 414 | if m.is_present("charnum") { 415 | let cfg = Config { 416 | charnum: value_t_or_exit!(m.value_of("charnum"), usize), 417 | fn_name: m.value_of("path").map(PathBuf::from), 418 | substitute_file: m.value_of("substitute_file").map(PathBuf::from), 419 | ..Default::default() 420 | }; 421 | if !m.is_present("linenum") { 422 | // Because of the hack to allow fqn and linenum to share a single arg we set FQN 423 | // to None and set the charnum correctly using the FQN arg so there's no 424 | // hackery later 425 | return Config { 426 | linenum: value_t_or_exit!(m.value_of("fqn"), usize), 427 | ..cfg 428 | }; 429 | } 430 | return Config { 431 | linenum: value_t_or_exit!(m.value_of("linenum"), usize), 432 | ..cfg 433 | }; 434 | } 435 | Config { 436 | fqn: m.value_of("fqn").map(ToOwned::to_owned), 437 | ..Default::default() 438 | } 439 | } 440 | } 441 | 442 | fn build_cli<'a, 'b>() -> App<'a, 'b> { 443 | // we use the more verbose "Builder Pattern" to create the CLI because it's a littel faster 444 | // than the less verbose "Usage String" method...faster, meaning runtime speed since that's 445 | // extremely important here 446 | App::new("racer") 447 | .version(env!("CARGO_PKG_VERSION")) 448 | .author("Phil Dawes") 449 | .about("A Rust code completion utility") 450 | .settings(&[ 451 | AppSettings::GlobalVersion, 452 | AppSettings::SubcommandRequiredElseHelp, 453 | ]) 454 | .arg( 455 | Arg::with_name("interface") 456 | .long("interface") 457 | .short("i") 458 | .takes_value(true) 459 | .possible_value("text") 460 | .possible_value("tab-text") 461 | .value_name("mode") 462 | .help("Interface mode"), 463 | ) 464 | .subcommand( 465 | SubCommand::with_name("complete") 466 | .about("performs completion and returns matches") 467 | // We set an explicit usage string here, instead of letting `clap` write one due to 468 | // using a single arg for multiple purposes 469 | .usage( 470 | "racer complete \n \ 471 | racer complete [substitute_file]", 472 | ) 473 | // Next we make it an error to run without any args 474 | .setting(AppSettings::ArgRequiredElseHelp) 475 | // Because we want a single arg to play two roles and be compatible with previous 476 | // racer releases, we have to be a little hacky here... 477 | // 478 | // We start by making 'fqn' the first positional arg, which will hold this dual value 479 | // of either an FQN as it says, or secretly a line-number 480 | .arg( 481 | Arg::with_name("fqn") 482 | .help("complete with a fully-qualified-name (e.g. std::io::)"), 483 | ) 484 | .arg( 485 | Arg::with_name("charnum") 486 | .help("The char number to search for matches") 487 | .requires("path"), 488 | ) 489 | .arg(Arg::with_name("path").help("The path to search for name to match")) 490 | .arg(Arg::with_name("substitute_file").help("An optional substitute file")) 491 | // 'linenum' **MUST** be last (or have the highest index so that it's never actually 492 | // used by the user, but still appears in the help text) 493 | .arg(Arg::with_name("linenum").help("The line number at which to find the match")), 494 | ) 495 | .subcommand( 496 | SubCommand::with_name("daemon") 497 | .about("start a process that receives the above commands via stdin"), 498 | ) 499 | .subcommand( 500 | SubCommand::with_name("find-definition") 501 | .about("finds the definition of a function") 502 | .arg( 503 | Arg::with_name("linenum") 504 | .help("The line number at which to find the match") 505 | .required(true), 506 | ) 507 | .arg( 508 | Arg::with_name("charnum") 509 | .help("The char number at which to find the match") 510 | .required(true), 511 | ) 512 | .arg( 513 | Arg::with_name("path") 514 | .help("The path to search for name to match") 515 | .required(true), 516 | ) 517 | .arg(Arg::with_name("substitute_file").help("An optional substitute file")), 518 | ) 519 | .subcommand( 520 | SubCommand::with_name("prefix") 521 | .arg( 522 | Arg::with_name("linenum") 523 | .help("The line number at which to find the match") 524 | .required(true), 525 | ) 526 | .arg( 527 | Arg::with_name("charnum") 528 | .help("The char number at which to find the match") 529 | .required(true), 530 | ) 531 | .arg( 532 | Arg::with_name("path") 533 | .help("The path to search for the match to prefix") 534 | .required(true), 535 | ), 536 | ) 537 | .subcommand( 538 | SubCommand::with_name("complete-with-snippet") 539 | .about("performs completion and returns more detailed matches") 540 | .usage( 541 | "racer complete-with-snippet \n \ 542 | racer complete-with-snippet [substitute_file]", 543 | ) 544 | .setting(AppSettings::ArgRequiredElseHelp) 545 | .arg( 546 | Arg::with_name("fqn") 547 | .help("complete with a fully-qualified-name (e.g. std::io::)"), 548 | ) 549 | .arg( 550 | Arg::with_name("charnum") 551 | .help("The char number to search for matches") 552 | .requires("path"), 553 | ) 554 | .arg(Arg::with_name("path").help("The path to search for name to match")) 555 | .arg(Arg::with_name("substitute_file").help("An optional substitute file")) 556 | .arg(Arg::with_name("linenum").help("The line number at which to find the match")), 557 | ) 558 | .subcommand( 559 | SubCommand::with_name("point") 560 | .about("converts linenum and charnum in a file to a point") 561 | // Next we make it an error to run without any args 562 | .setting(AppSettings::ArgRequiredElseHelp) 563 | .arg( 564 | Arg::with_name("linenum") 565 | .help("The line number at which to convert to point") 566 | .required(true), 567 | ) 568 | .arg( 569 | Arg::with_name("charnum") 570 | .help("The char number at which to convert to point") 571 | .required(true), 572 | ) 573 | .arg( 574 | Arg::with_name("path") 575 | .help("The path where the line and char occur") 576 | .required(true), 577 | ), 578 | ) 579 | .subcommand( 580 | SubCommand::with_name("coord") 581 | .about("converts a racer point to line and character numbers") 582 | // Next we make it an error to run without any args 583 | .setting(AppSettings::ArgRequiredElseHelp) 584 | .arg( 585 | Arg::with_name("point") 586 | .help("The point to convert to line and character coordinates") 587 | .required(true), 588 | ) 589 | .arg( 590 | Arg::with_name("path") 591 | .help("The path where the line and char occur") 592 | .required(true), 593 | ), 594 | ) 595 | .after_help("For more information about a specific command try 'racer --help'") 596 | } 597 | 598 | fn main() { 599 | use std::io::Write; 600 | 601 | env_logger::Builder::from_default_env() 602 | .format(|f, record| { 603 | writeln!( 604 | f, 605 | "{:>5} {}: {}: {}", 606 | record.level(), 607 | humantime::format_rfc3339_nanos(SystemTime::now()), 608 | record.module_path().unwrap_or("-"), 609 | record.args() 610 | ) 611 | }) 612 | .init(); 613 | 614 | let matches = build_cli().get_matches(); 615 | let interface = match matches.value_of("interface") { 616 | Some("tab-text") => Interface::TabText, 617 | Some("text") | _ => Interface::Text, 618 | }; 619 | 620 | validate_rust_src_path_env_var(); 621 | 622 | run(&matches, interface); 623 | } 624 | 625 | fn run(m: &ArgMatches<'_>, interface: Interface) { 626 | use crate::CompletePrinter::{Normal, WithSnippets}; 627 | // match raw subcommand, and get it's sub-matches "m" 628 | if let (name, Some(sub_m)) = m.subcommand() { 629 | let mut cfg = Config::from(sub_m); 630 | cfg.interface = interface; 631 | match name { 632 | "daemon" => daemon(&cfg), 633 | "prefix" => prefix(&cfg), 634 | "complete" => complete(cfg, Normal), 635 | "complete-with-snippet" => complete(cfg, WithSnippets), 636 | "find-definition" => find_definition(&cfg), 637 | "point" => point(&cfg), 638 | "coord" => coord(&cfg), 639 | _ => unreachable!(), 640 | } 641 | } 642 | } 643 | -------------------------------------------------------------------------------- /src/racer/benches.rs: -------------------------------------------------------------------------------- 1 | extern crate test; 2 | 3 | use std::env::var; 4 | use std::fs::File; 5 | use std::io::Read; 6 | use std::path::PathBuf; 7 | 8 | use codecleaner::code_chunks; 9 | use codeiter::StmtIndicesIter; 10 | use core::IndexedSource; 11 | use scopes::{mask_comments, mask_sub_scopes}; 12 | 13 | use self::test::Bencher; 14 | 15 | fn get_rust_file_str(path: &[&str]) -> String { 16 | let mut src_path = match var("RUST_SRC_PATH") { 17 | Ok(env) => PathBuf::from(&env), 18 | _ => panic!("Cannot find $RUST_SRC_PATH"), 19 | }; 20 | for &s in path.iter() { 21 | src_path.push(s); 22 | } 23 | 24 | let mut s = String::new(); 25 | File::open(&src_path) 26 | .unwrap() 27 | .read_to_string(&mut s) 28 | .unwrap(); 29 | s 30 | } 31 | 32 | #[bench] 33 | fn bench_code_chunks(b: &mut Bencher) { 34 | let src = &get_rust_file_str(&["liballoc", "vec.rs"]); 35 | b.iter(|| { 36 | test::black_box(code_chunks(src).collect::>()); 37 | }); 38 | } 39 | 40 | #[bench] 41 | fn bench_iter_stmts(b: &mut Bencher) { 42 | let src = &get_rust_file_str(&["liballoc", "vec.rs"]); 43 | b.iter(|| { 44 | test::black_box(StmtIndicesIter::from_parts(src, code_chunks(src)).collect::>()); 45 | }); 46 | } 47 | 48 | #[bench] 49 | fn bench_mask_comments(b: &mut Bencher) { 50 | let src_indexed = IndexedSource::new(get_rust_file_str(&["liballoc", "vec.rs"])); 51 | let src = src_indexed.as_src(); 52 | b.iter(|| { 53 | test::black_box(mask_comments(src)); 54 | }); 55 | } 56 | 57 | #[bench] 58 | fn bench_mask_sub_scopes(b: &mut Bencher) { 59 | let src = &get_rust_file_str(&["liballoc", "vec.rs"]); 60 | b.iter(|| { 61 | test::black_box(mask_sub_scopes(src)); 62 | }); 63 | } 64 | -------------------------------------------------------------------------------- /src/racer/codecleaner.rs: -------------------------------------------------------------------------------- 1 | use crate::core::{BytePos, ByteRange}; 2 | 3 | /// Type of the string 4 | #[derive(Clone, Copy, Debug)] 5 | enum StrStyle { 6 | /// normal string starts with " 7 | Cooked, 8 | /// Raw(n) => raw string started with n #s 9 | Raw(usize), 10 | } 11 | 12 | #[derive(Clone, Copy)] 13 | enum State { 14 | Code, 15 | Comment, 16 | CommentBlock, 17 | String(StrStyle), 18 | Char, 19 | Finished, 20 | } 21 | 22 | #[derive(Clone, Copy)] 23 | pub struct CodeIndicesIter<'a> { 24 | src: &'a str, 25 | pos: BytePos, 26 | state: State, 27 | } 28 | 29 | impl<'a> Iterator for CodeIndicesIter<'a> { 30 | type Item = ByteRange; 31 | 32 | fn next(&mut self) -> Option { 33 | match self.state { 34 | State::Code => Some(self.code()), 35 | State::Comment => Some(self.comment()), 36 | State::CommentBlock => Some(self.comment_block()), 37 | State::String(style) => Some(self.string(style)), 38 | State::Char => Some(self.char()), 39 | State::Finished => None, 40 | } 41 | } 42 | } 43 | 44 | impl<'a> CodeIndicesIter<'a> { 45 | fn code(&mut self) -> ByteRange { 46 | let mut pos = self.pos; 47 | let start = match self.state { 48 | State::String(_) | State::Char => pos.decrement(), // include quote 49 | _ => pos, 50 | }; 51 | let src_bytes = self.src.as_bytes(); 52 | for &b in &src_bytes[pos.0..] { 53 | pos = pos.increment(); 54 | match b { 55 | b'/' if src_bytes.len() > pos.0 => match src_bytes[pos.0] { 56 | b'/' => { 57 | self.state = State::Comment; 58 | self.pos = pos.increment(); 59 | return ByteRange::new(start, pos.decrement()); 60 | } 61 | b'*' => { 62 | self.state = State::CommentBlock; 63 | self.pos = pos.increment(); 64 | return ByteRange::new(start, pos.decrement()); 65 | } 66 | _ => {} 67 | }, 68 | b'"' => { 69 | // " 70 | let str_type = self.detect_str_type(pos); 71 | self.state = State::String(str_type); 72 | self.pos = pos; 73 | return ByteRange::new(start, pos); // include dblquotes 74 | } 75 | b'\'' => { 76 | // single quotes are also used for lifetimes, so we need to 77 | // be confident that this is not a lifetime. 78 | // Look for backslash starting the escape, or a closing quote: 79 | if src_bytes.len() > pos.increment().0 80 | && (src_bytes[pos.0] == b'\\' || src_bytes[pos.increment().0] == b'\'') 81 | { 82 | self.state = State::Char; 83 | self.pos = pos; 84 | return ByteRange::new(start, pos); // include single quote 85 | } 86 | } 87 | _ => {} 88 | } 89 | } 90 | 91 | self.state = State::Finished; 92 | ByteRange::new(start, self.src.len().into()) 93 | } 94 | 95 | fn comment(&mut self) -> ByteRange { 96 | let mut pos = self.pos; 97 | let src_bytes = self.src.as_bytes(); 98 | for &b in &src_bytes[pos.0..] { 99 | pos = pos.increment(); 100 | if b == b'\n' { 101 | if pos.0 + 2 <= src_bytes.len() && src_bytes[pos.0..pos.0 + 2] == [b'/', b'/'] { 102 | continue; 103 | } 104 | break; 105 | } 106 | } 107 | self.pos = pos; 108 | self.code() 109 | } 110 | 111 | fn comment_block(&mut self) -> ByteRange { 112 | let mut nesting_level = 0usize; 113 | let mut prev = b' '; 114 | let mut pos = self.pos; 115 | for &b in &self.src.as_bytes()[pos.0..] { 116 | pos = pos.increment(); 117 | match b { 118 | b'/' if prev == b'*' => { 119 | prev = b' '; 120 | if nesting_level == 0 { 121 | break; 122 | } else { 123 | nesting_level -= 1; 124 | } 125 | } 126 | b'*' if prev == b'/' => { 127 | prev = b' '; 128 | nesting_level += 1; 129 | } 130 | _ => { 131 | prev = b; 132 | } 133 | } 134 | } 135 | self.pos = pos; 136 | self.code() 137 | } 138 | 139 | fn string(&mut self, str_type: StrStyle) -> ByteRange { 140 | let src_bytes = self.src.as_bytes(); 141 | let mut pos = self.pos; 142 | match str_type { 143 | StrStyle::Raw(level) => { 144 | // raw string (e.g. br#"\"#) 145 | #[derive(Debug)] 146 | enum SharpState { 147 | Sharp { 148 | // number of preceding #s 149 | num_sharps: usize, 150 | // Position of last " 151 | quote_pos: BytePos, 152 | }, 153 | None, // No preceding "##... 154 | } 155 | let mut cur_state = SharpState::None; 156 | let mut end_was_found = false; 157 | // detect corresponding end(if start is r##", "##) greedily 158 | for (i, &b) in src_bytes[self.pos.0..].iter().enumerate() { 159 | match cur_state { 160 | SharpState::Sharp { 161 | num_sharps, 162 | quote_pos, 163 | } => { 164 | cur_state = match b { 165 | b'#' => SharpState::Sharp { 166 | num_sharps: num_sharps + 1, 167 | quote_pos, 168 | }, 169 | b'"' => SharpState::Sharp { 170 | num_sharps: 0, 171 | quote_pos: BytePos(i), 172 | }, 173 | _ => SharpState::None, 174 | } 175 | } 176 | SharpState::None => { 177 | if b == b'"' { 178 | cur_state = SharpState::Sharp { 179 | num_sharps: 0, 180 | quote_pos: BytePos(i), 181 | }; 182 | } 183 | } 184 | } 185 | if let SharpState::Sharp { 186 | num_sharps, 187 | quote_pos, 188 | } = cur_state 189 | { 190 | if num_sharps == level { 191 | end_was_found = true; 192 | pos += quote_pos.increment(); 193 | break; 194 | } 195 | } 196 | } 197 | if !end_was_found { 198 | pos = src_bytes.len().into(); 199 | } 200 | } 201 | StrStyle::Cooked => { 202 | let mut is_not_escaped = true; 203 | for &b in &src_bytes[pos.0..] { 204 | pos = pos.increment(); 205 | match b { 206 | b'"' if is_not_escaped => { 207 | break; 208 | } // " 209 | b'\\' => { 210 | is_not_escaped = !is_not_escaped; 211 | } 212 | _ => { 213 | is_not_escaped = true; 214 | } 215 | } 216 | } 217 | } 218 | }; 219 | self.pos = pos; 220 | self.code() 221 | } 222 | 223 | fn char(&mut self) -> ByteRange { 224 | let mut is_not_escaped = true; 225 | let mut pos = self.pos; 226 | for &b in &self.src.as_bytes()[pos.0..] { 227 | pos = pos.increment(); 228 | match b { 229 | b'\'' if is_not_escaped => { 230 | break; 231 | } 232 | b'\\' => { 233 | is_not_escaped = !is_not_escaped; 234 | } 235 | _ => { 236 | is_not_escaped = true; 237 | } 238 | } 239 | } 240 | self.pos = pos; 241 | self.code() 242 | } 243 | 244 | fn detect_str_type(&self, pos: BytePos) -> StrStyle { 245 | let src_bytes = self.src.as_bytes(); 246 | let mut sharp = 0; 247 | if pos == BytePos::ZERO { 248 | return StrStyle::Cooked; 249 | } 250 | // now pos is at one byte after ", so we have to start at pos - 2 251 | for &b in src_bytes[..pos.decrement().0].iter().rev() { 252 | match b { 253 | b'#' => sharp += 1, 254 | b'r' => return StrStyle::Raw(sharp), 255 | _ => return StrStyle::Cooked, 256 | } 257 | } 258 | StrStyle::Cooked 259 | } 260 | } 261 | 262 | /// Returns indices of chunks of code (minus comments and string contents) 263 | pub fn code_chunks(src: &str) -> CodeIndicesIter<'_> { 264 | CodeIndicesIter { 265 | src, 266 | state: State::Code, 267 | pos: BytePos::ZERO, 268 | } 269 | } 270 | 271 | #[cfg(test)] 272 | mod code_indices_iter_test { 273 | use super::*; 274 | use crate::testutils::{rejustify, slice}; 275 | 276 | #[test] 277 | fn removes_a_comment() { 278 | let src = &rejustify( 279 | " 280 | this is some code // this is a comment 281 | some more code 282 | ", 283 | ); 284 | let mut it = code_chunks(src); 285 | assert_eq!("this is some code ", slice(src, it.next().unwrap())); 286 | assert_eq!("some more code", slice(src, it.next().unwrap())); 287 | } 288 | 289 | #[test] 290 | fn removes_consecutive_comments() { 291 | let src = &rejustify( 292 | " 293 | this is some code // this is a comment 294 | // this is more comment 295 | // another comment 296 | some more code 297 | ", 298 | ); 299 | let mut it = code_chunks(src); 300 | assert_eq!("this is some code ", slice(src, it.next().unwrap())); 301 | assert_eq!("some more code", slice(src, it.next().unwrap())); 302 | } 303 | 304 | #[test] 305 | fn removes_string_contents() { 306 | let src = &rejustify( 307 | " 308 | this is some code \"this is a string\" more code 309 | ", 310 | ); 311 | let mut it = code_chunks(src); 312 | assert_eq!("this is some code \"", slice(src, it.next().unwrap())); 313 | assert_eq!("\" more code", slice(src, it.next().unwrap())); 314 | } 315 | 316 | #[test] 317 | fn removes_char_contents() { 318 | let src = &rejustify( 319 | " 320 | this is some code \'\"\' more code \'\\x00\' and \'\\\'\' that\'s it 321 | ", 322 | ); 323 | let mut it = code_chunks(src); 324 | assert_eq!("this is some code \'", slice(src, it.next().unwrap())); 325 | assert_eq!("\' more code \'", slice(src, it.next().unwrap())); 326 | assert_eq!("\' and \'", slice(src, it.next().unwrap())); 327 | assert_eq!("\' that\'s it", slice(src, it.next().unwrap())); 328 | } 329 | 330 | #[test] 331 | fn removes_string_contents_with_a_comment_in_it() { 332 | let src = &rejustify( 333 | " 334 | this is some code \"string with a // fake comment \" more code 335 | ", 336 | ); 337 | let mut it = code_chunks(src); 338 | assert_eq!("this is some code \"", slice(src, it.next().unwrap())); 339 | assert_eq!("\" more code", slice(src, it.next().unwrap())); 340 | } 341 | 342 | #[test] 343 | fn removes_a_comment_with_a_dbl_quote_in_it() { 344 | let src = &rejustify( 345 | " 346 | this is some code // comment with \" double quote 347 | some more code 348 | ", 349 | ); 350 | let mut it = code_chunks(src); 351 | assert_eq!("this is some code ", slice(src, it.next().unwrap())); 352 | assert_eq!("some more code", slice(src, it.next().unwrap())); 353 | } 354 | 355 | #[test] 356 | fn removes_multiline_comment() { 357 | let src = &rejustify( 358 | " 359 | this is some code /* this is a 360 | \"multiline\" comment */some more code 361 | ", 362 | ); 363 | let mut it = code_chunks(src); 364 | assert_eq!("this is some code ", slice(src, it.next().unwrap())); 365 | assert_eq!("some more code", slice(src, it.next().unwrap())); 366 | } 367 | 368 | #[test] 369 | fn handles_nesting_of_block_comments() { 370 | let src = &rejustify( 371 | " 372 | this is some code /* nested /* block */ comment */ some more code 373 | ", 374 | ); 375 | let mut it = code_chunks(src); 376 | assert_eq!("this is some code ", slice(src, it.next().unwrap())); 377 | assert_eq!(" some more code", slice(src, it.next().unwrap())); 378 | } 379 | 380 | #[test] 381 | fn handles_documentation_block_comments_nested_into_block_comments() { 382 | let src = &rejustify( 383 | " 384 | this is some code /* nested /** documentation block */ comment */ some more code 385 | ", 386 | ); 387 | let mut it = code_chunks(src); 388 | assert_eq!("this is some code ", slice(src, it.next().unwrap())); 389 | assert_eq!(" some more code", slice(src, it.next().unwrap())); 390 | } 391 | 392 | #[test] 393 | fn removes_string_with_escaped_dblquote_in_it() { 394 | let src = &rejustify( 395 | " 396 | this is some code \"string with a \\\" escaped dblquote fake comment \" more code 397 | ", 398 | ); 399 | 400 | let mut it = code_chunks(src); 401 | assert_eq!("this is some code \"", slice(src, it.next().unwrap())); 402 | assert_eq!("\" more code", slice(src, it.next().unwrap())); 403 | } 404 | 405 | #[test] 406 | fn removes_raw_string_with_dangling_escape_in_it() { 407 | let src = &rejustify( 408 | " 409 | this is some code br\" escaped dblquote raw string \\\" more code 410 | ", 411 | ); 412 | 413 | let mut it = code_chunks(src); 414 | assert_eq!("this is some code br\"", slice(src, it.next().unwrap())); 415 | assert_eq!("\" more code", slice(src, it.next().unwrap())); 416 | } 417 | 418 | #[test] 419 | fn removes_string_with_escaped_slash_before_dblquote_in_it() { 420 | let src = &rejustify(" 421 | this is some code \"string with an escaped slash, so dbl quote does end the string after all \\\\\" more code 422 | "); 423 | 424 | let mut it = code_chunks(src); 425 | assert_eq!("this is some code \"", slice(src, it.next().unwrap())); 426 | assert_eq!("\" more code", slice(src, it.next().unwrap())); 427 | } 428 | 429 | #[test] 430 | fn handles_tricky_bit_from_str_rs() { 431 | let src = &rejustify( 432 | " 433 | before(\"\\\\\'\\\\\\\"\\\\\\\\\"); 434 | more_code(\" skip me \") 435 | ", 436 | ); 437 | 438 | for range in code_chunks(src) { 439 | let range = || range.to_range(); 440 | println!("BLOB |{}|", &src[range()]); 441 | if src[range()].contains("skip me") { 442 | panic!("{}", &src[range()]); 443 | } 444 | } 445 | } 446 | 447 | #[test] 448 | fn removes_nested_rawstr() { 449 | let src = &rejustify( 450 | r####" 451 | this is some code br###""" r##""##"### more code 452 | "####, 453 | ); 454 | 455 | let mut it = code_chunks(src); 456 | assert_eq!("this is some code br###\"", slice(src, it.next().unwrap())); 457 | assert_eq!("\"### more code", slice(src, it.next().unwrap())); 458 | } 459 | 460 | } 461 | -------------------------------------------------------------------------------- /src/racer/codeiter.rs: -------------------------------------------------------------------------------- 1 | use std::iter::{Fuse, Iterator}; 2 | 3 | use crate::core::{BytePos, ByteRange}; 4 | use crate::scopes; 5 | use crate::util::is_whitespace_byte; 6 | 7 | /// An iterator which iterates statements. 8 | /// e.g. for "let a = 5; let b = 4;" it returns "let a = 5;" and then "let b = 4;" 9 | /// This iterator only works for comment-masked source codes. 10 | pub struct StmtIndicesIter<'a> { 11 | src: &'a str, 12 | pos: BytePos, 13 | end: BytePos, 14 | } 15 | 16 | impl<'a> Iterator for StmtIndicesIter<'a> { 17 | type Item = ByteRange; 18 | 19 | #[inline] 20 | fn next(&mut self) -> Option { 21 | let src_bytes = self.src.as_bytes(); 22 | let mut enddelim = b';'; 23 | let mut bracelevel = 0isize; 24 | let mut parenlevel = 0isize; 25 | let mut bracketlevel = 0isize; 26 | let mut pos = self.pos; 27 | for &b in &src_bytes[pos.0..self.end.0] { 28 | match b { 29 | b' ' | b'\r' | b'\n' | b'\t' => { 30 | pos += BytePos(1); 31 | } 32 | _ => { 33 | break; 34 | } 35 | } 36 | } 37 | let start = pos; 38 | // test attribute #[foo = bar] 39 | if pos < self.end && src_bytes[pos.0] == b'#' { 40 | enddelim = b']' 41 | }; 42 | // iterate through the chunk, looking for stmt end 43 | for &b in &src_bytes[pos.0..self.end.0] { 44 | pos += BytePos(1); 45 | match b { 46 | b'(' => { 47 | parenlevel += 1; 48 | } 49 | b')' => { 50 | parenlevel -= 1; 51 | } 52 | b'[' => { 53 | bracketlevel += 1; 54 | } 55 | b']' => { 56 | bracketlevel -= 1; 57 | } 58 | b'{' => { 59 | // if we are top level and stmt is not a 'use' or 'let' then 60 | // closebrace finishes the stmt 61 | if bracelevel == 0 62 | && parenlevel == 0 63 | && !(is_a_use_stmt(src_bytes, start, pos) 64 | || is_a_let_stmt(src_bytes, start, pos)) 65 | { 66 | enddelim = b'}'; 67 | } 68 | bracelevel += 1; 69 | } 70 | b'}' => { 71 | // have we reached the end of the scope? 72 | if bracelevel == 0 { 73 | self.pos = pos; 74 | return None; 75 | } 76 | bracelevel -= 1; 77 | } 78 | b'!' => { 79 | // macro if followed by at least one space or ( 80 | // FIXME: test with boolean 'not' expression 81 | if parenlevel == 0 && bracelevel == 0 && pos < self.end && (pos - start).0 > 1 { 82 | match src_bytes[pos.0] { 83 | b' ' | b'\r' | b'\n' | b'\t' | b'(' => { 84 | enddelim = b')'; 85 | } 86 | _ => {} 87 | } 88 | } 89 | } 90 | _ => {} 91 | } 92 | if parenlevel < 0 93 | || bracelevel < 0 94 | || bracketlevel < 0 95 | || (enddelim == b && bracelevel == 0 && parenlevel == 0 && bracketlevel == 0) 96 | { 97 | self.pos = pos; 98 | return Some(ByteRange::new(start, pos)); 99 | } 100 | } 101 | if start < self.end { 102 | self.pos = pos; 103 | return Some(ByteRange::new(start, self.end)); 104 | } 105 | None 106 | } 107 | } 108 | 109 | fn is_a_use_stmt(src_bytes: &[u8], start: BytePos, pos: BytePos) -> bool { 110 | let src = unsafe { ::std::str::from_utf8_unchecked(&src_bytes[start.0..pos.0]) }; 111 | scopes::use_stmt_start(&src).is_some() 112 | } 113 | 114 | fn is_a_let_stmt(src_bytes: &[u8], start: BytePos, pos: BytePos) -> bool { 115 | pos.0 > 3 116 | && &src_bytes[start.0..start.0 + 3] == b"let" 117 | && is_whitespace_byte(src_bytes[start.0 + 3]) 118 | } 119 | 120 | impl<'a> StmtIndicesIter<'a> { 121 | pub fn from_parts(src: &str) -> Fuse> { 122 | StmtIndicesIter { 123 | src, 124 | pos: BytePos::ZERO, 125 | end: BytePos(src.len()), 126 | } 127 | .fuse() 128 | } 129 | } 130 | 131 | #[cfg(test)] 132 | mod test { 133 | use std::iter::Fuse; 134 | 135 | use crate::codecleaner; 136 | use crate::testutils::{rejustify, slice}; 137 | 138 | use super::*; 139 | 140 | fn iter_stmts(src: &str) -> Fuse> { 141 | let idx: Vec<_> = codecleaner::code_chunks(&src).collect(); 142 | let code = scopes::mask_comments(src, &idx); 143 | let code: &'static str = Box::leak(code.into_boxed_str()); 144 | StmtIndicesIter::from_parts(code) 145 | } 146 | 147 | #[test] 148 | fn iterates_single_use_stmts() { 149 | let src = rejustify( 150 | " 151 | use std::Foo; // a comment 152 | use std::Bar; 153 | ", 154 | ); 155 | 156 | let mut it = iter_stmts(src.as_ref()); 157 | assert_eq!("use std::Foo;", slice(&src, it.next().unwrap())); 158 | assert_eq!("use std::Bar;", slice(&src, it.next().unwrap())); 159 | } 160 | 161 | #[test] 162 | fn iterates_array_stmts() { 163 | let src = rejustify( 164 | " 165 | let a: [i32; 2] = [1, 2]; 166 | let b = [[0], [1], [2]]; 167 | let c = ([1, 2, 3])[1]; 168 | ", 169 | ); 170 | 171 | let mut it = iter_stmts(src.as_ref()); 172 | assert_eq!("let a: [i32; 2] = [1, 2];", slice(&src, it.next().unwrap())); 173 | assert_eq!("let b = [[0], [1], [2]];", slice(&src, it.next().unwrap())); 174 | assert_eq!("let c = ([1, 2, 3])[1];", slice(&src, it.next().unwrap())); 175 | } 176 | 177 | #[test] 178 | fn iterates_use_stmt_over_two_lines() { 179 | let src = rejustify( 180 | " 181 | use std::{Foo, 182 | Bar}; // a comment 183 | ", 184 | ); 185 | let mut it = iter_stmts(src.as_ref()); 186 | assert_eq!( 187 | "use std::{Foo, 188 | Bar};", 189 | slice(&src, it.next().unwrap()) 190 | ); 191 | } 192 | 193 | #[test] 194 | fn iterates_use_stmt_without_the_prefix() { 195 | let src = rejustify( 196 | " 197 | pub use {Foo, 198 | Bar}; // this is also legit apparently 199 | ", 200 | ); 201 | let mut it = iter_stmts(src.as_ref()); 202 | assert_eq!( 203 | "pub use {Foo, 204 | Bar};", 205 | slice(&src, it.next().unwrap()) 206 | ); 207 | } 208 | 209 | #[test] 210 | fn iterates_while_stmt() { 211 | let src = rejustify( 212 | " 213 | while self.pos < 3 { } 214 | ", 215 | ); 216 | let mut it = iter_stmts(src.as_ref()); 217 | assert_eq!("while self.pos < 3 { }", slice(&src, it.next().unwrap())); 218 | } 219 | 220 | #[test] 221 | fn iterates_lambda_arg() { 222 | let src = rejustify( 223 | " 224 | myfn(|n|{}); 225 | ", 226 | ); 227 | let mut it = iter_stmts(src.as_ref()); 228 | assert_eq!("myfn(|n|{});", slice(&src, it.next().unwrap())); 229 | } 230 | 231 | #[test] 232 | fn iterates_macro() { 233 | let src = " 234 | mod foo; 235 | macro_rules! otry( 236 | ($e:expr) => (match $e { Some(e) => e, None => return }) 237 | ) 238 | mod bar; 239 | "; 240 | let mut it = iter_stmts(src.as_ref()); 241 | assert_eq!("mod foo;", slice(&src, it.next().unwrap())); 242 | assert_eq!( 243 | "macro_rules! otry( 244 | ($e:expr) => (match $e { Some(e) => e, None => return }) 245 | )", 246 | slice(&src, it.next().unwrap()) 247 | ); 248 | assert_eq!("mod bar;", slice(&src, it.next().unwrap())); 249 | } 250 | 251 | #[test] 252 | fn iterates_macro_invocation() { 253 | let src = " 254 | mod foo; 255 | local_data_key!(local_stdout: Box) // no ';' 256 | mod bar; 257 | "; 258 | let mut it = iter_stmts(src.as_ref()); 259 | assert_eq!("mod foo;", slice(&src, it.next().unwrap())); 260 | assert_eq!( 261 | "local_data_key!(local_stdout: Box)", 262 | slice(&src, it.next().unwrap()) 263 | ); 264 | assert_eq!("mod bar;", slice(&src, it.next().unwrap())); 265 | } 266 | 267 | #[test] 268 | fn iterates_if_else_stmt() { 269 | let src = " 270 | if self.pos < 3 { } else { } 271 | "; 272 | let mut it = iter_stmts(src.as_ref()); 273 | assert_eq!("if self.pos < 3 { }", slice(&src, it.next().unwrap())); 274 | assert_eq!("else { }", slice(&src, it.next().unwrap())); 275 | } 276 | 277 | #[test] 278 | fn iterates_inner_scope() { 279 | let src = &" 280 | while(self.pos < 3 { 281 | let a = 35; 282 | return a + 35; // should iterate this 283 | } 284 | { 285 | b = foo; // but not this 286 | } 287 | "[29..]; 288 | 289 | let mut it = iter_stmts(src.as_ref()); 290 | 291 | assert_eq!("let a = 35;", slice(&src, it.next().unwrap())); 292 | assert_eq!("return a + 35;", slice(&src, it.next().unwrap())); 293 | assert_eq!(None, it.next()); 294 | } 295 | 296 | #[test] 297 | fn iterates_module_attribute() { 298 | let src = rejustify( 299 | " 300 | #![license = \"BSD\"] 301 | #[test] 302 | ", 303 | ); 304 | let mut it = iter_stmts(src.as_ref()); 305 | assert_eq!("#![license = \"BSD\"]", slice(&src, it.next().unwrap())); 306 | assert_eq!("#[test]", slice(&src, it.next().unwrap())); 307 | } 308 | 309 | #[test] 310 | fn iterates_half_open_subscope_if_is_the_last_thing() { 311 | let src = " 312 | let something = 35; 313 | while self.pos < 3 { 314 | let a = 35; 315 | return a + 35; // should iterate this 316 | "; 317 | 318 | let mut it = iter_stmts(src.as_ref()); 319 | assert_eq!("let something = 35;", slice(&src, it.next().unwrap())); 320 | assert_eq!( 321 | "while self.pos < 3 { 322 | let a = 35; 323 | return a + 35; // should iterate this 324 | ", 325 | slice(&src, it.next().unwrap()) 326 | ); 327 | } 328 | 329 | #[test] 330 | fn iterates_ndarray() { 331 | let src = " 332 | let a = [[f64; 5]; 5]; 333 | pub struct Matrix44f(pub [[f64; 4]; 4]); 334 | "; 335 | let mut it = iter_stmts(src.as_ref()); 336 | assert_eq!("let a = [[f64; 5]; 5];", slice(&src, it.next().unwrap())); 337 | assert_eq!( 338 | "pub struct Matrix44f(pub [[f64; 4]; 4]);", 339 | slice(&src, it.next().unwrap()) 340 | ); 341 | } 342 | 343 | #[test] 344 | #[ignore] 345 | fn iterates_for_struct() { 346 | let src = " 347 | let a = 5; 348 | for St { a, b } in iter() { 349 | let b = a; 350 | } 351 | while let St { a, b } = iter().next() { 352 | 353 | } 354 | if let St(a) = hoge() { 355 | 356 | } 357 | "; 358 | let mut it = iter_stmts(src.as_ref()); 359 | assert_eq!("let a = 5;", slice(&src, it.next().unwrap())); 360 | assert_eq!( 361 | r"for St { a, b } in iter() { 362 | let b = a; 363 | }", 364 | slice(&src, it.next().unwrap()) 365 | ); 366 | } 367 | } 368 | -------------------------------------------------------------------------------- /src/racer/fileres.rs: -------------------------------------------------------------------------------- 1 | use crate::core::{BytePos, Coordinate, Match, MatchType, SearchType, Session, SessionExt}; 2 | use crate::matchers; 3 | use crate::nameres::RUST_SRC_PATH; 4 | use crate::project_model::Edition; 5 | use std::path::{Path, PathBuf}; 6 | 7 | /// get crate file from current path & crate name 8 | pub fn get_crate_file(name: &str, from_path: &Path, session: &Session<'_>) -> Option { 9 | debug!("get_crate_file {}, {:?}", name, from_path); 10 | get_std_file(name, session).or_else(|| get_outer_crates(name, from_path, session)) 11 | } 12 | 13 | pub fn get_std_file(name: &str, session: &Session<'_>) -> Option { 14 | if let Some(ref std_path) = *RUST_SRC_PATH { 15 | // try lib/lib.rs, like in the rust source dir 16 | let cratelibname = format!("lib{}", name); 17 | let filepath = std_path.join(cratelibname).join("lib.rs"); 18 | if filepath.exists() || session.contains_file(&filepath) { 19 | return Some(filepath); 20 | } 21 | // If not found, try using the new standard library directory layout 22 | let filepath = std_path.join(name).join("src").join("lib.rs"); 23 | if filepath.exists() || session.contains_file(&filepath) { 24 | return Some(filepath); 25 | } 26 | } 27 | return None; 28 | } 29 | 30 | /// 2018 style crate name resolution 31 | pub fn search_crate_names( 32 | searchstr: &str, 33 | search_type: SearchType, 34 | file_path: &Path, 35 | only_2018: bool, 36 | session: &Session<'_>, 37 | ) -> Vec { 38 | let manifest_path = try_vec!(session.project_model.discover_project_manifest(file_path)); 39 | if only_2018 { 40 | let edition = session 41 | .project_model 42 | .edition(&manifest_path) 43 | .unwrap_or(Edition::Ed2015); 44 | if edition < Edition::Ed2018 { 45 | return Vec::new(); 46 | } 47 | } 48 | let hyphenated = searchstr.replace('_', "-"); 49 | let searchstr = searchstr.to_owned(); 50 | session 51 | .project_model 52 | .search_dependencies( 53 | &manifest_path, 54 | Box::new(move |libname| match search_type { 55 | SearchType::ExactMatch => libname == hyphenated || libname == searchstr, 56 | SearchType::StartsWith => { 57 | libname.starts_with(&hyphenated) || libname.starts_with(&searchstr) 58 | } 59 | }), 60 | ) 61 | .into_iter() 62 | .map(|(name, path)| { 63 | let name = name.replace('-', "_"); 64 | let raw_src = session.load_raw_file(&path); 65 | Match { 66 | matchstr: name, 67 | filepath: path, 68 | point: BytePos::ZERO, 69 | coords: Some(Coordinate::start()), 70 | local: false, 71 | mtype: MatchType::Crate, 72 | contextstr: String::new(), 73 | docs: matchers::find_mod_doc(&raw_src, BytePos::ZERO), 74 | } 75 | }) 76 | .collect() 77 | } 78 | 79 | /// get module file from current path & crate name 80 | pub fn get_module_file(name: &str, parentdir: &Path, session: &Session<'_>) -> Option { 81 | // try just .rs 82 | let filepath = parentdir.join(format!("{}.rs", name)); 83 | if filepath.exists() || session.contains_file(&filepath) { 84 | return Some(filepath); 85 | } 86 | // try /mod.rs 87 | let filepath = parentdir.join(name).join("mod.rs"); 88 | if filepath.exists() || session.contains_file(&filepath) { 89 | return Some(filepath); 90 | } 91 | None 92 | } 93 | 94 | /// try to get outer crates 95 | /// if we have dependencies in cache, use it. 96 | /// else, call cargo-metadata(default) or fall back to rls 97 | fn get_outer_crates(libname: &str, from_path: &Path, session: &Session<'_>) -> Option { 98 | debug!( 99 | "[get_outer_crates] lib name: {:?}, from_path: {:?}", 100 | libname, from_path 101 | ); 102 | 103 | let manifest = session.project_model.discover_project_manifest(from_path)?; 104 | let res = session.project_model.resolve_dependency(&manifest, libname); 105 | res 106 | } 107 | -------------------------------------------------------------------------------- /src/racer/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(feature = "nightly", feature(test))] 2 | #![feature(control_flow_enum)] 3 | #![feature(try_trait_v2)] 4 | #![feature(rustc_private)] 5 | 6 | #[macro_use] 7 | extern crate log; 8 | #[macro_use] 9 | extern crate lazy_static; 10 | #[macro_use] 11 | extern crate bitflags; 12 | 13 | #[macro_use] 14 | extern crate derive_more; 15 | 16 | extern crate rustc_ast; 17 | extern crate rustc_ast_pretty; 18 | extern crate rustc_data_structures; 19 | extern crate rustc_errors; 20 | extern crate rustc_parse; 21 | extern crate rustc_session; 22 | extern crate rustc_span; 23 | 24 | #[macro_use] 25 | mod testutils; 26 | #[macro_use] 27 | mod util; 28 | mod ast; 29 | mod ast_types; 30 | mod codecleaner; 31 | mod codeiter; 32 | mod core; 33 | mod fileres; 34 | mod matchers; 35 | #[cfg(feature = "metadata")] 36 | mod metadata; 37 | mod nameres; 38 | mod primitive; 39 | mod project_model; 40 | mod scopes; 41 | mod snippets; 42 | mod typeinf; 43 | 44 | pub use crate::ast_types::PathSearch; 45 | pub use crate::core::{ 46 | complete_from_file, complete_fully_qualified_name, find_definition, is_use_stmt, to_coords, 47 | to_point, 48 | }; 49 | pub use crate::core::{ 50 | BytePos, ByteRange, Coordinate, FileCache, FileLoader, Location, Match, MatchType, Session, 51 | }; 52 | pub use crate::primitive::PrimKind; 53 | pub use crate::project_model::{Edition, ProjectModelProvider}; 54 | pub use crate::snippets::snippet_for_match; 55 | pub use crate::util::expand_ident; 56 | 57 | pub use crate::util::{get_rust_src_path, RustSrcPathError}; 58 | 59 | #[cfg(all(feature = "nightly", test))] 60 | mod benches; 61 | -------------------------------------------------------------------------------- /src/racer/metadata.rs: -------------------------------------------------------------------------------- 1 | use lazycell; 2 | extern crate racer_cargo_metadata as metadata; 3 | use self::lazycell::LazyCell; 4 | use self::metadata::mapping::{Edition as Ed, PackageIdx, PackageMap}; 5 | use crate::project_model::{Edition, ProjectModelProvider}; 6 | use std::cell::Cell; 7 | use std::path::{Path, PathBuf}; 8 | 9 | struct MetadataCache { 10 | pkg_map: LazyCell, 11 | manifest_path: Option, 12 | failed_to_fill: Cell, 13 | } 14 | 15 | impl MetadataCache { 16 | fn new(manifest_path: Option) -> Self { 17 | MetadataCache { 18 | pkg_map: LazyCell::new(), 19 | manifest_path, 20 | failed_to_fill: Cell::new(false), 21 | } 22 | } 23 | fn fill_impl(&self, manifest: &Path) -> Result<(), ()> { 24 | let meta = metadata::run(manifest, true) 25 | .or_else(|e| { 26 | if let metadata::ErrorKind::Subprocess(ref s) = e { 27 | // HACK: if --frozen failed, try again without --frozen 28 | // see https://github.com/rust-lang/cargo/blob/master/src/cargo/ops/registry.rs#L344 29 | if s.contains("--frozen") { 30 | info!("MetadataCache: try again without --frozen"); 31 | return metadata::run(manifest, false); 32 | } 33 | } 34 | Err(e) 35 | }) 36 | .map_err(|e| { 37 | warn!("Error in cargo metadata: {}", e); 38 | })?; 39 | let pkg_map = PackageMap::from_metadata(meta); 40 | self.pkg_map.fill(pkg_map).map_err(|_| { 41 | warn!("Error in initialize lazy cell"); 42 | }) 43 | } 44 | fn fill(&self, manifest: &Path) { 45 | if !self.pkg_map.filled() && !self.failed_to_fill.get() { 46 | self.failed_to_fill.set(self.fill_impl(manifest).is_err()); 47 | } 48 | } 49 | fn setup(&self, manifest: &Path) -> Option<(&PackageMap, PackageIdx)> { 50 | self.fill(manifest); 51 | let pkg_map: &PackageMap = self.pkg_map.borrow().unwrap(); 52 | let idx = if manifest.is_relative() { 53 | let path = manifest.canonicalize().ok()?; 54 | pkg_map.get_idx(&path)? 55 | } else { 56 | pkg_map.get_idx(manifest)? 57 | }; 58 | Some((pkg_map, idx)) 59 | } 60 | } 61 | 62 | impl ProjectModelProvider for MetadataCache { 63 | fn edition(&self, manifest: &Path) -> Option { 64 | let (pkg_map, idx) = self.setup(manifest)?; 65 | let edition = pkg_map.get_edition(idx); 66 | Some(match edition { 67 | Ed::Ed2015 => Edition::Ed2015, 68 | Ed::Ed2018 => Edition::Ed2018, 69 | Ed::Ed2021 => Edition::Ed2021, 70 | }) 71 | } 72 | fn discover_project_manifest(&self, path: &Path) -> Option { 73 | let cur_manifest = metadata::find_manifest(path)?; 74 | let manifest = self.manifest_path.as_ref()?; 75 | self.fill(manifest); 76 | Some(cur_manifest) 77 | } 78 | fn search_dependencies( 79 | &self, 80 | manifest: &Path, 81 | search_fn: Box bool>, 82 | ) -> Vec<(String, PathBuf)> { 83 | let (pkg_map, idx) = match self.setup(manifest) { 84 | Some(x) => x, 85 | None => return vec![], 86 | }; 87 | let deps = pkg_map 88 | .get_dependencies(idx) 89 | .iter() 90 | .filter(|(s, _)| search_fn(s)) 91 | .map(|(s, p)| (s.to_string(), p.to_path_buf())); 92 | let lib = pkg_map 93 | .get_lib(idx) 94 | .filter(|t| search_fn(&t.name)) 95 | .map(|t| (t.name.to_string(), t.src_path.to_path_buf())); 96 | deps.chain(lib).collect() 97 | } 98 | fn resolve_dependency(&self, manifest: &Path, libname: &str) -> Option { 99 | debug!( 100 | "MetadataCache::resolve_dependency manifest: {:?} libname: {}", 101 | manifest, libname 102 | ); 103 | let (pkg_map, idx) = self.setup(manifest)?; 104 | pkg_map 105 | .get_src_path_from_libname(idx, libname) 106 | .or_else(|| { 107 | let hyphnated = libname.replace('_', "-"); 108 | pkg_map.get_src_path_from_libname(idx, &hyphnated) 109 | }) 110 | .or_else(|| { 111 | let target = pkg_map.get_lib(idx)?; 112 | if target.name.replace('-', "_") == libname { 113 | Some(&target.src_path) 114 | } else { 115 | None 116 | } 117 | }) 118 | .map(|p| p.to_owned()) 119 | } 120 | } 121 | 122 | pub fn project_model(project_path: Option<&Path>) -> Box { 123 | let manifest = project_path.and_then(|p| metadata::find_manifest(p)); 124 | Box::new(MetadataCache::new(manifest)) 125 | } 126 | -------------------------------------------------------------------------------- /src/racer/primitive.rs: -------------------------------------------------------------------------------- 1 | use crate::core::{BytePos, Match, MatchType, Namespace, SearchType, Session}; 2 | use crate::matchers::ImportInfo; 3 | use crate::nameres::{self, RUST_SRC_PATH}; 4 | use rustc_ast::ast::{IntTy, LitIntType, UintTy}; 5 | use std::path::PathBuf; 6 | 7 | const PRIM_DOC: &str = "std/src/primitive_docs.rs"; 8 | const KEY_DOC: &str = "std/src/keyword_docs.rs"; 9 | 10 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 11 | pub enum PrimKind { 12 | Bool, 13 | Never, 14 | Char, 15 | Unit, 16 | Pointer, 17 | Array, 18 | Slice, 19 | Str, 20 | Tuple, 21 | F32, 22 | F64, 23 | I8, 24 | I16, 25 | I32, 26 | I64, 27 | I128, 28 | U8, 29 | U16, 30 | U32, 31 | U64, 32 | U128, 33 | Isize, 34 | Usize, 35 | Ref, 36 | Fn, 37 | Await, 38 | } 39 | 40 | const PRIM_MATCHES: [PrimKind; 17] = [ 41 | PrimKind::Bool, 42 | PrimKind::Char, 43 | PrimKind::Str, 44 | PrimKind::F32, 45 | PrimKind::F64, 46 | PrimKind::I8, 47 | PrimKind::I16, 48 | PrimKind::I32, 49 | PrimKind::I64, 50 | PrimKind::I128, 51 | PrimKind::U8, 52 | PrimKind::U16, 53 | PrimKind::U32, 54 | PrimKind::U64, 55 | PrimKind::U128, 56 | PrimKind::Isize, 57 | PrimKind::Usize, 58 | ]; 59 | 60 | impl PrimKind { 61 | pub(crate) fn from_litint(lit: LitIntType) -> Self { 62 | match lit { 63 | LitIntType::Signed(i) => match i { 64 | IntTy::I8 => PrimKind::I8, 65 | IntTy::I16 => PrimKind::I16, 66 | IntTy::I32 => PrimKind::I32, 67 | IntTy::I64 => PrimKind::I64, 68 | IntTy::I128 => PrimKind::I128, 69 | IntTy::Isize => PrimKind::Isize, 70 | }, 71 | LitIntType::Unsigned(u) => match u { 72 | UintTy::U8 => PrimKind::U8, 73 | UintTy::U16 => PrimKind::U16, 74 | UintTy::U32 => PrimKind::U32, 75 | UintTy::U64 => PrimKind::U64, 76 | UintTy::U128 => PrimKind::U128, 77 | UintTy::Usize => PrimKind::Usize, 78 | }, 79 | LitIntType::Unsuffixed => PrimKind::U32, 80 | } 81 | } 82 | fn impl_files(self) -> Option<&'static [&'static str]> { 83 | match self { 84 | PrimKind::Bool => None, 85 | PrimKind::Never => None, 86 | PrimKind::Char => Some(&["core/src/char/methods.rs"]), 87 | PrimKind::Unit => None, 88 | PrimKind::Pointer => Some(&["core/src/ptr.rs"]), 89 | PrimKind::Array => None, 90 | PrimKind::Slice => Some(&["core/src/slice/mod.rs", "alloc/src/slice.rs"]), 91 | PrimKind::Str => Some(&["core/src/str/mod.rs", "alloc/src/str.rs"]), 92 | PrimKind::Tuple => None, 93 | PrimKind::F32 => Some(&["std/src/f32.rs", "core/src/num/f32.rs"]), 94 | PrimKind::F64 => Some(&["std/src/f64.rs", "core/src/num/f64.rs"]), 95 | PrimKind::I8 => Some(&["core/src/num/mod.rs"]), 96 | PrimKind::I16 => Some(&["core/src/num/mod.rs"]), 97 | PrimKind::I32 => Some(&["core/src/num/mod.rs"]), 98 | PrimKind::I64 => Some(&["core/src/num/mod.rs"]), 99 | PrimKind::I128 => Some(&["core/src/num/mod.rs"]), 100 | PrimKind::U8 => Some(&["core/src/num/mod.rs"]), 101 | PrimKind::U16 => Some(&["core/src/num/mod.rs"]), 102 | PrimKind::U32 => Some(&["core/src/num/mod.rs"]), 103 | PrimKind::U64 => Some(&["core/src/num/mod.rs"]), 104 | PrimKind::U128 => Some(&["core/src/num/mod.rs"]), 105 | PrimKind::Isize => Some(&["core/src/num/mod.rs"]), 106 | PrimKind::Usize => Some(&["core/src/num/mod.rs"]), 107 | PrimKind::Ref => None, 108 | PrimKind::Fn => None, 109 | PrimKind::Await => None, 110 | } 111 | } 112 | fn is_keyword(self) -> bool { 113 | match self { 114 | PrimKind::Await => true, 115 | _ => false, 116 | } 117 | } 118 | fn match_name(self) -> &'static str { 119 | match self { 120 | PrimKind::Bool => "bool", 121 | PrimKind::Never => "never", 122 | PrimKind::Char => "char", 123 | PrimKind::Unit => "unit", 124 | PrimKind::Pointer => "pointer", 125 | PrimKind::Array => "array", 126 | PrimKind::Slice => "slice", 127 | PrimKind::Str => "str", 128 | PrimKind::Tuple => "tuple", 129 | PrimKind::F32 => "f32", 130 | PrimKind::F64 => "f64", 131 | PrimKind::I8 => "i8", 132 | PrimKind::I16 => "i16", 133 | PrimKind::I32 => "i32", 134 | PrimKind::I64 => "i64", 135 | PrimKind::I128 => "i128", 136 | PrimKind::U8 => "u8", 137 | PrimKind::U16 => "u16", 138 | PrimKind::U32 => "u32", 139 | PrimKind::U64 => "u64", 140 | PrimKind::U128 => "u128", 141 | PrimKind::Isize => "isize", 142 | PrimKind::Usize => "usize", 143 | PrimKind::Ref => "ref", 144 | PrimKind::Fn => "fn", 145 | PrimKind::Await => "await", 146 | } 147 | } 148 | pub(crate) fn get_impl_files(&self) -> Option> { 149 | let src_path = RUST_SRC_PATH.as_ref()?; 150 | let impls = self.impl_files()?; 151 | Some(impls.iter().map(|file| src_path.join(file)).collect()) 152 | } 153 | pub fn to_module_match(self) -> Option { 154 | let _impl_files = self.impl_files()?; 155 | Some(Match { 156 | matchstr: self.match_name().to_owned(), 157 | filepath: PathBuf::new(), 158 | point: BytePos::ZERO, 159 | coords: None, 160 | local: false, 161 | mtype: MatchType::Builtin(self), 162 | contextstr: String::new(), 163 | docs: String::new(), 164 | }) 165 | } 166 | pub fn to_doc_match(self, session: &Session<'_>) -> Option { 167 | let src_path = RUST_SRC_PATH.as_ref()?; 168 | let (path, seg) = if self.is_keyword() { 169 | ( 170 | src_path.join(KEY_DOC), 171 | format!("{}_keyword", self.match_name()), 172 | ) 173 | } else { 174 | ( 175 | src_path.join(PRIM_DOC), 176 | format!("prim_{}", self.match_name()), 177 | ) 178 | }; 179 | let mut m = nameres::resolve_name( 180 | &seg.into(), 181 | &path, 182 | BytePos::ZERO, 183 | SearchType::ExactMatch, 184 | Namespace::Mod, 185 | session, 186 | &ImportInfo::default(), 187 | ) 188 | .into_iter() 189 | .next()?; 190 | m.mtype = MatchType::Builtin(self); 191 | m.matchstr = self.match_name().to_owned(); 192 | Some(m) 193 | } 194 | } 195 | 196 | pub fn get_primitive_docs( 197 | searchstr: &str, 198 | stype: SearchType, 199 | session: &Session<'_>, 200 | out: &mut Vec, 201 | ) { 202 | for prim in PRIM_MATCHES.iter() { 203 | let prim_str = prim.match_name(); 204 | if (stype == SearchType::StartsWith && prim_str.starts_with(searchstr)) 205 | || (stype == SearchType::ExactMatch && prim_str == searchstr) 206 | { 207 | if let Some(m) = prim.to_doc_match(session) { 208 | out.push(m); 209 | if stype == SearchType::ExactMatch { 210 | return; 211 | } 212 | } 213 | } 214 | } 215 | } 216 | 217 | pub fn get_primitive_mods(searchstr: &str, stype: SearchType, out: &mut Vec) { 218 | for prim in PRIM_MATCHES.iter() { 219 | let prim_str = prim.match_name(); 220 | if (stype == SearchType::StartsWith && prim_str.starts_with(searchstr)) 221 | || (stype == SearchType::ExactMatch && prim_str == searchstr) 222 | { 223 | if let Some(matches) = prim.to_module_match() { 224 | out.push(matches); 225 | if stype == SearchType::ExactMatch { 226 | return; 227 | } 228 | } 229 | } 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /src/racer/project_model.rs: -------------------------------------------------------------------------------- 1 | use std::path::{Path, PathBuf}; 2 | 3 | #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)] 4 | pub enum Edition { 5 | Ed2015, 6 | Ed2018, 7 | Ed2021, 8 | } 9 | 10 | pub trait ProjectModelProvider { 11 | fn edition(&self, manifest: &Path) -> Option; 12 | fn discover_project_manifest(&self, path: &Path) -> Option; 13 | fn search_dependencies( 14 | &self, 15 | manifest: &Path, 16 | search_fn: Box bool>, 17 | ) -> Vec<(String, PathBuf)>; 18 | fn resolve_dependency(&self, manifest: &Path, dep_name: &str) -> Option; 19 | } 20 | -------------------------------------------------------------------------------- /src/racer/snippets.rs: -------------------------------------------------------------------------------- 1 | use crate::ast::with_error_checking_parse; 2 | use crate::core::{Match, Session}; 3 | use crate::typeinf::get_function_declaration; 4 | 5 | use rustc_ast::ast::AssocItemKind; 6 | use rustc_parse::parser::ForceCollect; 7 | 8 | /// Returns completion snippets usable by some editors 9 | /// 10 | /// Generates a snippet string given a `Match`. The provided snippet contains 11 | /// substrings like "${1:name}" which some editors can use to quickly fill in 12 | /// arguments. 13 | /// 14 | /// # Examples 15 | /// 16 | /// ```no_run 17 | /// extern crate racer; 18 | /// 19 | /// use std::path::Path; 20 | /// 21 | /// let path = Path::new("."); 22 | /// let cache = racer::FileCache::default(); 23 | /// let session = racer::Session::new(&cache, Some(path)); 24 | /// 25 | /// let m = racer::complete_fully_qualified_name( 26 | /// "std::fs::canonicalize", 27 | /// &path, 28 | /// &session 29 | /// ).next().unwrap(); 30 | /// 31 | /// let snip = racer::snippet_for_match(&m, &session); 32 | /// assert_eq!(snip, "canonicalize(${1:path})"); 33 | /// ``` 34 | pub fn snippet_for_match(m: &Match, session: &Session<'_>) -> String { 35 | if m.mtype.is_function() { 36 | let method = get_function_declaration(m, session); 37 | if let Some(m) = MethodInfo::from_source_str(&method) { 38 | m.snippet() 39 | } else { 40 | "".into() 41 | } 42 | } else { 43 | m.matchstr.clone() 44 | } 45 | } 46 | 47 | struct MethodInfo { 48 | name: String, 49 | args: Vec, 50 | } 51 | 52 | impl MethodInfo { 53 | ///Parses method declaration as string and returns relevant data 54 | fn from_source_str(source: &str) -> Option { 55 | let trim: &[_] = &['\n', '\r', '{', ' ']; 56 | let decorated = format!("{} {{}}()", source.trim_end_matches(trim)); 57 | 58 | trace!("MethodInfo::from_source_str: {:?}", decorated); 59 | with_error_checking_parse(decorated, |p| { 60 | if let Ok(Some(Some(method))) = p.parse_impl_item(ForceCollect::No) { 61 | if let AssocItemKind::Fn(ref fn_kind) = method.kind { 62 | let decl = &fn_kind.sig.decl; 63 | return Some(MethodInfo { 64 | // ident.as_str calls Ident.name.as_str 65 | name: method.ident.name.to_string(), 66 | args: decl 67 | .inputs 68 | .iter() 69 | .map(|arg| { 70 | let source_map = &p.sess.source_map(); 71 | let var_name = match source_map.span_to_snippet(arg.pat.span) { 72 | Ok(name) => name, 73 | _ => "".into(), 74 | }; 75 | match source_map.span_to_snippet(arg.ty.span) { 76 | Ok(ref type_name) if !type_name.is_empty() => { 77 | format!("{}: {}", var_name, type_name) 78 | } 79 | _ => var_name, 80 | } 81 | }) 82 | .collect(), 83 | }); 84 | } 85 | } 86 | debug!("Unable to parse method declaration. |{}|", source); 87 | None 88 | }) 89 | } 90 | 91 | ///Returns completion snippets usable by some editors 92 | fn snippet(&self) -> String { 93 | format!( 94 | "{}({})", 95 | self.name, 96 | &self 97 | .args 98 | .iter() 99 | .filter(|&s| !s.ends_with("self")) 100 | .enumerate() 101 | .fold(String::new(), |cur, (i, ref s)| { 102 | let arg = format!("${{{}:{}}}", i + 1, s); 103 | let delim = if i > 0 { ", " } else { "" }; 104 | cur + delim + &arg 105 | }) 106 | ) 107 | } 108 | } 109 | 110 | #[test] 111 | fn method_info_test() { 112 | let info = MethodInfo::from_source_str("pub fn new() -> Vec").unwrap(); 113 | assert_eq!(info.name, "new"); 114 | assert_eq!(info.args.len(), 0); 115 | assert_eq!(info.snippet(), "new()"); 116 | 117 | let info = MethodInfo::from_source_str("pub fn reserve(&mut self, additional: usize)").unwrap(); 118 | assert_eq!(info.name, "reserve"); 119 | assert_eq!(info.args.len(), 2); 120 | // it looks odd, but no problme because what our clients see is only snippet 121 | assert_eq!(info.args[0], "&mut self: &mut self"); 122 | assert_eq!(info.snippet(), "reserve(${1:additional: usize})"); 123 | } 124 | -------------------------------------------------------------------------------- /src/racer/testutils.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | use crate::core::ByteRange; 3 | 4 | pub fn rejustify(src: &str) -> String { 5 | let s = &src[1..]; // remove the newline 6 | let mut sb = String::new(); 7 | for l in s.lines() { 8 | let tabless = &l[4..]; 9 | sb.push_str(tabless); 10 | if !tabless.is_empty() { 11 | sb.push_str("\n"); 12 | } 13 | } 14 | let newlen = sb.len() - 1; // remove the trailing newline 15 | sb.truncate(newlen); 16 | sb 17 | } 18 | 19 | pub fn slice(src: &str, range: ByteRange) -> &str { 20 | &src[range.to_range()] 21 | } 22 | -------------------------------------------------------------------------------- /test_project/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "arrayvec" 5 | version = "0.4.7" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | dependencies = [ 8 | "nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", 9 | ] 10 | 11 | [[package]] 12 | name = "cfg-if" 13 | version = "0.1.5" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | 16 | [[package]] 17 | name = "crossbeam-deque" 18 | version = "0.2.0" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | dependencies = [ 21 | "crossbeam-epoch 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 22 | "crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 23 | ] 24 | 25 | [[package]] 26 | name = "crossbeam-epoch" 27 | version = "0.3.1" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | dependencies = [ 30 | "arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", 31 | "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 32 | "crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 33 | "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 34 | "memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 35 | "nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", 36 | "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 37 | ] 38 | 39 | [[package]] 40 | name = "crossbeam-utils" 41 | version = "0.2.2" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | dependencies = [ 44 | "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 45 | ] 46 | 47 | [[package]] 48 | name = "either" 49 | version = "1.5.0" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | 52 | [[package]] 53 | name = "lazy_static" 54 | version = "1.1.0" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | dependencies = [ 57 | "version_check 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 58 | ] 59 | 60 | [[package]] 61 | name = "libc" 62 | version = "0.2.43" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | 65 | [[package]] 66 | name = "memoffset" 67 | version = "0.2.1" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | 70 | [[package]] 71 | name = "nodrop" 72 | version = "0.1.12" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | 75 | [[package]] 76 | name = "num_cpus" 77 | version = "1.8.0" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | dependencies = [ 80 | "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", 81 | ] 82 | 83 | [[package]] 84 | name = "rand" 85 | version = "0.5.4" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | dependencies = [ 88 | "rand_core 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 89 | ] 90 | 91 | [[package]] 92 | name = "rand_core" 93 | version = "0.2.1" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | 96 | [[package]] 97 | name = "rayon" 98 | version = "1.0.2" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | dependencies = [ 101 | "crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 102 | "either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 103 | "rayon-core 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 104 | ] 105 | 106 | [[package]] 107 | name = "rayon-core" 108 | version = "1.4.1" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | dependencies = [ 111 | "crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 112 | "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 113 | "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", 114 | "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 115 | ] 116 | 117 | [[package]] 118 | name = "scopeguard" 119 | version = "0.3.3" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | 122 | [[package]] 123 | name = "test-crate2" 124 | version = "0.1.0" 125 | 126 | [[package]] 127 | name = "test-crate3" 128 | version = "0.1.0" 129 | dependencies = [ 130 | "rand 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", 131 | "rayon 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 132 | ] 133 | 134 | [[package]] 135 | name = "test-crate4" 136 | version = "0.1.0" 137 | dependencies = [ 138 | "rayon 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 139 | "test-crate2 0.1.0", 140 | ] 141 | 142 | [[package]] 143 | name = "test_fixtures" 144 | version = "0.1.0" 145 | dependencies = [ 146 | "test-crate2 0.1.0", 147 | ] 148 | 149 | [[package]] 150 | name = "test_project" 151 | version = "0.1.0" 152 | dependencies = [ 153 | "test-crate2 0.1.0", 154 | "test_fixtures 0.1.0", 155 | ] 156 | 157 | [[package]] 158 | name = "version_check" 159 | version = "0.1.4" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | 162 | [metadata] 163 | "checksum arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a1e964f9e24d588183fcb43503abda40d288c8657dfc27311516ce2f05675aef" 164 | "checksum cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0c4e7bb64a8ebb0d856483e1e682ea3422f883c5f5615a90d51a2c82fe87fdd3" 165 | "checksum crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f739f8c5363aca78cfb059edf753d8f0d36908c348f3d8d1503f03d8b75d9cf3" 166 | "checksum crossbeam-epoch 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "927121f5407de9956180ff5e936fe3cf4324279280001cd56b669d28ee7e9150" 167 | "checksum crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2760899e32a1d58d5abb31129f8fae5de75220bc2176e77ff7c627ae45c918d9" 168 | "checksum either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3be565ca5c557d7f59e7cfcf1844f9e3033650c929c6566f511e8005f205c1d0" 169 | "checksum lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca488b89a5657b0a2ecd45b95609b3e848cf1755da332a0da46e2b2b1cb371a7" 170 | "checksum libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "76e3a3ef172f1a0b9a9ff0dd1491ae5e6c948b94479a3021819ba7d860c8645d" 171 | "checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3" 172 | "checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2" 173 | "checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30" 174 | "checksum rand 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "12397506224b2f93e6664ffc4f664b29be8208e5157d3d90b44f09b5fae470ea" 175 | "checksum rand_core 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "edecf0f94da5551fc9b492093e30b041a891657db7940ee221f9d2f66e82eef2" 176 | "checksum rayon 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "df7a791f788cb4c516f0e091301a29c2b71ef680db5e644a7d68835c8ae6dbfa" 177 | "checksum rayon-core 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b055d1e92aba6877574d8fe604a63c8b5df60f60e5982bf7ccbb1338ea527356" 178 | "checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" 179 | "checksum version_check 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7716c242968ee87e5542f8021178248f267f295a5c4803beae8b8b7fd9bc6051" 180 | -------------------------------------------------------------------------------- /test_project/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | publish = false 3 | name = "test_project" 4 | description = "crate for testing racer's functionalities" 5 | version = "0.1.0" 6 | authors = ["Joe Wilm "] 7 | edition = "2018" 8 | 9 | [dependencies] 10 | test_fixtures = { path = "./test_fixtures" } 11 | test-crate2 = { path = "./test-crate2"} 12 | 13 | [workspace] 14 | members = ["test-crate2", "test-crate3", "test_fixtures", "test-crate4"] 15 | -------------------------------------------------------------------------------- /test_project/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Root test_project for testing racer 2 | 3 | pub use test_crate2::useless_func; 4 | 5 | pub struct TestStruct { 6 | pub name: &'static str, 7 | pub number: usize, 8 | } 9 | 10 | impl TestStruct { 11 | pub fn new() -> Self { 12 | TestStruct { 13 | name: "test-struct", 14 | number: 0, 15 | } 16 | } 17 | } 18 | 19 | pub(crate) fn name() {} 20 | 21 | pub mod submod; 22 | pub mod submod2018; 23 | -------------------------------------------------------------------------------- /test_project/src/submod/foo.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/racer-rust/racer/a457c235b545c5251570cda8fec1edeab05ab1d4/test_project/src/submod/foo.rs -------------------------------------------------------------------------------- /test_project/src/submod/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod foo; 2 | -------------------------------------------------------------------------------- /test_project/src/submod2018.rs: -------------------------------------------------------------------------------- 1 | pub mod foo2018; 2 | -------------------------------------------------------------------------------- /test_project/src/submod2018/foo2018.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/racer-rust/racer/a457c235b545c5251570cda8fec1edeab05ab1d4/test_project/src/submod2018/foo2018.rs -------------------------------------------------------------------------------- /test_project/test-crate2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test-crate2" 3 | version = "0.1.0" 4 | authors = ["kngwyu "] 5 | workspace = ".." 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /test_project/test-crate2/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This is test crate for racer, 2 | //! to check racer can detect `test_crate2` target from`test-crate2` package! 3 | 4 | /// really useless function 5 | pub fn useless_func() {} 6 | -------------------------------------------------------------------------------- /test_project/test-crate3/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test-crate3" 3 | version = "0.1.0" 4 | authors = ["kngwyu "] 5 | description = "test crate for extern crate~" 6 | workspace = ".." 7 | 8 | [dependencies] 9 | rayon = "1.0" 10 | 11 | [dependencies.rand] 12 | version = "0.5" 13 | default-features = false 14 | features = [] -------------------------------------------------------------------------------- /test_project/test-crate3/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /test_project/test-crate4/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test-crate4" 3 | version = "0.1.0" 4 | authors = ["kngwyu "] 5 | edition = "2018" 6 | description = "Test crate for 2018 edition path" 7 | workspace = ".." 8 | 9 | [dependencies] 10 | rayon = "1.0" 11 | test-crate2 = { path = "../test-crate2" } 12 | -------------------------------------------------------------------------------- /test_project/test-crate4/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! To test rust 2018 features 2 | -------------------------------------------------------------------------------- /test_project/test_fixtures/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test_fixtures" 3 | version = "0.1.0" 4 | authors = ["jaxx"] 5 | workspace = ".." 6 | edition = "2018" 7 | 8 | [lib] 9 | name = "fixtures" 10 | path = "src/lib.rs" 11 | 12 | [dependencies] 13 | test-crate2 = { path = "../test-crate2"} 14 | -------------------------------------------------------------------------------- /test_project/test_fixtures/src/foo.rs: -------------------------------------------------------------------------------- 1 | pub fn test() { 2 | println!("Hello from test function"); 3 | } 4 | -------------------------------------------------------------------------------- /test_project/test_fixtures/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This is a test project for racer. 2 | //! 3 | //! # Example: 4 | //! Basic Usage. 5 | //! 6 | //! ``` 7 | //! extern crate fixtures; 8 | //! use fixtures::foo; 9 | //! fn main { 10 | //! println!("Racer") 11 | //! } 12 | //! ``` 13 | //! 14 | //! ## Notes: 15 | //! - We should check racer can parse rust doc style comments 16 | //! - and some comments... 17 | 18 | #[path = "submod/bar.rs"] 19 | pub mod bar; 20 | 21 | pub mod foo; 22 | 23 | pub use test_crate2::useless_func; 24 | 25 | pub type UsizeVec = Vec; 26 | -------------------------------------------------------------------------------- /test_project/test_fixtures/src/submod/bar.rs: -------------------------------------------------------------------------------- 1 | pub fn bartest() { 2 | println!("Hello from bar test function"); 3 | } 4 | -------------------------------------------------------------------------------- /tests/alias.rs: -------------------------------------------------------------------------------- 1 | use racer::Coordinate; 2 | use racer_testutils::*; 3 | 4 | #[test] 5 | fn completes_static_methods_for_aliased_struct() { 6 | let src = r#" 7 | mod mymod { 8 | pub struct St { 9 | mem1: String, 10 | mem2: usize, 11 | } 12 | impl St { 13 | pub fn new() -> Self { 14 | St { mem1: "a".to_owned(), mem2: 5 } 15 | } 16 | } 17 | } 18 | fn main() { 19 | use mymod::St as S; 20 | let s = S::ne~ 21 | } 22 | "#; 23 | assert_eq!(get_only_completion(src, None).matchstr, "new"); 24 | } 25 | 26 | #[test] 27 | fn completes_names_for_aliased_module() { 28 | let src = r#" 29 | mod mymod { 30 | pub struct St { 31 | mem1: String, 32 | mem2: usize, 33 | } 34 | impl St { 35 | pub fn new() -> Self { 36 | St { mem1: "a".to_owned(), mem2: 5 } 37 | } 38 | } 39 | } 40 | fn main() { 41 | use mymod as m; 42 | let s = m::St::ne~ 43 | } 44 | "#; 45 | assert_eq!(get_only_completion(src, None).matchstr, "new"); 46 | } 47 | 48 | #[test] 49 | fn finds_definition_of_use_as() { 50 | let src = r#" 51 | mod mymod { 52 | pub struct St { 53 | mem1: String, 54 | mem2: usize, 55 | } 56 | } 57 | fn main() { 58 | use mymod::St as S; 59 | let s = S~::new(); 60 | } 61 | "#; 62 | let got = get_definition(src, None); 63 | assert_eq!(got.matchstr, "S"); 64 | assert_eq!(got.coords.unwrap(), Coordinate::new(9, 29)); 65 | } 66 | 67 | #[test] 68 | fn finds_definition_of_use_self_as() { 69 | let src = r#" 70 | mod mymod { 71 | pub struct St { 72 | mem1: String, 73 | mem2: usize, 74 | } 75 | } 76 | fn main() { 77 | use mymod::{self as m, St}; 78 | let s = m~ 79 | } 80 | "#; 81 | let got = get_definition(src, None); 82 | assert_eq!(got.matchstr, "m"); 83 | assert_eq!(got.coords.unwrap(), Coordinate::new(9, 32)); 84 | } 85 | 86 | // moved from system.rs 87 | #[test] 88 | fn follows_use_as() { 89 | let src2 = " 90 | pub fn myfn() {} 91 | pub fn foo() {} 92 | "; 93 | let src = " 94 | use src2::myfn as myfoofn; 95 | mod src2; 96 | fn main() { 97 | my~foofn(); 98 | } 99 | "; 100 | 101 | let dir = TmpDir::new(); 102 | let _src2 = dir.write_file("src2.rs", src2); 103 | let got = get_definition(src, Some(dir)); 104 | assert_eq!(got.matchstr, "myfoofn"); 105 | assert_eq!(got.contextstr, "src2::myfn as myfoofn"); 106 | } 107 | 108 | // moved from system.rs 109 | /// Verifies fix for https://github.com/racer-rust/racer/issues/753 110 | #[test] 111 | fn follows_use_as_in_braces() { 112 | let src = " 113 | mod m { 114 | pub struct Wrapper { 115 | pub x: i32, 116 | } 117 | 118 | pub struct Second { 119 | pub y: i32, 120 | } 121 | } 122 | 123 | fn main() { 124 | use m::{Wrapper as Wpr, Second}; 125 | let _ = W~pr { x: 1 }; 126 | } 127 | "; 128 | 129 | let got = get_definition(src, None); 130 | assert_eq!(got.matchstr, "Wpr"); 131 | assert_eq!(got.contextstr, "Wrapper as Wpr"); 132 | } 133 | 134 | // moved from system.rs 135 | #[test] 136 | fn completes_for_type_alias() { 137 | let src = " 138 | mod inner { 139 | pub type Alias = MyType; 140 | pub struct MyType; 141 | impl MyType { 142 | pub fn method(&self) {} 143 | } 144 | } 145 | 146 | fn foo() -> inner::Alias { 147 | inner::MyType 148 | } 149 | 150 | fn main() { 151 | foo().~ 152 | } 153 | "; 154 | 155 | assert_eq!(get_only_completion(src, None).matchstr, "method"); 156 | } 157 | 158 | // for use_nested_groups 159 | // moved from system.rs 160 | #[test] 161 | fn follows_use_aliased_self() { 162 | let src = r" 163 | use std::collections::{self as col, hash_map::*, HashMap}; 164 | fn main() { 165 | let heap = col::BinaryHeap::ne~w(); 166 | } 167 | "; 168 | 169 | let got = get_definition(src, None); 170 | assert_eq!(got.matchstr, "new"); 171 | } 172 | 173 | #[test] 174 | fn completes_for_alias_with_resolved_generics() { 175 | let src = " 176 | type Svec = Vec; 177 | fn main() { 178 | let v = Svec::new(); 179 | v[0].capa~ 180 | } 181 | "; 182 | assert_eq!(get_only_completion(src, None).matchstr, "capacity"); 183 | } 184 | 185 | #[test] 186 | fn completes_for_alias_with_type_param() { 187 | let src = " 188 | struct MyError; 189 | impl MyError { 190 | fn method(&self) {} 191 | } 192 | type Result = Result; 193 | fn fail() -> Result { 194 | Err(MyError) 195 | } 196 | fn main() { 197 | if let Err(e) = fail() { 198 | e.meth~ 199 | } 200 | } 201 | "; 202 | assert_eq!(get_only_completion(src, None).matchstr, "method"); 203 | } 204 | -------------------------------------------------------------------------------- /tests/async.rs: -------------------------------------------------------------------------------- 1 | use racer_testutils::*; 2 | 3 | #[test] 4 | fn completes_async_fn() { 5 | let src = r#" 6 | async fn say_hey() { 7 | println!("Hey!") 8 | } 9 | fn main() { 10 | say_h~ 11 | } 12 | "#; 13 | let got = get_only_completion(src, None); 14 | assert_eq!(got.matchstr, "say_hey"); 15 | } 16 | 17 | #[test] 18 | fn completes_poll() { 19 | let src = r#" 20 | async fn say_hey() { 21 | println!("Hey!") 22 | } 23 | async fn waiting_for() { 24 | let handle = say_hey(); 25 | handle.po~ 26 | } 27 | "#; 28 | let got = get_only_completion(src, None); 29 | assert_eq!(got.matchstr, "poll"); 30 | } 31 | 32 | #[test] 33 | fn completes_await() { 34 | let src = r#" 35 | async fn say_hey() { 36 | println!("Hey!") 37 | } 38 | async fn waiting_for() { 39 | let handle = say_hey(); 40 | handle.awa~ 41 | } 42 | "#; 43 | let got = get_only_completion(src, None); 44 | assert_eq!(got.matchstr, "await"); 45 | } 46 | 47 | #[test] 48 | fn completion_in_async_block() { 49 | let src = r#" 50 | fn main() { 51 | async { 52 | println~ 53 | } 54 | }"#; 55 | assert_eq!(get_definition(src, None).matchstr, "println!"); 56 | } 57 | -------------------------------------------------------------------------------- /tests/binary.rs: -------------------------------------------------------------------------------- 1 | use racer_testutils::*; 2 | 3 | #[test] 4 | fn completes_fields_for_binary_operators() { 5 | let src = r" 6 | use std::ops::Add; 7 | struct Left { 8 | x: i32, 9 | y: i32, 10 | } 11 | impl Add for Left { 12 | type Output = Left; 13 | fn add(self, other: Left) -> Self::Output { 14 | Left { 15 | x: self.x + other.x, 16 | y: self.y + other.y, 17 | } 18 | } 19 | } 20 | fn main() { 21 | let a = Left { x: 1, y: 0 } + Left { x: 2, y: 3 }; 22 | a.~ 23 | } 24 | "; 25 | let got = get_all_completions(src, None); 26 | assert!(got.iter().any(|ma| ma.matchstr == "x")); 27 | assert!(got.iter().any(|ma| ma.matchstr == "y")); 28 | assert!(got.iter().any(|ma| ma.matchstr == "add")); 29 | } 30 | 31 | #[test] 32 | fn completes_binary_operators_for_different_types() { 33 | let src = r" 34 | use std::ops::Sub; 35 | struct Left { 36 | x: i32, 37 | y: i32, 38 | } 39 | struct Right { 40 | x: i32, 41 | y: i32 42 | } 43 | impl Sub for Left { 44 | type Output = Left; 45 | fn sub(self, other: Right) -> Self::Output { 46 | Left { 47 | x: self.x - other.x, 48 | y: self.y - other.y, 49 | } 50 | } 51 | } 52 | fn main() { 53 | let a = Left { x: 1, y: 0 } - Right { x: 2, y: 3 }; 54 | a.~ 55 | } 56 | "; 57 | let got = get_all_completions(src, None); 58 | assert!(got.iter().any(|ma| ma.matchstr == "x")); 59 | assert!(got.iter().any(|ma| ma.matchstr == "y")); 60 | assert!(got.iter().any(|ma| ma.matchstr == "sub")); 61 | } 62 | 63 | #[test] 64 | fn test_operator_precedence_given_to_type_on_left() { 65 | let src = r" 66 | use std::ops::Sub; 67 | struct Left { 68 | x: i32, 69 | y: i32, 70 | } 71 | struct Right { 72 | x: i32, 73 | y: i32 74 | } 75 | struct Foo; 76 | impl Foo { 77 | fn foo(&self) -> Option { 78 | Some(33) 79 | } 80 | } 81 | struct Bar; 82 | impl Bar { 83 | fn bar(&self) -> Option { 84 | Some(33) 85 | } 86 | } 87 | impl Sub for Left { 88 | type Output = Foo; 89 | fn sub(self, other: Right) -> Self::Output { 90 | Foo 91 | } 92 | } 93 | impl Sub for Right { 94 | type Output = Bar; 95 | fn sub(self, other: Right) -> Self::Output { 96 | Bar 97 | } 98 | } 99 | fn main() { 100 | let a = Left { x: 1, y: 0 } - Right { x: 2, y: 3 }; 101 | a.~ 102 | } 103 | "; 104 | let got = get_one_completion(src, None); 105 | assert_eq!(got.matchstr, "foo"); 106 | } 107 | 108 | #[test] 109 | fn completes_if_operator_trait_is_not_explicit() { 110 | let src = r" 111 | use std::ops::Sub; 112 | struct Left { 113 | x: i32, 114 | y: i32, 115 | } 116 | struct Bar; 117 | impl Bar { 118 | fn bar(&self) -> Option { 119 | Some(33) 120 | } 121 | } 122 | impl Sub for Left { 123 | type Output = Bar; 124 | fn sub(self, other: Left) -> Self::Output { 125 | Bar 126 | } 127 | } 128 | fn main() { 129 | let a = Left { x: 1, y: 0 } - Left { x: 2, y: 3 }; 130 | a.~ 131 | } 132 | "; 133 | let got = get_one_completion(src, None); 134 | assert_eq!(got.matchstr, "bar"); 135 | } 136 | -------------------------------------------------------------------------------- /tests/consts.rs: -------------------------------------------------------------------------------- 1 | use racer_testutils::*; 2 | 3 | #[test] 4 | fn completes_methods_for_consts() { 5 | let src = r#" 6 | const FILE: &'static str = "main.rs"; 7 | fn main() { 8 | FILE.chars~ 9 | } 10 | "#; 11 | assert_eq!(get_only_completion(src, None).matchstr, "chars"); 12 | } 13 | 14 | #[test] 15 | fn completes_methods_for_const_array() { 16 | let src = r#" 17 | const VECTOR: [f64; 2] = [0.0, 0.0]; 18 | fn main() { 19 | VECTOR[0].atan2~ 20 | } 21 | "#; 22 | assert_eq!(get_only_completion(src, None).matchstr, "atan2"); 23 | } 24 | 25 | #[test] 26 | fn completes_methods_for_static() { 27 | let src = r#" 28 | static FILE: &'static str = "main.rs"; 29 | fn main() { 30 | FILE.chars~ 31 | } 32 | "#; 33 | assert_eq!(get_only_completion(src, None).matchstr, "chars"); 34 | } 35 | -------------------------------------------------------------------------------- /tests/external.rs: -------------------------------------------------------------------------------- 1 | use racer_testutils::*; 2 | 3 | #[test] 4 | fn completes_pub_fn_from_local_package() { 5 | let src = " 6 | extern crate fixtures; 7 | 8 | use fixtures::foo; 9 | 10 | fn main() { 11 | let x = foo::~ 12 | } 13 | "; 14 | 15 | with_test_project(|dir| { 16 | let srcdir = dir.nested_dir("src"); 17 | let got = get_one_completion(src, Some(srcdir)); 18 | assert_eq!("test", got.matchstr); 19 | }) 20 | } 21 | 22 | #[test] 23 | fn completes_pub_fn_from_local_submodule_package() { 24 | let src = " 25 | extern crate fixtures; 26 | 27 | use fixtures::bar; 28 | 29 | fn main() { 30 | let x = bar::~ 31 | } 32 | "; 33 | 34 | with_test_project(|dir| { 35 | let srcdir = dir.nested_dir("src"); 36 | let got = get_one_completion(src, Some(srcdir)); 37 | assert_eq!("bartest", got.matchstr); 38 | }) 39 | } 40 | 41 | #[test] 42 | fn follows_use_local_package() { 43 | let src = " 44 | extern crate fixtures; 45 | 46 | use fixtures::~ 47 | "; 48 | 49 | with_test_project(|dir| { 50 | let srcdir = dir.nested_dir("src"); 51 | let got = get_all_completions(src, Some(srcdir)); 52 | assert!(got.into_iter().any(|ma| ma.matchstr == "foo")); 53 | }) 54 | } 55 | 56 | #[test] 57 | fn follows_use_local_package_2018() { 58 | let src = " 59 | use test_crat~ 60 | "; 61 | 62 | with_test_project(|dir| { 63 | let cratedir = dir.nested_dir("test-crate4"); 64 | let testdir = cratedir.nested_dir("tests"); 65 | let got = get_all_completions(src, Some(testdir)); 66 | assert!(got.into_iter().any(|ma| ma.matchstr == "test_crate4")); 67 | }) 68 | } 69 | 70 | #[test] 71 | fn finds_extern_crate() { 72 | let src = " 73 | extern crate fixtures; 74 | f~ixtures 75 | "; 76 | 77 | with_test_project(|dir| { 78 | let srcdir = dir.nested_dir("src"); 79 | let got = get_definition(src, Some(srcdir)); 80 | assert_eq!(got.matchstr, "fixtures"); 81 | }) 82 | } 83 | 84 | // regression test for #800 85 | #[test] 86 | fn completes_typedef_in_external_crate() { 87 | let src = " 88 | extern crate fixtures; 89 | 90 | use fixtures::Usize~ 91 | "; 92 | 93 | with_test_project(|dir| { 94 | let srcdir = dir.nested_dir("src"); 95 | let got = get_only_completion(src, Some(srcdir)); 96 | assert_eq!(got.matchstr, "UsizeVec"); 97 | assert_eq!(got.contextstr, "pub type UsizeVec = Vec;"); 98 | }) 99 | } 100 | 101 | #[test] 102 | fn follows_external_re_export() { 103 | let src = " 104 | extern crate rayon; 105 | fn main() { 106 | rayon::sco~ 107 | } 108 | "; 109 | with_test_project(|dir| { 110 | let src_dir = dir.nested_dir("test-crate3").nested_dir("src"); 111 | let got = get_only_completion(src, Some(src_dir)); 112 | assert_eq!(got.matchstr, "scope"); 113 | }); 114 | } 115 | 116 | #[test] 117 | fn follows_rand_crate() { 118 | let src = " 119 | extern crate rand; 120 | use rand::{Rng, thread_rng}; 121 | fn main() { 122 | let mut rng: Box = Box::new(thread_rng()); 123 | rng.gen_rang~ 124 | } 125 | "; 126 | with_test_project(|dir| { 127 | let src_dir = dir.nested_dir("test-crate3").nested_dir("src"); 128 | let got = get_only_completion(src, Some(src_dir)); 129 | assert_eq!(got.matchstr, "gen_range"); 130 | }); 131 | } 132 | 133 | // For issue 826 134 | #[test] 135 | fn find_crate_doc() { 136 | let src = " 137 | extern crate fixtures; 138 | use fixtur~ 139 | "; 140 | let doc_str = r#"This is a test project for racer. 141 | 142 | # Example: 143 | Basic Usage. 144 | 145 | ``` 146 | extern crate fixtures; 147 | use fixtures::foo; 148 | fn main { 149 | println!("Racer") 150 | } 151 | ``` 152 | 153 | ## Notes: 154 | - We should check racer can parse rust doc style comments 155 | - and some comments..."#; 156 | with_test_project(|dir| { 157 | let srcdir = dir.nested_dir("src"); 158 | let got = get_one_completion(src, Some(srcdir)); 159 | assert_eq!(doc_str, got.docs); 160 | }) 161 | } 162 | 163 | // test for re-export 164 | #[test] 165 | fn follows_use_for_reexport() { 166 | let src = " 167 | extern crate fixtures; 168 | 169 | use fixtures::use~; 170 | "; 171 | with_test_project(|dir| { 172 | let srcdir = dir.nested_dir("src"); 173 | let got = get_only_completion(src, Some(srcdir)); 174 | assert_eq!(got.matchstr, "useless_func"); 175 | }) 176 | } 177 | 178 | #[test] 179 | fn completes_cratename_without_extern() { 180 | let src = " 181 | fn main() { 182 | rayon::sco~ 183 | } 184 | "; 185 | with_test_project(|dir| { 186 | let src_dir = dir.nested_dir("test-crate4").nested_dir("src"); 187 | let got = get_only_completion(src, Some(src_dir)); 188 | assert_eq!(got.matchstr, "scope"); 189 | }); 190 | } 191 | 192 | #[test] 193 | fn doesnt_complete_cratename_without_extern_in_2015() { 194 | let src = " 195 | fn main() { 196 | rayon::sco~ 197 | } 198 | "; 199 | with_test_project(|dir| { 200 | let src_dir = dir.nested_dir("test-crate3").nested_dir("src"); 201 | let got = get_all_completions(src, Some(src_dir)); 202 | assert!(got.is_empty()); 203 | }); 204 | } 205 | 206 | #[test] 207 | fn complete_extern_crate() { 208 | let src = " 209 | extern crate ray~ 210 | "; 211 | with_test_project(|dir| { 212 | let src_dir = dir.nested_dir("test-crate3").nested_dir("src"); 213 | let got = get_only_completion(src, Some(src_dir)); 214 | assert_eq!(got.matchstr, "rayon"); 215 | }); 216 | } 217 | -------------------------------------------------------------------------------- /tests/follow_self.rs: -------------------------------------------------------------------------------- 1 | use racer_testutils::*; 2 | 3 | #[test] 4 | fn follows_self_to_method() { 5 | let src = " 6 | struct Foo; 7 | impl Bar for Foo { 8 | pub fn method(self) { 9 | } 10 | 11 | pub fn another_method(self, feio: uint) { 12 | self.met~hod() 13 | } 14 | }"; 15 | 16 | let got = get_definition(src, None); 17 | assert_eq!("method", got.matchstr); 18 | } 19 | 20 | #[test] 21 | fn follows_self_to_method_in_vis_crate() { 22 | let src = " 23 | struct Foo; 24 | impl Bar for Foo { 25 | pub fn method(self) { 26 | } 27 | 28 | crate fn another_method(self, feio: uint) { 29 | self.met~hod() 30 | } 31 | }"; 32 | 33 | let got = get_definition(src, None); 34 | assert_eq!("method", got.matchstr); 35 | } 36 | 37 | #[test] 38 | fn follows_self_to_method_when_call_on_new_line() { 39 | let src = " 40 | struct Foo; 41 | impl Bar for Foo { 42 | pub fn method(self) -> Foo { 43 | } 44 | 45 | pub fn another_method(self, feio: uint) { 46 | self.method() 47 | .met~hod() 48 | } 49 | }"; 50 | 51 | let got = get_definition(src, None); 52 | assert_eq!("method", got.matchstr); 53 | } 54 | 55 | #[test] 56 | fn follows_self_to_trait_method() { 57 | let src = " 58 | trait Bar { 59 | fn method(self) { 60 | } 61 | fn another_method(self) { 62 | self.met~hod() 63 | } 64 | }"; 65 | 66 | let got = get_definition(src, None); 67 | assert_eq!("method", got.matchstr); 68 | } 69 | -------------------------------------------------------------------------------- /tests/for_arg.rs: -------------------------------------------------------------------------------- 1 | use racer_testutils::*; 2 | 3 | #[test] 4 | fn completes_methods_for_for_arg() { 5 | let src = " 6 | fn main() { 7 | let v: Vec = vec![]; 8 | for s in v { 9 | s.ca~ 10 | } 11 | } 12 | "; 13 | let got = get_only_completion(src, None); 14 | assert_eq!(got.matchstr, "capacity"); 15 | } 16 | 17 | #[test] 18 | fn completes_methods_for_ref_for_arg() { 19 | let src = " 20 | #[derive(Copy, Clone)] 21 | struct Usize(usize); 22 | impl Usize { fn method(&self) {} } 23 | fn main() { 24 | let v: Vec<&Usize> = vec![]; 25 | for &u in v { 26 | u.me~ 27 | } 28 | } 29 | "; 30 | let got = get_only_completion(src, None); 31 | assert_eq!(got.matchstr, "method"); 32 | } 33 | 34 | #[test] 35 | fn completes_methods_for_tupled_for_arg() { 36 | let src = " 37 | fn main() { 38 | let v: Vec<(String, usize)> = vec![]; 39 | for (s, i) in v { 40 | s.ca~ 41 | } 42 | } 43 | "; 44 | let got = get_only_completion(src, None); 45 | assert_eq!(got.matchstr, "capacity"); 46 | } 47 | 48 | #[test] 49 | fn completes_methods_for_tupls_for_arg() { 50 | let src = " 51 | fn main() { 52 | struct St(String); 53 | let v: Vec = vec![]; 54 | for St(s) in v { 55 | s.cap~ 56 | } 57 | } 58 | "; 59 | let got = get_only_completion(src, None); 60 | assert_eq!(got.matchstr, "capacity"); 61 | } 62 | 63 | #[test] 64 | fn completes_methods_for_complex_tupled_for_arg() { 65 | let src = " 66 | fn main() { 67 | struct St(String); 68 | let v: Vec<(String, St, usize)> = vec![]; 69 | for (s1, St(s2), i) in v { 70 | s2.ca~ 71 | } 72 | } 73 | "; 74 | let got = get_only_completion(src, None); 75 | assert_eq!(got.matchstr, "capacity"); 76 | let src = " 77 | #[derive(Copy, Clone)] 78 | struct Usize(usize); 79 | impl Usize { fn method(&self) {} } 80 | fn main() { 81 | let v: Vec<(String, &Usize)> = vec![]; 82 | for (s, &u) in v { 83 | u.me~ 84 | } 85 | } 86 | "; 87 | let got = get_only_completion(src, None); 88 | assert_eq!(got.matchstr, "method"); 89 | } 90 | 91 | // blocked by #946 92 | #[test] 93 | #[ignore] 94 | fn completes_methods_for_struct_for_arg() { 95 | let src = " 96 | fn main() { 97 | struct St { 98 | name: String, 99 | num: usize, 100 | __guard: (), 101 | } 102 | let v: Vec = vec![]; 103 | for St { name, num: n, .. } in v { 104 | name.cap~ 105 | } 106 | } 107 | "; 108 | let got = get_only_completion(src, None); 109 | assert_eq!(got.matchstr, "capacity"); 110 | } 111 | -------------------------------------------------------------------------------- /tests/if_let.rs: -------------------------------------------------------------------------------- 1 | use racer_testutils::*; 2 | 3 | #[test] 4 | fn enum_with_type_annotation() { 5 | let src = " 6 | fn main() { 7 | let s: Option = None; 8 | if let Some(s) = s{ 9 | s.capa~ 10 | } 11 | } 12 | "; 13 | let got = get_only_completion(src, None); 14 | assert_eq!(got.matchstr, "capacity"); 15 | } 16 | 17 | #[test] 18 | fn enum_variant_with_path() { 19 | let src = " 20 | fn main() { 21 | let s = Some(String::new()); 22 | if let Some(s) = s{ 23 | s.capa~ 24 | } 25 | } 26 | "; 27 | let got = get_only_completion(src, None); 28 | assert_eq!(got.matchstr, "capacity"); 29 | } 30 | 31 | #[test] 32 | fn enum_variant_with_tuple() { 33 | let src = " 34 | fn main() { 35 | let s = Some((String::new(), 0u32)); 36 | if let Some((s, _)) = s{ 37 | s.capa~ 38 | } 39 | } 40 | "; 41 | let got = get_only_completion(src, None); 42 | assert_eq!(got.matchstr, "capacity"); 43 | } 44 | -------------------------------------------------------------------------------- /tests/index.rs: -------------------------------------------------------------------------------- 1 | use racer_testutils::*; 2 | 3 | #[test] 4 | fn completes_methods_for_vec_index() { 5 | let src = " 6 | fn main() { 7 | let v: Vec = vec![]; 8 | v[0].capa~ 9 | } 10 | "; 11 | let got = get_only_completion(src, None); 12 | assert_eq!(got.matchstr, "capacity"); 13 | } 14 | 15 | #[test] 16 | fn completes_methods_for_vecdeque_index() { 17 | let src = " 18 | use std::collections::VecDeque; 19 | fn main() { 20 | let v: VecDeque = f(); 21 | v[0].capa~ 22 | } 23 | "; 24 | let got = get_only_completion(src, None); 25 | assert_eq!(got.matchstr, "capacity"); 26 | } 27 | -------------------------------------------------------------------------------- /tests/module_structure.rs: -------------------------------------------------------------------------------- 1 | use racer_testutils::*; 2 | 3 | #[test] 4 | fn finds_old_type_of_submodule() { 5 | let src = " 6 | use submod::f~oo; 7 | "; 8 | 9 | with_test_project(|dir| { 10 | let srcdir = dir.nested_dir("src"); 11 | let got = get_definition(src, Some(srcdir)); 12 | assert_eq!(got.matchstr, "foo"); 13 | assert!(got.filepath.ends_with("src/submod/foo.rs")); 14 | }) 15 | } 16 | 17 | #[test] 18 | fn finds_old_type_of_module() { 19 | let src = " 20 | use s~ubmod::foo; 21 | "; 22 | 23 | with_test_project(|dir| { 24 | let srcdir = dir.nested_dir("src"); 25 | let got = get_definition(src, Some(srcdir)); 26 | assert_eq!(got.matchstr, "submod"); 27 | assert!(got.filepath.ends_with("src/submod/mod.rs")); 28 | }) 29 | } 30 | 31 | #[test] 32 | fn finds_new_type_of_module() { 33 | let src = " 34 | use s~ubmod2018::foo2018; 35 | "; 36 | 37 | with_test_project(|dir| { 38 | let srcdir = dir.nested_dir("src"); 39 | let got = get_definition(src, Some(srcdir)); 40 | assert_eq!(got.matchstr, "submod2018"); 41 | assert!(got.filepath.ends_with("src/submod2018.rs")); 42 | }) 43 | } 44 | 45 | #[test] 46 | fn finds_new_type_of_submodule() { 47 | let src = " 48 | use submod2018::f~oo2018; 49 | "; 50 | 51 | with_test_project(|dir| { 52 | let srcdir = dir.nested_dir("src"); 53 | let got = get_definition(src, Some(srcdir)); 54 | assert_eq!(got.matchstr, "foo2018"); 55 | assert!(got.filepath.ends_with("src/submod2018/foo2018.rs")); 56 | }) 57 | } 58 | -------------------------------------------------------------------------------- /tests/primitive.rs: -------------------------------------------------------------------------------- 1 | use racer_testutils::*; 2 | 3 | #[test] 4 | fn finds_def_of_i64() { 5 | let src = " 6 | fn main() { 7 | let a: i64~ = 0; 8 | } 9 | "; 10 | let got = get_definition(src, None); 11 | assert_eq!(got.matchstr, "i64", "{:?}", got); 12 | } 13 | 14 | #[test] 15 | fn get_def_of_str_method() { 16 | let src = r#" 17 | fn check() { 18 | "hello".to_lowerca~se(); 19 | } 20 | "#; 21 | 22 | let got = get_definition(src, None); 23 | assert_eq!("to_lowercase", got.matchstr); 24 | } 25 | 26 | #[test] 27 | fn completes_liballoc_method_for_str() { 28 | let src = r#" 29 | fn in_let() { 30 | let foo = "hello"; 31 | foo.to_lowerc~ 32 | } 33 | "#; 34 | 35 | let got = get_only_completion(src, None); 36 | assert_eq!(got.matchstr, "to_lowercase"); 37 | } 38 | 39 | #[test] 40 | fn completes_libcore_method_for_str() { 41 | let src = r#" 42 | fn in_let() { 43 | let foo = "hello"; 44 | foo.le~ 45 | } 46 | "#; 47 | 48 | let got = get_only_completion(src, None); 49 | assert_eq!(got.matchstr, "len"); 50 | } 51 | 52 | // Experimental featrue 53 | #[test] 54 | fn completes_tuple_field() { 55 | let src = " 56 | fn foo() -> (usize, usize) { (1, 2) } 57 | fn main() { 58 | foo().~ 59 | } 60 | "; 61 | let got = get_all_completions(src, None); 62 | assert!(got 63 | .into_iter() 64 | .all(|ma| ma.matchstr == "0" || ma.matchstr == "1")) 65 | } 66 | 67 | #[test] 68 | fn completes_methods_for_char() { 69 | let src = r#" 70 | fn main() { 71 | 'c'.is_upperc~ 72 | } 73 | "#; 74 | 75 | let got = get_only_completion(src, None); 76 | assert_eq!(got.matchstr, "is_uppercase"); 77 | } 78 | 79 | #[test] 80 | fn completes_slice_methods_for_array() { 81 | let src = r#" 82 | fn main() { 83 | [1, 2, 3].split_mu~ 84 | } 85 | "#; 86 | 87 | let got = get_only_completion(src, None); 88 | assert_eq!(got.matchstr, "split_mut"); 89 | } 90 | 91 | #[test] 92 | fn completes_methods_for_slice() { 93 | let src = r#" 94 | fn slice() -> &'static [usize] { 95 | &[1, 2, 3] 96 | } 97 | fn main() { 98 | let s = slice(); 99 | s.split_first_m~ 100 | } 101 | "#; 102 | 103 | let got = get_only_completion(src, None); 104 | assert_eq!(got.matchstr, "split_first_mut"); 105 | } 106 | 107 | #[test] 108 | fn completes_slice_methods_for_vec() { 109 | let src = r#" 110 | fn main() { 111 | let v = vec![]; 112 | v.split_first_m~ 113 | } 114 | "#; 115 | 116 | let got = get_only_completion(src, None); 117 | assert_eq!(got.matchstr, "split_first_mut"); 118 | } 119 | -------------------------------------------------------------------------------- /tests/struct_field.rs: -------------------------------------------------------------------------------- 1 | use racer_testutils::*; 2 | 3 | #[test] 4 | fn follows_struct_field_in_constructor() { 5 | let src = " 6 | pub struct UserData { 7 | name: String, 8 | id: usize, 9 | } 10 | fn main() { 11 | UserData { 12 | na~ 13 | } 14 | }"; 15 | assert_eq!(get_only_completion(src, None).matchstr, "name"); 16 | } 17 | 18 | #[test] 19 | fn follows_struct_field_for_typedef() { 20 | let src = " 21 | pub struct UserData { 22 | name: String, 23 | id: usize, 24 | } 25 | type U = UserData; 26 | fn main() { 27 | U { 28 | na~ 29 | } 30 | }"; 31 | assert_eq!(get_only_completion(src, None).matchstr, "name"); 32 | } 33 | 34 | #[test] 35 | fn follows_struct_field_for_use_as() { 36 | let src = " 37 | mod m { 38 | pub struct UserData { 39 | name: String, 40 | id: usize, 41 | } 42 | } 43 | fn main() { 44 | use m::UserData as U; 45 | U { 46 | na~ 47 | } 48 | }"; 49 | assert_eq!(get_only_completion(src, None).matchstr, "name"); 50 | } 51 | 52 | #[test] 53 | fn follows_enum_variant_field_in_constructor() { 54 | let src = " 55 | enum UserData { 56 | Type1 { name: String, id: usize }, 57 | Type2, 58 | } 59 | fn main() { 60 | UserData::Type1 { nam~ } 61 | }"; 62 | assert_eq!(get_only_completion(src, None).matchstr, "name"); 63 | } 64 | 65 | #[test] 66 | fn follows_struct_field_in_let() { 67 | let src = " 68 | struct UserData { 69 | name: String, 70 | id: usize, 71 | } 72 | fn main() { 73 | let UserData { id, nam~ } = f(); 74 | }"; 75 | assert_eq!(get_only_completion(src, None).matchstr, "name"); 76 | } 77 | 78 | #[test] 79 | fn follows_struct_field_in_if_let() { 80 | let src = " 81 | struct UserData { 82 | name: String, 83 | id: usize, 84 | } 85 | fn main() { 86 | if let UserData { id, nam~ } = f() { 87 | 88 | } 89 | }"; 90 | assert_eq!(get_only_completion(src, None).matchstr, "name"); 91 | } 92 | 93 | #[test] 94 | fn finds_struct_field_in_constructor() { 95 | let src = r#" 96 | struct UserData { 97 | name: String, 98 | id: usize, 99 | } 100 | fn main() { 101 | UserData { 102 | name: "abc".to_owned(), 103 | i~d: 104 | } 105 | }"#; 106 | assert_eq!(get_definition(src, None).matchstr, "id"); 107 | } 108 | 109 | #[test] 110 | fn dont_find_struct_field_in_unsafe() { 111 | let src = r#" 112 | fn main() { 113 | unsafe { 114 | println~ 115 | } 116 | }"#; 117 | assert_eq!(get_definition(src, None).matchstr, "println!"); 118 | } 119 | 120 | #[test] 121 | fn struct_field_scalar_primitive_types() { 122 | let src = " 123 | struct Foo<'a> { 124 | reference: &'a u8, 125 | array: [u8; 5], 126 | slice: &'a [u8], 127 | } 128 | 129 | fn foo(x: Foo) { 130 | x.~ 131 | } 132 | "; 133 | 134 | let completions = get_all_completions(src, None); 135 | assert_eq!(completions.len(), 3); 136 | 137 | for completion in completions { 138 | let expected = match completion.matchstr.as_ref() { 139 | "reference" => "reference: &'a u8", 140 | "array" => "array: [u8; 5]", 141 | "slice" => "slice: &'a [u8]", 142 | _ => panic!("unexpected match from Foo struct ({})", completion.matchstr), 143 | }; 144 | 145 | assert_eq!(completion.contextstr, expected); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /tests/trait_bounds.rs: -------------------------------------------------------------------------------- 1 | use racer_testutils::*; 2 | 3 | #[test] 4 | fn finds_type_parameter_for_fnarg() { 5 | let src = " 6 | fn main() { 7 | trait Trait { 8 | fn method(&self); 9 | } 10 | fn func(arg: &T) { 11 | arg.meth~ 12 | } 13 | } 14 | "; 15 | assert_eq!(get_only_completion(src, None).matchstr, "method"); 16 | } 17 | 18 | #[test] 19 | fn completes_methods_for_fnarg_by_trait_bounds() { 20 | let src = " 21 | fn main() { 22 | trait Trait { 23 | fn method(&self); 24 | } 25 | fn func(arg: &T) { 26 | arg.meth~ 27 | } 28 | } 29 | "; 30 | assert_eq!(get_only_completion(src, None).matchstr, "method"); 31 | } 32 | 33 | #[test] 34 | fn completes_methods_for_fnarg_by_where_clause() { 35 | let src = " 36 | fn main() { 37 | trait Trait { 38 | fn method(&self); 39 | } 40 | fn func(arg: &T) 41 | where 42 | T: Trait, 43 | { 44 | arg.meth~ 45 | } 46 | } 47 | "; 48 | assert_eq!(get_only_completion(src, None).matchstr, "method"); 49 | } 50 | 51 | #[test] 52 | fn completes_external_methods_for_fnarg_by_trait_bounds() { 53 | let src = " 54 | fn main() { 55 | fn func(arg: &T) { 56 | arg.clo~ 57 | } 58 | } 59 | "; 60 | assert!(get_all_completions(src, None) 61 | .into_iter() 62 | .any(|ma| ma.matchstr == "clone")); 63 | } 64 | 65 | #[test] 66 | fn completes_inherited_methods_for_fnarg_by_trait_bounds() { 67 | let src = " 68 | fn main() { 69 | trait Inherited { 70 | fn inherited(&self); 71 | } 72 | trait Trait: Inherited { 73 | fn method(&self); 74 | } 75 | fn func(arg: &T) { 76 | arg.inheri~ 77 | } 78 | } 79 | "; 80 | assert_eq!(get_only_completion(src, None).matchstr, "inherited"); 81 | } 82 | 83 | // test for checking racer don't cause INF loop 84 | #[test] 85 | fn completes_inherited_methods_with_cycle() { 86 | let src = " 87 | fn main() { 88 | trait Inherited2: Inherited1 {} 89 | trait Inherited1: Inherited2 { 90 | fn inherited(&self); 91 | } 92 | trait Trait: Inherited1 { 93 | fn method(&self); 94 | } 95 | fn func(arg: &T) { 96 | arg.inheri~ 97 | } 98 | } 99 | "; 100 | assert_eq!(get_only_completion(src, None).matchstr, "inherited"); 101 | } 102 | 103 | #[test] 104 | fn completes_impled_bounds_for_fnarg() { 105 | let src = " 106 | fn main() { 107 | struct St(T); 108 | trait Trait: Sized { 109 | fn method(&self); 110 | } 111 | impl St { 112 | fn new(t: T) -> Self { 113 | t.met~ 114 | } 115 | } 116 | } 117 | "; 118 | assert_eq!(get_only_completion(src, None).matchstr, "method"); 119 | } 120 | 121 | #[test] 122 | fn completes_impled_bounds_for_self() { 123 | let src = " 124 | fn main() { 125 | trait Trait: Sized { 126 | fn method(&self); 127 | } 128 | impl Clone for T { 129 | fn f(self) -> Self { 130 | self.me~ 131 | } 132 | } 133 | } 134 | "; 135 | assert_eq!(get_only_completion(src, None).matchstr, "method"); 136 | } 137 | 138 | #[test] 139 | fn completes_impled_bounds_for_self_field() { 140 | let src = " 141 | fn main() { 142 | struct St { 143 | mem1: String, 144 | mem2: T, 145 | } 146 | trait Trait: Sized { 147 | fn method(&self); 148 | } 149 | impl St { 150 | fn f(self) -> Self { 151 | self.mem2.m~ 152 | } 153 | } 154 | } 155 | "; 156 | assert_eq!(get_only_completion(src, None).matchstr, "method"); 157 | } 158 | 159 | #[test] 160 | fn completes_impled_bounds_for_ref_self_field() { 161 | let src = " 162 | fn main() { 163 | struct St<'a, T> { 164 | mem1: String, 165 | mem2: &'a T, 166 | } 167 | trait Trait: Sized { 168 | fn method(&self); 169 | } 170 | impl St { 171 | fn f(self) -> Self { 172 | self.mem2.m~ 173 | } 174 | } 175 | } 176 | "; 177 | assert_eq!(get_only_completion(src, None).matchstr, "method"); 178 | } 179 | 180 | #[test] 181 | fn completes_fn_bounds_for_struct_member() { 182 | let src = " 183 | fn main() { 184 | struct St { 185 | mem1: String, 186 | mem2: T, 187 | } 188 | fn f(st: St) { 189 | st.mem2.clone_fro~ 190 | } 191 | } 192 | "; 193 | assert_eq!(get_only_completion(src, None).matchstr, "clone_from"); 194 | } 195 | 196 | #[test] 197 | fn completes_impl_trait_in_arg_position() { 198 | let src = " 199 | struct MyType; 200 | trait Foo { 201 | fn foo(&self); 202 | } 203 | impl Foo for MyType { 204 | fn foo(&self) { 205 | unimplemented!(); 206 | } 207 | } 208 | 209 | fn bar(b: impl Foo) { 210 | b.~ 211 | } 212 | "; 213 | assert_eq!(get_only_completion(src, None).matchstr, "foo"); 214 | } 215 | 216 | #[test] 217 | fn completes_impl_trait_in_fn_return_position() { 218 | let src = " 219 | struct MyType; 220 | trait Foo { 221 | fn foo(&self); 222 | } 223 | impl Foo for MyType { 224 | fn foo(&self) { 225 | unimplemented!(); 226 | } 227 | } 228 | fn bar() -> impl Foo { 229 | MyType 230 | } 231 | fn main() { 232 | let mytype = bar(); 233 | mytype.~ 234 | } 235 | "; 236 | assert_eq!(get_only_completion(src, None).matchstr, "foo"); 237 | } 238 | 239 | #[test] 240 | fn completes_returned_impl_trait_in_othermod() { 241 | let src = " 242 | fn main() { 243 | m::it().meth~ 244 | } 245 | mod empty { } 246 | mod m { 247 | pub trait Trait { 248 | fn method(&self) {} 249 | } 250 | pub fn it() -> impl Trait { 251 | } 252 | } 253 | "; 254 | assert_eq!(get_only_completion(src, None).matchstr, "method"); 255 | } 256 | 257 | #[test] 258 | fn completes_arg_impl_trait_in_othermod() { 259 | let src = " 260 | fn f(t: impl m::Trait) { 261 | t.me~ 262 | } 263 | mod empty { } 264 | mod m { 265 | pub trait Trait { 266 | fn method(&self) {} 267 | } 268 | } 269 | "; 270 | assert_eq!(get_only_completion(src, None).matchstr, "method"); 271 | } 272 | 273 | #[test] 274 | fn completes_unsafe_trait_methods_for_fnarg() { 275 | let src = " 276 | fn main() { 277 | unsafe trait Trait { 278 | fn method(&self); 279 | } 280 | fn func(arg: &T) { 281 | unsafe { arg.meth~ } 282 | } 283 | } 284 | "; 285 | assert_eq!(get_only_completion(src, None).matchstr, "method"); 286 | } 287 | 288 | #[test] 289 | fn completes_assoc_type_for_type_param_in_fn() { 290 | let src = " 291 | trait Object { 292 | type BaseObj: Object; 293 | } 294 | fn f(o: O) { 295 | O::Base~ 296 | } 297 | "; 298 | assert_eq!(get_only_completion(src, None).matchstr, "BaseObj"); 299 | } 300 | 301 | #[test] 302 | fn completes_assoc_fn_for_type_param_in_fn() { 303 | let src = " 304 | trait Object { 305 | fn init_typeobj() {} 306 | } 307 | fn f(o: O) { 308 | O::init_~ 309 | } 310 | "; 311 | assert_eq!(get_only_completion(src, None).matchstr, "init_typeobj"); 312 | } 313 | 314 | #[test] 315 | fn completes_assoc_type_for_type_param_in_impl() { 316 | let src = " 317 | trait Object { 318 | type BaseObj: Object; 319 | } 320 | struct ObjectWrapper { 321 | inner: O, 322 | } 323 | impl ObjectWrapper { 324 | const A: O::BaseO~ 325 | } 326 | "; 327 | assert_eq!(get_only_completion(src, None).matchstr, "BaseObj"); 328 | } 329 | 330 | #[test] 331 | fn completes_assoc_fn_for_type_param_in_impl() { 332 | let src = " 333 | trait Object { 334 | fn init_typeobj() {} 335 | } 336 | struct ObjectWrapper { 337 | inner: O, 338 | } 339 | impl ObjectWrapper { 340 | fn method(&self) { 341 | O::init~ 342 | } 343 | } 344 | "; 345 | assert_eq!(get_only_completion(src, None).matchstr, "init_typeobj"); 346 | } 347 | 348 | #[test] 349 | fn completes_assoc_constant_for_type_param_impl_bound() { 350 | let src = " 351 | trait Object { 352 | const OFFSET: usize; 353 | } 354 | struct ObjectWrapper { 355 | inner: O, 356 | } 357 | impl ObjectWrapper { 358 | fn method(&self) { 359 | O::OFF~ 360 | } 361 | } 362 | "; 363 | assert_eq!(get_only_completion(src, None).matchstr, "OFFSET"); 364 | } 365 | 366 | #[test] 367 | fn print_trait_object() { 368 | let src = " 369 | trait Object {} 370 | struct Obj(Box); 371 | fn main() { 372 | let obj: Obj = function(); 373 | obj.~ 374 | } 375 | "; 376 | let got = get_only_completion(src, None); 377 | assert_eq!(got.matchstr, "0"); 378 | assert_eq!(got.contextstr, "Box"); 379 | } 380 | -------------------------------------------------------------------------------- /tests/try.rs: -------------------------------------------------------------------------------- 1 | use racer_testutils::*; 2 | 3 | #[test] 4 | fn try_operator() { 5 | let src = " 6 | pub struct Foo(u16); 7 | 8 | #[derive(Debug, Clone, PartialEq, Eq)] 9 | pub struct OddError; 10 | 11 | fn be_even(val: Foo) -> Result { 12 | if val.0 % 2 == 1 { 13 | Err(OddError) 14 | } else { 15 | Ok(val) 16 | } 17 | } 18 | 19 | pub fn half(val: Foo) -> Result { 20 | Ok(Foo(be_even(val)?.~0 / 2)) 21 | } 22 | "; 23 | 24 | let got = get_definition(src, None); 25 | assert_eq!("0", got.matchstr); 26 | } 27 | 28 | #[test] 29 | fn try_operator_struct() { 30 | let src = " 31 | struct Foo { 32 | pub bar: String, 33 | pub baz: bool, 34 | } 35 | 36 | struct LongError; 37 | 38 | fn validate(s: String) -> Result { 39 | if s.chars().count() < 10 { 40 | Ok(Foo { bar: s, baz: true }) 41 | } else { 42 | Err(()) 43 | } 44 | } 45 | 46 | fn process(s: String) -> Result { 47 | Ok(validate(s)?.b~az) 48 | } 49 | "; 50 | 51 | let got = get_all_completions(src, None); 52 | assert_eq!(2, got.len()); 53 | assert_eq!("bar", got[0].matchstr); 54 | assert_eq!("baz", got[1].matchstr); 55 | } 56 | 57 | #[test] 58 | fn let_then_try_with_struct() { 59 | let src = " 60 | struct Foo { 61 | pub bar: String, 62 | pub baz: bool, 63 | } 64 | 65 | struct LongError; 66 | 67 | fn validate(s: String) -> Result { 68 | if s.chars().count() < 10 { 69 | Ok(Foo { bar: s, baz: true }) 70 | } else { 71 | Err(()) 72 | } 73 | } 74 | 75 | fn process(s: String) -> Result { 76 | let foo = validate(s); 77 | Ok(foo?.b~az) 78 | } 79 | "; 80 | 81 | let got = get_all_completions(src, None); 82 | assert_eq!(2, got.len()); 83 | assert_eq!("bar", got[0].matchstr); 84 | assert_eq!("baz", got[1].matchstr); 85 | } 86 | 87 | #[test] 88 | fn let_try() { 89 | let src = " 90 | pub struct Foo(u16); 91 | 92 | #[derive(Debug, Clone, PartialEq, Eq)] 93 | pub struct OddError; 94 | 95 | fn be_even(val: Foo) -> Result { 96 | if val.0 % 2 == 1 { 97 | Err(OddError) 98 | } else { 99 | Ok(val) 100 | } 101 | } 102 | 103 | pub fn half(val: Foo) -> Result { 104 | let foo = be_even(val)?; 105 | Ok(Foo(foo.~0 / 2)) 106 | } 107 | "; 108 | 109 | let got = get_definition(src, None); 110 | assert_eq!("0", got.matchstr); 111 | } 112 | 113 | #[test] 114 | fn let_try_socket() { 115 | let src = r#" 116 | use std::net::UdpSocket; 117 | use std::io; 118 | fn main() -> io::Result<()> { 119 | let sock = UdpSocket::bind("127.0.0.1:1234")?; 120 | sock.multicast_~ 121 | } 122 | "#; 123 | let got = get_all_completions(src, None); 124 | assert!(got.into_iter().any(|ma| ma.matchstr == "multicast_loop_v6")); 125 | } 126 | 127 | #[test] 128 | fn let_try_option() { 129 | let src = r#" 130 | fn f() -> Option { 131 | Some(String::new()) 132 | } 133 | fn f2() -> Option<()> { 134 | let s = f()?; 135 | s.as_mut_v~ 136 | } 137 | "#; 138 | let got = get_only_completion(src, None); 139 | assert_eq!(got.matchstr, "as_mut_vec"); 140 | } 141 | 142 | #[test] 143 | fn try_option() { 144 | let src = r#" 145 | fn f() -> Option { 146 | Some(String::new()) 147 | } 148 | fn f2() -> Option<()> { 149 | f()?.as_mut_v~ 150 | } 151 | "#; 152 | let got = get_only_completion(src, None); 153 | assert_eq!(got.matchstr, "as_mut_vec"); 154 | } 155 | -------------------------------------------------------------------------------- /tests/union.rs: -------------------------------------------------------------------------------- 1 | use racer_testutils::*; 2 | 3 | #[test] 4 | fn completes_union() { 5 | let src = r#" 6 | #[repr(C)] 7 | union MyUnion { 8 | f1: u32, 9 | f2: f32, 10 | } 11 | let u: MyU~ 12 | "#; 13 | let got = get_only_completion(src, None); 14 | assert_eq!(got.matchstr, "MyUnion"); 15 | } 16 | 17 | #[test] 18 | fn completes_maybe_uninit() { 19 | let src = r#" 20 | let u: std::mem::Mayb~ 21 | "#; 22 | let got = get_only_completion(src, None); 23 | assert_eq!(got.matchstr, "MaybeUninit"); 24 | } 25 | 26 | #[test] 27 | fn completes_union_member() { 28 | let src = r#" 29 | #[repr(C)] 30 | union MyUnion { 31 | uint_member: u32, 32 | float_member: f32, 33 | } 34 | impl MyUnion { 35 | fn new() -> Self { Self { uint_member: 10 } } 36 | } 37 | let uni = unsafe { MyUnion::new().uint~ }; 38 | "#; 39 | let got = get_only_completion(src, None); 40 | assert_eq!(got.matchstr, "uint_member"); 41 | } 42 | 43 | #[test] 44 | fn completes_union_method() { 45 | let src = r#" 46 | #[repr(C)] 47 | union MyUnion { 48 | uint_member: u32, 49 | float_member: f32, 50 | } 51 | impl MyUnion { 52 | fn new() -> Self { Self { uint_member: 10 } } 53 | fn double(self) -> Self { 54 | Self { 55 | uint_member: unsafe { self.uint_member * 2 } 56 | } 57 | } 58 | } 59 | let uni = unsafe { MyUnion::new().dou~ }; 60 | "#; 61 | let got = get_only_completion(src, None); 62 | assert_eq!(got.matchstr, "double"); 63 | } 64 | -------------------------------------------------------------------------------- /testutils/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | workspace = ".." 3 | name = "racer-testutils" 4 | description = "test library for racer, don't use" 5 | license = "MIT" 6 | version = "0.1.0" 7 | authors = ["Yuji Kanagawa "] 8 | edition = "2018" 9 | 10 | [dependencies] 11 | racer = { path = "../", version = "2"} 12 | tempfile = "3.0" 13 | -------------------------------------------------------------------------------- /testutils/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! system test utilities for racer 2 | use racer; 3 | 4 | use racer::{complete_from_file, find_definition as racer_find_definition, BytePos, Match}; 5 | use std::fmt; 6 | use std::fs; 7 | use std::io::{self, Write}; 8 | use std::path::{Path, PathBuf}; 9 | use std::thread; 10 | use tempfile::{Builder, NamedTempFile, TempDir}; 11 | 12 | /// name temp file 13 | pub fn tmpname() -> String { 14 | let thread = thread::current(); 15 | let taskname = thread.name().unwrap(); 16 | let taskname = taskname.replace("::", "-"); 17 | format!("racer-{}", taskname) 18 | } 19 | 20 | /// Wrapper of NamedTempfile 21 | /// 22 | /// **Note** NamedTempFile is removed when it drops. 23 | /// So, if you want to use some file in TempDir for test, you mustn't drop it. 24 | /// # Example 25 | /// ```ignore 26 | /// let dir = TmpDir::new(); 27 | /// let _mymod = dir.write_file("mymod.rs", modfile); 28 | /// let got = get_all_completions(src, Some(dir)); 29 | /// ``` 30 | pub struct TmpFile { 31 | inner: NamedTempFile, 32 | } 33 | 34 | impl TmpFile { 35 | /// create new TmpFile named `src`. 36 | pub fn new(src: &str) -> Self { 37 | let name = tmpname(); 38 | let mut file = Builder::new() 39 | .prefix(&name) 40 | .rand_bytes(0) 41 | .tempfile() 42 | .expect("failed to create tempfile."); 43 | file.as_file_mut() 44 | .write_all(src.as_bytes()) 45 | .expect("couldn't write to temp file"); 46 | TmpFile { inner: file } 47 | } 48 | /// returns path of inner temp file. 49 | pub fn path(&self) -> &Path { 50 | self.inner.path() 51 | } 52 | } 53 | 54 | impl fmt::Debug for TmpFile { 55 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 56 | write!(f, "TmpFile: {:?}", self.path()) 57 | } 58 | } 59 | 60 | impl AsRef for TmpFile { 61 | fn as_ref(&self) -> &Path { 62 | self.path() 63 | } 64 | } 65 | 66 | /// Wrapper of TempDir 67 | pub enum TmpDir { 68 | /// TempDir created by Builder 69 | Tmp(TempDir), 70 | /// path to existing TempDir 71 | Path(PathBuf), 72 | } 73 | 74 | impl TmpDir { 75 | /// create new TmpDir under '/tmp/' with a name including the name of test function. 76 | /// e.g. 'racer-follows_use_local_packagePK0WXy' 77 | pub fn new() -> Self { 78 | let name = tmpname(); 79 | let dir = Builder::new() 80 | .prefix(&name) 81 | .tempdir() 82 | .expect("failed to create tempdir."); 83 | TmpDir::Tmp(dir) 84 | } 85 | /// Create a new directory named 'dir_name' under self. 86 | /// If 'dir_name' already exists, return TmpDir containing its path. 87 | pub fn nested_dir(&self, dir_name: &str) -> Self { 88 | let path = self.path(); 89 | let new_path = path.join(dir_name); 90 | if new_path.exists() { 91 | TmpDir::Path(new_path) 92 | } else { 93 | let dir = Builder::new() 94 | .prefix(&dir_name) 95 | .rand_bytes(0) 96 | .tempdir_in(path) 97 | .expect("failed to create nested tempdir."); 98 | TmpDir::Tmp(dir) 99 | } 100 | } 101 | /// Create a new file named 'file_name' under self, and then write 'src' to it. 102 | pub fn write_file(&self, file_name: &str, src: &str) -> TmpFile { 103 | let path = self.path(); 104 | let mut file = Builder::new() 105 | .prefix(file_name) 106 | .rand_bytes(0) 107 | .tempfile_in(path) 108 | .expect("failed to create nested tempfile."); 109 | file.as_file_mut() 110 | .write_all(src.as_bytes()) 111 | .expect("couldn't write to temp file"); 112 | TmpFile { inner: file } 113 | } 114 | /// returns the path of self. 115 | pub fn path(&self) -> &Path { 116 | match self { 117 | TmpDir::Tmp(dir) => dir.path(), 118 | TmpDir::Path(buf) => buf, 119 | } 120 | } 121 | } 122 | 123 | impl AsRef for TmpDir { 124 | fn as_ref(&self) -> &Path { 125 | self.path() 126 | } 127 | } 128 | 129 | impl fmt::Debug for TmpDir { 130 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 131 | write!(f, "TmpDir: {:?}", self.path()) 132 | } 133 | } 134 | 135 | /// copy test_project/* into TempDir 136 | fn setup_test_project() -> TmpDir { 137 | let tmp_dir = TmpDir::new(); 138 | let test_project = Path::new(env!("CARGO_MANIFEST_DIR")).join("../test_project"); 139 | // copy test project to temp dir recursively 140 | fn copy_dirs(abs_path: &Path, tmp_path: &Path) -> io::Result<()> { 141 | if !abs_path.is_dir() { 142 | return Ok(()); 143 | } 144 | for entry in fs::read_dir(abs_path)? { 145 | let entry = entry?; 146 | let path = entry.path(); 147 | let relative = path 148 | .strip_prefix(abs_path) 149 | .expect("[setup_test_project] failed to strip_prefix(bug)"); 150 | let relative = relative 151 | .to_str() 152 | .expect("[setup_test_project] failed to get &str from path"); 153 | if relative == "target" { 154 | continue; 155 | } 156 | let nxt_tmp_path = tmp_path.join(relative); 157 | if path.is_dir() { 158 | fs::create_dir(&nxt_tmp_path)?; 159 | copy_dirs(&path, &nxt_tmp_path)?; 160 | } else { 161 | fs::File::create(&nxt_tmp_path)?; 162 | fs::copy(&path, &nxt_tmp_path)?; 163 | } 164 | } 165 | Ok(()) 166 | } 167 | copy_dirs(&test_project, tmp_dir.path()).unwrap(); 168 | tmp_dir 169 | } 170 | 171 | /// exec test with test project 172 | pub fn with_test_project(test: F) { 173 | let dir = setup_test_project(); 174 | test(dir) 175 | } 176 | 177 | /// get position where you want to test completion and source code 178 | pub fn get_pos_and_source(src: &str) -> (BytePos, String) { 179 | let point = src.find('~').unwrap(); 180 | (point.into(), src.replace('~', "")) 181 | } 182 | 183 | /// Return the completions for the given source. 184 | /// 185 | /// The point to find completions at must be marked with '~'. 186 | pub fn get_all_completions(src: &str, dir: Option) -> Vec { 187 | get_all_completions_with_name(src, dir, "src.rs") 188 | } 189 | 190 | pub fn get_all_completions_with_name(src: &str, dir: Option, fname: &str) -> Vec { 191 | let dir = dir.unwrap_or_else(|| TmpDir::new()); 192 | let (completion_point, clean_src) = get_pos_and_source(src); 193 | let path = dir.write_file(fname, &clean_src); 194 | let cache = racer::FileCache::default(); 195 | let session = racer::Session::new(&cache, Some(path.as_ref())); 196 | complete_from_file(&path, completion_point, &session).collect() 197 | } 198 | 199 | /// Return the first completion for the given source. 200 | pub fn get_one_completion(src: &str, dir: Option) -> Match { 201 | get_all_completions(src, dir).swap_remove(0) 202 | } 203 | 204 | /// Return the first completion for the given source, which must be 205 | /// the only one. 206 | /// 207 | /// # Panics 208 | /// Panics if there is not exactly one completion. 209 | pub fn get_only_completion(src: &str, dir: Option) -> Match { 210 | let mut all = get_all_completions(src, dir); 211 | match (all.pop(), all.as_slice()) { 212 | (Some(head), &[]) => head, 213 | (head, tail) => panic!("head: {:?}, tail: {:?}", head, tail), 214 | } 215 | } 216 | 217 | /// Return the definition for the given source. 218 | /// 219 | /// The point to find the definition at must be marked with '~'. 220 | /// 221 | /// # Panics 222 | /// Panics if there are no matches 223 | pub fn get_definition(src: &str, dir: Option) -> Match { 224 | find_definition(src, dir).unwrap() 225 | } 226 | 227 | /// Optionally returns a defintion for the given source 228 | pub fn find_definition(src: &str, dir: Option) -> Option { 229 | find_definition_with_name(src, dir, "src.rs") 230 | } 231 | 232 | /// Optionally returns a defintion for the given source 233 | pub fn find_definition_with_name(src: &str, dir: Option, fname: &str) -> Option { 234 | let dir = dir.unwrap_or_else(|| TmpDir::new()); 235 | let (completion_point, clean_src) = get_pos_and_source(src); 236 | let path = dir.write_file(fname, &clean_src); 237 | let cache = racer::FileCache::default(); 238 | let session = racer::Session::new(&cache, Some(path.as_ref())); 239 | racer_find_definition(&path, completion_point, &session) 240 | } 241 | --------------------------------------------------------------------------------