diff --git a/crates/ra_analysis/src/imp.rs b/crates/ra_analysis/src/imp.rs index f1403cb5d7..a67b1717a7 100644 --- a/crates/ra_analysis/src/imp.rs +++ b/crates/ra_analysis/src/imp.rs @@ -257,6 +257,38 @@ impl AnalysisImpl { vec![] } + pub fn find_all_refs(&self, file_id: FileId, offset: TextUnit, _token: &JobToken) -> Vec<(FileId, TextRange)> { + let root = self.root(file_id); + let file = root.syntax(file_id); + let syntax = file.syntax(); + + let mut ret = vec![]; + + // Find the symbol we are looking for + if let Some(name_ref) = find_node_at_offset::(syntax, offset) { + + // We are only handing local references for now + if let Some(resolved) = resolve_local_name(&file, offset, name_ref) { + + ret.push((file_id, resolved.1)); + + if let Some(fn_def) = find_node_at_offset::(syntax, offset) { + + let refs : Vec<_> = fn_def.syntax().descendants() + .filter_map(ast::NameRef::cast) + .filter(|n: &ast::NameRef| resolve_local_name(&file, n.syntax().range().start(), *n) == Some(resolved.clone())) + .collect(); + + for r in refs { + ret.push((file_id, r.syntax().range())); + } + } + } + } + + ret + } + pub fn diagnostics(&self, file_id: FileId) -> Vec { let root = self.root(file_id); let module_tree = root.module_tree(); diff --git a/crates/ra_analysis/src/lib.rs b/crates/ra_analysis/src/lib.rs index 2eeacaabe3..46cc0722b2 100644 --- a/crates/ra_analysis/src/lib.rs +++ b/crates/ra_analysis/src/lib.rs @@ -217,6 +217,9 @@ impl Analysis { self.imp .approximately_resolve_symbol(file_id, offset, token) } + pub fn find_all_refs(&self, file_id: FileId, offset: TextUnit, token: &JobToken) -> Vec<(FileId, TextRange)> { + self.imp.find_all_refs(file_id, offset, token) + } pub fn parent_module(&self, file_id: FileId) -> Vec<(FileId, FileSymbol)> { self.imp.parent_module(file_id) } diff --git a/crates/ra_analysis/tests/tests.rs b/crates/ra_analysis/tests/tests.rs index e0c637d65d..0c2c69ea0d 100644 --- a/crates/ra_analysis/tests/tests.rs +++ b/crates/ra_analysis/tests/tests.rs @@ -10,6 +10,8 @@ use std::sync::Arc; use ra_analysis::{ Analysis, AnalysisHost, CrateGraph, CrateId, FileId, FileResolver, FnDescriptor, JobHandle, }; +use ra_syntax::TextRange; + use relative_path::{RelativePath, RelativePathBuf}; use rustc_hash::FxHashMap; use test_utils::{assert_eq_dbg, extract_offset}; @@ -225,3 +227,43 @@ fn bar() { assert_eq!(desc.ret_type, None); assert_eq!(param, Some(1)); } + +fn get_all_refs(text: &str) -> Vec<(FileId, TextRange)> { + let (offset, code) = extract_offset(text); + let code = code.as_str(); + + let (_handle, token) = JobHandle::new(); + let snap = analysis(&[("/lib.rs", code)]); + + snap.find_all_refs(FileId(1), offset, &token) +} + +#[test] +fn test_find_all_refs_for_local() { + let code = r#" + fn main() { + let mut i = 1; + let j = 1; + i = i<|> + j; + + { + i = 0; + } + + i = 5; + }"#; + + let refs = get_all_refs(code); + assert_eq!(refs.len(), 5); +} + +#[test] +fn test_find_all_refs_for_param_inside() { + let code = r#" + fn foo(i : u32) -> u32 { + i<|> + }"#; + + let refs = get_all_refs(code); + assert_eq!(refs.len(), 2); +} \ No newline at end of file diff --git a/crates/ra_editor/src/scope/fn_scope.rs b/crates/ra_editor/src/scope/fn_scope.rs index 9088e5a608..f10bdf6571 100644 --- a/crates/ra_editor/src/scope/fn_scope.rs +++ b/crates/ra_editor/src/scope/fn_scope.rs @@ -270,7 +270,6 @@ pub fn resolve_local_name<'a>( .filter(|entry| shadowed.insert(entry.name())) .filter(|entry| entry.name() == name_ref.text()) .nth(0); - eprintln!("ret = {:?}", ret); ret } diff --git a/crates/ra_lsp_server/src/caps.rs b/crates/ra_lsp_server/src/caps.rs index 1dd4957911..b6436b646c 100644 --- a/crates/ra_lsp_server/src/caps.rs +++ b/crates/ra_lsp_server/src/caps.rs @@ -2,7 +2,7 @@ use languageserver_types::{ CodeActionProviderCapability, CompletionOptions, DocumentOnTypeFormattingOptions, ExecuteCommandOptions, FoldingRangeProviderCapability, ServerCapabilities, SignatureHelpOptions, TextDocumentSyncCapability, TextDocumentSyncKind, - TextDocumentSyncOptions, + TextDocumentSyncOptions, RenameProviderCapability, RenameOptions }; pub fn server_capabilities() -> ServerCapabilities { @@ -27,7 +27,7 @@ pub fn server_capabilities() -> ServerCapabilities { definition_provider: Some(true), type_definition_provider: None, implementation_provider: None, - references_provider: None, + references_provider: Some(true), document_highlight_provider: None, document_symbol_provider: Some(true), workspace_symbol_provider: Some(true), @@ -40,7 +40,9 @@ pub fn server_capabilities() -> ServerCapabilities { more_trigger_character: None, }), folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)), - rename_provider: None, + rename_provider: Some(RenameProviderCapability::Options(RenameOptions{ + prepare_provider: Some(true) + })), color_provider: None, execute_command_provider: Some(ExecuteCommandOptions { commands: vec!["apply_code_action".to_string()], diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs index c25b638520..639fe45537 100644 --- a/crates/ra_lsp_server/src/main_loop/handlers.rs +++ b/crates/ra_lsp_server/src/main_loop/handlers.rs @@ -4,6 +4,7 @@ use languageserver_types::{ CodeActionResponse, Command, CompletionItem, CompletionItemKind, Diagnostic, DiagnosticSeverity, DocumentSymbol, FoldingRange, FoldingRangeKind, FoldingRangeParams, InsertTextFormat, Location, Position, SymbolInformation, TextDocumentIdentifier, TextEdit, + RenameParams, WorkspaceEdit, PrepareRenameResponse }; use ra_analysis::{FileId, FoldKind, JobToken, Query, RunnableKind}; use ra_syntax::text_utils::contains_offset_nonstrict; @@ -17,6 +18,8 @@ use crate::{ Result, }; +use std::collections::HashMap; + pub fn handle_syntax_tree( world: ServerWorld, params: req::SyntaxTreeParams, @@ -460,6 +463,81 @@ pub fn handle_signature_help( } } +pub fn handle_prepare_rename( + world: ServerWorld, + params: req::TextDocumentPositionParams, + token: JobToken, +) -> Result> { + let file_id = params.text_document.try_conv_with(&world)?; + let line_index = world.analysis().file_line_index(file_id); + let offset = params.position.conv_with(&line_index); + + // We support renaming references like handle_rename does. + // In the future we may want to reject the renaming of things like keywords here too. + let refs = world.analysis().find_all_refs(file_id, offset, &token); + if refs.is_empty() { + return Ok(None); + } + + let r = refs.first().unwrap(); + let loc = to_location(r.0, r.1, &world, &line_index)?; + + Ok(Some(PrepareRenameResponse::Range(loc.range))) +} + +pub fn handle_rename( + world: ServerWorld, + params: RenameParams, + token: JobToken, +) -> Result> { + let file_id = params.text_document.try_conv_with(&world)?; + let line_index = world.analysis().file_line_index(file_id); + let offset = params.position.conv_with(&line_index); + + if params.new_name.is_empty() { + return Ok(None); + } + + let refs = world.analysis().find_all_refs(file_id, offset, &token); + if refs.is_empty() { + return Ok(None); + } + + let mut changes = HashMap::new(); + for r in refs { + if let Ok(loc) = to_location(r.0, r.1, &world, &line_index) { + changes.entry(loc.uri).or_insert(Vec::new()).push( + TextEdit { + range: loc.range, + new_text: params.new_name.clone() + }); + } + } + + Ok(Some(WorkspaceEdit { + changes: Some(changes), + + // TODO: return this instead if client/server support it. See #144 + document_changes : None, + })) +} + +pub fn handle_references( + world: ServerWorld, + params: req::ReferenceParams, + token: JobToken, +) -> Result>> { + let file_id = params.text_document.try_conv_with(&world)?; + let line_index = world.analysis().file_line_index(file_id); + let offset = params.position.conv_with(&line_index); + + let refs = world.analysis().find_all_refs(file_id, offset, &token); + + Ok(Some(refs.into_iter() + .filter_map(|r| to_location(r.0, r.1, &world, &line_index).ok()) + .collect())) +} + pub fn handle_code_action( world: ServerWorld, params: req::CodeActionParams, diff --git a/crates/ra_lsp_server/src/main_loop/mod.rs b/crates/ra_lsp_server/src/main_loop/mod.rs index a11baf4aae..165f2e78f0 100644 --- a/crates/ra_lsp_server/src/main_loop/mod.rs +++ b/crates/ra_lsp_server/src/main_loop/mod.rs @@ -248,6 +248,9 @@ fn on_request( .on::(handlers::handle_code_action)? .on::(handlers::handle_folding_range)? .on::(handlers::handle_signature_help)? + .on::(handlers::handle_prepare_rename)? + .on::(handlers::handle_rename)? + .on::(handlers::handle_references)? .finish(); match req { Ok((id, handle)) => { diff --git a/crates/ra_lsp_server/src/req.rs b/crates/ra_lsp_server/src/req.rs index b76bfbcbc6..6cd04d84cd 100644 --- a/crates/ra_lsp_server/src/req.rs +++ b/crates/ra_lsp_server/src/req.rs @@ -7,7 +7,7 @@ pub use languageserver_types::{ CompletionResponse, DocumentOnTypeFormattingParams, DocumentSymbolParams, DocumentSymbolResponse, ExecuteCommandParams, Hover, InitializeResult, PublishDiagnosticsParams, SignatureHelp, TextDocumentEdit, TextDocumentPositionParams, - TextEdit, WorkspaceSymbolParams, + TextEdit, WorkspaceSymbolParams, ReferenceParams, }; pub enum SyntaxTree {} diff --git a/editors/code/package-lock.json b/editors/code/package-lock.json index 33c5203b25..fe304623fe 100644 --- a/editors/code/package-lock.json +++ b/editors/code/package-lock.json @@ -2074,8 +2074,7 @@ "semver": { "version": "5.5.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", - "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==", - "dev": true + "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==" }, "source-map": { "version": "0.6.1", @@ -2509,9 +2508,9 @@ } }, "vsce": { - "version": "1.51.1", - "resolved": "https://registry.npmjs.org/vsce/-/vsce-1.51.1.tgz", - "integrity": "sha512-Hf2HE9O/MRQHxUUgWHAm7mOkz0K5swuF2smaE/sP7+OWp/5DdIPFwmLEYCCZHxG25l3GBRoO0dAL8S5w//et+g==", + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/vsce/-/vsce-1.52.0.tgz", + "integrity": "sha512-k+KYoTx1sacpYf2BHTA7GN82MNSlf2N4EuppFWwtTN/Sh6fWzIJafxxCNBCDK0H+5NDWfRGZheBY8C3/HOE2ZA==", "dev": true, "requires": { "cheerio": "1.0.0-rc.2", @@ -2561,10 +2560,11 @@ "integrity": "sha512-perEnXQdQOJMTDFNv+UF3h1Y0z4iSiaN9jIlb0OqIYgosPCZGYh/MCUlkFtV2668PL69lRDO32hmvL2yiidUYg==" }, "vscode-languageclient": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-4.4.2.tgz", - "integrity": "sha512-9TUzsg1UM6n1UEyPlWbDf7tK1wJAK7UGFRmGDN8sz4KmbbDiVRh6YicaB/5oRSVTpuV47PdJpYlOl3SJ0RiK1Q==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-5.1.1.tgz", + "integrity": "sha512-jMxshi+BPRQFNG8GB00dJv7ldqMda0be26laYYll/udtJuHbog6RqK10GSxHWDN0PgY0b0m5fePyTk3bq8a0TA==", "requires": { + "semver": "5.5.1", "vscode-languageserver-protocol": "3.13.0" } }, diff --git a/editors/code/package.json b/editors/code/package.json index eeb6dd816e..ea84a1ccb4 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -28,7 +28,7 @@ "singleQuote": true }, "dependencies": { - "vscode-languageclient": "^4.4.0" + "vscode-languageclient": "^5.1.1" }, "devDependencies": { "@types/mocha": "^2.2.42", @@ -37,7 +37,7 @@ "tslint": "^5.11.0", "tslint-config-prettier": "^1.15.0", "typescript": "^2.6.1", - "vsce": "^1.51.1", + "vsce": "^1.52.0", "vscode": "^1.1.21" }, "activationEvents": [