--- /dev/null
+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())
+ }
+}
--- /dev/null
+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<|> {}
+ }
+ "#,
+ );
+ }
+}
--- /dev/null
+---
+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
+ }
+]
--- /dev/null
+---
+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
+ }
+]
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,
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()
}
mod parent_module;
mod rename;
mod impls;
+mod assits;
#[cfg(test)]
mod marks;
}
#[derive(Default)]
-struct AssistBuilder {
+pub struct AssistBuilder {
edit: TextEditBuilder,
cursor_position: Option<TextUnit>,
}
}
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> {
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)
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 {