mirror of
https://github.com/rust-lang/rust-analyzer
synced 2025-01-12 21:28:51 +00:00
Add derive intention
This commit is contained in:
parent
5953a348bd
commit
1141d448d9
6 changed files with 114 additions and 187 deletions
|
@ -1,11 +1,12 @@
|
||||||
use {TextUnit, File, EditBuilder, Edit};
|
use {TextUnit, File, EditBuilder, Edit};
|
||||||
use libsyntax2::{
|
use libsyntax2::{
|
||||||
ast::AstNode,
|
ast::{self, AstNode},
|
||||||
SyntaxKind::COMMA,
|
SyntaxKind::COMMA,
|
||||||
SyntaxNodeRef,
|
SyntaxNodeRef,
|
||||||
|
SyntaxRoot,
|
||||||
algo::{
|
algo::{
|
||||||
Direction, siblings,
|
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> {
|
fn non_trivia_sibling(node: SyntaxNodeRef, direction: Direction) -> Option<SyntaxNodeRef> {
|
||||||
siblings(node, direction)
|
siblings(node, direction)
|
||||||
.skip(1)
|
.skip(1)
|
||||||
.find(|node| !node.kind().is_trivia())
|
.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()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ pub use self::{
|
||||||
extend_selection::extend_selection,
|
extend_selection::extend_selection,
|
||||||
symbols::{StructureNode, file_structure, FileSymbol, file_symbols},
|
symbols::{StructureNode, file_structure, FileSymbol, file_symbols},
|
||||||
edit::{EditBuilder, Edit, AtomEdit},
|
edit::{EditBuilder, Edit, AtomEdit},
|
||||||
code_actions::{flip_comma},
|
code_actions::{flip_comma, add_derive},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
|
@ -267,6 +267,31 @@ impl<R: TreeRoot> AstNode<R> for NeverType<R> {
|
||||||
|
|
||||||
impl<R: TreeRoot> 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
|
// ParenType
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct ParenType<R: TreeRoot = Arc<SyntaxRoot>> {
|
pub struct ParenType<R: TreeRoot = Arc<SyntaxRoot>> {
|
||||||
|
|
|
@ -261,6 +261,8 @@ Grammar(
|
||||||
"ForType",
|
"ForType",
|
||||||
"ImplTraitType",
|
"ImplTraitType",
|
||||||
"DynTraitType",
|
"DynTraitType",
|
||||||
])
|
]),
|
||||||
|
|
||||||
|
"NominalDef": ( enum: ["StructDef", "EnumDef"]),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -87,27 +87,28 @@ pub fn handle_code_action(
|
||||||
let file = world.file_syntax(&path)?;
|
let file = world.file_syntax(&path)?;
|
||||||
let line_index = world.file_line_index(&path)?;
|
let line_index = world.file_line_index(&path)?;
|
||||||
let offset = params.range.conv_with(&line_index).start();
|
let offset = params.range.conv_with(&line_index).start();
|
||||||
let ret = if libeditor::flip_comma(&file, offset).is_some() {
|
let mut ret = Vec::new();
|
||||||
let cmd = apply_code_action_cmd(
|
|
||||||
ActionId::FlipComma,
|
let actions = &[
|
||||||
params.text_document,
|
(ActionId::FlipComma, libeditor::flip_comma(&file, offset).is_some()),
|
||||||
offset,
|
(ActionId::AddDerive, libeditor::add_derive(&file, offset).is_some()),
|
||||||
);
|
];
|
||||||
Some(vec![cmd])
|
|
||||||
} else {
|
for (id, edit) in actions {
|
||||||
None
|
if *edit {
|
||||||
};
|
let cmd = apply_code_action_cmd(*id, params.text_document.clone(), offset);
|
||||||
Ok(ret)
|
ret.push(cmd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Ok(Some(ret));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_workspace_symbol(
|
pub fn handle_workspace_symbol(
|
||||||
world: World,
|
world: World,
|
||||||
params: req::WorkspaceSymbolParams,
|
params: req::WorkspaceSymbolParams,
|
||||||
) -> Result<Option<Vec<SymbolInformation>>> {
|
) -> Result<Option<Vec<SymbolInformation>>> {
|
||||||
let mut acc = Vec::new();
|
let all_symbols = params.query.contains("#");
|
||||||
|
|
||||||
let query = {
|
let query = {
|
||||||
let all_symbols = params.query.contains("#");
|
|
||||||
let query: String = params.query.chars()
|
let query: String = params.query.chars()
|
||||||
.filter(|&c| c != '#')
|
.filter(|&c| c != '#')
|
||||||
.collect();
|
.collect();
|
||||||
|
@ -118,19 +119,29 @@ pub fn handle_workspace_symbol(
|
||||||
q.limit(128);
|
q.limit(128);
|
||||||
q
|
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) {
|
return Ok(Some(res));
|
||||||
let line_index = world.file_line_index(path)?;
|
|
||||||
let info = SymbolInformation {
|
fn exec_query(world: &World, query: Query) -> Result<Vec<SymbolInformation>> {
|
||||||
name: symbol.name.to_string(),
|
let mut res = Vec::new();
|
||||||
kind: symbol.kind.conv(),
|
for (path, symbol) in world.world_symbols(query) {
|
||||||
location: (path, symbol.node_range).try_conv_with(&line_index)?,
|
let line_index = world.file_line_index(path)?;
|
||||||
container_name: None,
|
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(res)
|
||||||
};
|
}
|
||||||
|
|
||||||
Ok(Some(acc))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_goto_definition(
|
pub fn handle_goto_definition(
|
||||||
|
@ -161,28 +172,28 @@ pub fn handle_execute_command(
|
||||||
}
|
}
|
||||||
let arg = params.arguments.pop().unwrap();
|
let arg = params.arguments.pop().unwrap();
|
||||||
let arg: ActionRequest = from_value(arg)?;
|
let arg: ActionRequest = from_value(arg)?;
|
||||||
match arg.id {
|
let path = arg.text_document.file_path()?;
|
||||||
ActionId::FlipComma => {
|
let file = world.file_syntax(&path)?;
|
||||||
let path = arg.text_document.file_path()?;
|
let edit = match arg.id {
|
||||||
let file = world.file_syntax(&path)?;
|
ActionId::FlipComma => libeditor::flip_comma(&file, arg.offset).map(|edit| edit()),
|
||||||
let line_index = world.file_line_index(&path)?;
|
ActionId::AddDerive => libeditor::add_derive(&file, arg.offset).map(|edit| edit()),
|
||||||
let edit = match libeditor::flip_comma(&file, arg.offset) {
|
};
|
||||||
Some(edit) => edit(),
|
let edit = match edit {
|
||||||
None => bail!("command not applicable"),
|
Some(edit) => edit,
|
||||||
};
|
None => bail!("command not applicable"),
|
||||||
let mut changes = HashMap::new();
|
};
|
||||||
changes.insert(
|
let line_index = world.file_line_index(&path)?;
|
||||||
arg.text_document.uri,
|
let mut changes = HashMap::new();
|
||||||
edit.conv_with(&line_index),
|
changes.insert(
|
||||||
);
|
arg.text_document.uri,
|
||||||
let edit = WorkspaceEdit {
|
edit.conv_with(&line_index),
|
||||||
changes: Some(changes),
|
);
|
||||||
document_changes: None,
|
let edit = WorkspaceEdit {
|
||||||
};
|
changes: Some(changes),
|
||||||
|
document_changes: None,
|
||||||
|
};
|
||||||
|
|
||||||
Ok(req::ApplyWorkspaceEditParams { edit })
|
Ok(req::ApplyWorkspaceEditParams { edit })
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
@ -207,13 +218,15 @@ fn apply_code_action_cmd(id: ActionId, doc: TextDocumentIdentifier, offset: Text
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Copy)]
|
#[derive(Serialize, Deserialize, Clone, Copy)]
|
||||||
enum ActionId {
|
enum ActionId {
|
||||||
FlipComma
|
FlipComma,
|
||||||
|
AddDerive,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ActionId {
|
impl ActionId {
|
||||||
fn title(&self) -> &'static str {
|
fn title(&self) -> &'static str {
|
||||||
match *self {
|
match *self {
|
||||||
ActionId::FlipComma => "Flip `,`",
|
ActionId::FlipComma => "Flip `,`",
|
||||||
|
ActionId::AddDerive => "Add `#[derive]`",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue