Add derive intention

This commit is contained in:
Aleksey Kladov 2018-08-14 13:33:44 +03:00
parent 5953a348bd
commit 1141d448d9
6 changed files with 114 additions and 187 deletions

View file

@ -1,11 +1,12 @@
use {TextUnit, File, EditBuilder, Edit};
use libsyntax2::{
ast::AstNode,
ast::{self, AstNode},
SyntaxKind::COMMA,
SyntaxNodeRef,
SyntaxRoot,
algo::{
Direction, siblings,
find_leaf_at_offset,
find_leaf_at_offset, ancestors,
},
};
@ -24,10 +25,32 @@ pub fn flip_comma<'a>(file: &'a File, offset: TextUnit) -> Option<impl FnOnce()
})
}
pub fn add_derive<'a>(file: &'a File, offset: TextUnit) -> Option<impl FnOnce() -> Edit + 'a> {
let syntax = file.syntax();
let syntax = syntax.as_ref();
let nominal = find_node::<ast::NominalDef<_>>(syntax, offset)?;
Some(move || {
let mut edit = EditBuilder::new();
edit.insert(nominal.syntax().range().start(), "#[derive()]\n".to_string());
edit.finish()
})
}
fn non_trivia_sibling(node: SyntaxNodeRef, direction: Direction) -> Option<SyntaxNodeRef> {
siblings(node, direction)
.skip(1)
.find(|node| !node.kind().is_trivia())
}
fn find_non_trivia_leaf(syntax: SyntaxNodeRef, offset: TextUnit) -> Option<SyntaxNodeRef> {
find_leaf_at_offset(syntax, offset)
.find(|leaf| !leaf.kind().is_trivia())
}
fn find_node<'a, N: AstNode<&'a SyntaxRoot>>(syntax: SyntaxNodeRef<'a>, offset: TextUnit) -> Option<N> {
let leaf = find_non_trivia_leaf(syntax, offset)?;
ancestors(leaf)
.filter_map(N::cast)
.next()
}

View file

@ -21,7 +21,7 @@ pub use self::{
extend_selection::extend_selection,
symbols::{StructureNode, file_structure, FileSymbol, file_symbols},
edit::{EditBuilder, Edit, AtomEdit},
code_actions::{flip_comma},
code_actions::{flip_comma, add_derive},
};
#[derive(Debug)]

View file

