aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/ci.yaml4
-rw-r--r--.github/workflows/release.yaml124
-rw-r--r--Cargo.toml2
-rw-r--r--crates/ra_hir_expand/src/builtin_macro.rs12
-rw-r--r--crates/ra_hir_expand/src/db.rs1
-rw-r--r--crates/ra_hir_ty/src/tests/macros.rs81
-rw-r--r--crates/ra_ide/src/lib.rs4
-rw-r--r--crates/ra_ide/src/references.rs253
-rw-r--r--crates/ra_lsp_server/src/conv.rs14
-rw-r--r--crates/ra_lsp_server/src/main_loop/handlers.rs34
-rw-r--r--crates/ra_syntax/src/ast/expr_extensions.rs1
-rw-r--r--editors/code/package.json5
12 files changed, 462 insertions, 73 deletions
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index e7e0d599e..3a2bdb4a6 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -20,6 +20,7 @@ jobs:
20 RUN_SLOW_TESTS: 1 20 RUN_SLOW_TESTS: 1
21 RUSTUP_MAX_RETRIES: 10 21 RUSTUP_MAX_RETRIES: 10
22 CARGO_NET_RETRY: 10 22 CARGO_NET_RETRY: 10
23 PROFILE: debug
23 steps: 24 steps:
24 25
25 - name: Checkout repository 26 - name: Checkout repository
@@ -75,8 +76,7 @@ jobs:
75 76
76 - name: Prepare cache 2 77 - name: Prepare cache 2
77 if: matrix.os == 'windows-latest' 78 if: matrix.os == 'windows-latest'
78 run: Remove-Item ./target/debug/xtask.exe 79 run: Remove-Item ./target/${{ env.PROFILE }}/xtask.exe
79
80 80
81 type-script: 81 type-script:
82 name: TypeScript 82 name: TypeScript
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
new file mode 100644
index 000000000..6cb27482d
--- /dev/null
+++ b/.github/workflows/release.yaml
@@ -0,0 +1,124 @@
1name: CI-Release
2on:
3 pull_request:
4 push:
5 branches:
6 - master
7
8jobs:
9 rust:
10 name: Rust
11 runs-on: ${{ matrix.os }}
12 strategy:
13 matrix:
14 os: [ubuntu-latest, windows-latest, macos-latest]
15 env:
16 RUSTFLAGS: -D warnings
17 CARGO_INCREMENTAL: 0
18 RUN_SLOW_TESTS: 1
19 RUSTUP_MAX_RETRIES: 10
20 CARGO_NET_RETRY: 10
21 PROFILE: release
22 steps:
23
24 - name: Checkout repository
25 uses: actions/checkout@v1
26
27 # We need to disable the existing toolchain to avoid updating rust-docs
28 # which takes a long time. The fastest way to do this is to rename the
29 # existing folder, as deleting it takes about as much time as not doing
30 # anything and just updating rust-docs.
31 - name: Rename existing rust toolchain (Windows)
32 if: matrix.os == 'windows-latest'
33 run: Rename-Item C:\Users\runneradmin\.rustup\toolchains\stable-x86_64-pc-windows-msvc C:\Users\runneradmin\.rustup\toolchains\stable-x86_64-pc-windows-msvc.old
34
35 - name: Install Rust toolchain
36 uses: actions-rs/toolchain@v1
37 with:
38 toolchain: stable
39 profile: minimal
40 override: true
41 components: rustfmt, rust-src
42
43 - name: Cache cargo registry
44 uses: actions/cache@v1
45 with:
46 path: ~/.cargo/registry
47 key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
48
49 - name: Cache cargo index
50 uses: actions/cache@v1
51 with:
52 path: ~/.cargo/git
53 key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}
54
55 - name: Cache cargo target dir
56 uses: actions/cache@v1
57 with:
58 path: target
59 key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
60
61 - name: Compile and Test
62 uses: actions-rs/cargo@v1
63 with:
64 command: test
65 args: --release --all-targets
66
67 - name: Prepare cache
68 run: cargo xtask pre-cache
69
70 - name: Prepare cache 2
71 if: matrix.os == 'windows-latest'
72 run: Remove-Item ./target/${{ env.PROFILE }}/xtask.exe
73
74 - name: Creat distribution dir
75 run: mkdir ./dist
76
77 - name: Copy binaries (non-win)
78 if: matrix.os != 'windows-latest'
79 run: cp ./target/${{ env.PROFILE }}/ra_lsp_server ./dist
80
81 - name: Copy binaries (win)
82 if: matrix.os == 'windows-latest'
83 run: copy ./target/${{ env.PROFILE }}/ra_lsp_server.* ./dist
84
85 - name: Upload artifacts
86 uses: actions/upload-artifact@v1
87 with:
88 name: server-${{ matrix.os }}
89 path: ./dist
90
91 type-script:
92 name: TypeScript
93 runs-on: ubuntu-latest
94 env:
95 CXX: g++-4.9
96 CC: gcc-4.9
97 steps:
98 - name: Checkout repository
99 uses: actions/checkout@v1
100
101 - name: Install Nodejs
102 uses: actions/setup-node@v1
103 with:
104 node-version: 12.x
105
106 - run: npm ci
107 working-directory: ./editors/code
108 - run: npm run package --scripts-prepend-node-path
109 working-directory: ./editors/code
110
111 - name: Create distribution directory
112 run: mkdir ./dist
113
114 - name: Copy vscode extension
115 run: mkdir ./dist/code && cp ./editors/code/*.vsix ./dist/code/
116
117 - name: Copy emacs mode
118 run: cp -R ./editors/emacs ./dist/
119
120 - name: Upload artifacts
121 uses: actions/upload-artifact@v1
122 with:
123 name: editors
124 path: ./dist
diff --git a/Cargo.toml b/Cargo.toml
index 97508c57b..3753a4143 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,6 +8,8 @@ debug = 0
8 8
9[profile.release] 9[profile.release]
10incremental = true 10incremental = true
11lto = true
12codegen-units = 1
11debug = 0 # set this to 1 or 2 to get more useful backtraces in debugger 13debug = 0 # set this to 1 or 2 to get more useful backtraces in debugger
12 14
13[patch.'crates-io'] 15[patch.'crates-io']
diff --git a/crates/ra_hir_expand/src/builtin_macro.rs b/crates/ra_hir_expand/src/builtin_macro.rs
index 2c119269c..e9e275670 100644
--- a/crates/ra_hir_expand/src/builtin_macro.rs
+++ b/crates/ra_hir_expand/src/builtin_macro.rs
@@ -62,8 +62,13 @@ register_builtin! {
62} 62}
63 63
64fn to_line_number(db: &dyn AstDatabase, file: HirFileId, pos: TextUnit) -> usize { 64fn to_line_number(db: &dyn AstDatabase, file: HirFileId, pos: TextUnit) -> usize {
65 // FIXME: Use expansion info
66 let file_id = file.original_file(db); 65 let file_id = file.original_file(db);
66
67 // FIXME: if the file is coming from macro, we return a dummy value for now.
68 if file.call_node(db).map(|it| it.file_id != file_id.into()).unwrap_or(true) {
69 return 0;
70 }
71
67 let text = db.file_text(file_id); 72 let text = db.file_text(file_id);
68 let mut line_num = 1; 73 let mut line_num = 1;
69 74
@@ -150,8 +155,11 @@ fn option_env_expand(
150} 155}
151 156
152fn to_col_number(db: &dyn AstDatabase, file: HirFileId, pos: TextUnit) -> usize { 157fn to_col_number(db: &dyn AstDatabase, file: HirFileId, pos: TextUnit) -> usize {
153 // FIXME: Use expansion info
154 let file_id = file.original_file(db); 158 let file_id = file.original_file(db);
159 // FIXME: if the file is coming from macro, we return a dummy value for now.
160 if file.call_node(db).map(|it| it.file_id != file_id.into()).unwrap_or(true) {
161 return 0;
162 }
155 let text = db.file_text(file_id); 163 let text = db.file_text(file_id);
156 164
157 let pos = pos.to_usize(); 165 let pos = pos.to_usize();
diff --git a/crates/ra_hir_expand/src/db.rs b/crates/ra_hir_expand/src/db.rs
index 5d12c1584..af5b22d1c 100644
--- a/crates/ra_hir_expand/src/db.rs
+++ b/crates/ra_hir_expand/src/db.rs
@@ -190,7 +190,6 @@ fn to_fragment_kind(db: &dyn AstDatabase, macro_call_id: MacroCallId) -> Fragmen
190 TUPLE_EXPR => FragmentKind::Expr, 190 TUPLE_EXPR => FragmentKind::Expr,
191 PAREN_EXPR => FragmentKind::Expr, 191 PAREN_EXPR => FragmentKind::Expr,
192 192
193 // FIXME: Add tests for following cases in hir_ty
194 FOR_EXPR => FragmentKind::Expr, 193 FOR_EXPR => FragmentKind::Expr,
195 PATH_EXPR => FragmentKind::Expr, 194 PATH_EXPR => FragmentKind::Expr,
196 LAMBDA_EXPR => FragmentKind::Expr, 195 LAMBDA_EXPR => FragmentKind::Expr,
diff --git a/crates/ra_hir_ty/src/tests/macros.rs b/crates/ra_hir_ty/src/tests/macros.rs
index 69c695cc8..9d09d93a7 100644
--- a/crates/ra_hir_ty/src/tests/macros.rs
+++ b/crates/ra_hir_ty/src/tests/macros.rs
@@ -136,6 +136,87 @@ fn main() {
136} 136}
137 137
138#[test] 138#[test]
139fn expr_macro_expanded_in_various_places() {
140 assert_snapshot!(
141 infer(r#"
142macro_rules! spam {
143 () => (1isize);
144}
145
146fn spam() {
147 spam!();
148 (spam!());
149 spam!().spam(spam!());
150 for _ in spam!() {}
151 || spam!();
152 while spam!() {}
153 break spam!();
154 return spam!();
155 match spam!() {
156 _ if spam!() => spam!(),
157 }
158 spam!()(spam!());
159 Spam { spam: spam!() };
160 spam!()[spam!()];
161 await spam!();
162 spam!() as usize;
163 &spam!();
164 -spam!();
165 spam!()..spam!();
166 spam!() + spam!();
167}
168"#),
169 @r###"
170 ![0; 6) '1isize': isize
171 ![0; 6) '1isize': isize
172 ![0; 6) '1isize': isize
173 ![0; 6) '1isize': isize
174 ![0; 6) '1isize': isize
175 ![0; 6) '1isize': isize
176 ![0; 6) '1isize': isize
177 ![0; 6) '1isize': isize
178 ![0; 6) '1isize': isize
179 ![0; 6) '1isize': isize
180 ![0; 6) '1isize': isize
181 ![0; 6) '1isize': isize
182 ![0; 6) '1isize': isize
183 ![0; 6) '1isize': isize
184 ![0; 6) '1isize': isize
185 ![0; 6) '1isize': isize
186 ![0; 6) '1isize': isize
187 ![0; 6) '1isize': isize
188 ![0; 6) '1isize': isize
189 ![0; 6) '1isize': isize
190 ![0; 6) '1isize': isize
191 ![0; 6) '1isize': isize
192 ![0; 6) '1isize': isize
193 ![0; 6) '1isize': isize
194 ![0; 6) '1isize': isize
195 [54; 457) '{ ...!(); }': !
196 [88; 109) 'spam!(...am!())': {unknown}
197 [115; 134) 'for _ ...!() {}': ()
198 [119; 120) '_': {unknown}
199 [132; 134) '{}': ()
200 [139; 149) '|| spam!()': || -> isize
201 [155; 171) 'while ...!() {}': ()
202 [169; 171) '{}': ()
203 [176; 189) 'break spam!()': !
204 [195; 209) 'return spam!()': !
205 [215; 269) 'match ... }': isize
206 [239; 240) '_': isize
207 [274; 290) 'spam!(...am!())': {unknown}
208 [296; 318) 'Spam {...m!() }': {unknown}
209 [324; 340) 'spam!(...am!()]': {unknown}
210 [365; 381) 'spam!(... usize': usize
211 [387; 395) '&spam!()': &isize
212 [401; 409) '-spam!()': isize
213 [415; 431) 'spam!(...pam!()': {unknown}
214 [437; 454) 'spam!(...pam!()': isize
215 "###
216 );
217}
218
219#[test]
139fn infer_type_value_macro_having_same_name() { 220fn infer_type_value_macro_having_same_name() {
140 assert_snapshot!( 221 assert_snapshot!(
141 infer(r#" 222 infer(r#"
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs
index 7b187eba3..4d8deb21c 100644
--- a/crates/ra_ide/src/lib.rs
+++ b/crates/ra_ide/src/lib.rs
@@ -75,7 +75,9 @@ pub use crate::{
75 inlay_hints::{InlayHint, InlayKind}, 75 inlay_hints::{InlayHint, InlayKind},
76 line_index::{LineCol, LineIndex}, 76 line_index::{LineCol, LineIndex},
77 line_index_utils::translate_offset_with_edit, 77 line_index_utils::translate_offset_with_edit,
78 references::{Reference, ReferenceKind, ReferenceSearchResult, SearchScope}, 78 references::{
79 Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult, SearchScope,
80 },
79 runnables::{Runnable, RunnableKind}, 81 runnables::{Runnable, RunnableKind},
80 source_change::{FileSystemEdit, SourceChange, SourceFileEdit}, 82 source_change::{FileSystemEdit, SourceChange, SourceFileEdit},
81 syntax_highlighting::HighlightedRange, 83 syntax_highlighting::HighlightedRange,
diff --git a/crates/ra_ide/src/references.rs b/crates/ra_ide/src/references.rs
index 5a3ec4eb9..4e52e0e7b 100644
--- a/crates/ra_ide/src/references.rs
+++ b/crates/ra_ide/src/references.rs
@@ -19,8 +19,9 @@ use once_cell::unsync::Lazy;
19use ra_db::{SourceDatabase, SourceDatabaseExt}; 19use ra_db::{SourceDatabase, SourceDatabaseExt};
20use ra_prof::profile; 20use ra_prof::profile;
21use ra_syntax::{ 21use ra_syntax::{
22 algo::find_node_at_offset, ast, AstNode, SourceFile, SyntaxKind, SyntaxNode, TextUnit, 22 algo::find_node_at_offset,
23 TokenAtOffset, 23 ast::{self, NameOwner},
24 match_ast, AstNode, SourceFile, SyntaxKind, SyntaxNode, TextRange, TextUnit, TokenAtOffset,
24}; 25};
25 26
26use crate::{ 27use crate::{
@@ -37,15 +38,22 @@ pub use self::search_scope::SearchScope;
37 38
38#[derive(Debug, Clone)] 39#[derive(Debug, Clone)]
39pub struct ReferenceSearchResult { 40pub struct ReferenceSearchResult {
40 declaration: NavigationTarget, 41 declaration: Declaration,
41 declaration_kind: ReferenceKind,
42 references: Vec<Reference>, 42 references: Vec<Reference>,
43} 43}
44 44
45#[derive(Debug, Clone)] 45#[derive(Debug, Clone)]
46pub struct Declaration {
47 pub nav: NavigationTarget,
48 pub kind: ReferenceKind,
49 pub access: Option<ReferenceAccess>,
50}
51
52#[derive(Debug, Clone)]
46pub struct Reference { 53pub struct Reference {
47 pub file_range: FileRange, 54 pub file_range: FileRange,
48 pub kind: ReferenceKind, 55 pub kind: ReferenceKind,
56 pub access: Option<ReferenceAccess>,
49} 57}
50 58
51#[derive(Debug, Clone, PartialEq)] 59#[derive(Debug, Clone, PartialEq)]
@@ -54,11 +62,21 @@ pub enum ReferenceKind {
54 Other, 62 Other,
55} 63}
56 64
65#[derive(Debug, Copy, Clone, PartialEq)]
66pub enum ReferenceAccess {
67 Read,
68 Write,
69}
70
57impl ReferenceSearchResult { 71impl ReferenceSearchResult {
58 pub fn declaration(&self) -> &NavigationTarget { 72 pub fn declaration(&self) -> &Declaration {
59 &self.declaration 73 &self.declaration
60 } 74 }
61 75
76 pub fn decl_target(&self) -> &NavigationTarget {
77 &self.declaration.nav
78 }
79
62 pub fn references(&self) -> &[Reference] { 80 pub fn references(&self) -> &[Reference] {
63 &self.references 81 &self.references
64 } 82 }
@@ -72,7 +90,7 @@ impl ReferenceSearchResult {
72} 90}
73 91
74// allow turning ReferenceSearchResult into an iterator 92// allow turning ReferenceSearchResult into an iterator
75// over FileRanges 93// over References
76impl IntoIterator for ReferenceSearchResult { 94impl IntoIterator for ReferenceSearchResult {
77 type Item = Reference; 95 type Item = Reference;
78 type IntoIter = std::vec::IntoIter<Reference>; 96 type IntoIter = std::vec::IntoIter<Reference>;
@@ -81,10 +99,11 @@ impl IntoIterator for ReferenceSearchResult {
81 let mut v = Vec::with_capacity(self.len()); 99 let mut v = Vec::with_capacity(self.len());
82 v.push(Reference { 100 v.push(Reference {
83 file_range: FileRange { 101 file_range: FileRange {
84 file_id: self.declaration.file_id(), 102 file_id: self.declaration.nav.file_id(),
85 range: self.declaration.range(), 103 range: self.declaration.nav.range(),
86 }, 104 },
87 kind: self.declaration_kind, 105 kind: self.declaration.kind,
106 access: self.declaration.access,
88 }); 107 });
89 v.append(&mut self.references); 108 v.append(&mut self.references);
90 v.into_iter() 109 v.into_iter()
@@ -131,15 +150,20 @@ pub(crate) fn find_all_refs(
131 } 150 }
132 }; 151 };
133 152
153 let decl_range = declaration.range();
154
155 let declaration = Declaration {
156 nav: declaration,
157 kind: ReferenceKind::Other,
158 access: decl_access(&def.kind, &name, &syntax, decl_range),
159 };
160
134 let references = process_definition(db, def, name, search_scope) 161 let references = process_definition(db, def, name, search_scope)
135 .into_iter() 162 .into_iter()
136 .filter(|r| search_kind == ReferenceKind::Other || search_kind == r.kind) 163 .filter(|r| search_kind == ReferenceKind::Other || search_kind == r.kind)
137 .collect(); 164 .collect();
138 165
139 Some(RangeInfo::new( 166 Some(RangeInfo::new(range, ReferenceSearchResult { declaration, references }))
140 range,
141 ReferenceSearchResult { declaration, references, declaration_kind: ReferenceKind::Other },
142 ))
143} 167}
144 168
145fn find_name<'a>( 169fn find_name<'a>(
@@ -201,7 +225,12 @@ fn process_definition(
201 } else { 225 } else {
202 ReferenceKind::Other 226 ReferenceKind::Other
203 }; 227 };
204 refs.push(Reference { file_range: FileRange { file_id, range }, kind }); 228
229 refs.push(Reference {
230 file_range: FileRange { file_id, range },
231 kind,
232 access: reference_access(&d.kind, &name_ref),
233 });
205 } 234 }
206 } 235 }
207 } 236 }
@@ -210,11 +239,69 @@ fn process_definition(
210 refs 239 refs
211} 240}
212 241
242fn decl_access(
243 kind: &NameKind,
244 name: &str,
245 syntax: &SyntaxNode,
246 range: TextRange,
247) -> Option<ReferenceAccess> {
248 match kind {
249 NameKind::Local(_) | NameKind::Field(_) => {}
250 _ => return None,
251 };
252
253 let stmt = find_node_at_offset::<ast::LetStmt>(syntax, range.start())?;
254 if let Some(_) = stmt.initializer() {
255 let pat = stmt.pat()?;
256 match pat {
257 ast::Pat::BindPat(it) => {
258 if it.name()?.text().as_str() == name {
259 return Some(ReferenceAccess::Write);
260 }
261 }
262 _ => {}
263 }
264 }
265
266 None
267}
268
269fn reference_access(kind: &NameKind, name_ref: &ast::NameRef) -> Option<ReferenceAccess> {
270 // Only Locals and Fields have accesses for now.
271 match kind {
272 NameKind::Local(_) | NameKind::Field(_) => {}
273 _ => return None,
274 };
275
276 let mode = name_ref.syntax().ancestors().find_map(|node| {
277 match_ast! {
278 match (node) {
279 ast::BinExpr(expr) => {
280 if expr.op_kind()?.is_assignment() {
281 // If the variable or field ends on the LHS's end then it's a Write (covers fields and locals).
282 // FIXME: This is not terribly accurate.
283 if let Some(lhs) = expr.lhs() {
284 if lhs.syntax().text_range().end() == name_ref.syntax().text_range().end() {
285 return Some(ReferenceAccess::Write);
286 }
287 }
288 }
289 return Some(ReferenceAccess::Read);
290 },
291 _ => {None}
292 }
293 }
294 });
295
296 // Default Locals and Fields to read
297 mode.or(Some(ReferenceAccess::Read))
298}
299
213#[cfg(test)] 300#[cfg(test)]
214mod tests { 301mod tests {
215 use crate::{ 302 use crate::{
216 mock_analysis::{analysis_and_position, single_file_with_position, MockAnalysis}, 303 mock_analysis::{analysis_and_position, single_file_with_position, MockAnalysis},
217 Reference, ReferenceKind, ReferenceSearchResult, SearchScope, 304 Declaration, Reference, ReferenceSearchResult, SearchScope,
218 }; 305 };
219 306
220 #[test] 307 #[test]
@@ -234,8 +321,7 @@ mod tests {
234 let refs = get_all_refs(code); 321 let refs = get_all_refs(code);
235 check_result( 322 check_result(
236 refs, 323 refs,
237 "Foo STRUCT_DEF FileId(1) [5; 39) [12; 15)", 324 "Foo STRUCT_DEF FileId(1) [5; 39) [12; 15) Other",
238 ReferenceKind::Other,
239 &["FileId(1) [142; 145) StructLiteral"], 325 &["FileId(1) [142; 145) StructLiteral"],
240 ); 326 );
241 } 327 }
@@ -258,13 +344,12 @@ mod tests {
258 let refs = get_all_refs(code); 344 let refs = get_all_refs(code);
259 check_result( 345 check_result(
260 refs, 346 refs,
261 "i BIND_PAT FileId(1) [33; 34)", 347 "i BIND_PAT FileId(1) [33; 34) Other Write",
262 ReferenceKind::Other,
263 &[ 348 &[
264 "FileId(1) [67; 68) Other", 349 "FileId(1) [67; 68) Other Write",
265 "FileId(1) [71; 72) Other", 350 "FileId(1) [71; 72) Other Read",
266 "FileId(1) [101; 102) Other", 351 "FileId(1) [101; 102) Other Write",
267 "FileId(1) [127; 128) Other", 352 "FileId(1) [127; 128) Other Write",
268 ], 353 ],
269 ); 354 );
270 } 355 }
@@ -279,9 +364,8 @@ mod tests {
279 let refs = get_all_refs(code); 364 let refs = get_all_refs(code);
280 check_result( 365 check_result(
281 refs, 366 refs,
282 "i BIND_PAT FileId(1) [12; 13)", 367 "i BIND_PAT FileId(1) [12; 13) Other",
283 ReferenceKind::Other, 368 &["FileId(1) [38; 39) Other Read"],
284 &["FileId(1) [38; 39) Other"],
285 ); 369 );
286 } 370 }
287 371
@@ -295,9 +379,8 @@ mod tests {
295 let refs = get_all_refs(code); 379 let refs = get_all_refs(code);
296 check_result( 380 check_result(
297 refs, 381 refs,
298 "i BIND_PAT FileId(1) [12; 13)", 382 "i BIND_PAT FileId(1) [12; 13) Other",
299 ReferenceKind::Other, 383 &["FileId(1) [38; 39) Other Read"],
300 &["FileId(1) [38; 39) Other"],
301 ); 384 );
302 } 385 }
303 386
@@ -317,9 +400,8 @@ mod tests {
317 let refs = get_all_refs(code); 400 let refs = get_all_refs(code);
318 check_result( 401 check_result(
319 refs, 402 refs,
320 "spam RECORD_FIELD_DEF FileId(1) [66; 79) [70; 74)", 403 "spam RECORD_FIELD_DEF FileId(1) [66; 79) [70; 74) Other",
321 ReferenceKind::Other, 404 &["FileId(1) [152; 156) Other Read"],
322 &["FileId(1) [152; 156) Other"],
323 ); 405 );
324 } 406 }
325 407
@@ -334,7 +416,7 @@ mod tests {
334 "#; 416 "#;
335 417
336 let refs = get_all_refs(code); 418 let refs = get_all_refs(code);
337 check_result(refs, "f FN_DEF FileId(1) [88; 104) [91; 92)", ReferenceKind::Other, &[]); 419 check_result(refs, "f FN_DEF FileId(1) [88; 104) [91; 92) Other", &[]);
338 } 420 }
339 421
340 #[test] 422 #[test]
@@ -349,7 +431,7 @@ mod tests {
349 "#; 431 "#;
350 432
351 let refs = get_all_refs(code); 433 let refs = get_all_refs(code);
352 check_result(refs, "B ENUM_VARIANT FileId(1) [83; 84) [83; 84)", ReferenceKind::Other, &[]); 434 check_result(refs, "B ENUM_VARIANT FileId(1) [83; 84) [83; 84) Other", &[]);
353 } 435 }
354 436
355 #[test] 437 #[test]
@@ -390,8 +472,7 @@ mod tests {
390 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap(); 472 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap();
391 check_result( 473 check_result(
392 refs, 474 refs,
393 "Foo STRUCT_DEF FileId(2) [16; 50) [27; 30)", 475 "Foo STRUCT_DEF FileId(2) [16; 50) [27; 30) Other",
394 ReferenceKind::Other,
395 &["FileId(1) [52; 55) StructLiteral", "FileId(3) [77; 80) StructLiteral"], 476 &["FileId(1) [52; 55) StructLiteral", "FileId(3) [77; 80) StructLiteral"],
396 ); 477 );
397 } 478 }
@@ -421,8 +502,7 @@ mod tests {
421 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap(); 502 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap();
422 check_result( 503 check_result(
423 refs, 504 refs,
424 "foo SOURCE_FILE FileId(2) [0; 35)", 505 "foo SOURCE_FILE FileId(2) [0; 35) Other",
425 ReferenceKind::Other,
426 &["FileId(1) [13; 16) Other"], 506 &["FileId(1) [13; 16) Other"],
427 ); 507 );
428 } 508 }
@@ -451,8 +531,7 @@ mod tests {
451 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap(); 531 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap();
452 check_result( 532 check_result(
453 refs, 533 refs,
454 "Foo STRUCT_DEF FileId(3) [0; 41) [18; 21)", 534 "Foo STRUCT_DEF FileId(3) [0; 41) [18; 21) Other",
455 ReferenceKind::Other,
456 &["FileId(2) [20; 23) Other", "FileId(2) [46; 49) StructLiteral"], 535 &["FileId(2) [20; 23) Other", "FileId(2) [46; 49) StructLiteral"],
457 ); 536 );
458 } 537 }
@@ -480,8 +559,7 @@ mod tests {
480 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap(); 559 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap();
481 check_result( 560 check_result(
482 refs, 561 refs,
483 "quux FN_DEF FileId(1) [18; 34) [25; 29)", 562 "quux FN_DEF FileId(1) [18; 34) [25; 29) Other",
484 ReferenceKind::Other,
485 &["FileId(2) [16; 20) Other", "FileId(3) [16; 20) Other"], 563 &["FileId(2) [16; 20) Other", "FileId(3) [16; 20) Other"],
486 ); 564 );
487 565
@@ -489,8 +567,7 @@ mod tests {
489 analysis.find_all_refs(pos, Some(SearchScope::single_file(bar))).unwrap().unwrap(); 567 analysis.find_all_refs(pos, Some(SearchScope::single_file(bar))).unwrap().unwrap();
490 check_result( 568 check_result(
491 refs, 569 refs,
492 "quux FN_DEF FileId(1) [18; 34) [25; 29)", 570 "quux FN_DEF FileId(1) [18; 34) [25; 29) Other",
493 ReferenceKind::Other,
494 &["FileId(3) [16; 20) Other"], 571 &["FileId(3) [16; 20) Other"],
495 ); 572 );
496 } 573 }
@@ -509,33 +586,99 @@ mod tests {
509 let refs = get_all_refs(code); 586 let refs = get_all_refs(code);
510 check_result( 587 check_result(
511 refs, 588 refs,
512 "m1 MACRO_CALL FileId(1) [9; 63) [46; 48)", 589 "m1 MACRO_CALL FileId(1) [9; 63) [46; 48) Other",
513 ReferenceKind::Other,
514 &["FileId(1) [96; 98) Other", "FileId(1) [114; 116) Other"], 590 &["FileId(1) [96; 98) Other", "FileId(1) [114; 116) Other"],
515 ); 591 );
516 } 592 }
517 593
594 #[test]
595 fn test_basic_highlight_read_write() {
596 let code = r#"
597 fn foo() {
598 let i<|> = 0;
599 i = i + 1;
600 }"#;
601
602 let refs = get_all_refs(code);
603 check_result(
604 refs,
605 "i BIND_PAT FileId(1) [36; 37) Other Write",
606 &["FileId(1) [55; 56) Other Write", "FileId(1) [59; 60) Other Read"],
607 );
608 }
609
610 #[test]
611 fn test_basic_highlight_field_read_write() {
612 let code = r#"
613 struct S {
614 f: u32,
615 }
616
617 fn foo() {
618 let mut s = S{f: 0};
619 s.f<|> = 0;
620 }"#;
621
622 let refs = get_all_refs(code);
623 check_result(
624 refs,
625 "f RECORD_FIELD_DEF FileId(1) [32; 38) [32; 33) Other",
626 &["FileId(1) [96; 97) Other Read", "FileId(1) [117; 118) Other Write"],
627 );
628 }
629
630 #[test]
631 fn test_basic_highlight_decl_no_write() {
632 let code = r#"
633 fn foo() {
634 let i<|>;
635 i = 1;
636 }"#;
637
638 let refs = get_all_refs(code);
639 check_result(
640 refs,
641 "i BIND_PAT FileId(1) [36; 37) Other",
642 &["FileId(1) [51; 52) Other Write"],
643 );
644 }
645
518 fn get_all_refs(text: &str) -> ReferenceSearchResult { 646 fn get_all_refs(text: &str) -> ReferenceSearchResult {
519 let (analysis, position) = single_file_with_position(text); 647 let (analysis, position) = single_file_with_position(text);
520 analysis.find_all_refs(position, None).unwrap().unwrap() 648 analysis.find_all_refs(position, None).unwrap().unwrap()
521 } 649 }
522 650
523 fn check_result( 651 fn check_result(res: ReferenceSearchResult, expected_decl: &str, expected_refs: &[&str]) {
524 res: ReferenceSearchResult,
525 expected_decl: &str,
526 decl_kind: ReferenceKind,
527 expected_refs: &[&str],
528 ) {
529 res.declaration().assert_match(expected_decl); 652 res.declaration().assert_match(expected_decl);
530 assert_eq!(res.declaration_kind, decl_kind);
531
532 assert_eq!(res.references.len(), expected_refs.len()); 653 assert_eq!(res.references.len(), expected_refs.len());
533 res.references().iter().enumerate().for_each(|(i, r)| r.assert_match(expected_refs[i])); 654 res.references().iter().enumerate().for_each(|(i, r)| r.assert_match(expected_refs[i]));
534 } 655 }
535 656
657 impl Declaration {
658 fn debug_render(&self) -> String {
659 let mut s = format!("{} {:?}", self.nav.debug_render(), self.kind);
660 if let Some(access) = self.access {
661 s.push_str(&format!(" {:?}", access));
662 }
663 s
664 }
665
666 fn assert_match(&self, expected: &str) {
667 let actual = self.debug_render();
668 test_utils::assert_eq_text!(expected.trim(), actual.trim(),);
669 }
670 }
671
536 impl Reference { 672 impl Reference {
537 fn debug_render(&self) -> String { 673 fn debug_render(&self) -> String {
538 format!("{:?} {:?} {:?}", self.file_range.file_id, self.file_range.range, self.kind) 674 let mut s = format!(
675 "{:?} {:?} {:?}",
676 self.file_range.file_id, self.file_range.range, self.kind
677 );
678 if let Some(access) = self.access {
679 s.push_str(&format!(" {:?}", access));
680 }
681 s
539 } 682 }
540 683
541 fn assert_match(&self, expected: &str) { 684 fn assert_match(&self, expected: &str) {
diff --git a/crates/ra_lsp_server/src/conv.rs b/crates/ra_lsp_server/src/conv.rs
index c260b51c4..562699b7c 100644
--- a/crates/ra_lsp_server/src/conv.rs
+++ b/crates/ra_lsp_server/src/conv.rs
@@ -9,7 +9,7 @@ use lsp_types::{
9use ra_ide::{ 9use ra_ide::{
10 translate_offset_with_edit, CompletionItem, CompletionItemKind, FileId, FilePosition, 10 translate_offset_with_edit, CompletionItem, CompletionItemKind, FileId, FilePosition,
11 FileRange, FileSystemEdit, Fold, FoldKind, InsertTextFormat, LineCol, LineIndex, 11 FileRange, FileSystemEdit, Fold, FoldKind, InsertTextFormat, LineCol, LineIndex,
12 NavigationTarget, RangeInfo, Severity, SourceChange, SourceFileEdit, 12 NavigationTarget, RangeInfo, ReferenceAccess, Severity, SourceChange, SourceFileEdit,
13}; 13};
14use ra_syntax::{SyntaxKind, TextRange, TextUnit}; 14use ra_syntax::{SyntaxKind, TextRange, TextUnit};
15use ra_text_edit::{AtomTextEdit, TextEdit}; 15use ra_text_edit::{AtomTextEdit, TextEdit};
@@ -53,6 +53,18 @@ impl Conv for SyntaxKind {
53 } 53 }
54} 54}
55 55
56impl Conv for ReferenceAccess {
57 type Output = ::lsp_types::DocumentHighlightKind;
58
59 fn conv(self) -> Self::Output {
60 use lsp_types::DocumentHighlightKind;
61 match self {
62 ReferenceAccess::Read => DocumentHighlightKind::Read,
63 ReferenceAccess::Write => DocumentHighlightKind::Write,
64 }
65 }
66}
67
56impl Conv for CompletionItemKind { 68impl Conv for CompletionItemKind {
57 type Output = ::lsp_types::CompletionItemKind; 69 type Output = ::lsp_types::CompletionItemKind;
58 70
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs
index a5b6f48af..a592f0a12 100644
--- a/crates/ra_lsp_server/src/main_loop/handlers.rs
+++ b/crates/ra_lsp_server/src/main_loop/handlers.rs
@@ -536,18 +536,32 @@ pub fn handle_references(
536 536
537 let locations = if params.context.include_declaration { 537 let locations = if params.context.include_declaration {
538 refs.into_iter() 538 refs.into_iter()
539 .filter_map(|r| { 539 .filter_map(|reference| {
540 let line_index = world.analysis().file_line_index(r.file_range.file_id).ok()?; 540 let line_index =
541 to_location(r.file_range.file_id, r.file_range.range, &world, &line_index).ok() 541 world.analysis().file_line_index(reference.file_range.file_id).ok()?;
542 to_location(
543 reference.file_range.file_id,
544 reference.file_range.range,
545 &world,
546 &line_index,
547 )
548 .ok()
542 }) 549 })
543 .collect() 550 .collect()
544 } else { 551 } else {
545 // Only iterate over the references if include_declaration was false 552 // Only iterate over the references if include_declaration was false
546 refs.references() 553 refs.references()
547 .iter() 554 .iter()
548 .filter_map(|r| { 555 .filter_map(|reference| {
549 let line_index = world.analysis().file_line_index(r.file_range.file_id).ok()?; 556 let line_index =
550 to_location(r.file_range.file_id, r.file_range.range, &world, &line_index).ok() 557 world.analysis().file_line_index(reference.file_range.file_id).ok()?;
558 to_location(
559 reference.file_range.file_id,
560 reference.file_range.range,
561 &world,
562 &line_index,
563 )
564 .ok()
551 }) 565 })
552 .collect() 566 .collect()
553 }; 567 };
@@ -836,10 +850,10 @@ pub fn handle_document_highlight(
836 850
837 Ok(Some( 851 Ok(Some(
838 refs.into_iter() 852 refs.into_iter()
839 .filter(|r| r.file_range.file_id == file_id) 853 .filter(|reference| reference.file_range.file_id == file_id)
840 .map(|r| DocumentHighlight { 854 .map(|reference| DocumentHighlight {
841 range: r.file_range.range.conv_with(&line_index), 855 range: reference.file_range.range.conv_with(&line_index),
842 kind: None, 856 kind: reference.access.map(|it| it.conv()),
843 }) 857 })
844 .collect(), 858 .collect(),
845 )) 859 ))
diff --git a/crates/ra_syntax/src/ast/expr_extensions.rs b/crates/ra_syntax/src/ast/expr_extensions.rs
index 23b6aa901..3dfecfe76 100644
--- a/crates/ra_syntax/src/ast/expr_extensions.rs
+++ b/crates/ra_syntax/src/ast/expr_extensions.rs
@@ -144,6 +144,7 @@ impl BinOp {
144 } 144 }
145 } 145 }
146} 146}
147
147impl ast::BinExpr { 148impl ast::BinExpr {
148 pub fn op_details(&self) -> Option<(SyntaxToken, BinOp)> { 149 pub fn op_details(&self) -> Option<(SyntaxToken, BinOp)> {
149 self.syntax().children_with_tokens().filter_map(|it| it.into_token()).find_map(|c| { 150 self.syntax().children_with_tokens().filter_map(|it| it.into_token()).find_map(|c| {
diff --git a/editors/code/package.json b/editors/code/package.json
index 7d809a2d3..e7fc314f3 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -217,7 +217,10 @@
217 "description": "Trace requests to the ra_lsp_server" 217 "description": "Trace requests to the ra_lsp_server"
218 }, 218 },
219 "rust-analyzer.lruCapacity": { 219 "rust-analyzer.lruCapacity": {
220 "type": "number", 220 "type": [
221 "number",
222 "null"
223 ],
221 "default": null, 224 "default": null,
222 "description": "Number of syntax trees rust-analyzer keeps in memory" 225 "description": "Number of syntax trees rust-analyzer keeps in memory"
223 }, 226 },