migrate add impl items to the new editing API

This commit is contained in:
Aleksey Kladov 2019-09-28 20:09:57 +03:00
parent 5dbbfda34a
commit 0840ec038b
4 changed files with 109 additions and 80 deletions

View File

@ -2,7 +2,7 @@ use hir::db::HirDatabase;
use ra_db::FileRange;
use ra_fmt::{leading_indent, reindent};
use ra_syntax::{
algo::{find_covering_element, find_node_at_offset},
algo::{self, find_covering_element, find_node_at_offset},
AstNode, SourceFile, SyntaxElement, SyntaxNode, SyntaxToken, TextRange, TextUnit,
TokenAtOffset,
};
@ -177,6 +177,12 @@ impl AssistBuilder {
&mut self.edit
}
pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) {
for (from, to) in algo::diff(old.syntax(), new.syntax()) {
self.edit.replace(from.text_range(), to.to_string())
}
}
fn build(self) -> AssistAction {
AssistAction {
edit: self.edit.finish(),

View File

@ -4,7 +4,7 @@ use ra_syntax::{
SmolStr,
};
use crate::{ast_editor::AstEditor, Assist, AssistCtx, AssistId};
use crate::{Assist, AssistCtx, AssistId};
#[derive(PartialEq)]
enum AddMissingImplMembersMode {
@ -79,14 +79,13 @@ fn add_missing_impl_members_inner(
ast::ImplItem::FnDef(def) => edit::strip_attrs_and_docs(add_body(def).into()),
_ => edit::strip_attrs_and_docs(it),
});
let mut ast_editor = AstEditor::new(impl_item_list);
ast_editor.append_items(items);
let first_new_item = ast_editor.ast().impl_items().nth(n_existing_items).unwrap();
let cursor_position = first_new_item.syntax().text_range().start();
ast_editor.into_text_edit(edit.text_edit_builder());
let new_impl_item_list = impl_item_list.append_items(items);
let cursor_position = {
let first_new_item = new_impl_item_list.impl_items().nth(n_existing_items).unwrap();
first_new_item.syntax().text_range().start()
};
edit.replace_ast(impl_item_list, new_impl_item_list);
edit.set_cursor(cursor_position);
});

View File

