fill match arm

This commit is contained in:
gfreezy 2019-02-04 00:27:36 +08:00
parent 581c97a5c3
commit bfaefed3f6
7 changed files with 300 additions and 9 deletions

View File

@ -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())
}
}

View File

@ -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<|> {}
}
"#,
);
}
}

View File

@ -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
}
]

View File

@ -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
}
]

View File

@ -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<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()
}

View File

@ -26,6 +26,7 @@ mod syntax_highlighting;
mod parent_module;
mod rename;
mod impls;
mod assits;
#[cfg(test)]
mod marks;

View 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 @@ 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<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 @@ impl AssistBuilder {
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 {