Add some assist ranges

This commit is contained in:
robojumper 2019-02-08 22:43:13 +01:00
parent 12e3b4c70b
commit a3622eb629
6 changed files with 60 additions and 10 deletions

2
Cargo.lock generated
View file

@ -1,3 +1,5 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "0.6.9" version = "0.6.9"

View file

@ -24,6 +24,7 @@ pub(crate) fn add_derive(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
} }
Some(tt) => tt.syntax().range().end() - TextUnit::of_char(')'), Some(tt) => tt.syntax().range().end() - TextUnit::of_char(')'),
}; };
edit.target(nominal.syntax().range());
edit.set_cursor(offset) edit.set_cursor(offset)
}) })
} }

View file

@ -16,7 +16,7 @@ pub(crate) enum Assist {
/// `AssistCtx` allows to apply an assist or check if it could be applied. /// `AssistCtx` allows to apply an assist or check if it could be applied.
/// ///
/// Assists use a somewhat overengineered approach, given the current needs. The /// Assists use a somewhat over-engineered approach, given the current needs. The
/// assists workflow consists of two phases. In the first phase, a user asks for /// assists workflow consists of two phases. In the first phase, a user asks for
/// the list of available assists. In the second phase, the user picks a /// the list of available assists. In the second phase, the user picks a
/// particular assist and it gets applied. /// particular assist and it gets applied.
@ -106,6 +106,7 @@ impl<'a, DB: HirDatabase> AssistCtx<'a, DB> {
pub(crate) struct AssistBuilder { pub(crate) struct AssistBuilder {
edit: TextEditBuilder, edit: TextEditBuilder,
cursor_position: Option<TextUnit>, cursor_position: Option<TextUnit>,
target: Option<TextRange>,
} }
impl AssistBuilder { impl AssistBuilder {
@ -138,7 +139,15 @@ impl AssistBuilder {
self.cursor_position = Some(offset) self.cursor_position = Some(offset)
} }
pub(crate) fn target(&mut self, target: TextRange) {
self.target = Some(target)
}
fn build(self) -> AssistAction { fn build(self) -> AssistAction {
AssistAction { edit: self.edit.finish(), cursor_position: self.cursor_position } AssistAction {
edit: self.edit.finish(),
cursor_position: self.cursor_position,
target: self.target,
}
} }
} }

View file

@ -20,7 +20,7 @@ fn add_vis(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
_ => false, _ => false,
}); });
let offset = if let Some(keyword) = item_keyword { let (offset, target) = if let Some(keyword) = item_keyword {
let parent = keyword.parent()?; let parent = keyword.parent()?;
let def_kws = vec![FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF]; let def_kws = vec![FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF];
// Parent is not a definition, can't add visibility // Parent is not a definition, can't add visibility
@ -31,17 +31,18 @@ fn add_vis(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
if parent.children().any(|child| child.kind() == VISIBILITY) { if parent.children().any(|child| child.kind() == VISIBILITY) {
return None; return None;
} }
vis_offset(parent) (vis_offset(parent), parent.range())
} else { } else {
let ident = ctx.leaf_at_offset().find(|leaf| leaf.kind() == IDENT)?; let ident = ctx.leaf_at_offset().find(|leaf| leaf.kind() == IDENT)?;
let field = ident.ancestors().find_map(ast::NamedFieldDef::cast)?; let field = ident.ancestors().find_map(ast::NamedFieldDef::cast)?;
if field.name()?.syntax().range() != ident.range() && field.visibility().is_some() { if field.name()?.syntax().range() != ident.range() && field.visibility().is_some() {
return None; return None;
} }
vis_offset(field.syntax()) (vis_offset(field.syntax()), field.syntax().range())
}; };
ctx.build("make pub(crate)", |edit| { ctx.build("make pub(crate)", |edit| {
edit.target(target);
edit.insert(offset, "pub(crate) "); edit.insert(offset, "pub(crate) ");
edit.set_cursor(offset); edit.set_cursor(offset);
}) })
@ -60,13 +61,15 @@ fn vis_offset(node: &SyntaxNode) -> TextUnit {
fn change_vis(ctx: AssistCtx<impl HirDatabase>, vis: &ast::Visibility) -> Option<Assist> { fn change_vis(ctx: AssistCtx<impl HirDatabase>, vis: &ast::Visibility) -> Option<Assist> {
if vis.syntax().text() == "pub" { if vis.syntax().text() == "pub" {
return ctx.build("chage to pub(crate)", |edit| { return ctx.build("change to pub(crate)", |edit| {
edit.target(vis.syntax().range());
edit.replace(vis.syntax().range(), "pub(crate)"); edit.replace(vis.syntax().range(), "pub(crate)");
edit.set_cursor(vis.syntax().range().start()); edit.set_cursor(vis.syntax().range().start());
}); });
} }
if vis.syntax().text() == "pub(crate)" { if vis.syntax().text() == "pub(crate)" {
return ctx.build("chage to pub", |edit| { return ctx.build("change to pub", |edit| {
edit.target(vis.syntax().range());
edit.replace(vis.syntax().range(), "pub"); edit.replace(vis.syntax().range(), "pub");
edit.set_cursor(vis.syntax().range().start()); edit.set_cursor(vis.syntax().range().start());
}); });

View file

@ -8,7 +8,7 @@
mod assist_ctx; mod assist_ctx;
use ra_text_edit::TextEdit; use ra_text_edit::TextEdit;
use ra_syntax::{TextUnit, SyntaxNode, Direction}; use ra_syntax::{TextRange, TextUnit, SyntaxNode, Direction};
use ra_db::FileRange; use ra_db::FileRange;
use hir::db::HirDatabase; use hir::db::HirDatabase;
@ -23,6 +23,7 @@ pub struct AssistLabel {
pub struct AssistAction { pub struct AssistAction {
pub edit: TextEdit, pub edit: TextEdit,
pub cursor_position: Option<TextUnit>, pub cursor_position: Option<TextUnit>,
pub target: Option<TextRange>,
} }
/// Return all the assists applicable at the given position. /// Return all the assists applicable at the given position.
@ -53,15 +54,26 @@ pub fn assists<H>(db: &H, range: FileRange) -> Vec<(AssistLabel, AssistAction)>
where where
H: HirDatabase + 'static, H: HirDatabase + 'static,
{ {
use std::cmp::Ordering;
AssistCtx::with_ctx(db, range, true, |ctx| { AssistCtx::with_ctx(db, range, true, |ctx| {
all_assists() let mut a = all_assists()
.iter() .iter()
.filter_map(|f| f(ctx.clone())) .filter_map(|f| f(ctx.clone()))
.map(|a| match a { .map(|a| match a {
Assist::Resolved(label, action) => (label, action), Assist::Resolved(label, action) => (label, action),
Assist::Unresolved(..) => unreachable!(), Assist::Unresolved(..) => unreachable!(),
}) })
.collect() .collect::<Vec<(AssistLabel, AssistAction)>>();
a.sort_unstable_by(|a, b| match a {
// Some(y) < Some(x) < None for y < x
(_, AssistAction { target: Some(a), .. }) => match b {
(_, AssistAction { target: Some(b), .. }) => a.len().cmp(&b.len()),
_ => Ordering::Less,
},
_ => Ordering::Greater,
});
a
}) })
} }
@ -162,5 +174,27 @@ mod helpers {
let assist = AssistCtx::with_ctx(&db, frange, true, assist); let assist = AssistCtx::with_ctx(&db, frange, true, assist);
assert!(assist.is_none()); assert!(assist.is_none());
} }
}
#[cfg(test)]
mod tests {
use hir::mock::MockDatabase;
use ra_syntax::TextRange;
use ra_db::FileRange;
use test_utils::extract_offset;
#[test]
fn assist_order() {
let before = "struct Foo { <|>bar: u32 }";
let (before_cursor_pos, before) = extract_offset(before);
let (db, _source_root, file_id) = MockDatabase::with_single_file(&before);
let frange =
FileRange { file_id, range: TextRange::offset_len(before_cursor_pos, 0.into()) };
let assists = super::assists(&db, frange);
let mut assists = assists.iter();
assert_eq!(assists.next().expect("expected assist").0.label, "make pub(crate)");
assert_eq!(assists.next().expect("expected assist").0.label, "add `#[derive]`");
}
} }

View file

@ -24,6 +24,7 @@ pub(crate) fn split_import(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
}; };
ctx.build("split import", |edit| { ctx.build("split import", |edit| {
edit.target(colon_colon.range());
edit.insert(l_curly, "{"); edit.insert(l_curly, "{");
edit.insert(r_curly, "}"); edit.insert(r_curly, "}");
edit.set_cursor(l_curly + TextUnit::of_str("{")); edit.set_cursor(l_curly + TextUnit::of_str("{"));