move add_missing_members to structured editing API

Currently, this is more code, and we also loose auto-indenting of
bodies, but, long-term, this is the right approach
This commit is contained in:
Aleksey Kladov 2019-04-22 13:01:33 +03:00
parent b811922a53
commit 268e739c94
4 changed files with 229 additions and 144 deletions

View File

@ -1,14 +1,9 @@
use std::fmt::Write;
use crate::{Assist, AssistId, AssistCtx};
use crate::{Assist, AssistId, AssistCtx, ast_editor::{AstEditor, AstBuilder}};
use hir::db::HirDatabase;
use ra_syntax::{SmolStr, SyntaxKind, TextRange, TextUnit, TreeArc};
use ra_syntax::ast::{self, AstNode, AstToken, FnDef, ImplItem, ImplItemKind, NameOwner};
use ra_syntax::{SmolStr, TreeArc};
use ra_syntax::ast::{self, AstNode, FnDef, ImplItem, ImplItemKind, NameOwner};
use ra_db::FilePosition;
use ra_fmt::{leading_indent, reindent};
use itertools::Itertools;
enum AddMissingImplMembersMode {
DefaultMethodsOnly,
@ -76,48 +71,35 @@ fn add_missing_impl_members_inner(
}
ctx.add_action(AssistId(assist_id), label, |edit| {
let (parent_indent, indent) = {
// FIXME: Find a way to get the indent already used in the file.
// Now, we copy the indent of first item or indent with 4 spaces relative to impl block
const DEFAULT_INDENT: &str = " ";
let first_item = impl_item_list.impl_items().next();
let first_item_indent =
first_item.and_then(|i| leading_indent(i.syntax())).map(ToOwned::to_owned);
let impl_block_indent = leading_indent(impl_node.syntax()).unwrap_or_default();
let n_existing_items = impl_item_list.impl_items().count();
let fns = missing_fns.into_iter().map(add_body_and_strip_docstring).collect::<Vec<_>>();
(
impl_block_indent.to_owned(),
first_item_indent.unwrap_or_else(|| impl_block_indent.to_owned() + DEFAULT_INDENT),
)
};
let mut ast_editor = AstEditor::new(impl_item_list);
if n_existing_items == 0 {
ast_editor.make_multiline();
}
ast_editor.append_functions(fns.iter().map(|it| &**it));
let first_new_item = ast_editor.ast().impl_items().nth(n_existing_items).unwrap();
let cursor_poisition = first_new_item.syntax().range().start();
ast_editor.into_text_edit(edit.text_edit_builder());
let changed_range = {
let children = impl_item_list.syntax().children_with_tokens();
let last_whitespace =
children.filter_map(|it| ast::Whitespace::cast(it.as_token()?)).last();
last_whitespace.map(|w| w.syntax().range()).unwrap_or_else(|| {
let in_brackets = impl_item_list.syntax().range().end() - TextUnit::of_str("}");
TextRange::from_to(in_brackets, in_brackets)
})
};
let func_bodies = format!("\n{}", missing_fns.into_iter().map(build_func_body).join("\n"));
let trailing_whitespace = format!("\n{}", parent_indent);
let func_bodies = reindent(&func_bodies, &indent) + &trailing_whitespace;
let replaced_text_range = TextUnit::of_str(&func_bodies);
edit.replace(changed_range, func_bodies);
// FIXME: place the cursor on the first unimplemented?
edit.set_cursor(
changed_range.start() + replaced_text_range - TextUnit::of_str(&trailing_whitespace),
);
edit.set_cursor(cursor_poisition);
});
ctx.build()
}
fn add_body_and_strip_docstring(fn_def: &ast::FnDef) -> TreeArc<ast::FnDef> {
let mut ast_editor = AstEditor::new(fn_def);
if fn_def.body().is_none() {
ast_editor.set_body(&AstBuilder::<ast::Block>::single_expr(
&AstBuilder::<ast::Expr>::unimplemented(),
));
}
ast_editor.strip_attrs_and_docs();
ast_editor.ast().to_owned()
}
/// Given an `ast::ImplBlock`, resolves the target trait (the one being
/// implemented) to a `ast::TraitDef`.
fn resolve_target_trait_def(
@ -134,22 +116,6 @@ fn resolve_target_trait_def(
}
}
fn build_func_body(def: &ast::FnDef) -> String {
let mut buf = String::new();
for child in def.syntax().children_with_tokens() {
match (child.prev_sibling_or_token().map(|c| c.kind()), child.kind()) {
(_, SyntaxKind::SEMI) => buf.push_str(" {\n unimplemented!()\n}"),
(_, SyntaxKind::ATTR) | (_, SyntaxKind::COMMENT) => {}
(Some(SyntaxKind::ATTR), SyntaxKind::WHITESPACE)
| (Some(SyntaxKind::COMMENT), SyntaxKind::WHITESPACE) => {}
_ => write!(buf, "{}", child).unwrap(),
};
}
buf.trim_end().to_string()
}
#[cfg(test)]
mod tests {
use super::*;
@ -170,7 +136,7 @@ struct S;
impl Foo for S {
fn bar(&self) {}
<|>
<|>
}",
"
trait Foo {
@ -183,12 +149,9 @@ struct S;
impl Foo for S {
fn bar(&self) {}
fn foo(&self) {
unimplemented!()
}
fn baz(&self) {
unimplemented!()
}<|>
<|>fn foo(&self) { unimplemented!() }
fn baz(&self) { unimplemented!() }
}",
);
}
@ -208,7 +171,7 @@ struct S;
impl Foo for S {
fn bar(&self) {}
<|>
<|>
}",
"
trait Foo {
@ -221,9 +184,8 @@ struct S;
impl Foo for S {
fn bar(&self) {}
fn foo(&self) {
unimplemented!()
}<|>
<|>fn foo(&self) { unimplemented!() }
}",
);
}
@ -240,9 +202,7 @@ impl Foo for S { <|> }",
trait Foo { fn foo(&self); }
struct S;
impl Foo for S {
fn foo(&self) {
unimplemented!()
}<|>
<|>fn foo(&self) { unimplemented!() }
}",
);
}
@ -259,9 +219,7 @@ impl Foo for S {}<|>",
trait Foo { fn foo(&self); }
struct S;
impl Foo for S {
fn foo(&self) {
unimplemented!()
}<|>
<|>fn foo(&self) { unimplemented!() }
}",
)
}
@ -291,35 +249,6 @@ impl Foo for S { <|> }",
)
}
#[test]
fn test_indented_impl_block() {
check_assist(
add_missing_impl_members,
"
trait Foo {
fn valid(some: u32) -> bool;
}
struct S;
mod my_mod {
impl crate::Foo for S { <|> }
}",
"
trait Foo {
fn valid(some: u32) -> bool;
}
struct S;
mod my_mod {
impl crate::Foo for S {
fn valid(some: u32) -> bool {
unimplemented!()
}<|>
}
}",
)
}
#[test]
fn test_with_docstring_and_attrs() {
check_assist(
@ -342,9 +271,7 @@ trait Foo {
}
struct S;
impl Foo for S {
fn foo(&self) {
unimplemented!()
}<|>
<|>fn foo(&self) { unimplemented!() }
}"#,
)
}
@ -367,7 +294,7 @@ trait Foo {
}
struct S;
impl Foo for S {
fn valid(some: u32) -> bool { false }<|>
<|>fn valid(some: u32) -> bool { false }
}",
)
}

