]> git.lizzy.rs Git - rust.git/commitdiff
fill match arm
authorgfreezy <gfreezy@gmail.com>
Sun, 3 Feb 2019 16:27:36 +0000 (00:27 +0800)
committergfreezy <gfreezy@gmail.com>
Sun, 3 Feb 2019 16:27:36 +0000 (00:27 +0800)
crates/ra_ide_api/src/assits.rs [new file with mode: 0644]
crates/ra_ide_api/src/assits/fill_match_arm.rs [new file with mode: 0644]
crates/ra_ide_api/src/assits/snapshots/tests__fill_match_arm1.snap [new file with mode: 0644]
crates/ra_ide_api/src/assits/snapshots/tests__fill_match_arm2.snap [new file with mode: 0644]
crates/ra_ide_api/src/imp.rs
crates/ra_ide_api/src/lib.rs
crates/ra_ide_api_light/src/assists.rs

diff --git a/crates/ra_ide_api/src/assits.rs b/crates/ra_ide_api/src/assits.rs
new file mode 100644 (file)
index 0000000..2da251d
--- /dev/null
@@ -0,0 +1,89 @@
+mod fill_match_arm;
+
+use ra_syntax::{
+    TextRange, SourceFile, AstNode,
+    algo::find_node_at_offset,
+};
+use ra_ide_api_light::{
+    LocalEdit,
+    assists::{
+        Assist,
+        AssistBuilder
+    }
+};
+use crate::{
+    db::RootDatabase,
+    FileId
+};
+
+/// Return all the assists applicable at the given position.
+pub(crate) fn assists(
+    db: &RootDatabase,
+    file_id: FileId,
+    file: &SourceFile,
+    range: TextRange,
+) -> Vec<LocalEdit> {
+    let ctx = AssistCtx::new(db, file_id, file, range);
+    [fill_match_arm::fill_match_arm]
+        .iter()
+        .filter_map(|&assist| ctx.clone().apply(assist))
+        .collect()
+}
+
+#[derive(Debug, Clone)]
+pub struct AssistCtx<'a> {
+    file_id: FileId,
+    source_file: &'a SourceFile,
+    db: &'a RootDatabase,
+    range: TextRange,
+    should_compute_edit: bool,
+}
+
+impl<'a> AssistCtx<'a> {
+    pub(crate) fn new(
+        db: &'a RootDatabase,
+        file_id: FileId,
+        source_file: &'a SourceFile,
+        range: TextRange,
+    ) -> AssistCtx<'a> {
+        AssistCtx {
+            source_file,
+            file_id,
+            db,
+            range,
+            should_compute_edit: false,
+        }
+    }
+
+    pub fn apply(mut self, assist: fn(AssistCtx) -> Option<Assist>) -> Option<LocalEdit> {
+        self.should_compute_edit = true;
+        match assist(self) {
+            None => None,
+            Some(Assist::Edit(e)) => Some(e),
+            Some(Assist::Applicable) => unreachable!(),
+        }
+    }
+
+    #[allow(unused)]
+    pub fn check(mut self, assist: fn(AssistCtx) -> Option<Assist>) -> bool {
+        self.should_compute_edit = false;
+        match assist(self) {
+            None => false,
+            Some(Assist::Edit(_)) => unreachable!(),
+            Some(Assist::Applicable) => true,
+        }
+    }
+
+    fn build(self, label: impl Into<String>, f: impl FnOnce(&mut AssistBuilder)) -> Option<Assist> {
+        if !self.should_compute_edit {
+            return Some(Assist::Applicable);
+        }
+        let mut edit = AssistBuilder::default();
+        f(&mut edit);
+        Some(edit.build(label))
+    }
+
+    pub(crate) fn node_at_offset<N: AstNode>(&self) -> Option<&'a N> {
+        find_node_at_offset(self.source_file.syntax(), self.range.start())
+    }
+}
diff --git a/crates/ra_ide_api/src/assits/fill_match_arm.rs b/crates/ra_ide_api/src/assits/fill_match_arm.rs
new file mode 100644 (file)
index 0000000..d433861
--- /dev/null
@@ -0,0 +1,157 @@
+use std::fmt::Write;
+use hir::{
+    AdtDef,
+    source_binder,
+    Ty,
+    FieldSource,
+};
+use ra_ide_api_light::{
+    assists::{
+        Assist,
+        AssistBuilder
+    }
+};
+use ra_syntax::{
+    ast::{
+        self,
+        AstNode,
+    }
+};
+
+use crate::assits::AssistCtx;
+
+pub fn fill_match_arm(ctx: AssistCtx) -> Option<Assist> {
+    let match_expr = ctx.node_at_offset::<ast::MatchExpr>()?;
+
+    // We already have some match arms, so we don't provide any assists.
+    match match_expr.match_arm_list() {
+        Some(arm_list) if arm_list.arms().count() > 0 => {
+            return None;
+        }
+        _ => {}
+    }
+
+    let expr = match_expr.expr()?;
+    let function = source_binder::function_from_child_node(ctx.db, ctx.file_id, expr.syntax())?;
+    let infer_result = function.infer(ctx.db);
+    let syntax_mapping = function.body_syntax_mapping(ctx.db);
+    let node_expr = syntax_mapping.node_expr(expr)?;
+    let match_expr_ty = infer_result[node_expr].clone();
+    match match_expr_ty {
+        Ty::Adt { def_id, .. } => match def_id {
+            AdtDef::Enum(e) => {
+                let mut buf = format!("match {} {{\n", expr.syntax().text().to_string());
+                let variants = e.variants(ctx.db);
+                for variant in variants {
+                    let name = variant.name(ctx.db)?;
+                    write!(
+                        &mut buf,
+                        "    {}::{}",
+                        e.name(ctx.db)?.to_string(),
+                        name.to_string()
+                    )
+                    .expect("write fmt");
+
+                    let pat = variant
+                        .fields(ctx.db)
+                        .into_iter()
+                        .map(|field| {
+                            let name = field.name(ctx.db).to_string();
+                            let (_, source) = field.source(ctx.db);
+                            match source {
+                                FieldSource::Named(_) => name,
+                                FieldSource::Pos(_) => "_".to_string(),
+                            }
+                        })
+                        .collect::<Vec<_>>();
+
+                    match pat.first().map(|s| s.as_str()) {
+                        Some("_") => write!(&mut buf, "({})", pat.join(", ")).expect("write fmt"),
+                        Some(_) => write!(&mut buf, "{{{}}}", pat.join(", ")).expect("write fmt"),
+                        None => (),
+                    };
+
+                    buf.push_str(" => (),\n");
+                }
+                buf.push_str("}");
+                ctx.build("fill match arms", |edit: &mut AssistBuilder| {
+                    edit.replace_node_and_indent(match_expr.syntax(), buf);
+                })
+            }
+            _ => None,
+        },
+        _ => None,
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use insta::assert_debug_snapshot_matches;
+
+    use ra_syntax::{TextRange, TextUnit};
+
+    use crate::{
+        FileRange,
+        mock_analysis::{analysis_and_position, single_file_with_position}
+};
+    use ra_db::SourceDatabase;
+
+    fn test_assit(name: &str, code: &str) {
+        let (analysis, position) = if code.contains("//-") {
+            analysis_and_position(code)
+        } else {
+            single_file_with_position(code)
+        };
+        let frange = FileRange {
+            file_id: position.file_id,
+            range: TextRange::offset_len(position.offset, TextUnit::from(1)),
+        };
+        let source_file = analysis
+            .with_db(|db| db.parse(frange.file_id))
+            .expect("source file");
+        let ret = analysis
+            .with_db(|db| crate::assits::assists(db, frange.file_id, &source_file, frange.range))
+            .expect("assits");
+
+        assert_debug_snapshot_matches!(name, ret);
+    }
+
+    #[test]
+    fn test_fill_match_arm() {
+        test_assit(
+            "fill_match_arm1",
+            r#"
+        enum A {
+            As,
+            Bs,
+            Cs(String),
+            Ds(String, String),
+            Es{x: usize, y: usize}
+        }
+
+        fn main() {
+            let a = A::As;
+            match a<|>
+        }
+        "#,
+        );
+
+        test_assit(
+            "fill_match_arm2",
+            r#"
+        enum A {
+            As,
+            Bs,
+            Cs(String),
+            Ds(String, String),
+            Es{x: usize, y: usize}
+        }
+
+        fn main() {
+            let a = A::As;
+            match a<|> {}
+        }
+        "#,
+        );
+    }
+}
diff --git a/crates/ra_ide_api/src/assits/snapshots/tests__fill_match_arm1.snap b/crates/ra_ide_api/src/assits/snapshots/tests__fill_match_arm1.snap
new file mode 100644 (file)
index 0000000..980726d
--- /dev/null
@@ -0,0 +1,20 @@
+---
+created: "2019-02-03T15:38:46.094184+00:00"
+creator: insta@0.5.2
+expression: ret
+source: crates/ra_ide_api/src/assits/fill_match_arm.rs
+---
+[
+    LocalEdit {
+        label: "fill match arms",
+        edit: TextEdit {
+            atoms: [
+                AtomTextEdit {
+                    delete: [211; 218),
+                    insert: "match a {\n                A::As => (),\n                A::Bs => (),\n                A::Cs(_) => (),\n                A::Ds(_, _) => (),\n                A::Es{x, y} => (),\n            }"
+                }
+            ]
+        },
+        cursor_position: None
+    }
+]
diff --git a/crates/ra_ide_api/src/assits/snapshots/tests__fill_match_arm2.snap b/crates/ra_ide_api/src/assits/snapshots/tests__fill_match_arm2.snap
new file mode 100644 (file)
index 0000000..cee0efe
--- /dev/null
@@ -0,0 +1,20 @@
+---
+created: "2019-02-03T15:41:34.640074+00:00"
+creator: insta@0.5.2
+expression: ret
+source: crates/ra_ide_api/src/assits/fill_match_arm.rs
+---
+[
+    LocalEdit {
+        label: "fill match arms",
+        edit: TextEdit {
+            atoms: [
+                AtomTextEdit {
+                    delete: [211; 221),
+                    insert: "match a {\n                A::As => (),\n                A::Bs => (),\n                A::Cs(_) => (),\n                A::Ds(_, _) => (),\n                A::Es{x, y} => (),\n            }"
+                }
+            ]
+        },
+        cursor_position: None
+    }
+]
index 31e0f5d6de2d6d98e24ce95b8a1a9802b4d7c342..5f672367caf59e4d106e17a5cb0f90f1cfc0ca88 100644 (file)
@@ -10,7 +10,7 @@
     SourceDatabase, SourceRoot, SourceRootId,
     salsa::{Database, SweepStrategy},
 };
