Repository: rust-analyzer/rust-analyzer (rust)

Issue:
Local var renaming strips `mut` away
Before:

```rust
fn test() {
    let mut x = 42;
    let y = x;
}
```

After (rename x to foo):

```rust
fn test() {
    let foo = 42;
    let y = foo;
}
```

Pull request description:
Move renames into ra_analysis and rename the correct range
Fixes #230. Supersedes #235.

TODO: add some tests for this

Patch:
diff --git a/crates/ra_analysis/src/completion/complete_keyword.rs b/crates/ra_analysis/src/completion/complete_keyword.rs
index b2486104a5..2869e67e0b 100644
--- a/crates/ra_analysis/src/completion/complete_keyword.rs
+++ b/crates/ra_analysis/src/completion/complete_keyword.rs
@@ -32,13 +32,8 @@ pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte
         acc.add(keyword("else if", "else if $0 {}"));
     }
     if is_in_loop_body(ctx.leaf) {
-        if ctx.can_be_stmt {
-            acc.add(keyword("continue", "continue;"));
-            acc.add(keyword("break", "break;"));
-        } else {
-            acc.add(keyword("continue", "continue"));
-            acc.add(keyword("break", "break"));
-        }
+        acc.add(keyword("continue", "continue"));
+        acc.add(keyword("break", "break"));
     }
     acc.add_all(complete_return(fn_def, ctx.can_be_stmt));
 }
@@ -206,8 +201,8 @@ mod tests {
             match "match $0 {}"
             while "while $0 {}"
             loop "loop {$0}"
-            continue "continue;"
-            break "break;"
+            continue "continue"
+            break "break"
             return "return $0;"
             "#,
         );
@@ -227,28 +222,4 @@ mod tests {
             "#,
         );
     }
-
-    #[test]
-    fn no_semi_after_break_continue_in_expr() {
-        check_keyword_completion(
-            r"
-            fn f() {
-                loop {
-                    match () {
-                        () => br<|>
-                    }
-                }
-            }
-            ",
-            r#"
-            if "if $0 {}"
-            match "match $0 {}"
-            while "while $0 {}"
-            loop "loop {$0}"
-            continue "continue"
-            break "break"
-            return "return"
-            "#,
-        )
-    }
 }
diff --git a/crates/ra_analysis/src/imp.rs b/crates/ra_analysis/src/imp.rs
index bff2e00c9b..5ed374c791 100644
--- a/crates/ra_analysis/src/imp.rs
+++ b/crates/ra_analysis/src/imp.rs
@@ -288,7 +288,11 @@ impl AnalysisImpl {
             Some(it) => it,
         };
 