@ -1,136 +0,0 @@
use {Node, NodeType, TextUnit, TextRange};
use ::visitor::{visitor, process_subtree_bottom_up};
pub fn child_of_type(node: Node, ty: NodeType) -> Option<Node> {
node.children().find(|n| n.ty() == ty)
}
pub fn children_of_type<'f>(node: Node<'f>, ty: NodeType) -> Box<Iterator<Item=Node<'f>> + 'f> {
Box::new(node.children().filter(move |n| n.ty() == ty))
}
pub fn subtree<'f>(node: Node<'f>) -> Box<Iterator<Item=Node<'f>> + 'f> {
Box::new(node.children().flat_map(subtree).chain(::std::iter::once(node)))
}
pub fn descendants_of_type<'f>(node: Node<'f>, ty: NodeType) -> Vec<Node<'f>> {
process_subtree_bottom_up(
node,
visitor(Vec::new())
.visit_nodes(&[ty], |node, nodes| nodes.push(node))
)
}
pub fn child_of_type_exn(node: Node, ty: NodeType) -> Node {
child_of_type(node, ty).unwrap_or_else(|| {
panic!("No child of type {:?} for {:?}\
----\
{}\
----", ty, node.ty(), node.text())
})
}
pub fn ancestors(node: Node) -> Ancestors {
Ancestors(Some(node))
}
pub struct Ancestors<'f>(Option<Node<'f>>);
impl<'f> Iterator for Ancestors<'f> {
type Item = Node<'f>;
fn next(&mut self) -> Option<Self::Item> {
let current = self.0;
self.0 = current.and_then(|n| n.parent());
current
}
}
pub fn is_leaf(node: Node) -> bool {
node.children().next().is_none() && !node.range().is_empty()
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum Direction {
Left, Right
}
pub fn sibling(node: Node, dir: Direction) -> Option<Node> {
let (parent, idx) = child_position(node)?;
let idx = match dir {
Direction::Left => idx.checked_sub(1)?,
Direction::Right => idx + 1,
};
parent.children().nth(idx)
}
pub mod ast {
use {Node, AstNode, TextUnit, AstChildren};
use visitor::{visitor, process_subtree_bottom_up};
use super::{ancestors, find_leaf_at_offset, LeafAtOffset};
pub fn ancestor<'f, T: AstNode<'f>>(node: Node<'f>) -> Option<T> {
ancestors(node)
.filter_map(T::wrap)
.next()
}
pub fn ancestor_exn<'f, T: AstNode<'f>>(node: Node<'f>) -> T {
ancestor(node).unwrap()
}
pub fn children_of_type<'f, N: AstNode<'f>>(node: Node<'f>) -> AstChildren<N> {
AstChildren::new(node.children())
}
pub fn descendants_of_type<'f, N: AstNode<'f>>(node: Node<'f>) -> Vec<N> {
process_subtree_bottom_up(
node,
visitor(Vec::new())
.visit::<N, _>(|node, acc| acc.push(node))
)
}
pub fn node_at_offset<'f, T: AstNode<'f>>(node: Node<'f>, offset: TextUnit) -> Option<T> {
match find_leaf_at_offset(node, offset) {
LeafAtOffset::None => None,
LeafAtOffset::Single(node) => ancestor(node),
LeafAtOffset::Between(left, right) => ancestor(left).or_else(|| ancestor(right)),
}
}
}
pub mod traversal {
use {Node};
pub fn bottom_up<'f, F: FnMut(Node<'f>)>(node: Node<'f>, mut f: F)
{
go(node, &mut f);
fn go<'f, F: FnMut(Node<'f>)>(node: Node<'f>, f: &mut F) {
for child in node.children() {
go(child, f)
}
f(node);
}
}
}
fn child_position(child: Node) -> Option<(Node, usize)> {
child.parent()
.map(|parent| {
(parent, parent.children().position(|n| n == child).unwrap())
})
}
fn common_ancestor<'f>(n1: Node<'f>, n2: Node<'f>) -> Node<'f> {
for p in ancestors(n1) {
if ancestors(n2).any(|a| a == p) {
return p;
}
}
panic!("Can't find common ancestor of {:?} and {:?}", n1, n2)
}

View file

@ -267,6 +267,31 @@ impl<R: TreeRoot> AstNode<R> for NeverType<R> {
impl<R: TreeRoot> NeverType<R> {}
// NominalDef
#[derive(Debug, Clone, Copy)]
pub enum NominalDef<R: TreeRoot = Arc<SyntaxRoot>> {
StructDef(StructDef<R>),
EnumDef(EnumDef<R>),
}
impl<R: TreeRoot> AstNode<R> for NominalDef<R> {
fn cast(syntax: SyntaxNode<R>) -> Option<Self> {
match syntax.kind() {
STRUCT_DEF => Some(NominalDef::StructDef(StructDef { syntax })),
ENUM_DEF => Some(NominalDef::EnumDef(EnumDef { syntax })),
_ => None,
}
}
fn syntax(&self) -> &SyntaxNode<R> {
match self {
NominalDef::StructDef(inner) => inner.syntax(),
NominalDef::EnumDef(inner) => inner.syntax(),
}
}
}
impl<R: TreeRoot> NominalDef<R> {}
// ParenType
#[derive(Debug, Clone, Copy)]
pub struct ParenType<R: TreeRoot = Arc<SyntaxRoot>> {

View file

@ -261,6 +261,8 @@ Grammar(
"ForType",
"ImplTraitType",
"DynTraitType",
])
]),
"NominalDef": ( enum: ["StructDef", "EnumDef"]),
},
)

View file

