Repository: rust-analyzer/rust-analyzer
Pull request #305 (2018-12-20 19:43:20)

Fold curly blocks


Patch:
diff --git a/crates/ra_editor/src/folding_ranges.rs b/crates/ra_editor/src/folding_ranges.rs
index 2a8fa3cda9..da542ecf08 100644
--- a/crates/ra_editor/src/folding_ranges.rs
+++ b/crates/ra_editor/src/folding_ranges.rs
@@ -10,6 +10,7 @@ use ra_syntax::{
 pub enum FoldKind {
     Comment,
     Imports,
+    Block,
 }
 
 #[derive(Debug)]
@@ -62,6 +63,8 @@ fn fold_kind(kind: SyntaxKind) -> Option<FoldKind> {
     match kind {
         COMMENT => Some(FoldKind::Comment),
         USE_ITEM => Some(FoldKind::Imports),
+        NAMED_FIELD_DEF_LIST | FIELD_PAT_LIST | ITEM_LIST | EXTERN_ITEM_LIST | USE_TREE_LIST
+        | BLOCK | ENUM_VARIANT_LIST => Some(FoldKind::Block),
         _ => None,
     }
 }
@@ -170,7 +173,7 @@ mod tests {
     use test_utils::extract_ranges;
 
     fn do_check(text: &str, fold_kinds: &[FoldKind]) {
-        let (ranges, text) = extract_ranges(text);
+        let (ranges, text) = extract_ranges(text, "fold");
         let file = SourceFileNode::parse(&text);
         let folds = folding_ranges(&file);
 
@@ -198,26 +201,27 @@ mod tests {
     #[test]
     fn test_fold_comments() {
         let text = r#"
-<|>// Hello
+<fold>// Hello
 // this is a multiline
 // comment
-//<|>
+//</fold>
 
 // But this is not
 
-fn main() {
-    <|>// We should
+fn main() <fold>{
+    <fold>// We should
     // also
     // fold
-    // this one.<|>
-    <|>//! But this one is different
-    //! because it has another flavor<|>
-    <|>/* As does this
-    multiline comment */<|>
-}"#;
+    // this one.</fold>
+    <fold>//! But this one is different
+    //! because it has another flavor</fold>
+    <fold>/* As does this
+    multiline comment */</fold>
+}</fold>"#;
 
         let fold_kinds = &[
             FoldKind::Comment,
+            FoldKind::Block,
             FoldKind::Comment,
             FoldKind::Comment,
             FoldKind::Comment,
@@ -228,60 +232,66 @@ fn main() {
     #[test]
     fn test_fold_imports() {
         let text = r#"
-<|>use std::{
+<fold>use std::<fold>{
     str,
     vec,
     io as iop
-};<|>
+}</fold>;</fold>
 
-fn main() {
-}"#;
+fn main() <fold>{
+}</fold>"#;
 
-        let folds = &[FoldKind::Imports];
+        let folds = &[FoldKind::Imports, FoldKind::Block, FoldKind::Block];
         do_check(text, folds);
     }
 
     #[test]
     fn test_fold_import_groups() {
         let text = r#"
-<|>use std::str;
+<fold>use std::str;
 use std::vec;
-use std::io as iop;<|>
+use std::io as iop;</fold>
 
-<|>use std::mem;
-use std::f64;<|>
+<fold>use std::mem;
+use std::f64;</fold>
 
 use std::collections::HashMap;
 // Some random comment
 use std::collections::VecDeque;
 
-fn main() {
-}"#;
+fn main() <fold>{
+}</fold>"#;
 
-        let folds = &[FoldKind::Imports, FoldKind::Imports];
+        let folds = &[FoldKind::Imports, FoldKind::Imports, FoldKind::Block];
         do_check(text, folds);
     }
 
     #[test]
     fn test_fold_import_and_groups() {
         let text = r#"
-<|>use std::str;
+<fold>use std::str;
 use std::vec;
-use std::io as iop;<|>
+use std::io as iop;</fold>
 
-<|>use std::mem;
-use std::f64;<|>
+<fold>use std::mem;
+use std::f64;</fold>
 
-<|>use std::collections::{
+<fold>use std::collections::<fold>{
     HashMap,
     VecDeque,
-};<|>
+}</fold>;</fold>
 // Some random comment
 
-fn main() {
-}"#;
+fn main() <fold>{
+}</fold>"#;
 
-        let folds = &[FoldKind::Imports, FoldKind::Imports, FoldKind::Imports];
+        let folds = &[
+            FoldKind::Imports,
+            FoldKind::Imports,
+            FoldKind::Imports,
+            FoldKind::Block,
+            FoldKind::Block,
+        ];
         do_check(text, folds);
     }
 
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs
index 572ae7fb53..801966304c 100644
--- a/crates/ra_lsp_server/src/main_loop/handlers.rs
+++ b/crates/ra_lsp_server/src/main_loop/handlers.rs
@@ -446,8 +446,9 @@ pub fn handle_folding_range(
             .into_iter()
             .map(|fold| {
                 let kind = match fold.kind {
-                    FoldKind::Comment => FoldingRangeKind::Comment,
-                    FoldKind::Imports => FoldingRangeKind::Imports,
+                    FoldKind::Comment => Some(FoldingRangeKind::Comment),
+                    FoldKind::Imports => Some(FoldingRangeKind::Imports),
+                    FoldKind::Block => None,
                 };
                 let range = fold.range.conv_with(&line_index);
                 FoldingRange {
@@ -455,7 +456,7 @@ pub fn handle_folding_range(
                     start_character: Some(range.start.character),
                     end_line: range.end.line,
                     end_character: Some(range.start.character),
-                    kind: Some(kind),
+                    kind,
                 }
             })
             .collect(),


Test patch:
diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs
index 0a94adb740..1ae800d7cc 100644
--- a/crates/test_utils/src/lib.rs
+++ b/crates/test_utils/src/lib.rs
@@ -66,15 +66,40 @@ pub fn try_extract_range(text: &str) -> Option<(TextRange, String)> {
     Some((TextRange::from_to(start, end), text))
 }
 
-pub fn extract_ranges(text: &str) -> (Vec<TextRange>, String) {
+/// Extracts ranges, marked with `<tag> </tag>` paris from the `text`
+pub fn extract_ranges(mut text: &str, tag: &str) -> (Vec<TextRange>, String) {
+    let open = format!("<{}>", tag);
+    let close = format!("</{}>", tag);
     let mut ranges = Vec::new();
-    let mut text = String::from(text);
-    while let Some((range, new_text)) = try_extract_range(&text) {
-        text = new_text;
-        ranges.push(range);
+    let mut res = String::new();
+    let mut stack = Vec::new();
+    loop {
+        match text.find('<') {
+            None => {
+                res.push_str(text);
+                break;
+            }
+            Some(i) => {
+                res.push_str(&text[..i]);
+                text = &text[i..];
+                if text.starts_with(&open) {
+                    text = &text[open.len()..];
+                    let from = TextUnit::of_str(&res);
+                    stack.push(from);
+                } else if text.starts_with(&close) {
+                    text = &text[close.len()..];
+                    let from = stack
+                        .pop()
+                        .unwrap_or_else(|| panic!("unmatched </{}>", tag));
+                    let to = TextUnit::of_str(&res);
+                    ranges.push(TextRange::from_to(from, to));
+                }
+            }
+        }
     }
-
-    (ranges, text)
+    assert!(stack.is_empty(), "unmatched <{}>", tag);
+    ranges.sort_by_key(|r| (r.start(), r.end()));
+    (ranges, res)
 }
 
 pub fn add_cursor(text: &str, offset: TextUnit) -> String {