From bfaefed3f61a29624ea3a0c94fcc498d34d0c31f Mon Sep 17 00:00:00 2001 From: gfreezy Date: Mon, 4 Feb 2019 00:27:36 +0800 Subject: fill match arm --- crates/ra_ide_api/src/assits.rs | 89 ++++++++++++ crates/ra_ide_api/src/assits/fill_match_arm.rs | 157 +++++++++++++++++++++ .../assits/snapshots/tests__fill_match_arm1.snap | 20 +++ .../assits/snapshots/tests__fill_match_arm2.snap | 20 +++ crates/ra_ide_api/src/imp.rs | 5 +- crates/ra_ide_api/src/lib.rs | 1 + crates/ra_ide_api_light/src/assists.rs | 17 ++- 7 files changed, 300 insertions(+), 9 deletions(-) create mode 100644 crates/ra_ide_api/src/assits.rs create mode 100644 crates/ra_ide_api/src/assits/fill_match_arm.rs create mode 100644 crates/ra_ide_api/src/assits/snapshots/tests__fill_match_arm1.snap create mode 100644 crates/ra_ide_api/src/assits/snapshots/tests__fill_match_arm2.snap diff --git a/crates/ra_ide_api/src/assits.rs b/crates/ra_ide_api/src/assits.rs new file mode 100644 index 000000000..2da251df5 --- /dev/null +++ b/crates/ra_ide_api/src/assits.rs @@ -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 { + 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) -> Option { + 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) -> 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, f: impl FnOnce(&mut AssistBuilder)) -> Option { + 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(&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 index 000000000..d433861a0 --- /dev/null +++ b/crates/ra_ide_api/src/assits/fill_match_arm.rs @@ -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 { + let match_expr = ctx.node_at_offset::()?; + + // 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::>(); + + 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 index 000000000..980726d92 --- /dev/null +++ b/crates/ra_ide_api/src/assits/snapshots/tests__fill_match_arm1.snap @@ -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 index 000000000..cee0efe74 --- /dev/null +++ b/crates/ra_ide_api/src/assits/snapshots/tests__fill_match_arm2.snap @@ -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 + } +] diff --git a/crates/ra_ide_api/src/imp.rs b/crates/ra_ide_api/src/imp.rs index 31e0f5d6d..5f672367c 100644 --- a/crates/ra_ide_api/src/imp.rs +++ b/crates/ra_ide_api/src/imp.rs @@ -10,7 +10,7 @@ use ra_db::{ 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 @@ impl db::RootDatabase { pub(crate) fn assists(&self, frange: FileRange) -> Vec { 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() } diff --git a/crates/ra_ide_api/src/lib.rs b/crates/ra_ide_api/src/lib.rs index 5d8acf9df..a087a2fff 100644 --- a/crates/ra_ide_api/src/lib.rs +++ b/crates/ra_ide_api/src/lib.rs @@ -26,6 +26,7 @@ mod syntax_highlighting; mod parent_module; mod rename; mod impls; +mod assits; #[cfg(test)] mod marks; diff --git a/crates/ra_ide_api_light/src/assists.rs b/crates/ra_ide_api_light/src/assists.rs index 8905b0419..e578805f1 100644 --- a/crates/ra_ide_api_light/src/assists.rs +++ b/crates/ra_ide_api_light/src/assists.rs @@ -104,7 +104,7 @@ pub enum Assist { } #[derive(Default)] -struct AssistBuilder { +pub struct AssistBuilder { edit: TextEditBuilder, cursor_position: Option, } @@ -142,11 +142,7 @@ impl<'a> AssistCtx<'a> { } 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) { self.edit.replace(range, replace_with.into()) } - fn replace_node_and_indent(&mut self, node: &SyntaxNode, replace_with: impl Into) { + pub fn replace_node_and_indent(&mut self, node: &SyntaxNode, replace_with: impl Into) { let mut replace_with = replace_with.into(); if let Some(indent) = leading_indent(node) { replace_with = reindent(&replace_with, indent) @@ -181,6 +177,13 @@ impl AssistBuilder { fn set_cursor(&mut self, offset: TextUnit) { self.cursor_position = Some(offset) } + pub fn build(self, label: impl Into) -> Assist { + Assist::Edit(LocalEdit { + label: label.into(), + cursor_position: self.cursor_position, + edit: self.edit.finish(), + }) + } } fn reindent(text: &str, indent: &str) -> String { -- cgit v1.2.3