Merge branch 'master' of github.com:rust-analyzer/rust-analyzer into fix_4311

This commit is contained in:
Benjamin Coenen 2020-05-06 09:57:00 +02:00
commit c4d128e454
61 changed files with 659 additions and 569 deletions

View file

@ -4,14 +4,13 @@ use ra_db::FileRange;
use ra_fmt::{leading_indent, reindent};
use ra_ide_db::RootDatabase;
use ra_syntax::{
algo::{self, find_covering_element, find_node_at_offset},
algo::{self, find_covering_element, find_node_at_offset, SyntaxRewriter},
AstNode, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize,
TokenAtOffset,
};
use ra_text_edit::TextEditBuilder;
use crate::{AssistAction, AssistFile, AssistId, AssistLabel, GroupLabel, ResolvedAssist};
use algo::SyntaxRewriter;
#[derive(Clone, Debug)]
pub(crate) struct Assist(pub(crate) Vec<AssistInfo>);
@ -38,13 +37,10 @@ impl AssistInfo {
pub(crate) fn into_resolved(self) -> Option<ResolvedAssist> {
let label = self.label;
let group_label = self.group_label;
self.action.map(|action| ResolvedAssist { label, group_label, action })
self.action.map(|action| ResolvedAssist { label, action })
}
}
pub(crate) type AssistHandler = fn(AssistCtx) -> Option<Assist>;
/// `AssistCtx` allows to apply an assist or check if it could be applied.
///
/// Assists use a somewhat over-engineered approach, given the current needs. The
@ -100,7 +96,7 @@ impl<'a> AssistCtx<'a> {
label: impl Into<String>,
f: impl FnOnce(&mut ActionBuilder),
) -> Option<Assist> {
let label = AssistLabel::new(label.into(), id);
let label = AssistLabel::new(id, label.into(), None);
let mut info = AssistInfo::new(label);
if self.should_compute_edit {
@ -116,7 +112,8 @@ impl<'a> AssistCtx<'a> {
}
pub(crate) fn add_assist_group(self, group_name: impl Into<String>) -> AssistGroup<'a> {
AssistGroup { ctx: self, group_name: group_name.into(), assists: Vec::new() }
let group = GroupLabel(group_name.into());
AssistGroup { ctx: self, group, assists: Vec::new() }
}
pub(crate) fn token_at_offset(&self) -> TokenAtOffset<SyntaxToken> {
@ -146,7 +143,7 @@ impl<'a> AssistCtx<'a> {
pub(crate) struct AssistGroup<'a> {
ctx: AssistCtx<'a>,
group_name: String,
group: GroupLabel,
assists: Vec<AssistInfo>,
}
@ -157,9 +154,9 @@ impl<'a> AssistGroup<'a> {
label: impl Into<String>,
f: impl FnOnce(&mut ActionBuilder),
) {
let label = AssistLabel::new(label.into(), id);
let label = AssistLabel::new(id, label.into(), Some(self.group.clone()));
let mut info = AssistInfo::new(label).with_group(GroupLabel(self.group_name.clone()));
let mut info = AssistInfo::new(label).with_group(self.group.clone());
if self.ctx.should_compute_edit {
let action = {
let mut edit = ActionBuilder::new(&self.ctx);

View file

@ -30,6 +30,10 @@ fn check(assist_id: &str, before: &str, after: &str) {
)
});
let actual = assist.action.edit.apply(&before);
let actual = {
let mut actual = before.clone();
assist.action.edit.apply(&mut actual);
actual
};
assert_eq_text!(after, &actual);
}

View file

@ -10,7 +10,7 @@ use ra_syntax::{
use crate::{
ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams},
utils::{get_missing_impl_items, resolve_target_trait},
utils::{get_missing_assoc_items, resolve_target_trait},
Assist, AssistCtx, AssistId,
};
@ -112,25 +112,25 @@ fn add_missing_impl_members_inner(
let trait_ = resolve_target_trait(&ctx.sema, &impl_node)?;
let def_name = |item: &ast::ImplItem| -> Option<SmolStr> {
let def_name = |item: &ast::AssocItem| -> Option<SmolStr> {
match item {
ast::ImplItem::FnDef(def) => def.name(),
ast::ImplItem::TypeAliasDef(def) => def.name(),
ast::ImplItem::ConstDef(def) => def.name(),
ast::AssocItem::FnDef(def) => def.name(),
ast::AssocItem::TypeAliasDef(def) => def.name(),
ast::AssocItem::ConstDef(def) => def.name(),
}
.map(|it| it.text().clone())
};
let missing_items = get_missing_impl_items(&ctx.sema, &impl_node)
let missing_items = get_missing_assoc_items(&ctx.sema, &impl_node)
.iter()
.map(|i| match i {
hir::AssocItem::Function(i) => ast::ImplItem::FnDef(i.source(ctx.db).value),
hir::AssocItem::TypeAlias(i) => ast::ImplItem::TypeAliasDef(i.source(ctx.db).value),
hir::AssocItem::Const(i) => ast::ImplItem::ConstDef(i.source(ctx.db).value),
hir::AssocItem::Function(i) => ast::AssocItem::FnDef(i.source(ctx.db).value),
hir::AssocItem::TypeAlias(i) => ast::AssocItem::TypeAliasDef(i.source(ctx.db).value),
hir::AssocItem::Const(i) => ast::AssocItem::ConstDef(i.source(ctx.db).value),
})
.filter(|t| def_name(&t).is_some())
.filter(|t| match t {
ast::ImplItem::FnDef(def) => match mode {
ast::AssocItem::FnDef(def) => match mode {
AddMissingImplMembersMode::DefaultMethodsOnly => def.body().is_some(),
AddMissingImplMembersMode::NoDefaultMethods => def.body().is_none(),
},
@ -145,7 +145,7 @@ fn add_missing_impl_members_inner(
let sema = ctx.sema;
ctx.add_assist(AssistId(assist_id), label, |edit| {
let n_existing_items = impl_item_list.impl_items().count();
let n_existing_items = impl_item_list.assoc_items().count();
let source_scope = sema.scope_for_def(trait_);
let target_scope = sema.scope(impl_item_list.syntax());
let ast_transform = QualifyPaths::new(&target_scope, &source_scope)
@ -154,13 +154,13 @@ fn add_missing_impl_members_inner(
.into_iter()
.map(|it| ast_transform::apply(&*ast_transform, it))
.map(|it| match it {
ast::ImplItem::FnDef(def) => ast::ImplItem::FnDef(add_body(def)),
ast::AssocItem::FnDef(def) => ast::AssocItem::FnDef(add_body(def)),
_ => it,
})
.map(|it| edit::remove_attrs_and_docs(&it));
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();
let first_new_item = new_impl_item_list.assoc_items().nth(n_existing_items).unwrap();
first_new_item.syntax().text_range().start()
};

View file

@ -162,8 +162,8 @@ fn find_struct_impl(ctx: &AssistCtx, strukt: &ast::StructDef) -> Option<Option<a
fn has_new_fn(imp: &ast::ImplDef) -> bool {
if let Some(il) = imp.item_list() {
for item in il.impl_items() {
if let ast::ImplItem::FnDef(f) = item {
for item in il.assoc_items() {
if let ast::AssocItem::FnDef(f) = item {
if let Some(name) = f.name() {
if name.text().eq_ignore_ascii_case("new") {
return true;

View file

@ -47,8 +47,7 @@ fn add_vis(ctx: AssistCtx) -> Option<Assist> {
return None;
}
(vis_offset(&parent), keyword.text_range())
} else {
let field_name: ast::Name = ctx.find_node_at_offset()?;
} else if let Some(field_name) = ctx.find_node_at_offset::<ast::Name>() {
let field = field_name.syntax().ancestors().find_map(ast::RecordFieldDef::cast)?;
if field.name()? != field_name {
tested_by!(change_visibility_field_false_positive);
@ -58,6 +57,13 @@ fn add_vis(ctx: AssistCtx) -> Option<Assist> {
return None;
}
(vis_offset(field.syntax()), field_name.syntax().text_range())
} else if let Some(field) = ctx.find_node_at_offset::<ast::TupleFieldDef>() {
if field.visibility().is_some() {
return None;
}
(vis_offset(field.syntax()), field.syntax().text_range())
} else {
return None;
};
ctx.add_assist(AssistId("change_visibility"), "Change visibility to pub(crate)", |edit| {
@ -129,7 +135,8 @@ mod tests {
change_visibility,
r"struct S { <|>field: u32 }",
r"struct S { <|>pub(crate) field: u32 }",
)
);
check_assist(change_visibility, r"struct S ( <|>u32 )", r"struct S ( <|>pub(crate) u32 )");
}
#[test]

View file

@ -17,13 +17,13 @@ mod doc_tests;
pub mod utils;
pub mod ast_transform;
use hir::Semantics;
use ra_db::{FileId, FileRange};
use ra_ide_db::RootDatabase;
use ra_syntax::{TextRange, TextSize};
use ra_text_edit::TextEdit;
pub(crate) use crate::assist_ctx::{Assist, AssistCtx, AssistHandler};
use hir::Semantics;
pub(crate) use crate::assist_ctx::{Assist, AssistCtx};
/// Unique identifier of the assist, should not be shown to the user
/// directly.
@ -32,19 +32,20 @@ pub struct AssistId(pub &'static str);
#[derive(Debug, Clone)]
pub struct AssistLabel {
pub id: AssistId,
/// Short description of the assist, as shown in the UI.
pub label: String,
pub id: AssistId,
pub group: Option<GroupLabel>,
}
#[derive(Clone, Debug)]
pub struct GroupLabel(pub String);
impl AssistLabel {
pub(crate) fn new(label: String, id: AssistId) -> AssistLabel {
pub(crate) fn new(id: AssistId, label: String, group: Option<GroupLabel>) -> AssistLabel {
// FIXME: make fields private, so that this invariant can't be broken
assert!(label.starts_with(|c: char| c.is_uppercase()));
AssistLabel { label, id }
AssistLabel { id, label, group }
}
}
@ -60,7 +61,6 @@ pub struct AssistAction {
#[derive(Debug, Clone)]
pub struct ResolvedAssist {
pub label: AssistLabel,
pub group_label: Option<GroupLabel>,
pub action: AssistAction,
}
@ -109,7 +109,9 @@ pub fn resolved_assists(db: &RootDatabase, range: FileRange) -> Vec<ResolvedAssi
}
mod handlers {
use crate::AssistHandler;
use crate::{Assist, AssistCtx};
pub(crate) type Handler = fn(AssistCtx) -> Option<Assist>;
mod add_custom_impl;
mod add_derive;
@ -145,12 +147,13 @@ mod handlers {
mod reorder_fields;
mod unwrap_block;
pub(crate) fn all() -> &'static [AssistHandler] {
pub(crate) fn all() -> &'static [Handler] {
&[
// These are alphabetic for the foolish consistency
add_custom_impl::add_custom_impl,
add_derive::add_derive,
add_explicit_type::add_explicit_type,
add_from_impl_for_enum::add_from_impl_for_enum,
add_function::add_function,
add_impl::add_impl,
add_new::add_new,
@ -176,17 +179,18 @@ mod handlers {
raw_string::remove_hash,
remove_dbg::remove_dbg,
remove_mut::remove_mut,
reorder_fields::reorder_fields,
replace_if_let_with_match::replace_if_let_with_match,
replace_let_with_if_let::replace_let_with_if_let,
replace_qualified_name_with_use::replace_qualified_name_with_use,
replace_unwrap_with_match::replace_unwrap_with_match,
split_import::split_import,
add_from_impl_for_enum::add_from_impl_for_enum,
unwrap_block::unwrap_block,
// These are manually sorted for better priorities
add_missing_impl_members::add_missing_impl_members,
add_missing_impl_members::add_missing_default_members,
reorder_fields::reorder_fields,
// Are you sure you want to add new assist here, and not to the
// sorted list above?
]
}
}
@ -195,12 +199,12 @@ mod handlers {
mod helpers {
use std::sync::Arc;
use hir::Semantics;
use ra_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt};
use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase};
use test_utils::{add_cursor, assert_eq_text, extract_range_or_offset, RangeOrOffset};
use crate::{AssistCtx, AssistFile, AssistHandler};
use hir::Semantics;
use crate::{handlers::Handler, AssistCtx, AssistFile};
pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) {
let (mut db, file_id) = RootDatabase::with_single_file(text);
@ -210,22 +214,18 @@ mod helpers {
(db, file_id)
}
pub(crate) fn check_assist(
assist: AssistHandler,
ra_fixture_before: &str,
ra_fixture_after: &str,
) {
pub(crate) fn check_assist(assist: Handler, ra_fixture_before: &str, ra_fixture_after: &str) {
check(assist, ra_fixture_before, ExpectedResult::After(ra_fixture_after));
}
// FIXME: instead of having a separate function here, maybe use
// `extract_ranges` and mark the target as `<target> </target>` in the
// fixuture?
pub(crate) fn check_assist_target(assist: AssistHandler, ra_fixture: &str, target: &str) {
pub(crate) fn check_assist_target(assist: Handler, ra_fixture: &str, target: &str) {
check(assist, ra_fixture, ExpectedResult::Target(target));
}
pub(crate) fn check_assist_not_applicable(assist: AssistHandler, ra_fixture: &str) {
pub(crate) fn check_assist_not_applicable(assist: Handler, ra_fixture: &str) {
check(assist, ra_fixture, ExpectedResult::NotApplicable);
}
@ -235,7 +235,7 @@ mod helpers {
Target(&'a str),
}
fn check(assist: AssistHandler, before: &str, expected: ExpectedResult) {
fn check(assist: Handler, before: &str, expected: ExpectedResult) {
let (text_without_caret, file_with_caret_id, range_or_offset, db) =
if before.contains("//-") {
let (mut db, position) = RootDatabase::with_position(before);
@ -261,13 +261,13 @@ mod helpers {
(Some(assist), ExpectedResult::After(after)) => {
let action = assist.0[0].action.clone().unwrap();
let assisted_file_text = if let AssistFile::TargetFile(file_id) = action.file {
let mut actual = if let AssistFile::TargetFile(file_id) = action.file {
db.file_text(file_id).as_ref().to_owned()
} else {
text_without_caret
};
action.edit.apply(&mut actual);
let mut actual = action.edit.apply(&assisted_file_text);
match action.cursor_position {
None => {
if let RangeOrOffset::Offset(before_cursor_pos) = range_or_offset {

View file

@ -13,7 +13,7 @@ use rustc_hash::FxHashSet;
pub(crate) use insert_use::insert_use_statement;
pub fn get_missing_impl_items(
pub fn get_missing_assoc_items(
sema: &Semantics<RootDatabase>,
impl_def: &ast::ImplDef,
) -> Vec<hir::AssocItem> {
@ -23,21 +23,21 @@ pub fn get_missing_impl_items(
let mut impl_type = FxHashSet::default();
if let Some(item_list) = impl_def.item_list() {
for item in item_list.impl_items() {
for item in item_list.assoc_items() {
match item {
ast::ImplItem::FnDef(f) => {
ast::AssocItem::FnDef(f) => {
if let Some(n) = f.name() {
impl_fns_consts.insert(n.syntax().to_string());
}
}
ast::ImplItem::TypeAliasDef(t) => {
ast::AssocItem::TypeAliasDef(t) => {
if let Some(n) = t.name() {
impl_type.insert(n.syntax().to_string());
}
}
ast::ImplItem::ConstDef(c) => {
ast::AssocItem::ConstDef(c) => {
if let Some(n) = c.name() {
impl_fns_consts.insert(n.syntax().to_string());
}

View file

@ -24,7 +24,7 @@ pub use crate::conv::url_from_path_with_drive_lowercasing;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum FlycheckConfig {
CargoCommand { command: String, all_targets: bool, extra_args: Vec<String> },
CargoCommand { command: String, all_targets: bool, all_features: bool, extra_args: Vec<String> },
CustomCommand { command: String, args: Vec<String> },
}
@ -215,7 +215,7 @@ impl FlycheckThread {
self.check_process = None;
let mut cmd = match &self.config {
FlycheckConfig::CargoCommand { command, all_targets, extra_args } => {
FlycheckConfig::CargoCommand { command, all_targets, all_features, extra_args } => {
let mut cmd = Command::new(cargo_binary());
cmd.arg(command);
cmd.args(&["--workspace", "--message-format=json", "--manifest-path"]);
@ -223,6 +223,9 @@ impl FlycheckThread {
if *all_targets {
cmd.arg("--all-targets");
}
if *all_features {
cmd.arg("--all-features");
}
cmd.args(extra_args);
cmd
}

View file

@ -1211,7 +1211,7 @@ impl Type {
// This would be nicer if it just returned an iterator, but that runs into
// lifetime problems, because we need to borrow temp `CrateImplDefs`.
pub fn iterate_impl_items<T>(
pub fn iterate_assoc_items<T>(
self,
db: &dyn HirDatabase,
krate: Crate,

View file

@ -118,11 +118,12 @@ fn lower_enum(
module_id: ModuleId,
) {
let expander = CfgExpander::new(db, ast.file_id, module_id.krate);
let variants =
ast.value.variant_list().into_iter().flat_map(|it| it.variants()).filter(|var| {
let attrs = expander.parse_attrs(var);
expander.is_cfg_enabled(&attrs)
});
let variants = ast
.value
.variant_list()
.into_iter()
.flat_map(|it| it.variants())
.filter(|var| expander.is_cfg_enabled(var));
for var in variants {
trace.alloc(
|| var.clone(),
@ -215,8 +216,7 @@ fn lower_struct(
match &ast.value {
ast::StructKind::Tuple(fl) => {
for (i, fd) in fl.fields().enumerate() {
let attrs = expander.parse_attrs(&fd);
if !expander.is_cfg_enabled(&attrs) {
if !expander.is_cfg_enabled(&fd) {
continue;
}
@ -233,8 +233,7 @@ fn lower_struct(
}
ast::StructKind::Record(fl) => {
for fd in fl.fields() {
let attrs = expander.parse_attrs(&fd);
if !expander.is_cfg_enabled(&attrs) {
if !expander.is_cfg_enabled(&fd) {
continue;
}

View file

@ -60,7 +60,8 @@ impl CfgExpander {
Attrs::new(owner, &self.hygiene)
}
pub(crate) fn is_cfg_enabled(&self, attrs: &Attrs) -> bool {
pub(crate) fn is_cfg_enabled(&self, owner: &dyn ast::AttrsOwner) -> bool {
let attrs = self.parse_attrs(owner);
attrs.is_cfg_enabled(&self.cfg_options)
}
}
@ -141,12 +142,8 @@ impl Expander {
InFile { file_id: self.current_file_id, value }
}
pub(crate) fn parse_attrs(&self, owner: &dyn ast::AttrsOwner) -> Attrs {
self.cfg_expander.parse_attrs(owner)
}
pub(crate) fn is_cfg_enabled(&self, attrs: &Attrs) -> bool {
self.cfg_expander.is_cfg_enabled(attrs)
pub(crate) fn is_cfg_enabled(&self, owner: &dyn ast::AttrsOwner) -> bool {
self.cfg_expander.is_cfg_enabled(owner)
}
fn parse_path(&mut self, path: ast::Path) -> Option<Path> {

View file

@ -162,8 +162,7 @@ impl ExprCollector<'_> {
fn collect_expr(&mut self, expr: ast::Expr) -> ExprId {
let syntax_ptr = AstPtr::new(&expr);
let attrs = self.expander.parse_attrs(&expr);
if !self.expander.is_cfg_enabled(&attrs) {
if !self.expander.is_cfg_enabled(&expr) {
return self.missing_expr();
}
match expr {
@ -329,8 +328,7 @@ impl ExprCollector<'_> {
.fields()
.inspect(|field| field_ptrs.push(AstPtr::new(field)))
.filter_map(|field| {
let attrs = self.expander.parse_attrs(&field);
if !self.expander.is_cfg_enabled(&attrs) {
if !self.expander.is_cfg_enabled(&field) {
return None;
}
let name = field.field_name()?.as_name();
@ -575,9 +573,16 @@ impl ExprCollector<'_> {
self.body.item_scope.define_def(def);
if let Some(name) = name {
let vis = crate::visibility::Visibility::Public; // FIXME determine correctly
self.body
.item_scope
.push_res(name.as_name(), crate::per_ns::PerNs::from_def(def, vis));
let has_constructor = match def {
ModuleDefId::AdtId(AdtId::StructId(s)) => {
self.db.struct_data(s).variant_data.kind() != StructKind::Record
}
_ => true,
};
self.body.item_scope.push_res(
name.as_name(),
crate::per_ns::PerNs::from_def(def, vis, has_constructor),
);
}
}
}

View file

@ -9,7 +9,7 @@ use hir_expand::{
};
use ra_prof::profile;
use ra_syntax::ast::{
self, AstNode, ImplItem, ModuleItemOwner, NameOwner, TypeAscriptionOwner, TypeBoundsOwner,
self, AssocItem, AstNode, ModuleItemOwner, NameOwner, TypeAscriptionOwner, TypeBoundsOwner,
VisibilityOwner,
};
@ -164,7 +164,7 @@ impl TraitData {
items.extend(collect_items(
db,
&mut expander,
item_list.impl_items(),
item_list.assoc_items(),
src.file_id,
container,
));
@ -219,7 +219,7 @@ impl ImplData {
if let Some(item_list) = src.value.item_list() {
let mut expander = Expander::new(db, impl_loc.ast_id.file_id, module_id);
items.extend(
collect_items(db, &mut expander, item_list.impl_items(), src.file_id, container)
collect_items(db, &mut expander, item_list.assoc_items(), src.file_id, container)
.into_iter()
.map(|(_, item)| item),
);
@ -304,7 +304,7 @@ fn collect_items_in_macro(
let mut res = collect_items(
db,
expander,
items.value.items().filter_map(|it| ImplItem::cast(it.syntax().clone())),
items.value.items().filter_map(|it| AssocItem::cast(it.syntax().clone())),
items.file_id,
container,
);
@ -325,31 +325,30 @@ fn collect_items_in_macro(
fn collect_items(
db: &dyn DefDatabase,
expander: &mut Expander,
impl_items: impl Iterator<Item = ImplItem>,
assoc_items: impl Iterator<Item = AssocItem>,
file_id: crate::HirFileId,
container: AssocContainerId,
) -> Vec<(Name, AssocItemId)> {
let items = db.ast_id_map(file_id);
impl_items
assoc_items
.filter_map(|item_node| match item_node {
ast::ImplItem::FnDef(it) => {
ast::AssocItem::FnDef(it) => {
let name = it.name().map_or_else(Name::missing, |it| it.as_name());
let attrs = expander.parse_attrs(&it);
if !expander.is_cfg_enabled(&attrs) {
if !expander.is_cfg_enabled(&it) {
return None;
}
let def = FunctionLoc { container, ast_id: AstId::new(file_id, items.ast_id(&it)) }
.intern(db);
Some((name, def.into()))
}
ast::ImplItem::ConstDef(it) => {
ast::AssocItem::ConstDef(it) => {
let name = it.name().map_or_else(Name::missing, |it| it.as_name());
let def = ConstLoc { container, ast_id: AstId::new(file_id, items.ast_id(&it)) }
.intern(db);
Some((name, def.into()))
}
ast::ImplItem::TypeAliasDef(it) => {
ast::AssocItem::TypeAliasDef(it) => {
let name = it.name().map_or_else(Name::missing, |it| it.as_name());
let def =
TypeAliasLoc { container, ast_id: AstId::new(file_id, items.ast_id(&it)) }

View file

@ -151,13 +151,20 @@ impl ItemScope {
}
impl PerNs {
pub(crate) fn from_def(def: ModuleDefId, v: Visibility) -> PerNs {
pub(crate) fn from_def(def: ModuleDefId, v: Visibility, has_constructor: bool) -> PerNs {
match def {
ModuleDefId::ModuleId(_) => PerNs::types(def, v),
ModuleDefId::FunctionId(_) => PerNs::values(def, v),
ModuleDefId::AdtId(adt) => match adt {
AdtId::StructId(_) | AdtId::UnionId(_) => PerNs::both(def, def, v),
AdtId::UnionId(_) => PerNs::types(def, v),
AdtId::EnumId(_) => PerNs::types(def, v),
AdtId::StructId(_) => {
if has_constructor {
PerNs::both(def, def, v)
} else {
PerNs::types(def, v)
}
}
},
ModuleDefId::EnumVariantId(_) => PerNs::both(def, def, v),
ModuleDefId::ConstId(_) | ModuleDefId::StaticId(_) => PerNs::values(def, v),

View file

@ -830,7 +830,7 @@ impl ModCollector<'_, '_> {
let module = ModuleId { krate: self.def_collector.def_map.krate, local_id: res };
let def: ModuleDefId = module.into();
self.def_collector.def_map.modules[self.module_id].scope.define_def(def);
self.def_collector.update(self.module_id, &[(name, PerNs::from_def(def, vis))], vis);
self.def_collector.update(self.module_id, &[(name, PerNs::from_def(def, vis, false))], vis);
res
}
@ -844,6 +844,8 @@ impl ModCollector<'_, '_> {
let name = def.name.clone();
let container = ContainerId::ModuleId(module);
let vis = &def.visibility;
let mut has_constructor = false;
let def: ModuleDefId = match def.kind {
raw::DefKind::Function(ast_id) => FunctionLoc {
container: container.into(),
@ -851,7 +853,8 @@ impl ModCollector<'_, '_> {
}
.intern(self.def_collector.db)
.into(),
raw::DefKind::Struct(ast_id) => {
raw::DefKind::Struct(ast_id, mode) => {
has_constructor = mode != raw::StructDefKind::Record;
StructLoc { container, ast_id: AstId::new(self.file_id, ast_id) }
.intern(self.def_collector.db)
.into()
@ -894,7 +897,11 @@ impl ModCollector<'_, '_> {
.def_map
.resolve_visibility(self.def_collector.db, self.module_id, vis)
.unwrap_or(Visibility::Public);
self.def_collector.update(self.module_id, &[(name, PerNs::from_def(def, vis))], vis)
self.def_collector.update(
self.module_id,
&[(name, PerNs::from_def(def, vis, has_constructor))],
vis,
)
}
fn collect_derives(&mut self, attrs: &Attrs, def: &raw::DefData) {

View file

@ -155,10 +155,17 @@ pub(super) struct DefData {
pub(super) visibility: RawVisibility,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub(super) enum StructDefKind {
Record,
Tuple,
Unit,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub(super) enum DefKind {
Function(FileAstId<ast::FnDef>),
Struct(FileAstId<ast::StructDef>),
Struct(FileAstId<ast::StructDef>, StructDefKind),
Union(FileAstId<ast::UnionDef>),
Enum(FileAstId<ast::EnumDef>),
Const(FileAstId<ast::ConstDef>),
@ -171,7 +178,7 @@ impl DefKind {
pub fn ast_id(&self) -> FileAstId<ast::ModuleItem> {
match self {
DefKind::Function(it) => it.upcast(),
DefKind::Struct(it) => it.upcast(),
DefKind::Struct(it, _) => it.upcast(),
DefKind::Union(it) => it.upcast(),
DefKind::Enum(it) => it.upcast(),
DefKind::Const(it) => it.upcast(),
@ -236,9 +243,14 @@ impl RawItemsCollector {
return;
}
ast::ModuleItem::StructDef(it) => {
let kind = match it.kind() {
ast::StructKind::Record(_) => StructDefKind::Record,
ast::StructKind::Tuple(_) => StructDefKind::Tuple,
ast::StructKind::Unit => StructDefKind::Unit,
};
let id = self.source_ast_id_map.ast_id(&it);
let name = it.name();
(DefKind::Struct(id), name)
(DefKind::Struct(id, kind), name)
}
ast::ModuleItem::UnionDef(it) => {
let id = self.source_ast_id_map.ast_id(&it);

View file

@ -67,7 +67,7 @@ fn crate_def_map_smoke_test() {
Baz: t v
E: t
EXT: v
U: t v
U: t
ext: v
"###)
}

View file

@ -19,12 +19,12 @@ fn macro_rules_are_globally_visible() {
);
assert_snapshot!(map, @r###"
crate
Foo: t v
Foo: t
nested: t
crate::nested
Bar: t v
Baz: t v
Bar: t
Baz: t
"###);
}
@ -91,13 +91,13 @@ fn macro_rules_from_other_crates_are_visible() {
);
assert_snapshot!(map, @r###"
crate
Bar: t v
Foo: t v
Bar: t
Foo: t
bar: t
crate::bar
Bar: t v
Foo: t v
Bar: t
Foo: t
bar: t
"###);
}
@ -124,13 +124,13 @@ fn macro_rules_export_with_local_inner_macros_are_visible() {
);
assert_snapshot!(map, @r###"
crate
Bar: t v
Foo: t v
Bar: t
Foo: t
bar: t
crate::bar
Bar: t v
Foo: t v
Bar: t
Foo: t
bar: t
"###);
}
@ -161,13 +161,13 @@ fn local_inner_macros_makes_local_macros_usable() {
);
assert_snapshot!(map, @r###"
crate
Bar: t v
Foo: t v
Bar: t
Foo: t
bar: t
crate::bar
Bar: t v
Foo: t v
Bar: t
Foo: t
bar: t
"###);
}
@ -204,7 +204,7 @@ fn unexpanded_macro_should_expand_by_fixedpoint_loop() {
);
assert_snapshot!(map, @r###"
crate
Foo: t v
Foo: t
bar: m
foo: m
"###);

View file

@ -269,7 +269,7 @@ fn test() { S.foo()<|>; }
}
#[test]
fn infer_impl_items_generated_by_macros() {
fn infer_assoc_items_generated_by_macros() {
let t = type_at(
r#"
//- /main.rs
@ -288,7 +288,7 @@ fn test() { S.foo()<|>; }
}
#[test]
fn infer_impl_items_generated_by_macros_chain() {
fn infer_assoc_items_generated_by_macros_chain() {
let t = type_at(
r#"
//- /main.rs

View file

@ -534,6 +534,35 @@ fn foo(b: Bar) {
);
}
#[test]
fn issue_4235_name_conflicts() {
assert_snapshot!(
infer(r#"
struct FOO {}
static FOO:FOO = FOO {};
impl FOO {
fn foo(&self) {}
}
fn main() {
let a = &FOO;
a.foo();
}
"#), @r###"
32..38 'FOO {}': FOO
64..68 'self': &FOO
70..72 '{}': ()
86..120 '{ ...o(); }': ()
96..97 'a': &FOO
100..104 '&FOO': &FOO
101..104 'FOO': FOO
110..111 'a': &FOO
110..117 'a.foo()': ()
"###
);
}
#[test]
fn issue_4053_diesel_where_clauses() {
assert_snapshot!(

View file

@ -1,6 +1,6 @@
//! FIXME: write short doc here
use ra_assists::{resolved_assists, AssistAction, AssistLabel};
use ra_assists::{resolved_assists, AssistAction};
use ra_db::{FilePosition, FileRange};
use ra_ide_db::RootDatabase;
@ -21,27 +21,22 @@ pub(crate) fn assists(db: &RootDatabase, frange: FileRange) -> Vec<Assist> {
.into_iter()
.map(|assist| {
let file_id = frange.file_id;
let assist_label = &assist.label;
Assist {
id: assist_label.id,
label: assist_label.label.clone(),
group_label: assist.group_label.map(|it| it.0),
source_change: action_to_edit(assist.action, file_id, assist_label),
id: assist.label.id,
label: assist.label.label.clone(),
group_label: assist.label.group.map(|it| it.0),
source_change: action_to_edit(assist.action, file_id, assist.label.label.clone()),
}
})
.collect()
}
fn action_to_edit(
action: AssistAction,
file_id: FileId,
assist_label: &AssistLabel,
) -> SourceChange {
fn action_to_edit(action: AssistAction, file_id: FileId, label: String) -> SourceChange {
let file_id = match action.file {
ra_assists::AssistFile::TargetFile(it) => it,
_ => file_id,
};
let file_edit = SourceFileEdit { file_id, edit: action.edit };
SourceChange::source_file_edit(assist_label.label.clone(), file_edit)
SourceChange::source_file_edit(label, file_edit)
.with_cursor_opt(action.cursor_position.map(|offset| FilePosition { offset, file_id }))
}

View file

@ -84,7 +84,7 @@ pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
});
// Iterate assoc types separately
ty.iterate_impl_items(ctx.db, krate, |item| {
ty.iterate_assoc_items(ctx.db, krate, |item| {
if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) {
return None;
}

View file

@ -32,7 +32,7 @@
//! ```
use hir::{self, Docs, HasSource};
use ra_assists::utils::get_missing_impl_items;
use ra_assists::utils::get_missing_assoc_items;
use ra_syntax::{
ast::{self, edit, ImplDef},
AstNode, SyntaxKind, SyntaxNode, TextRange, T,
@ -50,7 +50,7 @@ pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext
if let Some((trigger, impl_def)) = completion_match(ctx) {
match trigger.kind() {
SyntaxKind::NAME_REF => {
get_missing_impl_items(&ctx.sema, &impl_def).iter().for_each(|item| match item {
get_missing_assoc_items(&ctx.sema, &impl_def).iter().for_each(|item| match item {
hir::AssocItem::Function(fn_item) => {
add_function_impl(&trigger, acc, ctx, &fn_item)
}
@ -64,34 +64,40 @@ pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext
}
SyntaxKind::FN_DEF => {
for missing_fn in get_missing_impl_items(&ctx.sema, &impl_def).iter().filter_map(
|item| match item {
hir::AssocItem::Function(fn_item) => Some(fn_item),
_ => None,
},
) {
for missing_fn in
get_missing_assoc_items(&ctx.sema, &impl_def).iter().filter_map(|item| {
match item {
hir::AssocItem::Function(fn_item) => Some(fn_item),
_ => None,
}
})
{
add_function_impl(&trigger, acc, ctx, &missing_fn);
}
}
SyntaxKind::TYPE_ALIAS_DEF => {
for missing_fn in get_missing_impl_items(&ctx.sema, &impl_def).iter().filter_map(
|item| match item {
hir::AssocItem::TypeAlias(type_item) => Some(type_item),
_ => None,
},
) {
for missing_fn in
get_missing_assoc_items(&ctx.sema, &impl_def).iter().filter_map(|item| {
match item {
hir::AssocItem::TypeAlias(type_item) => Some(type_item),
_ => None,
}
})
{
add_type_alias_impl(&trigger, acc, ctx, &missing_fn);
}
}
SyntaxKind::CONST_DEF => {
for missing_fn in get_missing_impl_items(&ctx.sema, &impl_def).iter().filter_map(
|item| match item {
hir::AssocItem::Const(const_item) => Some(const_item),
_ => None,
},
) {
for missing_fn in
get_missing_assoc_items(&ctx.sema, &impl_def).iter().filter_map(|item| {
match item {
hir::AssocItem::Const(const_item) => Some(const_item),
_ => None,
}
})
{
add_const_impl(&trigger, acc, ctx, &missing_fn);
}
}

View file

@ -9,7 +9,7 @@ use ra_syntax::{
SyntaxKind::*,
SyntaxNode, SyntaxToken, TextRange, TextSize,
};
use ra_text_edit::AtomTextEdit;
use ra_text_edit::Indel;
use crate::{call_info::ActiveParameter, completion::CompletionConfig, FilePosition};
@ -76,7 +76,7 @@ impl<'a> CompletionContext<'a> {
// actual completion.
let file_with_fake_ident = {
let parse = db.parse(position.file_id);
let edit = AtomTextEdit::insert(position.offset, "intellijRulezz".to_string());
let edit = Indel::insert(position.offset, "intellijRulezz".to_string());
parse.reparse(&edit).tree()
};
let fake_ident_token =

View file

@ -62,8 +62,8 @@ impl fmt::Debug for CompletionItem {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut s = f.debug_struct("CompletionItem");
s.field("label", &self.label()).field("source_range", &self.source_range());
if self.text_edit().as_atoms().len() == 1 {
let atom = &self.text_edit().as_atoms()[0];
if self.text_edit().as_indels().len() == 1 {
let atom = &self.text_edit().as_indels()[0];
s.field("delete", &atom.delete);
s.field("insert", &atom.insert);
} else {

View file

@ -64,7 +64,7 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
.unwrap_or_else(|| RelativePath::new(""))
.join(&d.candidate);
let create_file = FileSystemEdit::CreateFile { source_root, path };
let fix = SourceChange::file_system_edit("create module", create_file);
let fix = SourceChange::file_system_edit("Create module", create_file);
res.borrow_mut().push(Diagnostic {
range: sema.diagnostics_range(d).range,
message: d.message(),
@ -92,7 +92,7 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
algo::diff(&d.ast(db).syntax(), &field_list.syntax()).into_text_edit(&mut builder);
Some(SourceChange::source_file_edit_from(
"fill struct fields",
"Fill struct fields",
file_id,
builder.finish(),
))
@ -117,7 +117,7 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
let node = d.ast(db);
let replacement = format!("Ok({})", node.syntax());
let edit = TextEdit::replace(node.syntax().text_range(), replacement);
let fix = SourceChange::source_file_edit_from("wrap with ok", file_id, edit);
let fix = SourceChange::source_file_edit_from("Wrap with ok", file_id, edit);
res.borrow_mut().push(Diagnostic {
range: sema.diagnostics_range(d).range,
message: d.message(),
@ -199,7 +199,7 @@ fn check_struct_shorthand_initialization(
message: "Shorthand struct initialization".to_string(),
severity: Severity::WeakWarning,
fix: Some(SourceChange::source_file_edit(
"use struct shorthand initialization",
"Use struct shorthand initialization",
SourceFileEdit { file_id, edit },
)),
});
@ -241,7 +241,11 @@ mod tests {
diagnostics.pop().unwrap_or_else(|| panic!("no diagnostics for:\n{}\n", before));
let mut fix = diagnostic.fix.unwrap();
let edit = fix.source_file_edits.pop().unwrap().edit;
let actual = edit.apply(&before);
let actual = {
let mut actual = before.to_string();
edit.apply(&mut actual);
actual
};
assert_eq_text!(after, &actual);
}
@ -256,7 +260,11 @@ mod tests {
let mut fix = diagnostic.fix.unwrap();
let edit = fix.source_file_edits.pop().unwrap().edit;
let target_file_contents = analysis.file_text(file_position.file_id).unwrap();
let actual = edit.apply(&target_file_contents);
let actual = {
let mut actual = target_file_contents.to_string();
edit.apply(&mut actual);
actual
};
// Strip indent and empty lines from `after`, to match the behaviour of
// `parse_fixture` called from `analysis_and_position`.
@ -288,7 +296,11 @@ mod tests {
let diagnostic = analysis.diagnostics(file_id).unwrap().pop().unwrap();
let mut fix = diagnostic.fix.unwrap();
let edit = fix.source_file_edits.pop().unwrap().edit;
let actual = edit.apply(&before);
let actual = {
let mut actual = before.to_string();
edit.apply(&mut actual);
actual
};
assert_eq_text!(after, &actual);
}
@ -606,7 +618,7 @@ mod tests {
range: 0..8,
fix: Some(
SourceChange {
label: "create module",
label: "Create module",
source_file_edits: [],
file_system_edits: [
CreateFile {
@ -655,17 +667,17 @@ mod tests {
range: 224..233,
fix: Some(
SourceChange {
label: "fill struct fields",
label: "Fill struct fields",
source_file_edits: [
SourceFileEdit {
file_id: FileId(
1,
),
edit: TextEdit {
atoms: [
AtomTextEdit {
delete: 3..9,
indels: [
Indel {
insert: "{a:42, b: ()}",
delete: 3..9,
},
],
},

View file

@ -569,7 +569,11 @@ fn foo() {
let (sel, before) = extract_range(before);
let parse = SourceFile::parse(&before);
let result = join_lines(&parse.tree(), sel);
let actual = result.apply(&before);
let actual = {
let mut actual = before.to_string();
result.apply(&mut actual);
actual
};
assert_eq_text!(after, &actual);
}

View file

@ -122,7 +122,7 @@ fn rename_mod(
source_file_edits.extend(ref_edits);
}
Some(SourceChange::from_edits("rename", source_file_edits, file_system_edits))
Some(SourceChange::from_edits("Rename", source_file_edits, file_system_edits))
}
fn rename_reference(
@ -141,7 +141,7 @@ fn rename_reference(
return None;
}
Some(RangeInfo::new(range, SourceChange::source_file_edits("rename", edit)))
Some(RangeInfo::new(range, SourceChange::source_file_edits("Rename", edit)))
}
#[cfg(test)]
@ -530,17 +530,17 @@ mod tests {
RangeInfo {
range: 4..7,
info: SourceChange {
label: "rename",
label: "Rename",
source_file_edits: [
SourceFileEdit {
file_id: FileId(
2,
),
edit: TextEdit {
atoms: [
AtomTextEdit {
delete: 4..7,
indels: [
Indel {
insert: "foo2",
delete: 4..7,
},
],
},
@ -582,17 +582,17 @@ mod tests {
RangeInfo {
range: 4..7,
info: SourceChange {
label: "rename",
label: "Rename",
source_file_edits: [
SourceFileEdit {
file_id: FileId(
1,
),
edit: TextEdit {
atoms: [
AtomTextEdit {
delete: 4..7,
indels: [
Indel {
insert: "foo2",
delete: 4..7,
},
],
},
@ -665,17 +665,17 @@ mod tests {
RangeInfo {
range: 8..11,
info: SourceChange {
label: "rename",
label: "Rename",
source_file_edits: [
SourceFileEdit {
file_id: FileId(
2,
),
edit: TextEdit {
atoms: [
AtomTextEdit {
delete: 8..11,
indels: [
Indel {
insert: "foo2",
delete: 8..11,
},
],
},
@ -685,10 +685,10 @@ mod tests {
1,
),
edit: TextEdit {
atoms: [
AtomTextEdit {
delete: 27..30,
indels: [
Indel {
insert: "foo2",
delete: 27..30,
},
],
},
@ -720,13 +720,13 @@ mod tests {
if let Some(change) = source_change {
for edit in change.info.source_file_edits {
file_id = Some(edit.file_id);
for atom in edit.edit.as_atoms() {
text_edit_builder.replace(atom.delete, atom.insert.clone());
for indel in edit.edit.as_indels() {
text_edit_builder.replace(indel.delete, indel.insert.clone());
}
}
}
let result =
text_edit_builder.finish().apply(&*analysis.file_text(file_id.unwrap()).unwrap());
let mut result = analysis.file_text(file_id.unwrap()).unwrap().to_string();
text_edit_builder.finish().apply(&mut result);
assert_eq_text!(expected, &*result);
}
}

View file

@ -9,6 +9,7 @@ use ra_syntax::{
};
use crate::FileId;
use ast::DocCommentsOwner;
use std::fmt::Display;
#[derive(Debug)]
@ -37,6 +38,7 @@ pub enum RunnableKind {
Test { test_id: TestId, attr: TestAttr },
TestMod { path: String },
Bench { test_id: TestId },
DocTest { test_id: TestId },
Bin,
}
@ -81,6 +83,8 @@ fn runnable_fn(sema: &Semantics<RootDatabase>, fn_def: ast::FnDef) -> Option<Run
RunnableKind::Test { test_id, attr }
} else if fn_def.has_atom_attr("bench") {
RunnableKind::Bench { test_id }
} else if has_doc_test(&fn_def) {
RunnableKind::DocTest { test_id }
} else {
return None;
}
@ -117,6 +121,10 @@ fn has_test_related_attribute(fn_def: &ast::FnDef) -> bool {
.any(|attribute_text| attribute_text.contains("test"))
}
fn has_doc_test(fn_def: &ast::FnDef) -> bool {
fn_def.doc_comment_text().map_or(false, |comment| comment.contains("```"))
}
fn runnable_mod(sema: &Semantics<RootDatabase>, module: ast::Module) -> Option<Runnable> {
let has_test_function = module
.item_list()?
@ -194,6 +202,41 @@ mod tests {
);
}
#[test]
fn test_runnables_doc_test() {
let (analysis, pos) = analysis_and_position(
r#"
//- /lib.rs
<|> //empty
fn main() {}
/// ```
/// let x = 5;
/// ```
fn foo() {}
"#,
);
let runnables = analysis.runnables(pos.file_id).unwrap();
assert_debug_snapshot!(&runnables,
@r###"
[
Runnable {
range: 1..21,
kind: Bin,
},
Runnable {
range: 22..64,
kind: DocTest {
test_id: Path(
"foo",
),
},
},
]
"###
);
}
#[test]
fn test_runnables_module() {
let (analysis, pos) = analysis_and_position(

View file

@ -35,8 +35,10 @@ impl SourceChange {
/// Creates a new SourceChange with the given label,
/// containing only the given `SourceFileEdits`.
pub(crate) fn source_file_edits<L: Into<String>>(label: L, edits: Vec<SourceFileEdit>) -> Self {
let label = label.into();
assert!(label.starts_with(char::is_uppercase));
SourceChange {
label: label.into(),
label: label,
source_file_edits: edits,
file_system_edits: vec![],
cursor_position: None,

View file

@ -401,16 +401,22 @@ fn render_replace(
ignored_comments: &Vec<Comment>,
template: &SsrTemplate,
) -> String {
let mut builder = TextEditBuilder::default();
for element in template.template.descendants() {
if let Some(var) = template.placeholders.get(&element) {
builder.replace(element.text_range(), binding[var].to_string())
let edit = {
let mut builder = TextEditBuilder::default();
for element in template.template.descendants() {
if let Some(var) = template.placeholders.get(&element) {
builder.replace(element.text_range(), binding[var].to_string())
}
}
}
for comment in ignored_comments {
builder.insert(template.template.text_range().end(), comment.syntax().to_string())
}
builder.finish().apply(&template.template.text().to_string())
for comment in ignored_comments {
builder.insert(template.template.text_range().end(), comment.syntax().to_string())
}
builder.finish()
};
let mut text = template.template.text().to_string();
edit.apply(&mut text);
text
}
#[cfg(test)]
@ -505,7 +511,9 @@ mod tests {
);
let edit = replace(&matches, &query.template);
assert_eq!(edit.apply(input), "fn main() { bar(1+2); }");
let mut after = input.to_string();
edit.apply(&mut after);
assert_eq!(after, "fn main() { bar(1+2); }");
}
fn assert_ssr_transform(query: &str, input: &str, result: &str) {
@ -513,7 +521,9 @@ mod tests {
let code = SourceFile::parse(input).tree();
let matches = find(&query.pattern, code.syntax());
let edit = replace(&matches, &query.template);
assert_eq!(edit.apply(input), result);
let mut after = input.to_string();
edit.apply(&mut after);
assert_eq!(after, result);
}
#[test]

View file

@ -13,7 +13,11 @@ pub fn check_action<F: Fn(&SourceFile, TextSize) -> Option<TextEdit>>(
let (before_cursor_pos, before) = extract_offset(before);
let file = SourceFile::parse(&before).ok().unwrap();
let result = f(&file, before_cursor_pos).expect("code action is not applicable");
let actual = result.apply(&before);
let actual = {
let mut actual = before.to_string();
result.apply(&mut actual);
actual
};
let actual_cursor_pos =
result.apply_to_offset(before_cursor_pos).expect("cursor position is affected by the edit");
let actual = add_cursor(&actual, actual_cursor_pos);

View file

@ -142,10 +142,13 @@ mod tests {
fn do_type_char(char_typed: char, before: &str) -> Option<(String, SingleFileChange)> {
let (offset, before) = extract_offset(before);
let edit = TextEdit::insert(offset, char_typed.to_string());
let before = edit.apply(&before);
let mut before = before.to_string();
edit.apply(&mut before);
let parse = SourceFile::parse(&before);
on_char_typed_inner(&parse.tree(), offset, char_typed)
.map(|it| (it.edit.apply(&before), it))
on_char_typed_inner(&parse.tree(), offset, char_typed).map(|it| {
it.edit.apply(&mut before);
(before.to_string(), it)
})
}
fn type_char(char_typed: char, before: &str, after: &str) {

View file

@ -44,7 +44,7 @@ pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<Sour
Some(
SourceChange::source_file_edit(
"on enter",
"On enter",
SourceFileEdit { edit, file_id: position.file_id },
)
.with_cursor(FilePosition { offset: cursor_position, file_id: position.file_id }),
@ -96,7 +96,8 @@ mod tests {
let result = analysis.on_enter(FilePosition { offset, file_id }).unwrap()?;
assert_eq!(result.source_file_edits.len(), 1);
let actual = result.source_file_edits[0].edit.apply(&before);
let mut actual = before.to_string();
result.source_file_edits[0].edit.apply(&mut actual);
let actual = add_cursor(&actual, result.cursor_position.unwrap().offset);
Some(actual)
}

View file

@ -31,9 +31,19 @@ pub(crate) struct Utf16Char {
}
impl Utf16Char {
/// Returns the length in 8-bit UTF-8 code units.
fn len(&self) -> TextSize {
self.end - self.start
}
/// Returns the length in 16-bit UTF-16 code units.
fn len_utf16(&self) -> usize {
if self.len() == TextSize::from(4) {
2
} else {
1
}
}
}
impl LineIndex {
@ -110,7 +120,7 @@ impl LineIndex {
if let Some(utf16_chars) = self.utf16_lines.get(&line) {
for c in utf16_chars {
if c.end <= col {
res -= usize::from(c.len()) - 1;
res -= usize::from(c.len()) - c.len_utf16();
} else {
// From here on, all utf16 characters come *after* the character we are mapping,
// so we don't need to take them into account
@ -125,7 +135,7 @@ impl LineIndex {
if let Some(utf16_chars) = self.utf16_lines.get(&line) {
for c in utf16_chars {
if col > u32::from(c.start) {
col += u32::from(c.len()) - 1;
col += u32::from(c.len()) - c.len_utf16() as u32;
} else {
// From here on, all utf16 characters come *after* the character we are mapping,
// so we don't need to take them into account
@ -204,6 +214,9 @@ const C: char = 'メ';
// UTF-16 to UTF-8
assert_eq!(col_index.utf16_to_utf8_col(1, 19), TextSize::from(21));
let col_index = LineIndex::new("a𐐏b");
assert_eq!(col_index.utf16_to_utf8_col(0, 3), TextSize::from(5));
}
#[test]

View file

@ -10,7 +10,7 @@
use std::convert::TryInto;
use ra_syntax::{TextRange, TextSize};
use ra_text_edit::{AtomTextEdit, TextEdit};
use ra_text_edit::{Indel, TextEdit};
use crate::line_index::{LineCol, LineIndex, Utf16Char};
@ -182,14 +182,14 @@ struct TranslatedEdit<'a> {
}
struct Edits<'a> {
edits: &'a [AtomTextEdit],
edits: &'a [Indel],
current: Option<TranslatedEdit<'a>>,
acc_diff: i64,
}
impl<'a> Edits<'a> {
fn from_text_edit(text_edit: &'a TextEdit) -> Edits<'a> {
let mut x = Edits { edits: text_edit.as_atoms(), current: None, acc_diff: 0 };
let mut x = Edits { edits: text_edit.as_indels(), current: None, acc_diff: 0 };
x.advance_edit();
x
}

View file

@ -63,7 +63,7 @@ pub fn syntax_node_to_token_tree(node: &SyntaxNode) -> Option<(tt::Subtree, Toke
// * Items(SmallVec<[P<ast::Item>; 1]>) -> token_tree_to_items
//
// * TraitItems(SmallVec<[ast::TraitItem; 1]>)
// * ImplItems(SmallVec<[ast::ImplItem; 1]>)
// * AssocItems(SmallVec<[ast::AssocItem; 1]>)
// * ForeignItems(SmallVec<[ast::ForeignItem; 1]>
pub fn token_tree_to_syntax_node(

View file

@ -56,6 +56,9 @@ pub struct CargoConfig {
/// Runs cargo check on launch to figure out the correct values of OUT_DIR
pub load_out_dirs_from_check: bool,
/// rustc target
pub target: Option<String>,
}
impl Default for CargoConfig {
@ -65,6 +68,7 @@ impl Default for CargoConfig {
all_features: true,
features: Vec::new(),
load_out_dirs_from_check: false,
target: None,
}
}
}
@ -160,6 +164,9 @@ impl CargoWorkspace {
if let Some(parent) = cargo_toml.parent() {
meta.current_dir(parent);
}
if let Some(target) = cargo_features.target.as_ref() {
meta.other_options(&[String::from("--filter-platform"), target.clone()]);
}
let meta = meta.exec().with_context(|| {
format!("Failed to run `cargo metadata --manifest-path {}`", cargo_toml.display())
})?;

View file

@ -543,7 +543,7 @@ impl ProjectWorkspace {
}
}
pub fn get_rustc_cfg_options() -> CfgOptions {
pub fn get_rustc_cfg_options(target: Option<&String>) -> CfgOptions {
let mut cfg_options = CfgOptions::default();
// Some nightly-only cfgs, which are required for stdlib
@ -558,10 +558,12 @@ pub fn get_rustc_cfg_options() -> CfgOptions {
match (|| -> Result<String> {
// `cfg(test)` and `cfg(debug_assertion)` are handled outside, so we suppress them here.
let output = Command::new("rustc")
.args(&["--print", "cfg", "-O"])
.output()
.context("Failed to get output from rustc --print cfg -O")?;
let mut cmd = Command::new("rustc");
cmd.args(&["--print", "cfg", "-O"]);
if let Some(target) = target {
cmd.args(&["--target", target.as_str()]);
}
let output = cmd.output().context("Failed to get output from rustc --print cfg -O")?;
if !output.status.success() {
bail!(
"rustc --print cfg -O exited with exit code ({})",

View file

@ -79,7 +79,7 @@ where
impl ast::ItemList {
#[must_use]
pub fn append_items(&self, items: impl IntoIterator<Item = ast::ImplItem>) -> ast::ItemList {
pub fn append_items(&self, items: impl IntoIterator<Item = ast::AssocItem>) -> ast::ItemList {
let mut res = self.clone();
if !self.syntax().text().contains_char('\n') {
res = make_multiline(res);
@ -89,8 +89,8 @@ impl ast::ItemList {
}
#[must_use]
pub fn append_item(&self, item: ast::ImplItem) -> ast::ItemList {
let (indent, position) = match self.impl_items().last() {
pub fn append_item(&self, item: ast::AssocItem) -> ast::ItemList {
let (indent, position) = match self.assoc_items().last() {
Some(it) => (
leading_indent(it.syntax()).unwrap_or_default().to_string(),
InsertPosition::After(it.syntax().clone().into()),

View file

@ -196,7 +196,7 @@ pub struct ItemList {
impl ast::ModuleItemOwner for ItemList {}
impl ItemList {
pub fn l_curly_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['{']) }
pub fn impl_items(&self) -> AstChildren<ImplItem> { support::children(&self.syntax) }
pub fn assoc_items(&self) -> AstChildren<AssocItem> { support::children(&self.syntax) }
pub fn r_curly_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['}']) }
}
@ -1429,13 +1429,13 @@ impl ast::AttrsOwner for ModuleItem {}
impl ast::VisibilityOwner for ModuleItem {}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ImplItem {
pub enum AssocItem {
FnDef(FnDef),
TypeAliasDef(TypeAliasDef),
ConstDef(ConstDef),
}
impl ast::NameOwner for ImplItem {}
impl ast::AttrsOwner for ImplItem {}
impl ast::NameOwner for AssocItem {}
impl ast::AttrsOwner for AssocItem {}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ExternItem {
@ -3167,16 +3167,16 @@ impl AstNode for ModuleItem {
}
}
}
impl From<FnDef> for ImplItem {
fn from(node: FnDef) -> ImplItem { ImplItem::FnDef(node) }
impl From<FnDef> for AssocItem {
fn from(node: FnDef) -> AssocItem { AssocItem::FnDef(node) }
}
impl From<TypeAliasDef> for ImplItem {
fn from(node: TypeAliasDef) -> ImplItem { ImplItem::TypeAliasDef(node) }
impl From<TypeAliasDef> for AssocItem {
fn from(node: TypeAliasDef) -> AssocItem { AssocItem::TypeAliasDef(node) }
}
impl From<ConstDef> for ImplItem {
fn from(node: ConstDef) -> ImplItem { ImplItem::ConstDef(node) }
impl From<ConstDef> for AssocItem {
fn from(node: ConstDef) -> AssocItem { AssocItem::ConstDef(node) }
}
impl AstNode for ImplItem {
impl AstNode for AssocItem {
fn can_cast(kind: SyntaxKind) -> bool {
match kind {
FN_DEF | TYPE_ALIAS_DEF | CONST_DEF => true,
@ -3185,18 +3185,18 @@ impl AstNode for ImplItem {
}
fn cast(syntax: SyntaxNode) -> Option<Self> {
let res = match syntax.kind() {
FN_DEF => ImplItem::FnDef(FnDef { syntax }),
TYPE_ALIAS_DEF => ImplItem::TypeAliasDef(TypeAliasDef { syntax }),
CONST_DEF => ImplItem::ConstDef(ConstDef { syntax }),
FN_DEF => AssocItem::FnDef(FnDef { syntax }),
TYPE_ALIAS_DEF => AssocItem::TypeAliasDef(TypeAliasDef { syntax }),
CONST_DEF => AssocItem::ConstDef(ConstDef { syntax }),
_ => return None,
};
Some(res)
}
fn syntax(&self) -> &SyntaxNode {
match self {
ImplItem::FnDef(it) => &it.syntax,
ImplItem::TypeAliasDef(it) => &it.syntax,
ImplItem::ConstDef(it) => &it.syntax,
AssocItem::FnDef(it) => &it.syntax,
AssocItem::TypeAliasDef(it) => &it.syntax,
AssocItem::ConstDef(it) => &it.syntax,
}
}
}
@ -3641,7 +3641,7 @@ impl std::fmt::Display for ModuleItem {
std::fmt::Display::fmt(self.syntax(), f)
}
}
impl std::fmt::Display for ImplItem {
impl std::fmt::Display for AssocItem {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
std::fmt::Display::fmt(self.syntax(), f)
}

View file

@ -5,7 +5,7 @@ use std::{
str::{self, FromStr},
};
use ra_text_edit::AtomTextEdit;
use ra_text_edit::Indel;
use crate::{validation, AstNode, SourceFile, TextRange};
@ -22,7 +22,7 @@ pub fn check_parser(text: &str) {
#[derive(Debug, Clone)]
pub struct CheckReparse {
text: String,
edit: AtomTextEdit,
edit: Indel,
edited_text: String,
}
@ -43,7 +43,7 @@ impl CheckReparse {
TextRange::at(delete_start.try_into().unwrap(), delete_len.try_into().unwrap());
let edited_text =
format!("{}{}{}", &text[..delete_start], &insert, &text[delete_start + delete_len..]);
let edit = AtomTextEdit { delete, insert };
let edit = Indel { delete, insert };
Some(CheckReparse { text, edit, edited_text })
}

View file

@ -39,7 +39,7 @@ pub mod fuzz;
use std::{marker::PhantomData, sync::Arc};
use ra_text_edit::AtomTextEdit;
use ra_text_edit::Indel;
use stdx::format_to;
use crate::syntax_node::GreenNode;
@ -126,13 +126,13 @@ impl Parse<SourceFile> {
buf
}
pub fn reparse(&self, edit: &AtomTextEdit) -> Parse<SourceFile> {
self.incremental_reparse(edit).unwrap_or_else(|| self.full_reparse(edit))
pub fn reparse(&self, indel: &Indel) -> Parse<SourceFile> {
self.incremental_reparse(indel).unwrap_or_else(|| self.full_reparse(indel))
}
fn incremental_reparse(&self, edit: &AtomTextEdit) -> Option<Parse<SourceFile>> {
fn incremental_reparse(&self, indel: &Indel) -> Option<Parse<SourceFile>> {
// FIXME: validation errors are not handled here
parsing::incremental_reparse(self.tree().syntax(), edit, self.errors.to_vec()).map(
parsing::incremental_reparse(self.tree().syntax(), indel, self.errors.to_vec()).map(
|(green_node, errors, _reparsed_range)| Parse {
green: green_node,
errors: Arc::new(errors),
@ -141,8 +141,9 @@ impl Parse<SourceFile> {
)
}
fn full_reparse(&self, edit: &AtomTextEdit) -> Parse<SourceFile> {
let text = edit.apply(self.tree().syntax().text().to_string());
fn full_reparse(&self, indel: &Indel) -> Parse<SourceFile> {
let mut text = self.tree().syntax().text().to_string();
indel.apply(&mut text);
SourceFile::parse(&text)
}
}

View file

@ -7,7 +7,7 @@
//! and try to parse only this block.
use ra_parser::Reparser;
use ra_text_edit::AtomTextEdit;
use ra_text_edit::Indel;
use crate::{
algo,
@ -24,7 +24,7 @@ use crate::{
pub(crate) fn incremental_reparse(
node: &SyntaxNode,
edit: &AtomTextEdit,
edit: &Indel,
errors: Vec<SyntaxError>,
) -> Option<(GreenNode, Vec<SyntaxError>, TextRange)> {
if let Some((green, new_errors, old_range)) = reparse_token(node, &edit) {
@ -39,7 +39,7 @@ pub(crate) fn incremental_reparse(
fn reparse_token<'node>(
root: &'node SyntaxNode,
edit: &AtomTextEdit,
edit: &Indel,
) -> Option<(GreenNode, Vec<SyntaxError>, TextRange)> {
let prev_token = algo::find_covering_element(root, edit.delete).as_token()?.clone();
let prev_token_kind = prev_token.kind();
@ -88,7 +88,7 @@ fn reparse_token<'node>(
fn reparse_block<'node>(
root: &'node SyntaxNode,
edit: &AtomTextEdit,
edit: &Indel,
) -> Option<(GreenNode, Vec<SyntaxError>, TextRange)> {
let (node, reparser) = find_reparsable_node(root, edit.delete)?;
let text = get_text_after_edit(node.clone().into(), edit);
@ -108,15 +108,15 @@ fn reparse_block<'node>(
Some((node.replace_with(green), new_parser_errors, node.text_range()))
}
fn get_text_after_edit(element: SyntaxElement, edit: &AtomTextEdit) -> String {
let edit =
AtomTextEdit::replace(edit.delete - element.text_range().start(), edit.insert.clone());
fn get_text_after_edit(element: SyntaxElement, edit: &Indel) -> String {
let edit = Indel::replace(edit.delete - element.text_range().start(), edit.insert.clone());
let text = match element {
let mut text = match element {
NodeOrToken::Token(token) => token.text().to_string(),
NodeOrToken::Node(node) => node.text().to_string(),
};
edit.apply(text)
edit.apply(&mut text);
text
}
fn is_contextual_kw(text: &str) -> bool {
@ -167,7 +167,7 @@ fn merge_errors(
old_errors: Vec<SyntaxError>,
new_errors: Vec<SyntaxError>,
range_before_reparse: TextRange,
edit: &AtomTextEdit,
edit: &Indel,
) -> Vec<SyntaxError> {
let mut res = Vec::new();
@ -198,8 +198,12 @@ mod tests {
fn do_check(before: &str, replace_with: &str, reparsed_len: u32) {
let (range, before) = extract_range(before);
let edit = AtomTextEdit::replace(range, replace_with.to_owned());
let after = edit.apply(before.clone());
let edit = Indel::replace(range, replace_with.to_owned());
let after = {
let mut after = before.clone();
edit.apply(&mut after);
after
};
let fully_reparsed = SourceFile::parse(&after);
let incrementally_reparsed: Parse<SourceFile> = {

View file

@ -1,36 +1,144 @@
//! FIXME: write short doc here
mod text_edit;
//! Representation of a `TextEdit`.
//!
//! `rust-analyzer` never mutates text itself and only sends diffs to clients,
//! so `TextEdit` is the ultimate representation of the work done by
//! rust-analyzer.
use text_size::{TextRange, TextSize};
pub use crate::text_edit::{TextEdit, TextEditBuilder};
/// Must not overlap with other `AtomTextEdit`s
/// `InsertDelete` -- a single "atomic" change to text
///
/// Must not overlap with other `InDel`s
#[derive(Debug, Clone)]
pub struct AtomTextEdit {
pub struct Indel {
pub insert: String,
/// Refers to offsets in the original text
pub delete: TextRange,
pub insert: String,
}
impl AtomTextEdit {
pub fn replace(range: TextRange, replace_with: String) -> AtomTextEdit {
AtomTextEdit { delete: range, insert: replace_with }
#[derive(Debug, Clone)]
pub struct TextEdit {
indels: Vec<Indel>,
}
#[derive(Debug, Default)]
pub struct TextEditBuilder {
indels: Vec<Indel>,
}
impl Indel {
pub fn insert(offset: TextSize, text: String) -> Indel {
Indel::replace(TextRange::empty(offset), text)
}
pub fn delete(range: TextRange) -> Indel {
Indel::replace(range, String::new())
}
pub fn replace(range: TextRange, replace_with: String) -> Indel {
Indel { delete: range, insert: replace_with }
}
pub fn delete(range: TextRange) -> AtomTextEdit {
AtomTextEdit::replace(range, String::new())
}
pub fn insert(offset: TextSize, text: String) -> AtomTextEdit {
AtomTextEdit::replace(TextRange::empty(offset), text)
}
pub fn apply(&self, mut text: String) -> String {
pub fn apply(&self, text: &mut String) {
let start: usize = self.delete.start().into();
let end: usize = self.delete.end().into();
text.replace_range(start..end, &self.insert);
text
}
}
impl TextEdit {
pub fn insert(offset: TextSize, text: String) -> TextEdit {
let mut builder = TextEditBuilder::default();
builder.insert(offset, text);
builder.finish()
}
pub fn delete(range: TextRange) -> TextEdit {
let mut builder = TextEditBuilder::default();
builder.delete(range);
builder.finish()
}
pub fn replace(range: TextRange, replace_with: String) -> TextEdit {
let mut builder = TextEditBuilder::default();
builder.replace(range, replace_with);
builder.finish()
}
pub(crate) fn from_indels(mut indels: Vec<Indel>) -> TextEdit {
indels.sort_by_key(|a| (a.delete.start(), a.delete.end()));
for (a1, a2) in indels.iter().zip(indels.iter().skip(1)) {
assert!(a1.delete.end() <= a2.delete.start())
}
TextEdit { indels }
}
pub fn as_indels(&self) -> &[Indel] {
&self.indels
}
pub fn apply(&self, text: &mut String) {
match self.indels.len() {
0 => return,
1 => {
self.indels[0].apply(text);
return;
}
_ => (),
}
let mut total_len = TextSize::of(&*text);
for indel in self.indels.iter() {
total_len += TextSize::of(&indel.insert);
total_len -= indel.delete.end() - indel.delete.start();
}
let mut buf = String::with_capacity(total_len.into());
let mut prev = 0;
for indel in self.indels.iter() {
let start: usize = indel.delete.start().into();
let end: usize = indel.delete.end().into();
if start > prev {
buf.push_str(&text[prev..start]);
}
buf.push_str(&indel.insert);
prev = end;
}
buf.push_str(&text[prev..text.len()]);
assert_eq!(TextSize::of(&buf), total_len);
// FIXME: figure out a way to mutate the text in-place or reuse the
// memory in some other way
*text = buf
}
pub fn apply_to_offset(&self, offset: TextSize) -> Option<TextSize> {
let mut res = offset;
for indel in self.indels.iter() {
if indel.delete.start() >= offset {
break;
}
if offset < indel.delete.end() {
return None;
}
res += TextSize::of(&indel.insert);
res -= indel.delete.len();
}
Some(res)
}
}
impl TextEditBuilder {
pub fn replace(&mut self, range: TextRange, replace_with: String) {
self.indels.push(Indel::replace(range, replace_with))
}
pub fn delete(&mut self, range: TextRange) {
self.indels.push(Indel::delete(range))
}
pub fn insert(&mut self, offset: TextSize, text: String) {
self.indels.push(Indel::insert(offset, text))
}
pub fn finish(self) -> TextEdit {
TextEdit::from_indels(self.indels)
}
pub fn invalidates_offset(&self, offset: TextSize) -> bool {
self.indels.iter().any(|indel| indel.delete.contains_inclusive(offset))
}
}

View file

@ -1,102 +0,0 @@
//! FIXME: write short doc here
use crate::AtomTextEdit;
use text_size::{TextRange, TextSize};
#[derive(Debug, Clone)]
pub struct TextEdit {
atoms: Vec<AtomTextEdit>,
}
#[derive(Debug, Default)]
pub struct TextEditBuilder {
atoms: Vec<AtomTextEdit>,
}
impl TextEditBuilder {
pub fn replace(&mut self, range: TextRange, replace_with: String) {
self.atoms.push(AtomTextEdit::replace(range, replace_with))
}
pub fn delete(&mut self, range: TextRange) {
self.atoms.push(AtomTextEdit::delete(range))
}
pub fn insert(&mut self, offset: TextSize, text: String) {
self.atoms.push(AtomTextEdit::insert(offset, text))
}
pub fn finish(self) -> TextEdit {
TextEdit::from_atoms(self.atoms)
}
pub fn invalidates_offset(&self, offset: TextSize) -> bool {
self.atoms.iter().any(|atom| atom.delete.contains_inclusive(offset))
}
}
impl TextEdit {
pub fn insert(offset: TextSize, text: String) -> TextEdit {
let mut builder = TextEditBuilder::default();
builder.insert(offset, text);
builder.finish()
}
pub fn delete(range: TextRange) -> TextEdit {
let mut builder = TextEditBuilder::default();
builder.delete(range);
builder.finish()
}
pub fn replace(range: TextRange, replace_with: String) -> TextEdit {
let mut builder = TextEditBuilder::default();
builder.replace(range, replace_with);
builder.finish()
}
pub(crate) fn from_atoms(mut atoms: Vec<AtomTextEdit>) -> TextEdit {
atoms.sort_by_key(|a| (a.delete.start(), a.delete.end()));
for (a1, a2) in atoms.iter().zip(atoms.iter().skip(1)) {
assert!(a1.delete.end() <= a2.delete.start())
}
TextEdit { atoms }
}
pub fn as_atoms(&self) -> &[AtomTextEdit] {
&self.atoms
}
pub fn apply(&self, text: &str) -> String {
let mut total_len = TextSize::of(text);
for atom in self.atoms.iter() {
total_len += TextSize::of(&atom.insert);
total_len -= atom.delete.end() - atom.delete.start();
}
let mut buf = String::with_capacity(total_len.into());
let mut prev = 0;
for atom in self.atoms.iter() {
let start: usize = atom.delete.start().into();
let end: usize = atom.delete.end().into();
if start > prev {
buf.push_str(&text[prev..start]);
}
buf.push_str(&atom.insert);
prev = end;
}
buf.push_str(&text[prev..text.len()]);
assert_eq!(TextSize::of(&buf), total_len);
buf
}
pub fn apply_to_offset(&self, offset: TextSize) -> Option<TextSize> {
let mut res = offset;
for atom in self.atoms.iter() {
if atom.delete.start() >= offset {
break;
}
if offset < atom.delete.end() {
return None;
}
res += TextSize::of(&atom.insert);
res -= atom.delete.len();
}
Some(res)
}
}

View file

@ -17,10 +17,10 @@ pub fn server_capabilities() -> ServerCapabilities {
ServerCapabilities {
text_document_sync: Some(TextDocumentSyncCapability::Options(TextDocumentSyncOptions {
open_close: Some(true),
change: Some(if env::var("RA_PROFILE").is_ok() {
TextDocumentSyncKind::Incremental
} else {
change: Some(if env::var("RA_NO_INCREMENTAL_SYNC").is_ok() {
TextDocumentSyncKind::Full
} else {
TextDocumentSyncKind::Incremental
}),
will_save: None,
will_save_wait_until: None,

View file

@ -27,7 +27,7 @@ impl CargoTargetSpec {
RunnableKind::Test { test_id, attr } => {
args.push("test".to_string());
if let Some(spec) = spec {
spec.push_to(&mut args);
spec.push_to(&mut args, kind);
}
extra_args.push(test_id.to_string());
if let TestId::Path(_) = test_id {
@ -35,13 +35,13 @@ impl CargoTargetSpec {
}
extra_args.push("--nocapture".to_string());
if attr.ignore {
extra_args.push("--ignored".to_string())
extra_args.push("--ignored".to_string());
}
}
RunnableKind::TestMod { path } => {
args.push("test".to_string());
if let Some(spec) = spec {
spec.push_to(&mut args);
spec.push_to(&mut args, kind);
}
extra_args.push(path.to_string());
extra_args.push("--nocapture".to_string());
@ -49,7 +49,7 @@ impl CargoTargetSpec {
RunnableKind::Bench { test_id } => {
args.push("bench".to_string());
if let Some(spec) = spec {
spec.push_to(&mut args);
spec.push_to(&mut args, kind);
}
extra_args.push(test_id.to_string());
if let TestId::Path(_) = test_id {
@ -57,10 +57,19 @@ impl CargoTargetSpec {
}
extra_args.push("--nocapture".to_string());
}
RunnableKind::DocTest { test_id } => {
args.push("test".to_string());
args.push("--doc".to_string());
if let Some(spec) = spec {
spec.push_to(&mut args, kind);
}
extra_args.push(test_id.to_string());
extra_args.push("--nocapture".to_string());
}
RunnableKind::Bin => {
args.push("run".to_string());
if let Some(spec) = spec {
spec.push_to(&mut args);
spec.push_to(&mut args, kind);
}
}
}
@ -91,9 +100,14 @@ impl CargoTargetSpec {
Ok(res)
}
pub(crate) fn push_to(self, buf: &mut Vec<String>) {
pub(crate) fn push_to(self, buf: &mut Vec<String>, kind: &RunnableKind) {
buf.push("--package".to_string());
buf.push(self.package);
// Can't mix --doc with other target flags
if let RunnableKind::DocTest { .. } = kind {
return;
}
match self.target_kind {
TargetKind::Bin => {
buf.push("--bin".to_string());

View file

@ -149,7 +149,7 @@ pub(crate) fn load(
// FIXME: cfg options?
let default_cfg_options = {
let mut opts = get_rustc_cfg_options();
let mut opts = get_rustc_cfg_options(None);
opts.insert_atom("test".into());
opts.insert_atom("debug_assertion".into());
opts

View file

@ -89,6 +89,7 @@ impl Default for Config {
check: Some(FlycheckConfig::CargoCommand {
command: "check".to_string(),
all_targets: true,
all_features: true,
extra_args: Vec::new(),
}),
@ -131,6 +132,7 @@ impl Config {
set(value, "/cargo/allFeatures", &mut self.cargo.all_features);
set(value, "/cargo/features", &mut self.cargo.features);
set(value, "/cargo/loadOutDirsFromCheck", &mut self.cargo.load_out_dirs_from_check);
set(value, "/cargo/target", &mut self.cargo.target);
match get(value, "/procMacro/enable") {
Some(true) => {
@ -172,12 +174,13 @@ impl Config {
}
// otherwise configure command customizations
_ => {
if let Some(FlycheckConfig::CargoCommand { command, extra_args, all_targets })
if let Some(FlycheckConfig::CargoCommand { command, extra_args, all_targets, all_features })
= &mut self.check
{
set(value, "/checkOnSave/extraArgs", extra_args);
set(value, "/checkOnSave/command", command);
set(value, "/checkOnSave/allTargets", all_targets);
set(value, "/checkOnSave/allFeatures", all_features);
}
}
};

View file

@ -15,7 +15,7 @@ use ra_ide::{
ReferenceAccess, Severity, SourceChange, SourceFileEdit,
};
use ra_syntax::{SyntaxKind, TextRange, TextSize};
use ra_text_edit::{AtomTextEdit, TextEdit};
use ra_text_edit::{Indel, TextEdit};
use ra_vfs::LineEndings;
use crate::{
@ -124,23 +124,22 @@ impl ConvWith<(&LineIndex, LineEndings)> for CompletionItem {
let mut text_edit = None;
// LSP does not allow arbitrary edits in completion, so we have to do a
// non-trivial mapping here.
for atom_edit in self.text_edit().as_atoms() {
if atom_edit.delete.contains_range(self.source_range()) {
text_edit = Some(if atom_edit.delete == self.source_range() {
atom_edit.conv_with((ctx.0, ctx.1))
for indel in self.text_edit().as_indels() {
if indel.delete.contains_range(self.source_range()) {
text_edit = Some(if indel.delete == self.source_range() {
indel.conv_with((ctx.0, ctx.1))
} else {
assert!(self.source_range().end() == atom_edit.delete.end());
let range1 =
TextRange::new(atom_edit.delete.start(), self.source_range().start());
assert!(self.source_range().end() == indel.delete.end());
let range1 = TextRange::new(indel.delete.start(), self.source_range().start());
let range2 = self.source_range();
let edit1 = AtomTextEdit::replace(range1, String::new());
let edit2 = AtomTextEdit::replace(range2, atom_edit.insert.clone());
let edit1 = Indel::replace(range1, String::new());
let edit2 = Indel::replace(range2, indel.insert.clone());
additional_text_edits.push(edit1.conv_with((ctx.0, ctx.1)));
edit2.conv_with((ctx.0, ctx.1))
})
} else {
assert!(self.source_range().intersect(atom_edit.delete).is_none());
additional_text_edits.push(atom_edit.conv_with((ctx.0, ctx.1)));
assert!(self.source_range().intersect(indel.delete).is_none());
additional_text_edits.push(indel.conv_with((ctx.0, ctx.1)));
}
}
let text_edit = text_edit.unwrap();
@ -257,11 +256,11 @@ impl ConvWith<(&LineIndex, LineEndings)> for TextEdit {
type Output = Vec<lsp_types::TextEdit>;
fn conv_with(self, ctx: (&LineIndex, LineEndings)) -> Vec<lsp_types::TextEdit> {
self.as_atoms().iter().map_conv_with(ctx).collect()
self.as_indels().iter().map_conv_with(ctx).collect()
}
}
impl ConvWith<(&LineIndex, LineEndings)> for &AtomTextEdit {
impl ConvWith<(&LineIndex, LineEndings)> for &Indel {
type Output = lsp_types::TextEdit;
fn conv_with(
@ -522,7 +521,7 @@ impl TryConvWith<&WorldSnapshot> for SourceFileEdit {
let line_index = world.analysis().file_line_index(self.file_id)?;
let line_endings = world.file_line_endings(self.file_id);
let edits =
self.edit.as_atoms().iter().map_conv_with((&line_index, line_endings)).collect();
self.edit.as_indels().iter().map_conv_with((&line_index, line_endings)).collect();
Ok(TextDocumentEdit { text_document, edits })
}
}

View file

@ -676,13 +676,13 @@ fn apply_document_changes(
// remember the last valid line in the index and only rebuild it if needed.
enum IndexValid {
All,
UpToLine(u64),
UpToLineExclusive(u64),
}
impl IndexValid {
fn covers(&self, line: u64) -> bool {
match *self {
IndexValid::UpToLine(to) => to >= line,
IndexValid::UpToLineExclusive(to) => to > line,
_ => true,
}
}
@ -692,10 +692,10 @@ fn apply_document_changes(
for change in content_changes {
match change.range {
Some(range) => {
if !index_valid.covers(range.start.line) {
if !index_valid.covers(range.end.line) {
line_index = Cow::Owned(LineIndex::new(&old_text));
}
index_valid = IndexValid::UpToLine(range.start.line);
index_valid = IndexValid::UpToLineExclusive(range.start.line);
let range = range.conv_with(&line_index);
let mut text = old_text.to_owned();
match std::panic::catch_unwind(move || {
@ -713,7 +713,7 @@ fn apply_document_changes(
}
None => {
*old_text = change.text;
index_valid = IndexValid::UpToLine(0);
index_valid = IndexValid::UpToLineExclusive(0);
}
}
}
@ -1083,5 +1083,17 @@ mod tests {
assert_eq!(text, "the quick foxes\nDREAM\nthey have quiet dreams\nDON'T THEY?\n");
run(&mut text, c![0, 10; 1, 5 => "", 2, 0; 2, 12 => ""]);
assert_eq!(text, "the quick \nthey have quiet dreams\n");
text = String::from("❤️");
run(&mut text, c![0, 0; 0, 0 => "a"]);
assert_eq!(text, "a❤");
text = String::from("a\nb");
run(&mut text, c![0, 1; 1, 0 => "\nțc", 0, 1; 1, 1 => "d"]);
assert_eq!(text, "adcb");
text = String::from("a\nb");
run(&mut text, c![0, 1; 1, 0 => "ț\nc", 0, 2; 0, 2 => "c"]);
assert_eq!(text, "ațc\ncb");
}
}

View file

@ -835,6 +835,7 @@ pub fn handle_code_lens(
for runnable in world.analysis().runnables(file_id)? {
let title = match &runnable.kind {
RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => "▶️\u{fe0e}Run Test",
RunnableKind::DocTest { .. } => "▶️\u{fe0e}Run Doctest",
RunnableKind::Bench { .. } => "Run Bench",
RunnableKind::Bin => "Run",
}
@ -1018,6 +1019,7 @@ fn to_lsp_runnable(
RunnableKind::Test { test_id, .. } => format!("test {}", test_id),
RunnableKind::TestMod { path } => format!("test-mod {}", path),
RunnableKind::Bench { test_id } => format!("bench {}", test_id),
RunnableKind::DocTest { test_id, .. } => format!("doctest {}", test_id),
RunnableKind::Bin => "run binary".to_string(),
};
Ok(req::Runnable {

View file

@ -131,7 +131,7 @@ impl WorldState {
// FIXME: Read default cfgs from config
let default_cfg_options = {
let mut opts = get_rustc_cfg_options();
let mut opts = get_rustc_cfg_options(config.cargo.target.as_ref());
opts.insert_atom("test".into());
opts.insert_atom("debug_assertion".into());
opts

View file

@ -337,7 +337,7 @@ fn main() {}
"arguments": [
{
"cursorPosition": null,
"label": "create module",
"label": "Create module",
"workspaceEdit": {
"documentChanges": [
{
@ -349,9 +349,9 @@ fn main() {}
}
],
"command": "rust-analyzer.applySourceChange",
"title": "create module"
"title": "Create module"
},
"title": "create module"
"title": "Create module"
}
]),
);
@ -420,7 +420,7 @@ fn main() {{}}
"arguments": [
{
"cursorPosition": null,
"label": "create module",
"label": "Create module",
"workspaceEdit": {
"documentChanges": [
{
@ -432,9 +432,9 @@ fn main() {{}}
}
],
"command": "rust-analyzer.applySourceChange",
"title": "create module"
"title": "Create module"
},
"title": "create module"
"title": "Create module"
}
]),
);
@ -500,7 +500,7 @@ fn main() {{}}
"position": { "character": 4, "line": 1 },
"textDocument": { "uri": "file:///[..]src/m0.rs" }
},
"label": "on enter",
"label": "On enter",
"workspaceEdit": {
"documentChanges": [
{
@ -552,7 +552,7 @@ version = \"0.0.0\"
"position": { "line": 1, "character": 4 },
"textDocument": { "uri": "file:///[..]src/main.rs" }
},
"label": "on enter",
"label": "On enter",
"workspaceEdit": {
"documentChanges": [
{

View file

@ -57,7 +57,11 @@ To disable this notification put the following to `settings.json`
----
====
The server binary is stored in `~/.config/Code/User/globalStorage/matklad.rust-analyzer` (Linux) or in `~/.Library/Application Support/Code/User/globalStorage/matklad.rust-analyzer` (macOS) or in `%APPDATA%\Code\User\globalStorage` (Windows).
The server binary is stored in:
* Linux: `~/.config/Code/User/globalStorage/matklad.rust-analyzer`
* macOS: `~/Library/Application Support/Code/User/globalStorage/matklad.rust-analyzer`
* Windows: `%APPDATA%\Code\User\globalStorage`
Note that we only support the latest version of VS Code.
@ -159,11 +163,11 @@ Emacs support is maintained as part of the https://github.com/emacs-lsp/lsp-mode
3. Run `lsp` in a Rust buffer.
4. (Optionally) bind commands like `lsp-rust-analyzer-join-lines`, `lsp-extend-selection` and `lsp-rust-analyzer-expand-macro` to keys.
=== Vim
=== Vim/NeoVim
Prerequisites: You have installed the <<rust-analyzer-language-server-binary,`rust-analyzer` binary>>.
Prerequisites: You have installed the <<rust-analyzer-language-server-binary,`rust-analyzer` binary>>. Not needed if the extension can install/update it on its own, coc-rust-analyzer is one example.
The are several LSP client implementations for vim:
The are several LSP client implementations for vim or neovim:
==== coc-rust-analyzer

View file

@ -1066,11 +1066,6 @@
"integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
"dev": true
},
"jsonc-parser": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.2.1.tgz",
"integrity": "sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w=="
},
"leven": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",

View file

@ -33,7 +33,6 @@
"fix": " tsfmt -r && eslint -c .eslintrc.js --ext ts ./src --fix"
},
"dependencies": {
"jsonc-parser": "^2.2.1",
"node-fetch": "^2.6.0",
"vscode-languageclient": "7.0.0-next.1"
},
@ -233,6 +232,14 @@
"default": false,
"markdownDescription": "Run `cargo check` on startup to get the correct value for package OUT_DIRs"
},
"rust-analyzer.cargo.target": {
"type": [
"null",
"string"
],
"default": null,
"description": "Specify the compilation target"
},
"rust-analyzer.rustfmt.extraArgs": {
"type": "array",
"items": {
@ -288,6 +295,11 @@
"default": true,
"markdownDescription": "Check all targets and tests (will be passed as `--all-targets`)"
},
"rust-analyzer.checkOnSave.allFeatures": {
"type": "boolean",
"default": true,
"markdownDescription": "Check with all features (will be passed as `--all-features`)"
},
"rust-analyzer.inlayHints.typeHints": {
"type": "boolean",
"default": true,

View file

@ -1,129 +0,0 @@
import * as fs from 'fs';
import * as jsonc from 'jsonc-parser';
import * as path from 'path';
import * as vscode from 'vscode';
export interface TextMateRuleSettings {
foreground?: string;
background?: string;
fontStyle?: string;
}
export class ColorTheme {
private rules: Map<string, TextMateRuleSettings> = new Map();
static load(): ColorTheme {
// Find out current color theme
const themeName = vscode.workspace
.getConfiguration('workbench')
.get('colorTheme');
if (typeof themeName !== 'string') {
// console.warn('workbench.colorTheme is', themeName)
return new ColorTheme();
}
return loadThemeNamed(themeName);
}
static fromRules(rules: TextMateRule[]): ColorTheme {
const res = new ColorTheme();
for (const rule of rules) {
const scopes = typeof rule.scope === 'undefined'
? []
: typeof rule.scope === 'string'
? [rule.scope]
: rule.scope;
for (const scope of scopes) {
res.rules.set(scope, rule.settings);
}
}
return res;
}
lookup(scopes: string[]): TextMateRuleSettings {
let res: TextMateRuleSettings = {};
for (const scope of scopes) {
this.rules.forEach((value, key) => {
if (scope.startsWith(key)) {
res = mergeRuleSettings(res, value);
}
});
}
return res;
}
mergeFrom(other: ColorTheme) {
other.rules.forEach((value, key) => {
const merged = mergeRuleSettings(this.rules.get(key), value);
this.rules.set(key, merged);
});
}
}
function loadThemeNamed(themeName: string): ColorTheme {
function isTheme(extension: vscode.Extension<unknown>): boolean {
return (
extension.extensionKind === vscode.ExtensionKind.UI &&
extension.packageJSON.contributes &&
extension.packageJSON.contributes.themes
);
}
const themePaths: string[] = vscode.extensions.all
.filter(isTheme)
.flatMap(
ext => ext.packageJSON.contributes.themes
.filter((it: any) => (it.id || it.label) === themeName)
.map((it: any) => path.join(ext.extensionPath, it.path))
);
const res = new ColorTheme();
for (const themePath of themePaths) {
res.mergeFrom(loadThemeFile(themePath));
}
const globalCustomizations: any = vscode.workspace.getConfiguration('editor').get('tokenColorCustomizations');
res.mergeFrom(ColorTheme.fromRules(globalCustomizations?.textMateRules ?? []));
const themeCustomizations: any = vscode.workspace.getConfiguration('editor.tokenColorCustomizations').get(`[${themeName}]`);
res.mergeFrom(ColorTheme.fromRules(themeCustomizations?.textMateRules ?? []));
return res;
}
function loadThemeFile(themePath: string): ColorTheme {
let text;
try {
text = fs.readFileSync(themePath, 'utf8');
} catch {
return new ColorTheme();
}
const obj = jsonc.parse(text);
const tokenColors: TextMateRule[] = obj?.tokenColors ?? [];
const res = ColorTheme.fromRules(tokenColors);
for (const include of obj?.include ?? []) {
const includePath = path.join(path.dirname(themePath), include);
res.mergeFrom(loadThemeFile(includePath));
}
return res;
}
interface TextMateRule {
scope: string | string[];
settings: TextMateRuleSettings;
}
function mergeRuleSettings(
defaultSetting: TextMateRuleSettings | undefined,
override: TextMateRuleSettings,
): TextMateRuleSettings {
return {
foreground: override.foreground ?? defaultSetting?.foreground,
background: override.background ?? defaultSetting?.background,
fontStyle: override.fontStyle ?? defaultSetting?.fontStyle,
};
}

View file

@ -373,7 +373,7 @@ pub(crate) const AST_SRC: AstSrc = AstSrc {
struct ItemList: ModuleItemOwner {
T!['{'],
impl_items: [ImplItem],
assoc_items: [AssocItem],
T!['}']
}
@ -685,7 +685,7 @@ pub(crate) const AST_SRC: AstSrc = AstSrc {
}
/* impl blocks can also contain MacroCall */
enum ImplItem: NameOwner, AttrsOwner {
enum AssocItem: NameOwner, AttrsOwner {
FnDef, TypeAliasDef, ConstDef
}

View file

@ -115,7 +115,6 @@ impl TidyDocs {
"ra_prof",
"ra_project_model",
"ra_syntax",
"ra_text_edit",
"ra_tt",
"ra_hir_ty",
];