View File

@ -1,4 +1,4 @@
use std::iter;
use std::{iter, ops::RangeInclusive};
use arrayvec::ArrayVec;
use ra_text_edit::TextEditBuilder;
@ -26,6 +26,7 @@ impl<N: AstNode> AstEditor<N> {
&*self.ast
}
#[must_use]
fn insert_children<'a>(
&self,
position: InsertPosition<SyntaxElement<'_>>,
@ -34,6 +35,46 @@ impl<N: AstNode> AstEditor<N> {
let new_syntax = self.ast().syntax().insert_children(position, to_insert);
N::cast(&new_syntax).unwrap().to_owned()
}
#[must_use]
fn replace_children<'a>(
&self,
to_delete: RangeInclusive<SyntaxElement<'_>>,
to_insert: impl Iterator<Item = SyntaxElement<'a>>,
) -> TreeArc<N> {
let new_syntax = self.ast().syntax().replace_children(to_delete, to_insert);
N::cast(&new_syntax).unwrap().to_owned()
}
fn do_make_multiline(&mut self) {
let l_curly =
match self.ast().syntax().children_with_tokens().find(|it| it.kind() == L_CURLY) {
Some(it) => it,
None => return,
};
let sibling = match l_curly.next_sibling_or_token() {
Some(it) => it,
None => return,
};
let existing_ws = match sibling.as_token() {
None => None,
Some(tok) if tok.kind() != WHITESPACE => None,
Some(ws) => {
if ws.text().contains('\n') {
return;
}
Some(ws)
}
};
let indent = leading_indent(self.ast().syntax()).unwrap_or("");
let ws = tokens::WsBuilder::new(&format!("\n{}", indent));
let to_insert = iter::once(ws.ws().into());
self.ast = match existing_ws {
None => self.insert_children(InsertPosition::After(l_curly), to_insert),
Some(ws) => self.replace_children(RangeInclusive::new(ws.into(), ws.into()), to_insert),
};
}
}
impl AstEditor<ast::NamedFieldList> {
@ -42,23 +83,7 @@ impl AstEditor<ast::NamedFieldList> {
}
pub fn make_multiline(&mut self) {
let l_curly = match self.l_curly() {
Some(it) => it,
None => return,
};
let sibling = match l_curly.next_sibling_or_token() {
Some(it) => it,
None => return,
};
if sibling.as_token().map(|it| it.text().contains('\n')) == Some(true) {
return;
}
let ws = tokens::WsBuilder::new(&format!(
"\n{}",
leading_indent(self.ast().syntax()).unwrap_or("")
));
self.ast = self.insert_children(InsertPosition::After(l_curly), iter::once(ws.ws().into()));
self.do_make_multiline()
}
pub fn insert_field(
@ -132,6 +157,79 @@ impl AstEditor<ast::NamedFieldList> {
}
}
impl AstEditor<ast::ItemList> {
pub fn make_multiline(&mut self) {
self.do_make_multiline()
}
pub fn append_functions<'a>(&mut self, fns: impl Iterator<Item = &'a ast::FnDef>) {
fns.for_each(|it| self.append_function(it))
}
pub fn append_function(&mut self, fn_def: &ast::FnDef) {
let (indent, position) = match self.ast().impl_items().last() {
Some(it) => (
leading_indent(it.syntax()).unwrap_or("").to_string(),
InsertPosition::After(it.syntax().into()),
),
None => match self.l_curly() {
Some(it) => (
" ".to_string() + leading_indent(self.ast().syntax()).unwrap_or(""),
InsertPosition::After(it),
),
None => return,
},
};
let ws = tokens::WsBuilder::new(&format!("\n{}", indent));
let to_insert: ArrayVec<[SyntaxElement; 2]> =
[ws.ws().into(), fn_def.syntax().into()].into();
self.ast = self.insert_children(position, to_insert.into_iter());
}
fn l_curly(&self) -> Option<SyntaxElement> {
self.ast().syntax().children_with_tokens().find(|it| it.kind() == L_CURLY)
}
}
impl AstEditor<ast::FnDef> {
pub fn set_body(&mut self, body: &ast::Block) {
let mut to_insert: ArrayVec<[SyntaxElement; 2]> = ArrayVec::new();
let old_body_or_semi: SyntaxElement = if let Some(old_body) = self.ast().body() {
old_body.syntax().into()
} else if let Some(semi) = self.ast().semicolon_token() {
to_insert.push(tokens::single_space().into());
semi.into()
} else {
to_insert.push(tokens::single_space().into());
to_insert.push(body.syntax().into());
self.ast = self.insert_children(InsertPosition::Last, to_insert.into_iter());
return;
};
to_insert.push(body.syntax().into());
let replace_range = RangeInclusive::new(old_body_or_semi, old_body_or_semi);
self.ast = self.replace_children(replace_range, to_insert.into_iter())
}
pub fn strip_attrs_and_docs(&mut self) {
loop {
if let Some(start) = self
.ast()
.syntax()
.children_with_tokens()
.find(|it| it.kind() == ATTR || it.kind() == COMMENT)
{
let end = match start.next_sibling_or_token() {
Some(el) if el.kind() == WHITESPACE => el,
Some(_) | None => start,
};
self.ast = self.replace_children(RangeInclusive::new(start, end), iter::empty());
} else {
break;
}
}
}
}
pub struct AstBuilder<N: AstNode> {
_phantom: std::marker::PhantomData<N>,
}
@ -149,6 +247,16 @@ impl AstBuilder<ast::NamedField> {
}
}
impl AstBuilder<ast::Block> {
fn from_text(text: &str) -> TreeArc<ast::Block> {
ast_node_from_file_text(&format!("fn f() {}", text))
}
pub fn single_expr(e: &ast::Expr) -> TreeArc<ast::Block> {
Self::from_text(&format!("{{ {} }}", e.syntax()))
}
}
impl AstBuilder<ast::Expr> {
fn from_text(text: &str) -> TreeArc<ast::Expr> {
ast_node_from_file_text(&format!("fn f() {{ {}; }}", text))
@ -157,6 +265,10 @@ impl AstBuilder<ast::Expr> {
pub fn unit() -> TreeArc<ast::Expr> {
Self::from_text("()")
}
pub fn unimplemented() -> TreeArc<ast::Expr> {
Self::from_text("unimplemented!()")
}
}
impl AstBuilder<ast::NameRef> {
@ -197,6 +309,16 @@ mod tokens {
.unwrap()
}
#[allow(unused)]
pub(crate) fn single_newline() -> SyntaxToken<'static> {
SOURCE_FILE
.syntax()
.descendants_with_tokens()
.filter_map(|it| it.as_token())
.find(|it| it.kind() == WHITESPACE && it.text().as_str() == "\n")
.unwrap()
}
pub(crate) struct WsBuilder(TreeArc<SourceFile>);
impl WsBuilder {

View File

@ -210,6 +210,15 @@ impl ast::EnumVariant {
}
}
impl ast::FnDef {
pub fn semicolon_token(&self) -> Option<SyntaxToken<'_>> {
self.syntax()
.last_child_or_token()
.and_then(|it| it.as_token())
.filter(|it| it.kind() == SEMI)
}
}
impl ast::LetStmt {
pub fn has_semi(&self) -> bool {
match self.syntax().last_child_or_token() {

View File

@ -7,6 +7,7 @@
//! modules just wraps its API.
use std::{
ops::RangeInclusive,
fmt::{self, Write},
any::Any,
borrow::Borrow,
@ -323,8 +324,6 @@ impl SyntaxNode {
///
/// This is a type-unsafe low-level editing API, if you need to use it,
/// prefer to create a type-safe abstraction on top of it instead.
///
///
pub fn insert_children<'a>(
&self,
position: InsertPosition<SyntaxElement<'_>>,
@ -338,12 +337,6 @@ impl SyntaxNode {
let old_children = self.0.green().children();
let get_anchor_pos = |anchor: SyntaxElement| -> usize {
self.children_with_tokens()
.position(|it| it == anchor)
.expect("anchor is not a child of current element")
};
let new_children = match position {
InsertPosition::First => {
to_insert.chain(old_children.iter().cloned()).collect::<Box<[_]>>()
@ -353,7 +346,8 @@ impl SyntaxNode {
}
InsertPosition::Before(anchor) | InsertPosition::After(anchor) => {
let take_anchor = if let InsertPosition::After(_) = position { 1 } else { 0 };
let (before, after) = old_children.split_at(get_anchor_pos(anchor) + take_anchor);
let split_at = self.position_of_child(anchor) + take_anchor;
let (before, after) = old_children.split_at(split_at);
before
.iter()
.cloned()
@ -363,6 +357,33 @@ impl SyntaxNode {
}
};
self.with_children(new_children)
}
/// Replaces all nodes in `to_delete` with nodes from `to_insert`
///
/// This is a type-unsafe low-level editing API, if you need to use it,
/// prefer to create a type-safe abstraction on top of it instead.
pub fn replace_children<'a>(
&self,
to_delete: RangeInclusive<SyntaxElement<'_>>,
to_insert: impl Iterator<Item = SyntaxElement<'a>>,
) -> TreeArc<SyntaxNode> {
let start = self.position_of_child(*to_delete.start());
let end = self.position_of_child(*to_delete.end());
let old_children = self.0.green().children();
let new_children = old_children[..start]
.iter()
.cloned()
.chain(to_insert.map(to_green_element))
.chain(old_children[end + 1..].iter().cloned())
.collect::<Box<[_]>>();
self.with_children(new_children)
}
fn with_children(&self, new_children: Box<[rowan::GreenElement]>) -> TreeArc<SyntaxNode> {
let len = new_children.iter().map(|it| it.text_len()).sum::<TextUnit>();
let new_node = GreenNode::new(rowan::SyntaxKind(self.kind() as u16), new_children);
let new_file_node = self.replace_with(new_node);
let file = SourceFile::new(new_file_node, Vec::new());
@ -370,16 +391,22 @@ impl SyntaxNode {
// FIXME: use a more elegant way to re-fetch the node (#1185), make
// `range` private afterwards
let mut ptr = SyntaxNodePtr::new(self);
ptr.range = TextRange::from_to(ptr.range().start(), ptr.range().end() + delta);
ptr.range = TextRange::offset_len(ptr.range().start(), len);
return ptr.to_node(&file).to_owned();
}
fn to_green_element(element: SyntaxElement) -> rowan::GreenElement {
match element {
SyntaxElement::Node(node) => node.0.green().clone().into(),
SyntaxElement::Token(tok) => {
GreenToken::new(rowan::SyntaxKind(tok.kind() as u16), tok.text().clone()).into()
}
}
fn position_of_child(&self, child: SyntaxElement) -> usize {
self.children_with_tokens()
.position(|it| it == child)
.expect("elemetn is not a child of current element")
}
}
fn to_green_element(element: SyntaxElement) -> rowan::GreenElement {
match element {
SyntaxElement::Node(node) => node.0.green().clone().into(),
SyntaxElement::Token(tok) => {
GreenToken::new(rowan::SyntaxKind(tok.kind() as u16), tok.text().clone()).into()
}
}
}