-        let mut ret = vec![(position.file_id, binding.syntax().range())];
+        let mut ret = binding
+            .name()
+            .into_iter()
+            .map(|name| (position.file_id, name.syntax().range()))
+            .collect::<Vec<_>>();
         ret.extend(
             descr
                 .scopes(&*self.db)
@@ -505,7 +509,25 @@ impl AnalysisImpl {
         let infer = function.infer(&*self.db)?;
         Ok(infer.type_of_node(node).map(|t| t.to_string()))
     }
-
+    pub fn rename(
+        &self,
+        position: FilePosition,
+        new_name: &str,
+    ) -> Cancelable<Vec<SourceFileEdit>> {
+        let res = self
+            .find_all_refs(position)?
+            .iter()
+            .map(|(file_id, text_range)| SourceFileEdit {
+                file_id: *file_id,
+                edit: {
+                    let mut builder = ra_text_edit::TextEditBuilder::new();
+                    builder.replace(*text_range, new_name.into());
+                    builder.finish()
+                },
+            })
+            .collect::<Vec<_>>();
+        Ok(res)
+    }
     fn index_resolve(&self, name_ref: ast::NameRef) -> Cancelable<Vec<(FileId, FileSymbol)>> {
         let name = name_ref.text();
         let mut query = Query::new(name.to_string());
diff --git a/crates/ra_analysis/src/lib.rs b/crates/ra_analysis/src/lib.rs
index 9f5e9f358b..e561685100 100644
--- a/crates/ra_analysis/src/lib.rs
+++ b/crates/ra_analysis/src/lib.rs
@@ -367,6 +367,13 @@ impl Analysis {
     pub fn type_of(&self, frange: FileRange) -> Cancelable<Option<String>> {
         self.imp.type_of(frange)
     }
+    pub fn rename(
+        &self,
+        position: FilePosition,
+        new_name: &str,
+    ) -> Cancelable<Vec<SourceFileEdit>> {
+        self.imp.rename(position, new_name)
+    }
 }
 
 pub struct LibraryData {
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs
index a2c12a4c19..3b7a14a5c0 100644
--- a/crates/ra_lsp_server/src/main_loop/handlers.rs
+++ b/crates/ra_lsp_server/src/main_loop/handlers.rs
@@ -557,24 +557,19 @@ pub fn handle_rename(world: ServerWorld, params: RenameParams) -> Result<Option<
         .into());
     }
 
-    let refs = world
+    let renames = world
         .analysis()
-        .find_all_refs(FilePosition { file_id, offset })?;
-    if refs.is_empty() {
+        .rename(FilePosition { file_id, offset }, &*params.new_name)?;
+    if renames.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_with(Vec::new)
-                .push(TextEdit {
-                    range: loc.range,
-                    new_text: params.new_name.clone(),
-                });
-        }
+    for edit in renames {
+        changes
+            .entry(file_id.try_conv_with(&world)?)
+            .or_insert_with(Vec::new)
+            .extend(edit.edit.conv_with(&line_index));
     }
 
     Ok(Some(WorkspaceEdit {
diff --git a/crates/ra_syntax/src/grammar/patterns.rs b/crates/ra_syntax/src/grammar/patterns.rs
index 692ffbb8c2..64cdf0b1b0 100644
--- a/crates/ra_syntax/src/grammar/patterns.rs
+++ b/crates/ra_syntax/src/grammar/patterns.rs
@@ -48,16 +48,11 @@ fn atom_pat(p: &mut Parser, recovery_set: TokenSet) -> Option<CompletedMarker> {
     // test literal_pattern
     // fn main() {
     //     match () {
-    //         -1 => (),
     //         92 => (),
     //         'c' => (),
     //         "hello" => (),
     //     }
     // }
-    if p.at(MINUS) && (p.nth(1) == INT_NUMBER || p.nth(1) == FLOAT_NUMBER) {
-        p.bump();
-    }
-
     if let Some(m) = expressions::literal(p) {
         return Some(m);
     }


Test patch:
diff --git a/crates/ra_analysis/tests/tests.rs b/crates/ra_analysis/tests/tests.rs
index 210fa2a135..a314fbc40a 100644
--- a/crates/ra_analysis/tests/tests.rs
+++ b/crates/ra_analysis/tests/tests.rs
@@ -1,5 +1,5 @@
 use ra_syntax::TextRange;
-use test_utils::assert_eq_dbg;
+use test_utils::{assert_eq_dbg, assert_eq_text};
 
 use ra_analysis::{
     mock_analysis::{analysis_and_position, single_file, single_file_with_position, MockAnalysis},
@@ -453,3 +453,94 @@ fn test_find_all_refs_for_fn_param() {
     let refs = get_all_refs(code);
     assert_eq!(refs.len(), 2);
 }
+#[test]
+fn test_rename_for_local() {
+    test_rename(
+        r#"
+    fn main() {
+        let mut i = 1;
+        let j = 1;
+        i = i<|> + j;
+
+        {
+            i = 0;
+        }
+
+        i = 5;
+    }"#,
+        "k",
+        r#"
+    fn main() {
+        let mut k = 1;
+        let j = 1;
+        k = k + j;
+
+        {
+            k = 0;
+        }
+
+        k = 5;
+    }"#,
+    );
+}
+
+#[test]
+fn test_rename_for_param_inside() {
+    test_rename(
+        r#"
+    fn foo(i : u32) -> u32 {
+        i<|>
+    }"#,
+        "j",
+        r#"
+    fn foo(j : u32) -> u32 {
+        j
+    }"#,
+    );
+}
+
+#[test]
+fn test_rename_refs_for_fn_param() {
+    test_rename(
+        r#"
+    fn foo(i<|> : u32) -> u32 {
+        i
+    }"#,
+        "new_name",
+        r#"
+    fn foo(new_name : u32) -> u32 {
+        new_name
+    }"#,
+    );
+}
+
+#[test]
+fn test_rename_for_mut_param() {
+    test_rename(
+        r#"
+    fn foo(mut i<|> : u32) -> u32 {
+        i
+    }"#,
+        "new_name",
+        r#"
+    fn foo(mut new_name : u32) -> u32 {
+        new_name
+    }"#,
+    );
+}
+fn test_rename(text: &str, new_name: &str, expected: &str) {
+    let (analysis, position) = single_file_with_position(text);
+    let edits = analysis.rename(position, new_name).unwrap();
+    let mut text_edit_bulder = ra_text_edit::TextEditBuilder::new();
+    let mut file_id: Option<FileId> = None;
+    for edit in edits {
+        file_id = Some(edit.file_id);
+        for atom in edit.edit.as_atoms() {
+            text_edit_bulder.replace(atom.delete, atom.insert.clone());
+        }
+    }
+    let result = text_edit_bulder
+        .finish()
+        .apply(&*analysis.file_text(file_id.unwrap()));
+    assert_eq_text!(expected, &*result);
+}
diff --git a/crates/ra_syntax/tests/data/parser/inline/ok/0055_literal_pattern.rs b/crates/ra_syntax/tests/data/parser/inline/ok/0055_literal_pattern.rs
index 6dfd67b4ce..16f674d9db 100644
--- a/crates/ra_syntax/tests/data/parser/inline/ok/0055_literal_pattern.rs
+++ b/crates/ra_syntax/tests/data/parser/inline/ok/0055_literal_pattern.rs
@@ -1,6 +1,5 @@
 fn main() {
     match () {
-        -1 => (),
         92 => (),
         'c' => (),
         "hello" => (),
diff --git a/crates/ra_syntax/tests/data/parser/inline/ok/0055_literal_pattern.txt b/crates/ra_syntax/tests/data/parser/inline/ok/0055_literal_pattern.txt
index 71d62eaba7..99b289d562 100644
--- a/crates/ra_syntax/tests/data/parser/inline/ok/0055_literal_pattern.txt
+++ b/crates/ra_syntax/tests/data/parser/inline/ok/0055_literal_pattern.txt
@@ -1,5 +1,5 @@
-SOURCE_FILE@[0; 113)
-  FN_DEF@[0; 112)
+SOURCE_FILE@[0; 95)
+  FN_DEF@[0; 94)
     FN_KW@[0; 2)
     WHITESPACE@[2; 3)
     NAME@[3; 7)
@@ -8,23 +8,22 @@ SOURCE_FILE@[0; 113)
       L_PAREN@[7; 8)
       R_PAREN@[8; 9)
     WHITESPACE@[9; 10)
-    BLOCK@[10; 112)
+    BLOCK@[10; 94)
       L_CURLY@[10; 11)
       WHITESPACE@[11; 16)
-      MATCH_EXPR@[16; 110)
+      MATCH_EXPR@[16; 92)
         MATCH_KW@[16; 21)
         WHITESPACE@[21; 22)
         TUPLE_EXPR@[22; 24)
           L_PAREN@[22; 23)
           R_PAREN@[23; 24)
         WHITESPACE@[24; 25)
-        MATCH_ARM_LIST@[25; 110)
+        MATCH_ARM_LIST@[25; 92)
           L_CURLY@[25; 26)
           WHITESPACE@[26; 35)
           MATCH_ARM@[35; 43)
-            MINUS@[35; 36)
-            LITERAL@[36; 37)
-              INT_NUMBER@[36; 37) "1"
+            LITERAL@[35; 37)
+              INT_NUMBER@[35; 37) "92"
             WHITESPACE@[37; 38)
             FAT_ARROW@[38; 40)
             WHITESPACE@[40; 41)
@@ -33,40 +32,29 @@ SOURCE_FILE@[0; 113)
               R_PAREN@[42; 43)
           COMMA@[43; 44)
           WHITESPACE@[44; 53)
-          MATCH_ARM@[53; 61)
-            LITERAL@[53; 55)
-              INT_NUMBER@[53; 55) "92"
-            WHITESPACE@[55; 56)
-            FAT_ARROW@[56; 58)
-            WHITESPACE@[58; 59)
-            TUPLE_EXPR@[59; 61)
-              L_PAREN@[59; 60)
-              R_PAREN@[60; 61)
-          COMMA@[61; 62)
-          WHITESPACE@[62; 71)
-          MATCH_ARM@[71; 80)
-            LITERAL@[71; 74)
-              CHAR@[71; 74)
-            WHITESPACE@[74; 75)
-            FAT_ARROW@[75; 77)
-            WHITESPACE@[77; 78)
-            TUPLE_EXPR@[78; 80)
-              L_PAREN@[78; 79)
-              R_PAREN@[79; 80)
-          COMMA@[80; 81)
-          WHITESPACE@[81; 90)
-          MATCH_ARM@[90; 103)
-            LITERAL@[90; 97)
-              STRING@[90; 97)
-            WHITESPACE@[97; 98)
-            FAT_ARROW@[98; 100)
-            WHITESPACE@[100; 101)
-            TUPLE_EXPR@[101; 103)
-              L_PAREN@[101; 102)
-              R_PAREN@[102; 103)
-          COMMA@[103; 104)
-          WHITESPACE@[104; 109)
-          R_CURLY@[109; 110)
-      WHITESPACE@[110; 111)
-      R_CURLY@[111; 112)
-  WHITESPACE@[112; 113)
+          MATCH_ARM@[53; 62)
+            LITERAL@[53; 56)
+              CHAR@[53; 56)
+            WHITESPACE@[56; 57)
+            FAT_ARROW@[57; 59)
+            WHITESPACE@[59; 60)
+            TUPLE_EXPR@[60; 62)
+              L_PAREN@[60; 61)
+              R_PAREN@[61; 62)
+          COMMA@[62; 63)
+          WHITESPACE@[63; 72)
+          MATCH_ARM@[72; 85)
+            LITERAL@[72; 79)
+              STRING@[72; 79)
+            WHITESPACE@[79; 80)
+            FAT_ARROW@[80; 82)
+            WHITESPACE@[82; 83)
+            TUPLE_EXPR@[83; 85)
+              L_PAREN@[83; 84)
+              R_PAREN@[84; 85)
+          COMMA@[85; 86)
+          WHITESPACE@[86; 91)
+          R_CURLY@[91; 92)
+      WHITESPACE@[92; 93)
+      R_CURLY@[93; 94)
+  WHITESPACE@[94; 95)