@ -87,27 +87,28 @@ pub fn handle_code_action(
let file = world.file_syntax(&path)?;
let line_index = world.file_line_index(&path)?;
let offset = params.range.conv_with(&line_index).start();
let ret = if libeditor::flip_comma(&file, offset).is_some() {
let cmd = apply_code_action_cmd(
ActionId::FlipComma,
params.text_document,
offset,
);
Some(vec![cmd])
} else {
None
};
Ok(ret)
let mut ret = Vec::new();
let actions = &[
(ActionId::FlipComma, libeditor::flip_comma(&file, offset).is_some()),
(ActionId::AddDerive, libeditor::add_derive(&file, offset).is_some()),
];
for (id, edit) in actions {
if *edit {
let cmd = apply_code_action_cmd(*id, params.text_document.clone(), offset);
ret.push(cmd);
}
}
return Ok(Some(ret));
}
pub fn handle_workspace_symbol(
world: World,
params: req::WorkspaceSymbolParams,
) -> Result<Option<Vec<SymbolInformation>>> {
let mut acc = Vec::new();
let all_symbols = params.query.contains("#");
let query = {
let all_symbols = params.query.contains("#");
let query: String = params.query.chars()
.filter(|&c| c != '#')
.collect();
@ -118,19 +119,29 @@ pub fn handle_workspace_symbol(
q.limit(128);
q
};
let mut res = exec_query(&world, query)?;
if res.is_empty() && !all_symbols {
let mut query = Query::new(params.query);
query.limit(128);
res = exec_query(&world, query)?;
}
for (path, symbol) in world.world_symbols(query) {
let line_index = world.file_line_index(path)?;
let info = SymbolInformation {
name: symbol.name.to_string(),
kind: symbol.kind.conv(),
location: (path, symbol.node_range).try_conv_with(&line_index)?,
container_name: None,
return Ok(Some(res));
fn exec_query(world: &World, query: Query) -> Result<Vec<SymbolInformation>> {
let mut res = Vec::new();
for (path, symbol) in world.world_symbols(query) {
let line_index = world.file_line_index(path)?;
let info = SymbolInformation {
name: symbol.name.to_string(),
kind: symbol.kind.conv(),
location: (path, symbol.node_range).try_conv_with(&line_index)?,
container_name: None,
};
res.push(info);
};
acc.push(info);
};
Ok(Some(acc))
Ok(res)
}
}
pub fn handle_goto_definition(
@ -161,28 +172,28 @@ pub fn handle_execute_command(
}
let arg = params.arguments.pop().unwrap();
let arg: ActionRequest = from_value(arg)?;
match arg.id {
ActionId::FlipComma => {
let path = arg.text_document.file_path()?;
let file = world.file_syntax(&path)?;
let line_index = world.file_line_index(&path)?;
let edit = match libeditor::flip_comma(&file, arg.offset) {
Some(edit) => edit(),
None => bail!("command not applicable"),
};
let mut changes = HashMap::new();
changes.insert(
arg.text_document.uri,
edit.conv_with(&line_index),
);
let edit = WorkspaceEdit {
changes: Some(changes),
document_changes: None,
};
let path = arg.text_document.file_path()?;
let file = world.file_syntax(&path)?;
let edit = match arg.id {
ActionId::FlipComma => libeditor::flip_comma(&file, arg.offset).map(|edit| edit()),
ActionId::AddDerive => libeditor::add_derive(&file, arg.offset).map(|edit| edit()),
};
let edit = match edit {
Some(edit) => edit,
None => bail!("command not applicable"),
};
let line_index = world.file_line_index(&path)?;
let mut changes = HashMap::new();
changes.insert(
arg.text_document.uri,
edit.conv_with(&line_index),
);
let edit = WorkspaceEdit {
changes: Some(changes),
document_changes: None,
};
Ok(req::ApplyWorkspaceEditParams { edit })
}
}
Ok(req::ApplyWorkspaceEditParams { edit })
}
#[derive(Serialize, Deserialize)]
@ -207,13 +218,15 @@ fn apply_code_action_cmd(id: ActionId, doc: TextDocumentIdentifier, offset: Text
#[derive(Serialize, Deserialize, Clone, Copy)]
enum ActionId {
FlipComma
FlipComma,
AddDerive,
}
impl ActionId {
fn title(&self) -> &'static str {
match *self {
ActionId::FlipComma => "Flip `,`",
ActionId::AddDerive => "Add `#[derive]`",
}
}
}