-use ra_ide_api_light::{self, assists, LocalEdit, Severity};
+use ra_ide_api_light::{self, LocalEdit, Severity};
 use ra_syntax::{
     algo::find_node_at_offset, ast::{self, NameOwner}, AstNode,
     SourceFile,
@@ -238,8 +238,9 @@ pub(crate) fn diagnostics(&self, file_id: FileId) -> Vec<Diagnostic> {
 
     pub(crate) fn assists(&self, frange: FileRange) -> Vec<SourceChange> {
         let file = self.parse(frange.file_id);
-        assists::assists(&file, frange.range)
+        ra_ide_api_light::assists::assists(&file, frange.range)
             .into_iter()
+            .chain(crate::assits::assists(self, frange.file_id, &file, frange.range).into_iter())
             .map(|local_edit| SourceChange::from_local_edit(frange.file_id, local_edit))
             .collect()
     }
index 5d8acf9df0ffd52d2c6320c771fea9b94918473c..a087a2fff69db6ec3c92fc96b93d26ec756214cd 100644 (file)
@@ -26,6 +26,7 @@
 mod parent_module;
 mod rename;
 mod impls;
+mod assits;
 
 #[cfg(test)]
 mod marks;
index 8905b041913ec2360318523ee476b780e7c3e10a..e578805f1078de373075180f88123b5608cdd1d8 100644 (file)
@@ -104,7 +104,7 @@ pub enum Assist {
 }
 
 #[derive(Default)]
-struct AssistBuilder {
+pub struct AssistBuilder {
     edit: TextEditBuilder,
     cursor_position: Option<TextUnit>,
 }
@@ -142,11 +142,7 @@ fn build(self, label: impl Into<String>, f: impl FnOnce(&mut AssistBuilder)) ->
         }
         let mut edit = AssistBuilder::default();
         f(&mut edit);
-        Some(Assist::Edit(LocalEdit {
-            label: label.into(),
-            edit: edit.edit.finish(),
-            cursor_position: edit.cursor_position,
-        }))
+        Some(edit.build(label))
     }
 
     pub(crate) fn leaf_at_offset(&self) -> LeafAtOffset<&'a SyntaxNode> {
@@ -164,7 +160,7 @@ impl AssistBuilder {
     fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) {
         self.edit.replace(range, replace_with.into())
     }
-    fn replace_node_and_indent(&mut self, node: &SyntaxNode, replace_with: impl Into<String>) {
+    pub fn replace_node_and_indent(&mut self, node: &SyntaxNode, replace_with: impl Into<String>) {
         let mut replace_with = replace_with.into();
         if let Some(indent) = leading_indent(node) {
             replace_with = reindent(&replace_with, indent)
@@ -181,6 +177,13 @@ fn insert(&mut self, offset: TextUnit, text: impl Into<String>) {
     fn set_cursor(&mut self, offset: TextUnit) {
         self.cursor_position = Some(offset)
     }
+    pub fn build(self, label: impl Into<String>) -> Assist {
+        Assist::Edit(LocalEdit {
+            label: label.into(),
+            cursor_position: self.cursor_position,
+            edit: self.edit.finish(),
+        })
+    }
 }
 
 fn reindent(text: &str, indent: &str) -> String {