@ -7,9 +7,7 @@ use ra_fmt::leading_indent;
use ra_syntax::{
algo,
ast::{self, make::tokens, TypeBoundsOwner},
AstNode, Direction, InsertPosition, SyntaxElement,
SyntaxKind::*,
T,
AstNode, Direction, InsertPosition, SyntaxElement, T,
};
use ra_text_edit::TextEditBuilder;
@ -67,38 +65,6 @@ impl<N: AstNode> AstEditor<N> {
let new_syntax = algo::replace_children(self.ast().syntax(), to_delete, &mut to_insert);
N::cast(new_syntax).unwrap()
}
fn do_make_multiline(&mut self) {
let l_curly =
match self.ast().syntax().children_with_tokens().find(|it| it.kind() == T!['{']) {
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.clone())
}
};
let indent = leading_indent(self.ast().syntax()).unwrap_or("".into());
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.clone().into(), ws.into()), to_insert)
}
};
}
}
impl AstEditor<ast::RecordFieldList> {
@ -179,39 +145,6 @@ impl AstEditor<ast::RecordFieldList> {
}
}
impl AstEditor<ast::ItemList> {
pub fn append_items(&mut self, items: impl Iterator<Item = ast::ImplItem>) {
if !self.ast().syntax().text().contains_char('\n') {
self.do_make_multiline();
}
items.for_each(|it| self.append_item(it));
}
pub fn append_item(&mut self, item: ast::ImplItem) {
let (indent, position) = match self.ast().impl_items().last() {
Some(it) => (
leading_indent(it.syntax()).unwrap_or_default().to_string(),
InsertPosition::After(it.syntax().clone().into()),
),
None => match self.l_curly() {
Some(it) => (
" ".to_string() + &leading_indent(self.ast().syntax()).unwrap_or_default(),
InsertPosition::After(it),
),
None => return,
},
};
let ws = tokens::WsBuilder::new(&format!("\n{}", indent));
let to_insert: ArrayVec<[SyntaxElement; 2]> =
[ws.ws().into(), item.syntax().clone().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() == T!['{'])
}
}
impl AstEditor<ast::TypeParam> {
pub fn remove_bounds(&mut self) -> &mut Self {
let colon = match self.ast.colon_token() {

View File

@ -7,10 +7,14 @@ use arrayvec::ArrayVec;
use crate::{
algo,
ast::{self, make, AstNode},
InsertPosition, SyntaxElement,
ast::{
self,
make::{self, tokens},
AstNode,
},
AstToken, InsertPosition, SmolStr, SyntaxElement,
SyntaxKind::{ATTR, COMMENT, WHITESPACE},
SyntaxNode,
SyntaxNode, T,
};
impl ast::FnDef {
@ -33,6 +37,74 @@ impl ast::FnDef {
}
}
impl ast::ItemList {
#[must_use]
pub fn append_items(&self, items: impl Iterator<Item = ast::ImplItem>) -> ast::ItemList {
let mut res = self.clone();
if !self.syntax().text().contains_char('\n') {
res = res.make_multiline();
}
items.for_each(|it| res = res.append_item(it));
res
}
#[must_use]
pub fn append_item(&self, item: ast::ImplItem) -> ast::ItemList {
let (indent, position) = match self.impl_items().last() {
Some(it) => (
leading_indent(it.syntax()).unwrap_or_default().to_string(),
InsertPosition::After(it.syntax().clone().into()),
),
None => match self.l_curly() {
Some(it) => (
" ".to_string() + &leading_indent(self.syntax()).unwrap_or_default(),
InsertPosition::After(it),
),
None => return self.clone(),
},
};
let ws = tokens::WsBuilder::new(&format!("\n{}", indent));
let to_insert: ArrayVec<[SyntaxElement; 2]> =
[ws.ws().into(), item.syntax().clone().into()].into();
insert_children(self, position, to_insert.into_iter())
}
fn l_curly(&self) -> Option<SyntaxElement> {
self.syntax().children_with_tokens().find(|it| it.kind() == T!['{'])
}
fn make_multiline(&self) -> ast::ItemList {
let l_curly = match self.syntax().children_with_tokens().find(|it| it.kind() == T!['{']) {
Some(it) => it,
None => return self.clone(),
};
let sibling = match l_curly.next_sibling_or_token() {
Some(it) => it,
None => return self.clone(),
};
let existing_ws = match sibling.as_token() {
None => None,
Some(tok) if tok.kind() != WHITESPACE => None,
Some(ws) => {
if ws.text().contains('\n') {
return self.clone();
}
Some(ws.clone())
}
};
let indent = leading_indent(self.syntax()).unwrap_or("".into());
let ws = tokens::WsBuilder::new(&format!("\n{}", indent));
let to_insert = iter::once(ws.ws().into());
match existing_ws {
None => insert_children(self, InsertPosition::After(l_curly), to_insert),
Some(ws) => {
replace_children(self, RangeInclusive::new(ws.clone().into(), ws.into()), to_insert)
}
}
}
}
pub fn strip_attrs_and_docs<N: ast::AttrsOwner>(node: N) -> N {
N::cast(strip_attrs_and_docs_inner(node.syntax().clone())).unwrap()
}
@ -50,6 +122,25 @@ fn strip_attrs_and_docs_inner(mut node: SyntaxNode) -> SyntaxNode {
node
}
// Note this is copy-pasted from fmt. It seems like fmt should be a separate
// crate, but basic tree building should be this crate. However, tree building
// might want to call into fmt...
fn leading_indent(node: &SyntaxNode) -> Option<SmolStr> {
let prev_tokens = std::iter::successors(node.first_token(), |token| token.prev_token());
for token in prev_tokens {
if let Some(ws) = ast::Whitespace::cast(token.clone()) {
let ws_text = ws.text();
if let Some(pos) = ws_text.rfind('\n') {
return Some(ws_text[pos + 1..].into());
}
}
if token.text().contains('\n') {
break;
}
}
None
}
#[must_use]
fn insert_children<N: AstNode>(
parent: &N,