diff --git a/Cargo.lock b/Cargo.lock index 5d50a766fe..41855f22e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -68,9 +68,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5ca2cd0adc3f48f9e9ea5a6bbdf9ccc0bfade884847e484d452414c7ccffb3" +checksum = "53d1ccbaf7d9ec9537465a97bf19edc1a4e158ecb49fc16178202238c569cc42" [[package]] name = "bitflags" @@ -342,9 +342,9 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f59efc38004c988e4201d11d263b8171f49a2e7ec0bdbb71773433f271504a5e" +checksum = "affc17579b132fc2461adf7c575cc6e8b134ebca52c51f5411388965227dc695" dependencies = [ "cfg-if", "libc", @@ -464,6 +464,15 @@ dependencies = [ "libc", ] +[[package]] +name = "home" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2456aef2e6b6a9784192ae780c0f15bc57df0e918585282325e8c8ac27737654" +dependencies = [ + "winapi 0.3.8", +] + [[package]] name = "idna" version = "0.2.0" @@ -610,18 +619,18 @@ checksum = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005" [[package]] name = "libloading" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c4f51b790f5bdb65acb4cc94bb81d7b2ee60348a5431ac1467d390b017600b0" +checksum = "2cadb8e769f070c45df05c78c7520eb4cd17061d4ab262e43cfc68b4d00ac71c" dependencies = [ "winapi 0.3.8", ] [[package]] name = "linked-hash-map" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83" +checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a" [[package]] name = "lock_api" @@ -824,9 +833,9 @@ dependencies = [ [[package]] name = "paste" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3c897744f63f34f7ae3a024d9162bb5001f4ad661dd24bea0dc9f075d2de1c6" +checksum = "0a229b1c58c692edcaa5b9b0948084f130f55d2dcc15b02fcc5340b2b4521476" dependencies = [ "paste-impl", "proc-macro-hack", @@ -834,9 +843,9 @@ dependencies = [ [[package]] name = "paste-impl" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66fd6f92e3594f2dd7b3fc23e42d82e292f7bcda6d8e5dcd167072327234ab89" +checksum = "2e0bf239e447e67ff6d16a8bb5e4d4bd2343acf5066061c0e8e06ac5ba8ca68c" dependencies = [ "proc-macro-hack", "proc-macro2", @@ -886,9 +895,9 @@ checksum = "0d659fe7c6d27f25e9d80a1a094c223f5246f6a6596453e09d7229bf42750b63" [[package]] name = "proc-macro2" -version = "1.0.10" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df246d292ff63439fea9bc8c0a270bed0e390d5ebd4db4ba15aba81111b5abe3" +checksum = "8872cf6f48eee44265156c111456a700ab3483686b3f96df4cf5481c89157319" dependencies = [ "unicode-xid", ] @@ -958,6 +967,7 @@ dependencies = [ "jod-thread", "log", "lsp-types", + "ra_toolchain", "serde_json", ] @@ -1163,6 +1173,7 @@ dependencies = [ "ra_cfg", "ra_db", "ra_proc_macro", + "ra_toolchain", "rustc-hash", "serde", "serde_json", @@ -1194,6 +1205,13 @@ dependencies = [ "text-size", ] +[[package]] +name = "ra_toolchain" +version = "0.1.0" +dependencies = [ + "home", +] + [[package]] name = "ra_tt" version = "0.1.0" @@ -1581,9 +1599,9 @@ checksum = "ab16ced94dbd8a46c82fd81e3ed9a8727dac2977ea869d217bcc4ea1f122e81f" [[package]] name = "syn" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "410a7488c0a728c7ceb4ad59b9567eb4053d02e8cc7f5c0e0eeeb39518369213" +checksum = "e8e5aa70697bb26ee62214ae3288465ecec0000f05182f039b477001f08f5ae7" dependencies = [ "proc-macro2", "quote", diff --git a/crates/ra_assists/src/assist_context.rs b/crates/ra_assists/src/assist_context.rs new file mode 100644 index 0000000000..3085c4330c --- /dev/null +++ b/crates/ra_assists/src/assist_context.rs @@ -0,0 +1,233 @@ +//! See `AssistContext` + +use algo::find_covering_element; +use hir::Semantics; +use ra_db::{FileId, FileRange}; +use ra_fmt::{leading_indent, reindent}; +use ra_ide_db::{ + source_change::{SingleFileChange, SourceChange}, + RootDatabase, +}; +use ra_syntax::{ + algo::{self, find_node_at_offset, SyntaxRewriter}, + AstNode, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize, + TokenAtOffset, +}; +use ra_text_edit::TextEditBuilder; + +use crate::{Assist, AssistId, GroupLabel, ResolvedAssist}; + +/// `AssistContext` allows to apply an assist or check if it could be applied. +/// +/// 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 the list of available assists. In the second phase, the user picks a +/// particular assist and it gets applied. +/// +/// There are two peculiarities here: +/// +/// * first, we ideally avoid computing more things then necessary to answer "is +/// assist applicable" in the first phase. +/// * second, when we are applying assist, we don't have a guarantee that there +/// weren't any changes between the point when user asked for assists and when +/// they applied a particular assist. So, when applying assist, we need to do +/// all the checks from scratch. +/// +/// To avoid repeating the same code twice for both "check" and "apply" +/// functions, we use an approach reminiscent of that of Django's function based +/// views dealing with forms. Each assist receives a runtime parameter, +/// `resolve`. It first check if an edit is applicable (potentially computing +/// info required to compute the actual edit). If it is applicable, and +/// `resolve` is `true`, it then computes the actual edit. +/// +/// So, to implement the original assists workflow, we can first apply each edit +/// with `resolve = false`, and then applying the selected edit again, with +/// `resolve = true` this time. +/// +/// Note, however, that we don't actually use such two-phase logic at the +/// moment, because the LSP API is pretty awkward in this place, and it's much +/// easier to just compute the edit eagerly :-) +pub(crate) struct AssistContext<'a> { + pub(crate) sema: Semantics<'a, RootDatabase>, + pub(super) db: &'a RootDatabase, + pub(crate) frange: FileRange, + source_file: SourceFile, +} + +impl<'a> AssistContext<'a> { + pub fn new(sema: Semantics<'a, RootDatabase>, frange: FileRange) -> AssistContext<'a> { + let source_file = sema.parse(frange.file_id); + let db = sema.db; + AssistContext { sema, db, frange, source_file } + } + + // NB, this ignores active selection. + pub(crate) fn offset(&self) -> TextSize { + self.frange.range.start() + } + + pub(crate) fn token_at_offset(&self) -> TokenAtOffset { + self.source_file.syntax().token_at_offset(self.offset()) + } + pub(crate) fn find_token_at_offset(&self, kind: SyntaxKind) -> Option { + self.token_at_offset().find(|it| it.kind() == kind) + } + pub(crate) fn find_node_at_offset(&self) -> Option { + find_node_at_offset(self.source_file.syntax(), self.offset()) + } + pub(crate) fn find_node_at_offset_with_descend(&self) -> Option { + self.sema.find_node_at_offset_with_descend(self.source_file.syntax(), self.offset()) + } + pub(crate) fn covering_element(&self) -> SyntaxElement { + find_covering_element(self.source_file.syntax(), self.frange.range) + } + // FIXME: remove + pub(crate) fn covering_node_for_range(&self, range: TextRange) -> SyntaxElement { + find_covering_element(self.source_file.syntax(), range) + } +} + +pub(crate) struct Assists { + resolve: bool, + file: FileId, + buf: Vec<(Assist, Option)>, +} + +impl Assists { + pub(crate) fn new_resolved(ctx: &AssistContext) -> Assists { + Assists { resolve: true, file: ctx.frange.file_id, buf: Vec::new() } + } + pub(crate) fn new_unresolved(ctx: &AssistContext) -> Assists { + Assists { resolve: false, file: ctx.frange.file_id, buf: Vec::new() } + } + + pub(crate) fn finish_unresolved(self) -> Vec { + assert!(!self.resolve); + self.finish() + .into_iter() + .map(|(label, edit)| { + assert!(edit.is_none()); + label + }) + .collect() + } + + pub(crate) fn finish_resolved(self) -> Vec { + assert!(self.resolve); + self.finish() + .into_iter() + .map(|(label, edit)| ResolvedAssist { assist: label, source_change: edit.unwrap() }) + .collect() + } + + pub(crate) fn add( + &mut self, + id: AssistId, + label: impl Into, + target: TextRange, + f: impl FnOnce(&mut AssistBuilder), + ) -> Option<()> { + let label = Assist::new(id, label.into(), None, target); + self.add_impl(label, f) + } + pub(crate) fn add_group( + &mut self, + group: &GroupLabel, + id: AssistId, + label: impl Into, + target: TextRange, + f: impl FnOnce(&mut AssistBuilder), + ) -> Option<()> { + let label = Assist::new(id, label.into(), Some(group.clone()), target); + self.add_impl(label, f) + } + fn add_impl(&mut self, label: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> { + let change_label = label.label.clone(); + let source_change = if self.resolve { + let mut builder = AssistBuilder::new(self.file); + f(&mut builder); + Some(builder.finish(change_label)) + } else { + None + }; + + self.buf.push((label, source_change)); + Some(()) + } + + fn finish(mut self) -> Vec<(Assist, Option)> { + self.buf.sort_by_key(|(label, _edit)| label.target.len()); + self.buf + } +} + +pub(crate) struct AssistBuilder { + edit: TextEditBuilder, + cursor_position: Option, + file: FileId, +} + +impl AssistBuilder { + pub(crate) fn new(file: FileId) -> AssistBuilder { + AssistBuilder { edit: TextEditBuilder::default(), cursor_position: None, file } + } + + /// Remove specified `range` of text. + pub(crate) fn delete(&mut self, range: TextRange) { + self.edit.delete(range) + } + /// Append specified `text` at the given `offset` + pub(crate) fn insert(&mut self, offset: TextSize, text: impl Into) { + self.edit.insert(offset, text.into()) + } + /// Replaces specified `range` of text with a given string. + pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into) { + self.edit.replace(range, replace_with.into()) + } + pub(crate) fn replace_ast(&mut self, old: N, new: N) { + algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit) + } + /// Replaces specified `node` of text with a given string, reindenting the + /// string to maintain `node`'s existing indent. + // FIXME: remove in favor of ra_syntax::edit::IndentLevel::increase_indent + pub(crate) fn replace_node_and_indent( + &mut self, + node: &SyntaxNode, + replace_with: impl Into, + ) { + let mut replace_with = replace_with.into(); + if let Some(indent) = leading_indent(node) { + replace_with = reindent(&replace_with, &indent) + } + self.replace(node.text_range(), replace_with) + } + pub(crate) fn rewrite(&mut self, rewriter: SyntaxRewriter) { + let node = rewriter.rewrite_root().unwrap(); + let new = rewriter.rewrite(&node); + algo::diff(&node, &new).into_text_edit(&mut self.edit) + } + + /// Specify desired position of the cursor after the assist is applied. + pub(crate) fn set_cursor(&mut self, offset: TextSize) { + self.cursor_position = Some(offset) + } + // FIXME: better API + pub(crate) fn set_file(&mut self, assist_file: FileId) { + self.file = assist_file; + } + + // FIXME: kill this API + /// Get access to the raw `TextEditBuilder`. + pub(crate) fn text_edit_builder(&mut self) -> &mut TextEditBuilder { + &mut self.edit + } + + fn finish(self, change_label: String) -> SourceChange { + let edit = self.edit.finish(); + if edit.is_empty() && self.cursor_position.is_none() { + panic!("Only call `add_assist` if the assist can be applied") + } + SingleFileChange { label: change_label, edit, cursor_position: self.cursor_position } + .into_source_change(self.file) + } +} diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_ctx.rs deleted file mode 100644 index cbf1963b72..0000000000 --- a/crates/ra_assists/src/assist_ctx.rs +++ /dev/null @@ -1,265 +0,0 @@ -//! This module defines `AssistCtx` -- the API surface that is exposed to assists. -use hir::Semantics; -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, SyntaxRewriter}, - AstNode, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize, - TokenAtOffset, -}; -use ra_text_edit::TextEditBuilder; - -use crate::{AssistAction, AssistFile, AssistId, AssistLabel, GroupLabel, ResolvedAssist}; - -#[derive(Clone, Debug)] -pub(crate) struct Assist(pub(crate) Vec); - -#[derive(Clone, Debug)] -pub(crate) struct AssistInfo { - pub(crate) label: AssistLabel, - pub(crate) group_label: Option, - pub(crate) action: Option, -} - -impl AssistInfo { - fn new(label: AssistLabel) -> AssistInfo { - AssistInfo { label, group_label: None, action: None } - } - - fn resolved(self, action: AssistAction) -> AssistInfo { - AssistInfo { action: Some(action), ..self } - } - - fn with_group(self, group_label: GroupLabel) -> AssistInfo { - AssistInfo { group_label: Some(group_label), ..self } - } - - pub(crate) fn into_resolved(self) -> Option { - let label = self.label; - self.action.map(|action| ResolvedAssist { label, action }) - } -} - -/// `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 -/// 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 -/// particular assist and it gets applied. -/// -/// There are two peculiarities here: -/// -/// * first, we ideally avoid computing more things then necessary to answer -/// "is assist applicable" in the first phase. -/// * second, when we are applying assist, we don't have a guarantee that there -/// weren't any changes between the point when user asked for assists and when -/// they applied a particular assist. So, when applying assist, we need to do -/// all the checks from scratch. -/// -/// To avoid repeating the same code twice for both "check" and "apply" -/// functions, we use an approach reminiscent of that of Django's function based -/// views dealing with forms. Each assist receives a runtime parameter, -/// `should_compute_edit`. It first check if an edit is applicable (potentially -/// computing info required to compute the actual edit). If it is applicable, -/// and `should_compute_edit` is `true`, it then computes the actual edit. -/// -/// So, to implement the original assists workflow, we can first apply each edit -/// with `should_compute_edit = false`, and then applying the selected edit -/// again, with `should_compute_edit = true` this time. -/// -/// Note, however, that we don't actually use such two-phase logic at the -/// moment, because the LSP API is pretty awkward in this place, and it's much -/// easier to just compute the edit eagerly :-) -#[derive(Clone)] -pub(crate) struct AssistCtx<'a> { - pub(crate) sema: &'a Semantics<'a, RootDatabase>, - pub(crate) db: &'a RootDatabase, - pub(crate) frange: FileRange, - source_file: SourceFile, - should_compute_edit: bool, -} - -impl<'a> AssistCtx<'a> { - pub fn new( - sema: &'a Semantics<'a, RootDatabase>, - frange: FileRange, - should_compute_edit: bool, - ) -> AssistCtx<'a> { - let source_file = sema.parse(frange.file_id); - AssistCtx { sema, db: sema.db, frange, source_file, should_compute_edit } - } - - pub(crate) fn add_assist( - self, - id: AssistId, - label: impl Into, - target: TextRange, - f: impl FnOnce(&mut ActionBuilder), - ) -> Option { - let label = AssistLabel::new(id, label.into(), None, target); - - let mut info = AssistInfo::new(label); - if self.should_compute_edit { - let action = { - let mut edit = ActionBuilder::new(&self); - f(&mut edit); - edit.build() - }; - info = info.resolved(action) - }; - - Some(Assist(vec![info])) - } - - pub(crate) fn add_assist_group(self, group_name: impl Into) -> AssistGroup<'a> { - let group = GroupLabel(group_name.into()); - AssistGroup { ctx: self, group, assists: Vec::new() } - } - - pub(crate) fn token_at_offset(&self) -> TokenAtOffset { - self.source_file.syntax().token_at_offset(self.frange.range.start()) - } - - pub(crate) fn find_token_at_offset(&self, kind: SyntaxKind) -> Option { - self.token_at_offset().find(|it| it.kind() == kind) - } - - pub(crate) fn find_node_at_offset(&self) -> Option { - find_node_at_offset(self.source_file.syntax(), self.frange.range.start()) - } - - pub(crate) fn find_node_at_offset_with_descend(&self) -> Option { - self.sema - .find_node_at_offset_with_descend(self.source_file.syntax(), self.frange.range.start()) - } - - pub(crate) fn covering_element(&self) -> SyntaxElement { - find_covering_element(self.source_file.syntax(), self.frange.range) - } - pub(crate) fn covering_node_for_range(&self, range: TextRange) -> SyntaxElement { - find_covering_element(self.source_file.syntax(), range) - } -} - -pub(crate) struct AssistGroup<'a> { - ctx: AssistCtx<'a>, - group: GroupLabel, - assists: Vec, -} - -impl<'a> AssistGroup<'a> { - pub(crate) fn add_assist( - &mut self, - id: AssistId, - label: impl Into, - target: TextRange, - f: impl FnOnce(&mut ActionBuilder), - ) { - let label = AssistLabel::new(id, label.into(), Some(self.group.clone()), target); - - 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); - f(&mut edit); - edit.build() - }; - info = info.resolved(action) - }; - - self.assists.push(info) - } - - pub(crate) fn finish(self) -> Option { - if self.assists.is_empty() { - None - } else { - Some(Assist(self.assists)) - } - } -} - -pub(crate) struct ActionBuilder<'a, 'b> { - edit: TextEditBuilder, - cursor_position: Option, - file: AssistFile, - ctx: &'a AssistCtx<'b>, -} - -impl<'a, 'b> ActionBuilder<'a, 'b> { - fn new(ctx: &'a AssistCtx<'b>) -> Self { - Self { - edit: TextEditBuilder::default(), - cursor_position: None, - file: AssistFile::default(), - ctx, - } - } - - pub(crate) fn ctx(&self) -> &AssistCtx<'b> { - &self.ctx - } - - /// Replaces specified `range` of text with a given string. - pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into) { - self.edit.replace(range, replace_with.into()) - } - - /// Replaces specified `node` of text with a given string, reindenting the - /// string to maintain `node`'s existing indent. - // FIXME: remove in favor of ra_syntax::edit::IndentLevel::increase_indent - pub(crate) fn replace_node_and_indent( - &mut self, - node: &SyntaxNode, - replace_with: impl Into, - ) { - let mut replace_with = replace_with.into(); - if let Some(indent) = leading_indent(node) { - replace_with = reindent(&replace_with, &indent) - } - self.replace(node.text_range(), replace_with) - } - - /// Remove specified `range` of text. - #[allow(unused)] - pub(crate) fn delete(&mut self, range: TextRange) { - self.edit.delete(range) - } - - /// Append specified `text` at the given `offset` - pub(crate) fn insert(&mut self, offset: TextSize, text: impl Into) { - self.edit.insert(offset, text.into()) - } - - /// Specify desired position of the cursor after the assist is applied. - pub(crate) fn set_cursor(&mut self, offset: TextSize) { - self.cursor_position = Some(offset) - } - - /// Get access to the raw `TextEditBuilder`. - pub(crate) fn text_edit_builder(&mut self) -> &mut TextEditBuilder { - &mut self.edit - } - - pub(crate) fn replace_ast(&mut self, old: N, new: N) { - algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit) - } - pub(crate) fn rewrite(&mut self, rewriter: SyntaxRewriter) { - let node = rewriter.rewrite_root().unwrap(); - let new = rewriter.rewrite(&node); - algo::diff(&node, &new).into_text_edit(&mut self.edit) - } - - pub(crate) fn set_file(&mut self, assist_file: AssistFile) { - self.file = assist_file - } - - fn build(self) -> AssistAction { - let edit = self.edit.finish(); - if edit.is_empty() && self.cursor_position.is_none() { - panic!("Only call `add_assist` if the assist can be applied") - } - AssistAction { edit, cursor_position: self.cursor_position, file: self.file } - } -} diff --git a/crates/ra_assists/src/handlers/add_custom_impl.rs b/crates/ra_assists/src/handlers/add_custom_impl.rs index 869d4dc045..795a225a4d 100644 --- a/crates/ra_assists/src/handlers/add_custom_impl.rs +++ b/crates/ra_assists/src/handlers/add_custom_impl.rs @@ -6,7 +6,10 @@ use ra_syntax::{ }; use stdx::SepBy; -use crate::{Assist, AssistCtx, AssistId}; +use crate::{ + assist_context::{AssistContext, Assists}, + AssistId, +}; // Assist: add_custom_impl // @@ -25,7 +28,7 @@ use crate::{Assist, AssistCtx, AssistId}; // // } // ``` -pub(crate) fn add_custom_impl(ctx: AssistCtx) -> Option { +pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let input = ctx.find_node_at_offset::()?; let attr = input.syntax().parent().and_then(ast::Attr::cast)?; @@ -49,7 +52,7 @@ pub(crate) fn add_custom_impl(ctx: AssistCtx) -> Option { format!("Add custom impl '{}' for '{}'", trait_token.text().as_str(), annotated_name); let target = attr.syntax().text_range(); - ctx.add_assist(AssistId("add_custom_impl"), label, target, |edit| { + acc.add(AssistId("add_custom_impl"), label, target, |edit| { let new_attr_input = input .syntax() .descendants_with_tokens() diff --git a/crates/ra_assists/src/handlers/add_derive.rs b/crates/ra_assists/src/handlers/add_derive.rs index 2a6bb1caed..fb08c19e93 100644 --- a/crates/ra_assists/src/handlers/add_derive.rs +++ b/crates/ra_assists/src/handlers/add_derive.rs @@ -4,7 +4,7 @@ use ra_syntax::{ TextSize, }; -use crate::{Assist, AssistCtx, AssistId}; +use crate::{AssistContext, AssistId, Assists}; // Assist: add_derive // @@ -24,11 +24,11 @@ use crate::{Assist, AssistCtx, AssistId}; // y: u32, // } // ``` -pub(crate) fn add_derive(ctx: AssistCtx) -> Option { +pub(crate) fn add_derive(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let nominal = ctx.find_node_at_offset::()?; let node_start = derive_insertion_offset(&nominal)?; let target = nominal.syntax().text_range(); - ctx.add_assist(AssistId("add_derive"), "Add `#[derive]`", target, |edit| { + acc.add(AssistId("add_derive"), "Add `#[derive]`", target, |edit| { let derive_attr = nominal .attrs() .filter_map(|x| x.as_simple_call()) @@ -57,9 +57,10 @@ fn derive_insertion_offset(nominal: &ast::NominalDef) -> Option { #[cfg(test)] mod tests { - use super::*; use crate::tests::{check_assist, check_assist_target}; + use super::*; + #[test] fn add_derive_new() { check_assist( diff --git a/crates/ra_assists/src/handlers/add_explicit_type.rs b/crates/ra_assists/src/handlers/add_explicit_type.rs index a59ec16b2d..55409e5013 100644 --- a/crates/ra_assists/src/handlers/add_explicit_type.rs +++ b/crates/ra_assists/src/handlers/add_explicit_type.rs @@ -4,7 +4,7 @@ use ra_syntax::{ TextRange, }; -use crate::{Assist, AssistCtx, AssistId}; +use crate::{AssistContext, AssistId, Assists}; // Assist: add_explicit_type // @@ -21,7 +21,7 @@ use crate::{Assist, AssistCtx, AssistId}; // let x: i32 = 92; // } // ``` -pub(crate) fn add_explicit_type(ctx: AssistCtx) -> Option { +pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let stmt = ctx.find_node_at_offset::()?; let expr = stmt.initializer()?; let pat = stmt.pat()?; @@ -59,7 +59,7 @@ pub(crate) fn add_explicit_type(ctx: AssistCtx) -> Option { let db = ctx.db; let new_type_string = ty.display_truncated(db, None).to_string(); - ctx.add_assist( + acc.add( AssistId("add_explicit_type"), format!("Insert explicit type '{}'", new_type_string), pat_range, diff --git a/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs b/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs index 81deb3dfa3..275184e246 100644 --- a/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs +++ b/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs @@ -4,10 +4,10 @@ use ra_syntax::{ TextSize, }; use stdx::format_to; - -use crate::{utils::FamousDefs, Assist, AssistCtx, AssistId}; use test_utils::tested_by; +use crate::{utils::FamousDefs, AssistContext, AssistId, Assists}; + // Assist add_from_impl_for_enum // // Adds a From impl for an enum variant with one tuple field @@ -25,7 +25,7 @@ use test_utils::tested_by; // } // } // ``` -pub(crate) fn add_from_impl_for_enum(ctx: AssistCtx) -> Option { +pub(crate) fn add_from_impl_for_enum(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let variant = ctx.find_node_at_offset::()?; let variant_name = variant.name()?; let enum_name = variant.parent_enum().name()?; @@ -42,13 +42,13 @@ pub(crate) fn add_from_impl_for_enum(ctx: AssistCtx) -> Option { _ => return None, }; - if existing_from_impl(ctx.sema, &variant).is_some() { + if existing_from_impl(&ctx.sema, &variant).is_some() { tested_by!(test_add_from_impl_already_exists); return None; } let target = variant.syntax().text_range(); - ctx.add_assist( + acc.add( AssistId("add_from_impl_for_enum"), "Add From impl for this enum variant", target, diff --git a/crates/ra_assists/src/handlers/add_function.rs b/crates/ra_assists/src/handlers/add_function.rs index 1d9d4e638f..6b5616aa9c 100644 --- a/crates/ra_assists/src/handlers/add_function.rs +++ b/crates/ra_assists/src/handlers/add_function.rs @@ -1,13 +1,13 @@ +use hir::HirDisplay; +use ra_db::FileId; use ra_syntax::{ - ast::{self, AstNode}, + ast::{self, edit::IndentLevel, ArgListOwner, AstNode, ModuleItemOwner}, SyntaxKind, SyntaxNode, TextSize, }; - -use crate::{Assist, AssistCtx, AssistFile, AssistId}; -use ast::{edit::IndentLevel, ArgListOwner, ModuleItemOwner}; -use hir::HirDisplay; use rustc_hash::{FxHashMap, FxHashSet}; +use crate::{AssistContext, AssistId, Assists}; + // Assist: add_function // // Adds a stub function with a signature matching the function under the cursor. @@ -33,7 +33,7 @@ use rustc_hash::{FxHashMap, FxHashSet}; // } // // ``` -pub(crate) fn add_function(ctx: AssistCtx) -> Option { +pub(crate) fn add_function(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let path_expr: ast::PathExpr = ctx.find_node_at_offset()?; let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?; let path = path_expr.path()?; @@ -58,7 +58,7 @@ pub(crate) fn add_function(ctx: AssistCtx) -> Option { let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?; let target = call.syntax().text_range(); - ctx.add_assist(AssistId("add_function"), "Add function", target, |edit| { + acc.add(AssistId("add_function"), "Add function", target, |edit| { let function_template = function_builder.render(); edit.set_file(function_template.file); edit.set_cursor(function_template.cursor_offset); @@ -70,7 +70,7 @@ struct FunctionTemplate { insert_offset: TextSize, cursor_offset: TextSize, fn_def: ast::SourceFile, - file: AssistFile, + file: FileId, } struct FunctionBuilder { @@ -78,7 +78,7 @@ struct FunctionBuilder { fn_name: ast::Name, type_params: Option, params: ast::ParamList, - file: AssistFile, + file: FileId, needs_pub: bool, } @@ -86,13 +86,13 @@ impl FunctionBuilder { /// Prepares a generated function that matches `call` in `generate_in` /// (or as close to `call` as possible, if `generate_in` is `None`) fn from_call( - ctx: &AssistCtx, + ctx: &AssistContext, call: &ast::CallExpr, path: &ast::Path, target_module: Option>, ) -> Option { let needs_pub = target_module.is_some(); - let mut file = AssistFile::default(); + let mut file = ctx.frange.file_id; let target = if let Some(target_module) = target_module { let (in_file, target) = next_space_for_fn_in_module(ctx.sema.db, target_module)?; file = in_file; @@ -151,7 +151,7 @@ fn fn_name(call: &ast::Path) -> Option { /// Computes the type variables and arguments required for the generated function fn fn_args( - ctx: &AssistCtx, + ctx: &AssistContext, call: &ast::CallExpr, ) -> Option<(Option, ast::ParamList)> { let mut arg_names = Vec::new(); @@ -218,7 +218,7 @@ fn fn_arg_name(fn_arg: &ast::Expr) -> Option { } } -fn fn_arg_type(ctx: &AssistCtx, fn_arg: &ast::Expr) -> Option { +fn fn_arg_type(ctx: &AssistContext, fn_arg: &ast::Expr) -> Option { let ty = ctx.sema.type_of_expr(fn_arg)?; if ty.is_unknown() { return None; @@ -253,9 +253,8 @@ fn next_space_for_fn_after_call_site(expr: &ast::CallExpr) -> Option, -) -> Option<(AssistFile, GeneratedFunctionTarget)> { +) -> Option<(FileId, GeneratedFunctionTarget)> { let file = module.file_id.original_file(db); - let assist_file = AssistFile::TargetFile(file); let assist_item = match module.value { hir::ModuleSource::SourceFile(it) => { if let Some(last_item) = it.items().last() { @@ -272,7 +271,7 @@ fn next_space_for_fn_in_module( } } }; - Some((assist_file, assist_item)) + Some((file, assist_item)) } #[cfg(test)] diff --git a/crates/ra_assists/src/handlers/add_impl.rs b/crates/ra_assists/src/handlers/add_impl.rs index 557344ebb8..df114a0d84 100644 --- a/crates/ra_assists/src/handlers/add_impl.rs +++ b/crates/ra_assists/src/handlers/add_impl.rs @@ -4,7 +4,7 @@ use ra_syntax::{ }; use stdx::{format_to, SepBy}; -use crate::{Assist, AssistCtx, AssistId}; +use crate::{AssistContext, AssistId, Assists}; // Assist: add_impl // @@ -25,43 +25,36 @@ use crate::{Assist, AssistCtx, AssistId}; // // } // ``` -pub(crate) fn add_impl(ctx: AssistCtx) -> Option { +pub(crate) fn add_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let nominal = ctx.find_node_at_offset::()?; let name = nominal.name()?; let target = nominal.syntax().text_range(); - ctx.add_assist( - AssistId("add_impl"), - format!("Implement {}", name.text().as_str()), - target, - |edit| { - let type_params = nominal.type_param_list(); - let start_offset = nominal.syntax().text_range().end(); - let mut buf = String::new(); - buf.push_str("\n\nimpl"); - if let Some(type_params) = &type_params { - format_to!(buf, "{}", type_params.syntax()); - } - buf.push_str(" "); - buf.push_str(name.text().as_str()); - if let Some(type_params) = type_params { - let lifetime_params = type_params - .lifetime_params() - .filter_map(|it| it.lifetime_token()) - .map(|it| it.text().clone()); - let type_params = type_params - .type_params() - .filter_map(|it| it.name()) - .map(|it| it.text().clone()); + acc.add(AssistId("add_impl"), format!("Implement {}", name.text().as_str()), target, |edit| { + let type_params = nominal.type_param_list(); + let start_offset = nominal.syntax().text_range().end(); + let mut buf = String::new(); + buf.push_str("\n\nimpl"); + if let Some(type_params) = &type_params { + format_to!(buf, "{}", type_params.syntax()); + } + buf.push_str(" "); + buf.push_str(name.text().as_str()); + if let Some(type_params) = type_params { + let lifetime_params = type_params + .lifetime_params() + .filter_map(|it| it.lifetime_token()) + .map(|it| it.text().clone()); + let type_params = + type_params.type_params().filter_map(|it| it.name()).map(|it| it.text().clone()); - let generic_params = lifetime_params.chain(type_params).sep_by(", "); - format_to!(buf, "<{}>", generic_params) - } - buf.push_str(" {\n"); - edit.set_cursor(start_offset + TextSize::of(&buf)); - buf.push_str("\n}"); - edit.insert(start_offset, buf); - }, - ) + let generic_params = lifetime_params.chain(type_params).sep_by(", "); + format_to!(buf, "<{}>", generic_params) + } + buf.push_str(" {\n"); + edit.set_cursor(start_offset + TextSize::of(&buf)); + buf.push_str("\n}"); + edit.insert(start_offset, buf); + }) } #[cfg(test)] diff --git a/crates/ra_assists/src/handlers/add_missing_impl_members.rs b/crates/ra_assists/src/handlers/add_missing_impl_members.rs index 7df7865909..3482a75bfc 100644 --- a/crates/ra_assists/src/handlers/add_missing_impl_members.rs +++ b/crates/ra_assists/src/handlers/add_missing_impl_members.rs @@ -9,9 +9,10 @@ use ra_syntax::{ }; use crate::{ + assist_context::{AssistContext, Assists}, ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, utils::{get_missing_assoc_items, resolve_target_trait}, - Assist, AssistCtx, AssistId, + AssistId, }; #[derive(PartialEq)] @@ -50,8 +51,9 @@ enum AddMissingImplMembersMode { // // } // ``` -pub(crate) fn add_missing_impl_members(ctx: AssistCtx) -> Option { +pub(crate) fn add_missing_impl_members(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { add_missing_impl_members_inner( + acc, ctx, AddMissingImplMembersMode::NoDefaultMethods, "add_impl_missing_members", @@ -91,8 +93,9 @@ pub(crate) fn add_missing_impl_members(ctx: AssistCtx) -> Option { // // } // ``` -pub(crate) fn add_missing_default_members(ctx: AssistCtx) -> Option { +pub(crate) fn add_missing_default_members(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { add_missing_impl_members_inner( + acc, ctx, AddMissingImplMembersMode::DefaultMethodsOnly, "add_impl_default_members", @@ -101,11 +104,12 @@ pub(crate) fn add_missing_default_members(ctx: AssistCtx) -> Option { } fn add_missing_impl_members_inner( - ctx: AssistCtx, + acc: &mut Assists, + ctx: &AssistContext, mode: AddMissingImplMembersMode, assist_id: &'static str, label: &'static str, -) -> Option { +) -> Option<()> { let _p = ra_prof::profile("add_missing_impl_members_inner"); let impl_def = ctx.find_node_at_offset::()?; let impl_item_list = impl_def.item_list()?; @@ -142,12 +146,11 @@ fn add_missing_impl_members_inner( return None; } - let sema = ctx.sema; let target = impl_def.syntax().text_range(); - ctx.add_assist(AssistId(assist_id), label, target, |edit| { + acc.add(AssistId(assist_id), label, target, |edit| { 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 source_scope = ctx.sema.scope_for_def(trait_); + let target_scope = ctx.sema.scope(impl_item_list.syntax()); let ast_transform = QualifyPaths::new(&target_scope, &source_scope) .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_def)); let items = missing_items @@ -170,13 +173,12 @@ fn add_missing_impl_members_inner( } fn add_body(fn_def: ast::FnDef) -> ast::FnDef { - if fn_def.body().is_none() { - let body = make::block_expr(None, Some(make::expr_todo())); - let body = IndentLevel(1).increase_indent(body); - fn_def.with_body(body) - } else { - fn_def + if fn_def.body().is_some() { + return fn_def; } + let body = make::block_expr(None, Some(make::expr_todo())); + let body = IndentLevel(1).increase_indent(body); + fn_def.with_body(body) } #[cfg(test)] diff --git a/crates/ra_assists/src/handlers/add_new.rs b/crates/ra_assists/src/handlers/add_new.rs index 1c3f8435ab..fe7451dcfd 100644 --- a/crates/ra_assists/src/handlers/add_new.rs +++ b/crates/ra_assists/src/handlers/add_new.rs @@ -7,7 +7,7 @@ use ra_syntax::{ }; use stdx::{format_to, SepBy}; -use crate::{Assist, AssistCtx, AssistId}; +use crate::{AssistContext, AssistId, Assists}; // Assist: add_new // @@ -29,7 +29,7 @@ use crate::{Assist, AssistCtx, AssistId}; // } // // ``` -pub(crate) fn add_new(ctx: AssistCtx) -> Option { +pub(crate) fn add_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let strukt = ctx.find_node_at_offset::()?; // We want to only apply this to non-union structs with named fields @@ -42,7 +42,7 @@ pub(crate) fn add_new(ctx: AssistCtx) -> Option { let impl_def = find_struct_impl(&ctx, &strukt)?; let target = strukt.syntax().text_range(); - ctx.add_assist(AssistId("add_new"), "Add default constructor", target, |edit| { + acc.add(AssistId("add_new"), "Add default constructor", target, |edit| { let mut buf = String::with_capacity(512); if impl_def.is_some() { @@ -123,7 +123,7 @@ fn generate_impl_text(strukt: &ast::StructDef, code: &str) -> String { // // FIXME: change the new fn checking to a more semantic approach when that's more // viable (e.g. we process proc macros, etc) -fn find_struct_impl(ctx: &AssistCtx, strukt: &ast::StructDef) -> Option> { +fn find_struct_impl(ctx: &AssistContext, strukt: &ast::StructDef) -> Option> { let db = ctx.db; let module = strukt.syntax().ancestors().find(|node| { ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind()) diff --git a/crates/ra_assists/src/handlers/apply_demorgan.rs b/crates/ra_assists/src/handlers/apply_demorgan.rs index a5b26e5b93..0feba5e11f 100644 --- a/crates/ra_assists/src/handlers/apply_demorgan.rs +++ b/crates/ra_assists/src/handlers/apply_demorgan.rs @@ -1,6 +1,6 @@ use ra_syntax::ast::{self, AstNode}; -use crate::{utils::invert_boolean_expression, Assist, AssistCtx, AssistId}; +use crate::{utils::invert_boolean_expression, AssistContext, AssistId, Assists}; // Assist: apply_demorgan // @@ -21,7 +21,7 @@ use crate::{utils::invert_boolean_expression, Assist, AssistCtx, AssistId}; // if !(x == 4 && y) {} // } // ``` -pub(crate) fn apply_demorgan(ctx: AssistCtx) -> Option { +pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let expr = ctx.find_node_at_offset::()?; let op = expr.op_kind()?; let op_range = expr.op_token()?.text_range(); @@ -39,7 +39,7 @@ pub(crate) fn apply_demorgan(ctx: AssistCtx) -> Option { let rhs_range = rhs.syntax().text_range(); let not_rhs = invert_boolean_expression(rhs); - ctx.add_assist(AssistId("apply_demorgan"), "Apply De Morgan's law", op_range, |edit| { + acc.add(AssistId("apply_demorgan"), "Apply De Morgan's law", op_range, |edit| { edit.replace(op_range, opposite_op); edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text())); edit.replace(rhs_range, format!("{})", not_rhs.syntax().text())); diff --git a/crates/ra_assists/src/handlers/auto_import.rs b/crates/ra_assists/src/handlers/auto_import.rs index 2224b9714b..78d23150d3 100644 --- a/crates/ra_assists/src/handlers/auto_import.rs +++ b/crates/ra_assists/src/handlers/auto_import.rs @@ -1,5 +1,6 @@ use std::collections::BTreeSet; +use either::Either; use hir::{ AsAssocItem, AssocItemContainer, ModPath, Module, ModuleDef, PathResolution, Semantics, Trait, Type, @@ -12,12 +13,7 @@ use ra_syntax::{ }; use rustc_hash::FxHashSet; -use crate::{ - assist_ctx::{Assist, AssistCtx}, - utils::insert_use_statement, - AssistId, -}; -use either::Either; +use crate::{utils::insert_use_statement, AssistContext, AssistId, Assists, GroupLabel}; // Assist: auto_import // @@ -38,7 +34,7 @@ use either::Either; // } // # pub mod std { pub mod collections { pub struct HashMap { } } } // ``` -pub(crate) fn auto_import(ctx: AssistCtx) -> Option { +pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let auto_import_assets = AutoImportAssets::new(&ctx)?; let proposed_imports = auto_import_assets.search_for_imports(ctx.db); if proposed_imports.is_empty() { @@ -46,13 +42,19 @@ pub(crate) fn auto_import(ctx: AssistCtx) -> Option { } let range = ctx.sema.original_range(&auto_import_assets.syntax_under_caret).range; - let mut group = ctx.add_assist_group(auto_import_assets.get_import_group_message()); + let group = auto_import_assets.get_import_group_message(); for import in proposed_imports { - group.add_assist(AssistId("auto_import"), format!("Import `{}`", &import), range, |edit| { - insert_use_statement(&auto_import_assets.syntax_under_caret, &import, edit); - }); + acc.add_group( + &group, + AssistId("auto_import"), + format!("Import `{}`", &import), + range, + |builder| { + insert_use_statement(&auto_import_assets.syntax_under_caret, &import, ctx, builder); + }, + ); } - group.finish() + Some(()) } #[derive(Debug)] @@ -63,7 +65,7 @@ struct AutoImportAssets { } impl AutoImportAssets { - fn new(ctx: &AssistCtx) -> Option { + fn new(ctx: &AssistContext) -> Option { if let Some(path_under_caret) = ctx.find_node_at_offset_with_descend::() { Self::for_regular_path(path_under_caret, &ctx) } else { @@ -71,7 +73,7 @@ impl AutoImportAssets { } } - fn for_method_call(method_call: ast::MethodCallExpr, ctx: &AssistCtx) -> Option { + fn for_method_call(method_call: ast::MethodCallExpr, ctx: &AssistContext) -> Option { let syntax_under_caret = method_call.syntax().to_owned(); let module_with_name_to_import = ctx.sema.scope(&syntax_under_caret).module()?; Some(Self { @@ -81,7 +83,7 @@ impl AutoImportAssets { }) } - fn for_regular_path(path_under_caret: ast::Path, ctx: &AssistCtx) -> Option { + fn for_regular_path(path_under_caret: ast::Path, ctx: &AssistContext) -> Option { let syntax_under_caret = path_under_caret.syntax().to_owned(); if syntax_under_caret.ancestors().find_map(ast::UseItem::cast).is_some() { return None; @@ -104,8 +106,8 @@ impl AutoImportAssets { } } - fn get_import_group_message(&self) -> String { - match &self.import_candidate { + fn get_import_group_message(&self) -> GroupLabel { + let name = match &self.import_candidate { ImportCandidate::UnqualifiedName(name) => format!("Import {}", name), ImportCandidate::QualifierStart(qualifier_start) => { format!("Import {}", qualifier_start) @@ -116,7 +118,8 @@ impl AutoImportAssets { ImportCandidate::TraitMethod(_, trait_method_name) => { format!("Import a trait for method {}", trait_method_name) } - } + }; + GroupLabel(name) } fn search_for_imports(&self, db: &RootDatabase) -> BTreeSet { @@ -383,7 +386,7 @@ mod tests { } ", r" - use PubMod1::PubStruct; + use PubMod3::PubStruct; PubSt<|>ruct diff --git a/crates/ra_assists/src/handlers/change_return_type_to_result.rs b/crates/ra_assists/src/handlers/change_return_type_to_result.rs new file mode 100644 index 0000000000..5c907097e5 --- /dev/null +++ b/crates/ra_assists/src/handlers/change_return_type_to_result.rs @@ -0,0 +1,971 @@ +use ra_syntax::{ + ast::{self, BlockExpr, Expr, LoopBodyOwner}, + AstNode, + SyntaxKind::{COMMENT, WHITESPACE}, + SyntaxNode, TextSize, +}; + +use crate::{AssistContext, AssistId, Assists}; + +// Assist: change_return_type_to_result +// +// Change the function's return type to Result. +// +// ``` +// fn foo() -> i32<|> { 42i32 } +// ``` +// -> +// ``` +// fn foo() -> Result { Ok(42i32) } +// ``` +pub(crate) fn change_return_type_to_result(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { + let fn_def = ctx.find_node_at_offset::(); + let fn_def = &mut fn_def?; + let ret_type = &fn_def.ret_type()?.type_ref()?; + if ret_type.syntax().text().to_string().starts_with("Result<") { + return None; + } + + let block_expr = &fn_def.body()?; + let cursor_in_ret_type = + fn_def.ret_type()?.syntax().text_range().contains_range(ctx.frange.range); + if !cursor_in_ret_type { + return None; + } + + acc.add( + AssistId("change_return_type_to_result"), + "Change return type to Result", + ret_type.syntax().text_range(), + |edit| { + let mut tail_return_expr_collector = TailReturnCollector::new(); + tail_return_expr_collector.collect_jump_exprs(block_expr, false); + tail_return_expr_collector.collect_tail_exprs(block_expr); + + for ret_expr_arg in tail_return_expr_collector.exprs_to_wrap { + edit.replace_node_and_indent(&ret_expr_arg, format!("Ok({})", ret_expr_arg)); + } + edit.replace_node_and_indent(ret_type.syntax(), format!("Result<{}, >", ret_type)); + + if let Some(node_start) = result_insertion_offset(&ret_type) { + edit.set_cursor(node_start + TextSize::of(&format!("Result<{}, ", ret_type))); + } + }, + ) +} + +struct TailReturnCollector { + exprs_to_wrap: Vec, +} + +impl TailReturnCollector { + fn new() -> Self { + Self { exprs_to_wrap: vec![] } + } + /// Collect all`return` expression + fn collect_jump_exprs(&mut self, block_expr: &BlockExpr, collect_break: bool) { + let statements = block_expr.statements(); + for stmt in statements { + let expr = match &stmt { + ast::Stmt::ExprStmt(stmt) => stmt.expr(), + ast::Stmt::LetStmt(stmt) => stmt.initializer(), + }; + if let Some(expr) = &expr { + self.handle_exprs(expr, collect_break); + } + } + + // Browse tail expressions for each block + if let Some(expr) = block_expr.expr() { + if let Some(last_exprs) = get_tail_expr_from_block(&expr) { + for last_expr in last_exprs { + let last_expr = match last_expr { + NodeType::Node(expr) | NodeType::Leaf(expr) => expr, + }; + + if let Some(last_expr) = Expr::cast(last_expr.clone()) { + self.handle_exprs(&last_expr, collect_break); + } else if let Some(expr_stmt) = ast::Stmt::cast(last_expr) { + let expr_stmt = match &expr_stmt { + ast::Stmt::ExprStmt(stmt) => stmt.expr(), + ast::Stmt::LetStmt(stmt) => stmt.initializer(), + }; + if let Some(expr) = &expr_stmt { + self.handle_exprs(expr, collect_break); + } + } + } + } + } + } + + fn handle_exprs(&mut self, expr: &Expr, collect_break: bool) { + match expr { + Expr::BlockExpr(block_expr) => { + self.collect_jump_exprs(&block_expr, collect_break); + } + Expr::ReturnExpr(ret_expr) => { + if let Some(ret_expr_arg) = &ret_expr.expr() { + self.exprs_to_wrap.push(ret_expr_arg.syntax().clone()); + } + } + Expr::BreakExpr(break_expr) if collect_break => { + if let Some(break_expr_arg) = &break_expr.expr() { + self.exprs_to_wrap.push(break_expr_arg.syntax().clone()); + } + } + Expr::IfExpr(if_expr) => { + for block in if_expr.blocks() { + self.collect_jump_exprs(&block, collect_break); + } + } + Expr::LoopExpr(loop_expr) => { + if let Some(block_expr) = loop_expr.loop_body() { + self.collect_jump_exprs(&block_expr, collect_break); + } + } + Expr::ForExpr(for_expr) => { + if let Some(block_expr) = for_expr.loop_body() { + self.collect_jump_exprs(&block_expr, collect_break); + } + } + Expr::WhileExpr(while_expr) => { + if let Some(block_expr) = while_expr.loop_body() { + self.collect_jump_exprs(&block_expr, collect_break); + } + } + Expr::MatchExpr(match_expr) => { + if let Some(arm_list) = match_expr.match_arm_list() { + arm_list.arms().filter_map(|match_arm| match_arm.expr()).for_each(|expr| { + self.handle_exprs(&expr, collect_break); + }); + } + } + _ => {} + } + } + + fn collect_tail_exprs(&mut self, block: &BlockExpr) { + if let Some(expr) = block.expr() { + self.handle_exprs(&expr, true); + self.fetch_tail_exprs(&expr); + } + } + + fn fetch_tail_exprs(&mut self, expr: &Expr) { + if let Some(exprs) = get_tail_expr_from_block(expr) { + for node_type in &exprs { + match node_type { + NodeType::Leaf(expr) => { + self.exprs_to_wrap.push(expr.clone()); + } + NodeType::Node(expr) => match &Expr::cast(expr.clone()) { + Some(last_expr) => { + self.fetch_tail_exprs(last_expr); + } + None => { + self.exprs_to_wrap.push(expr.clone()); + } + }, + } + } + } + } +} + +#[derive(Debug)] +enum NodeType { + Leaf(SyntaxNode), + Node(SyntaxNode), +} + +/// Get a tail expression inside a block +fn get_tail_expr_from_block(expr: &Expr) -> Option> { + match expr { + Expr::IfExpr(if_expr) => { + let mut nodes = vec![]; + for block in if_expr.blocks() { + if let Some(block_expr) = block.expr() { + if let Some(tail_exprs) = get_tail_expr_from_block(&block_expr) { + nodes.extend(tail_exprs); + } + } else if let Some(last_expr) = block.syntax().last_child() { + nodes.push(NodeType::Node(last_expr)); + } else { + nodes.push(NodeType::Node(block.syntax().clone())); + } + } + Some(nodes) + } + Expr::LoopExpr(loop_expr) => { + loop_expr.syntax().last_child().map(|lc| vec![NodeType::Node(lc)]) + } + Expr::ForExpr(for_expr) => { + for_expr.syntax().last_child().map(|lc| vec![NodeType::Node(lc)]) + } + Expr::WhileExpr(while_expr) => { + while_expr.syntax().last_child().map(|lc| vec![NodeType::Node(lc)]) + } + Expr::BlockExpr(block_expr) => { + block_expr.expr().map(|lc| vec![NodeType::Node(lc.syntax().clone())]) + } + Expr::MatchExpr(match_expr) => { + let arm_list = match_expr.match_arm_list()?; + let arms: Vec = arm_list + .arms() + .filter_map(|match_arm| match_arm.expr()) + .map(|expr| match expr { + Expr::ReturnExpr(ret_expr) => NodeType::Node(ret_expr.syntax().clone()), + Expr::BreakExpr(break_expr) => NodeType::Node(break_expr.syntax().clone()), + _ => match expr.syntax().last_child() { + Some(last_expr) => NodeType::Node(last_expr), + None => NodeType::Node(expr.syntax().clone()), + }, + }) + .collect(); + + Some(arms) + } + Expr::BreakExpr(expr) => expr.expr().map(|e| vec![NodeType::Leaf(e.syntax().clone())]), + Expr::ReturnExpr(ret_expr) => Some(vec![NodeType::Node(ret_expr.syntax().clone())]), + Expr::CallExpr(call_expr) => Some(vec![NodeType::Leaf(call_expr.syntax().clone())]), + Expr::Literal(lit_expr) => Some(vec![NodeType::Leaf(lit_expr.syntax().clone())]), + Expr::TupleExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), + Expr::ArrayExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), + Expr::ParenExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), + Expr::PathExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), + Expr::Label(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), + Expr::RecordLit(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), + Expr::IndexExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), + Expr::MethodCallExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), + Expr::AwaitExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), + Expr::CastExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), + Expr::RefExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), + Expr::PrefixExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), + Expr::RangeExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), + Expr::BinExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), + Expr::MacroCall(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), + Expr::BoxExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), + _ => None, + } +} + +fn result_insertion_offset(ret_type: &ast::TypeRef) -> Option { + let non_ws_child = ret_type + .syntax() + .children_with_tokens() + .find(|it| it.kind() != COMMENT && it.kind() != WHITESPACE)?; + Some(non_ws_child.text_range().start()) +} + +#[cfg(test)] +mod tests { + + use crate::tests::{check_assist, check_assist_not_applicable}; + + use super::*; + + #[test] + fn change_return_type_to_result_simple() { + check_assist( + change_return_type_to_result, + r#"fn foo() -> i3<|>2 { + let test = "test"; + return 42i32; + }"#, + r#"fn foo() -> Result> { + let test = "test"; + return Ok(42i32); + }"#, + ); + } + + #[test] + fn change_return_type_to_result_simple_return_type() { + check_assist( + change_return_type_to_result, + r#"fn foo() -> i32<|> { + let test = "test"; + return 42i32; + }"#, + r#"fn foo() -> Result> { + let test = "test"; + return Ok(42i32); + }"#, + ); + } + + #[test] + fn change_return_type_to_result_simple_return_type_bad_cursor() { + check_assist_not_applicable( + change_return_type_to_result, + r#"fn foo() -> i32 { + let test = "test";<|> + return 42i32; + }"#, + ); + } + + #[test] + fn change_return_type_to_result_simple_with_cursor() { + check_assist( + change_return_type_to_result, + r#"fn foo() -> <|>i32 { + let test = "test"; + return 42i32; + }"#, + r#"fn foo() -> Result> { + let test = "test"; + return Ok(42i32); + }"#, + ); + } + + #[test] + fn change_return_type_to_result_simple_with_tail() { + check_assist( + change_return_type_to_result, + r#"fn foo() -><|> i32 { + let test = "test"; + 42i32 + }"#, + r#"fn foo() -> Result> { + let test = "test"; + Ok(42i32) + }"#, + ); + } + + #[test] + fn change_return_type_to_result_simple_with_tail_only() { + check_assist( + change_return_type_to_result, + r#"fn foo() -> i32<|> { + 42i32 + }"#, + r#"fn foo() -> Result> { + Ok(42i32) + }"#, + ); + } + #[test] + fn change_return_type_to_result_simple_with_tail_block_like() { + check_assist( + change_return_type_to_result, + r#"fn foo() -> i32<|> { + if true { + 42i32 + } else { + 24i32 + } + }"#, + r#"fn foo() -> Result> { + if true { + Ok(42i32) + } else { + Ok(24i32) + } + }"#, + ); + } + + #[test] + fn change_return_type_to_result_simple_with_nested_if() { + check_assist( + change_return_type_to_result, + r#"fn foo() -> i32<|> { + if true { + if false { + 1 + } else { + 2 + } + } else { + 24i32 + } + }"#, + r#"fn foo() -> Result> { + if true { + if false { + Ok(1) + } else { + Ok(2) + } + } else { + Ok(24i32) + } + }"#, + ); + } + + #[test] + fn change_return_type_to_result_simple_with_await() { + check_assist( + change_return_type_to_result, + r#"async fn foo() -> i<|>32 { + if true { + if false { + 1.await + } else { + 2.await + } + } else { + 24i32.await + } + }"#, + r#"async fn foo() -> Result> { + if true { + if false { + Ok(1.await) + } else { + Ok(2.await) + } + } else { + Ok(24i32.await) + } + }"#, + ); + } + + #[test] + fn change_return_type_to_result_simple_with_array() { + check_assist( + change_return_type_to_result, + r#"fn foo() -> [i32;<|> 3] { + [1, 2, 3] + }"#, + r#"fn foo() -> Result<[i32; 3], <|>> { + Ok([1, 2, 3]) + }"#, + ); + } + + #[test] + fn change_return_type_to_result_simple_with_cast() { + check_assist( + change_return_type_to_result, + r#"fn foo() -<|>> i32 { + if true { + if false { + 1 as i32 + } else { + 2 as i32 + } + } else { + 24 as i32 + } + }"#, + r#"fn foo() -> Result> { + if true { + if false { + Ok(1 as i32) + } else { + Ok(2 as i32) + } + } else { + Ok(24 as i32) + } + }"#, + ); + } + + #[test] + fn change_return_type_to_result_simple_with_tail_block_like_match() { + check_assist( + change_return_type_to_result, + r#"fn foo() -> i32<|> { + let my_var = 5; + match my_var { + 5 => 42i32, + _ => 24i32, + } + }"#, + r#"fn foo() -> Result> { + let my_var = 5; + match my_var { + 5 => Ok(42i32), + _ => Ok(24i32), + } + }"#, + ); + } + + #[test] + fn change_return_type_to_result_simple_with_loop_with_tail() { + check_assist( + change_return_type_to_result, + r#"fn foo() -> i32<|> { + let my_var = 5; + loop { + println!("test"); + 5 + } + + my_var + }"#, + r#"fn foo() -> Result> { + let my_var = 5; + loop { + println!("test"); + 5 + } + + Ok(my_var) + }"#, + ); + } + + #[test] + fn change_return_type_to_result_simple_with_loop_in_let_stmt() { + check_assist( + change_return_type_to_result, + r#"fn foo() -> i32<|> { + let my_var = let x = loop { + break 1; + }; + + my_var + }"#, + r#"fn foo() -> Result> { + let my_var = let x = loop { + break 1; + }; + + Ok(my_var) + }"#, + ); + } + + #[test] + fn change_return_type_to_result_simple_with_tail_block_like_match_return_expr() { + check_assist( + change_return_type_to_result, + r#"fn foo() -> i32<|> { + let my_var = 5; + let res = match my_var { + 5 => 42i32, + _ => return 24i32, + }; + + res + }"#, + r#"fn foo() -> Result> { + let my_var = 5; + let res = match my_var { + 5 => 42i32, + _ => return Ok(24i32), + }; + + Ok(res) + }"#, + ); + + check_assist( + change_return_type_to_result, + r#"fn foo() -> i32<|> { + let my_var = 5; + let res = if my_var == 5 { + 42i32 + } else { + return 24i32; + }; + + res + }"#, + r#"fn foo() -> Result> { + let my_var = 5; + let res = if my_var == 5 { + 42i32 + } else { + return Ok(24i32); + }; + + Ok(res) + }"#, + ); + } + + #[test] + fn change_return_type_to_result_simple_with_tail_block_like_match_deeper() { + check_assist( + change_return_type_to_result, + r#"fn foo() -> i32<|> { + let my_var = 5; + match my_var { + 5 => { + if true { + 42i32 + } else { + 25i32 + } + }, + _ => { + let test = "test"; + if test == "test" { + return bar(); + } + 53i32 + }, + } + }"#, + r#"fn foo() -> Result> { + let my_var = 5; + match my_var { + 5 => { + if true { + Ok(42i32) + } else { + Ok(25i32) + } + }, + _ => { + let test = "test"; + if test == "test" { + return Ok(bar()); + } + Ok(53i32) + }, + } + }"#, + ); + } + + #[test] + fn change_return_type_to_result_simple_with_tail_block_like_early_return() { + check_assist( + change_return_type_to_result, + r#"fn foo() -> i<|>32 { + let test = "test"; + if test == "test" { + return 24i32; + } + 53i32 + }"#, + r#"fn foo() -> Result> { + let test = "test"; + if test == "test" { + return Ok(24i32); + } + Ok(53i32) + }"#, + ); + } + + #[test] + fn change_return_type_to_result_simple_with_closure() { + check_assist( + change_return_type_to_result, + r#"fn foo(the_field: u32) -><|> u32 { + let true_closure = || { + return true; + }; + if the_field < 5 { + let mut i = 0; + + + if true_closure() { + return 99; + } else { + return 0; + } + } + + the_field + }"#, + r#"fn foo(the_field: u32) -> Result> { + let true_closure = || { + return true; + }; + if the_field < 5 { + let mut i = 0; + + + if true_closure() { + return Ok(99); + } else { + return Ok(0); + } + } + + Ok(the_field) + }"#, + ); + + check_assist( + change_return_type_to_result, + r#"fn foo(the_field: u32) -> u32<|> { + let true_closure = || { + return true; + }; + if the_field < 5 { + let mut i = 0; + + + if true_closure() { + return 99; + } else { + return 0; + } + } + let t = None; + + t.unwrap_or_else(|| the_field) + }"#, + r#"fn foo(the_field: u32) -> Result> { + let true_closure = || { + return true; + }; + if the_field < 5 { + let mut i = 0; + + + if true_closure() { + return Ok(99); + } else { + return Ok(0); + } + } + let t = None; + + Ok(t.unwrap_or_else(|| the_field)) + }"#, + ); + } + + #[test] + fn change_return_type_to_result_simple_with_weird_forms() { + check_assist( + change_return_type_to_result, + r#"fn foo() -> i32<|> { + let test = "test"; + if test == "test" { + return 24i32; + } + let mut i = 0; + loop { + if i == 1 { + break 55; + } + i += 1; + } + }"#, + r#"fn foo() -> Result> { + let test = "test"; + if test == "test" { + return Ok(24i32); + } + let mut i = 0; + loop { + if i == 1 { + break Ok(55); + } + i += 1; + } + }"#, + ); + + check_assist( + change_return_type_to_result, + r#"fn foo() -> i32<|> { + let test = "test"; + if test == "test" { + return 24i32; + } + let mut i = 0; + loop { + loop { + if i == 1 { + break 55; + } + i += 1; + } + } + }"#, + r#"fn foo() -> Result> { + let test = "test"; + if test == "test" { + return Ok(24i32); + } + let mut i = 0; + loop { + loop { + if i == 1 { + break Ok(55); + } + i += 1; + } + } + }"#, + ); + + check_assist( + change_return_type_to_result, + r#"fn foo() -> i3<|>2 { + let test = "test"; + let other = 5; + if test == "test" { + let res = match other { + 5 => 43, + _ => return 56, + }; + } + let mut i = 0; + loop { + loop { + if i == 1 { + break 55; + } + i += 1; + } + } + }"#, + r#"fn foo() -> Result> { + let test = "test"; + let other = 5; + if test == "test" { + let res = match other { + 5 => 43, + _ => return Ok(56), + }; + } + let mut i = 0; + loop { + loop { + if i == 1 { + break Ok(55); + } + i += 1; + } + } + }"#, + ); + + check_assist( + change_return_type_to_result, + r#"fn foo(the_field: u32) -> u32<|> { + if the_field < 5 { + let mut i = 0; + loop { + if i > 5 { + return 55u32; + } + i += 3; + } + + match i { + 5 => return 99, + _ => return 0, + }; + } + + the_field + }"#, + r#"fn foo(the_field: u32) -> Result> { + if the_field < 5 { + let mut i = 0; + loop { + if i > 5 { + return Ok(55u32); + } + i += 3; + } + + match i { + 5 => return Ok(99), + _ => return Ok(0), + }; + } + + Ok(the_field) + }"#, + ); + + check_assist( + change_return_type_to_result, + r#"fn foo(the_field: u32) -> u3<|>2 { + if the_field < 5 { + let mut i = 0; + + match i { + 5 => return 99, + _ => return 0, + } + } + + the_field + }"#, + r#"fn foo(the_field: u32) -> Result> { + if the_field < 5 { + let mut i = 0; + + match i { + 5 => return Ok(99), + _ => return Ok(0), + } + } + + Ok(the_field) + }"#, + ); + + check_assist( + change_return_type_to_result, + r#"fn foo(the_field: u32) -> u32<|> { + if the_field < 5 { + let mut i = 0; + + if i == 5 { + return 99 + } else { + return 0 + } + } + + the_field + }"#, + r#"fn foo(the_field: u32) -> Result> { + if the_field < 5 { + let mut i = 0; + + if i == 5 { + return Ok(99) + } else { + return Ok(0) + } + } + + Ok(the_field) + }"#, + ); + + check_assist( + change_return_type_to_result, + r#"fn foo(the_field: u32) -> <|>u32 { + if the_field < 5 { + let mut i = 0; + + if i == 5 { + return 99; + } else { + return 0; + } + } + + the_field + }"#, + r#"fn foo(the_field: u32) -> Result> { + if the_field < 5 { + let mut i = 0; + + if i == 5 { + return Ok(99); + } else { + return Ok(0); + } + } + + Ok(the_field) + }"#, + ); + } +} diff --git a/crates/ra_assists/src/handlers/change_visibility.rs b/crates/ra_assists/src/handlers/change_visibility.rs index 489db83e6e..e631766eff 100644 --- a/crates/ra_assists/src/handlers/change_visibility.rs +++ b/crates/ra_assists/src/handlers/change_visibility.rs @@ -7,10 +7,10 @@ use ra_syntax::{ }, SyntaxNode, TextSize, T, }; - -use crate::{Assist, AssistCtx, AssistId}; use test_utils::tested_by; +use crate::{AssistContext, AssistId, Assists}; + // Assist: change_visibility // // Adds or changes existing visibility specifier. @@ -22,14 +22,14 @@ use test_utils::tested_by; // ``` // pub(crate) fn frobnicate() {} // ``` -pub(crate) fn change_visibility(ctx: AssistCtx) -> Option { +pub(crate) fn change_visibility(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { if let Some(vis) = ctx.find_node_at_offset::() { - return change_vis(ctx, vis); + return change_vis(acc, vis); } - add_vis(ctx) + add_vis(acc, ctx) } -fn add_vis(ctx: AssistCtx) -> Option { +fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let item_keyword = ctx.token_at_offset().find(|leaf| match leaf.kind() { T![const] | T![fn] | T![mod] | T![struct] | T![enum] | T![trait] => true, _ => false, @@ -66,15 +66,10 @@ fn add_vis(ctx: AssistCtx) -> Option { return None; }; - ctx.add_assist( - AssistId("change_visibility"), - "Change visibility to pub(crate)", - target, - |edit| { - edit.insert(offset, "pub(crate) "); - edit.set_cursor(offset); - }, - ) + acc.add(AssistId("change_visibility"), "Change visibility to pub(crate)", target, |edit| { + edit.insert(offset, "pub(crate) "); + edit.set_cursor(offset); + }) } fn vis_offset(node: &SyntaxNode) -> TextSize { @@ -88,10 +83,10 @@ fn vis_offset(node: &SyntaxNode) -> TextSize { .unwrap_or_else(|| node.text_range().start()) } -fn change_vis(ctx: AssistCtx, vis: ast::Visibility) -> Option { +fn change_vis(acc: &mut Assists, vis: ast::Visibility) -> Option<()> { if vis.syntax().text() == "pub" { let target = vis.syntax().text_range(); - return ctx.add_assist( + return acc.add( AssistId("change_visibility"), "Change Visibility to pub(crate)", target, @@ -103,7 +98,7 @@ fn change_vis(ctx: AssistCtx, vis: ast::Visibility) -> Option { } if vis.syntax().text() == "pub(crate)" { let target = vis.syntax().text_range(); - return ctx.add_assist( + return acc.add( AssistId("change_visibility"), "Change visibility to pub", target, diff --git a/crates/ra_assists/src/handlers/early_return.rs b/crates/ra_assists/src/handlers/early_return.rs index 4bd6040b2a..810784ad57 100644 --- a/crates/ra_assists/src/handlers/early_return.rs +++ b/crates/ra_assists/src/handlers/early_return.rs @@ -9,7 +9,7 @@ use ra_syntax::{ }; use crate::{ - assist_ctx::{Assist, AssistCtx}, + assist_context::{AssistContext, Assists}, utils::invert_boolean_expression, AssistId, }; @@ -36,7 +36,7 @@ use crate::{ // bar(); // } // ``` -pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option { +pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let if_expr: ast::IfExpr = ctx.find_node_at_offset()?; if if_expr.else_branch().is_some() { return None; @@ -93,96 +93,91 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option { } then_block.syntax().last_child_or_token().filter(|t| t.kind() == R_CURLY)?; - let cursor_position = ctx.frange.range.start(); + let cursor_position = ctx.offset(); let target = if_expr.syntax().text_range(); - ctx.add_assist( - AssistId("convert_to_guarded_return"), - "Convert to guarded return", - target, - |edit| { - let if_indent_level = IndentLevel::from_node(&if_expr.syntax()); - let new_block = match if_let_pat { - None => { - // If. - let new_expr = { - let then_branch = - make::block_expr(once(make::expr_stmt(early_expression).into()), None); - let cond = invert_boolean_expression(cond_expr); - let e = make::expr_if(make::condition(cond, None), then_branch); - if_indent_level.increase_indent(e) - }; - replace(new_expr.syntax(), &then_block, &parent_block, &if_expr) - } - Some((path, bound_ident)) => { - // If-let. - let match_expr = { - let happy_arm = { - let pat = make::tuple_struct_pat( - path, - once(make::bind_pat(make::name("it")).into()), - ); - let expr = { - let name_ref = make::name_ref("it"); - let segment = make::path_segment(name_ref); - let path = make::path_unqualified(segment); - make::expr_path(path) - }; - make::match_arm(once(pat.into()), expr) - }; - - let sad_arm = make::match_arm( - // FIXME: would be cool to use `None` or `Err(_)` if appropriate - once(make::placeholder_pat().into()), - early_expression, - ); - - make::expr_match(cond_expr, make::match_arm_list(vec![happy_arm, sad_arm])) - }; - - let let_stmt = make::let_stmt( - make::bind_pat(make::name(&bound_ident.syntax().to_string())).into(), - Some(match_expr), - ); - let let_stmt = if_indent_level.increase_indent(let_stmt); - replace(let_stmt.syntax(), &then_block, &parent_block, &if_expr) - } - }; - edit.replace_ast(parent_block, ast::BlockExpr::cast(new_block).unwrap()); - edit.set_cursor(cursor_position); - - fn replace( - new_expr: &SyntaxNode, - then_block: &ast::BlockExpr, - parent_block: &ast::BlockExpr, - if_expr: &ast::IfExpr, - ) -> SyntaxNode { - let then_block_items = IndentLevel::from(1).decrease_indent(then_block.clone()); - let end_of_then = then_block_items.syntax().last_child_or_token().unwrap(); - let end_of_then = - if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) { - end_of_then.prev_sibling_or_token().unwrap() - } else { - end_of_then - }; - let mut then_statements = new_expr.children_with_tokens().chain( - then_block_items - .syntax() - .children_with_tokens() - .skip(1) - .take_while(|i| *i != end_of_then), - ); - replace_children( - &parent_block.syntax(), - RangeInclusive::new( - if_expr.clone().syntax().clone().into(), - if_expr.syntax().clone().into(), - ), - &mut then_statements, - ) + acc.add(AssistId("convert_to_guarded_return"), "Convert to guarded return", target, |edit| { + let if_indent_level = IndentLevel::from_node(&if_expr.syntax()); + let new_block = match if_let_pat { + None => { + // If. + let new_expr = { + let then_branch = + make::block_expr(once(make::expr_stmt(early_expression).into()), None); + let cond = invert_boolean_expression(cond_expr); + let e = make::expr_if(make::condition(cond, None), then_branch); + if_indent_level.increase_indent(e) + }; + replace(new_expr.syntax(), &then_block, &parent_block, &if_expr) } - }, - ) + Some((path, bound_ident)) => { + // If-let. + let match_expr = { + let happy_arm = { + let pat = make::tuple_struct_pat( + path, + once(make::bind_pat(make::name("it")).into()), + ); + let expr = { + let name_ref = make::name_ref("it"); + let segment = make::path_segment(name_ref); + let path = make::path_unqualified(segment); + make::expr_path(path) + }; + make::match_arm(once(pat.into()), expr) + }; + + let sad_arm = make::match_arm( + // FIXME: would be cool to use `None` or `Err(_)` if appropriate + once(make::placeholder_pat().into()), + early_expression, + ); + + make::expr_match(cond_expr, make::match_arm_list(vec![happy_arm, sad_arm])) + }; + + let let_stmt = make::let_stmt( + make::bind_pat(make::name(&bound_ident.syntax().to_string())).into(), + Some(match_expr), + ); + let let_stmt = if_indent_level.increase_indent(let_stmt); + replace(let_stmt.syntax(), &then_block, &parent_block, &if_expr) + } + }; + edit.replace_ast(parent_block, ast::BlockExpr::cast(new_block).unwrap()); + edit.set_cursor(cursor_position); + + fn replace( + new_expr: &SyntaxNode, + then_block: &ast::BlockExpr, + parent_block: &ast::BlockExpr, + if_expr: &ast::IfExpr, + ) -> SyntaxNode { + let then_block_items = IndentLevel::from(1).decrease_indent(then_block.clone()); + let end_of_then = then_block_items.syntax().last_child_or_token().unwrap(); + let end_of_then = + if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) { + end_of_then.prev_sibling_or_token().unwrap() + } else { + end_of_then + }; + let mut then_statements = new_expr.children_with_tokens().chain( + then_block_items + .syntax() + .children_with_tokens() + .skip(1) + .take_while(|i| *i != end_of_then), + ); + replace_children( + &parent_block.syntax(), + RangeInclusive::new( + if_expr.clone().syntax().clone().into(), + if_expr.syntax().clone().into(), + ), + &mut then_statements, + ) + } + }) } #[cfg(test)] diff --git a/crates/ra_assists/src/handlers/fill_match_arms.rs b/crates/ra_assists/src/handlers/fill_match_arms.rs index 7c8f8bdf24..13c1e7e801 100644 --- a/crates/ra_assists/src/handlers/fill_match_arms.rs +++ b/crates/ra_assists/src/handlers/fill_match_arms.rs @@ -5,7 +5,7 @@ use itertools::Itertools; use ra_ide_db::RootDatabase; use ra_syntax::ast::{self, make, AstNode, MatchArm, NameOwner, Pat}; -use crate::{Assist, AssistCtx, AssistId}; +use crate::{AssistContext, AssistId, Assists}; // Assist: fill_match_arms // @@ -31,7 +31,7 @@ use crate::{Assist, AssistCtx, AssistId}; // } // } // ``` -pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option { +pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let match_expr = ctx.find_node_at_offset::()?; let match_arm_list = match_expr.match_arm_list()?; @@ -93,7 +93,7 @@ pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option { } let target = match_expr.syntax().text_range(); - ctx.add_assist(AssistId("fill_match_arms"), "Fill match arms", target, |edit| { + acc.add(AssistId("fill_match_arms"), "Fill match arms", target, |edit| { let new_arm_list = match_arm_list.remove_placeholder().append_arms(missing_arms); edit.set_cursor(expr.syntax().text_range().start()); edit.replace_ast(match_arm_list, new_arm_list); diff --git a/crates/ra_assists/src/handlers/flip_binexpr.rs b/crates/ra_assists/src/handlers/flip_binexpr.rs index cb7264d7bb..692ba4895c 100644 --- a/crates/ra_assists/src/handlers/flip_binexpr.rs +++ b/crates/ra_assists/src/handlers/flip_binexpr.rs @@ -1,6 +1,6 @@ use ra_syntax::ast::{AstNode, BinExpr, BinOp}; -use crate::{Assist, AssistCtx, AssistId}; +use crate::{AssistContext, AssistId, Assists}; // Assist: flip_binexpr // @@ -17,7 +17,7 @@ use crate::{Assist, AssistCtx, AssistId}; // let _ = 2 + 90; // } // ``` -pub(crate) fn flip_binexpr(ctx: AssistCtx) -> Option { +pub(crate) fn flip_binexpr(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let expr = ctx.find_node_at_offset::()?; let lhs = expr.lhs()?.syntax().clone(); let rhs = expr.rhs()?.syntax().clone(); @@ -33,7 +33,7 @@ pub(crate) fn flip_binexpr(ctx: AssistCtx) -> Option { return None; } - ctx.add_assist(AssistId("flip_binexpr"), "Flip binary expression", op_range, |edit| { + acc.add(AssistId("flip_binexpr"), "Flip binary expression", op_range, |edit| { if let FlipAction::FlipAndReplaceOp(new_op) = action { edit.replace(op_range, new_op); } diff --git a/crates/ra_assists/src/handlers/flip_comma.rs b/crates/ra_assists/src/handlers/flip_comma.rs index 24982ae225..dfe2a7fedc 100644 --- a/crates/ra_assists/src/handlers/flip_comma.rs +++ b/crates/ra_assists/src/handlers/flip_comma.rs @@ -1,6 +1,6 @@ use ra_syntax::{algo::non_trivia_sibling, Direction, T}; -use crate::{Assist, AssistCtx, AssistId}; +use crate::{AssistContext, AssistId, Assists}; // Assist: flip_comma // @@ -17,7 +17,7 @@ use crate::{Assist, AssistCtx, AssistId}; // ((3, 4), (1, 2)); // } // ``` -pub(crate) fn flip_comma(ctx: AssistCtx) -> Option { +pub(crate) fn flip_comma(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let comma = ctx.find_token_at_offset(T![,])?; let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?; let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?; @@ -28,7 +28,7 @@ pub(crate) fn flip_comma(ctx: AssistCtx) -> Option { return None; } - ctx.add_assist(AssistId("flip_comma"), "Flip comma", comma.text_range(), |edit| { + acc.add(AssistId("flip_comma"), "Flip comma", comma.text_range(), |edit| { edit.replace(prev.text_range(), next.to_string()); edit.replace(next.text_range(), prev.to_string()); }) diff --git a/crates/ra_assists/src/handlers/flip_trait_bound.rs b/crates/ra_assists/src/handlers/flip_trait_bound.rs index 6a3b2df679..8a08702ab2 100644 --- a/crates/ra_assists/src/handlers/flip_trait_bound.rs +++ b/crates/ra_assists/src/handlers/flip_trait_bound.rs @@ -4,7 +4,7 @@ use ra_syntax::{ Direction, T, }; -use crate::{Assist, AssistCtx, AssistId}; +use crate::{AssistContext, AssistId, Assists}; // Assist: flip_trait_bound // @@ -17,7 +17,7 @@ use crate::{Assist, AssistCtx, AssistId}; // ``` // fn foo() { } // ``` -pub(crate) fn flip_trait_bound(ctx: AssistCtx) -> Option { +pub(crate) fn flip_trait_bound(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { // We want to replicate the behavior of `flip_binexpr` by only suggesting // the assist when the cursor is on a `+` let plus = ctx.find_token_at_offset(T![+])?; @@ -33,7 +33,7 @@ pub(crate) fn flip_trait_bound(ctx: AssistCtx) -> Option { ); let target = plus.text_range(); - ctx.add_assist(AssistId("flip_trait_bound"), "Flip trait bounds", target, |edit| { + acc.add(AssistId("flip_trait_bound"), "Flip trait bounds", target, |edit| { edit.replace(before.text_range(), after.to_string()); edit.replace(after.text_range(), before.to_string()); }) diff --git a/crates/ra_assists/src/handlers/inline_local_variable.rs b/crates/ra_assists/src/handlers/inline_local_variable.rs index e5765c845e..5b26814d30 100644 --- a/crates/ra_assists/src/handlers/inline_local_variable.rs +++ b/crates/ra_assists/src/handlers/inline_local_variable.rs @@ -5,7 +5,10 @@ use ra_syntax::{ }; use test_utils::tested_by; -use crate::{assist_ctx::ActionBuilder, Assist, AssistCtx, AssistId}; +use crate::{ + assist_context::{AssistContext, Assists}, + AssistId, +}; // Assist: inline_local_variable // @@ -23,7 +26,7 @@ use crate::{assist_ctx::ActionBuilder, Assist, AssistCtx, AssistId}; // (1 + 2) * 4; // } // ``` -pub(crate) fn inline_local_variable(ctx: AssistCtx) -> Option { +pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let let_stmt = ctx.find_node_at_offset::()?; let bind_pat = match let_stmt.pat()? { ast::Pat::BindPat(pat) => pat, @@ -33,7 +36,7 @@ pub(crate) fn inline_local_variable(ctx: AssistCtx) -> Option { tested_by!(test_not_inline_mut_variable); return None; } - if !bind_pat.syntax().text_range().contains_inclusive(ctx.frange.range.start()) { + if !bind_pat.syntax().text_range().contains_inclusive(ctx.offset()) { tested_by!(not_applicable_outside_of_bind_pat); return None; } @@ -107,20 +110,14 @@ pub(crate) fn inline_local_variable(ctx: AssistCtx) -> Option { let init_in_paren = format!("({})", &init_str); let target = bind_pat.syntax().text_range(); - ctx.add_assist( - AssistId("inline_local_variable"), - "Inline variable", - target, - move |edit: &mut ActionBuilder| { - edit.delete(delete_range); - for (desc, should_wrap) in refs.iter().zip(wrap_in_parens) { - let replacement = - if should_wrap { init_in_paren.clone() } else { init_str.clone() }; - edit.replace(desc.file_range.range, replacement) - } - edit.set_cursor(delete_range.start()) - }, - ) + acc.add(AssistId("inline_local_variable"), "Inline variable", target, move |builder| { + builder.delete(delete_range); + for (desc, should_wrap) in refs.iter().zip(wrap_in_parens) { + let replacement = if should_wrap { init_in_paren.clone() } else { init_str.clone() }; + builder.replace(desc.file_range.range, replacement) + } + builder.set_cursor(delete_range.start()) + }) } #[cfg(test)] diff --git a/crates/ra_assists/src/handlers/introduce_variable.rs b/crates/ra_assists/src/handlers/introduce_variable.rs index 3c340ff3b9..fdf3ada0d7 100644 --- a/crates/ra_assists/src/handlers/introduce_variable.rs +++ b/crates/ra_assists/src/handlers/introduce_variable.rs @@ -9,7 +9,7 @@ use ra_syntax::{ use stdx::format_to; use test_utils::tested_by; -use crate::{Assist, AssistCtx, AssistId}; +use crate::{AssistContext, AssistId, Assists}; // Assist: introduce_variable // @@ -27,7 +27,7 @@ use crate::{Assist, AssistCtx, AssistId}; // var_name * 4; // } // ``` -pub(crate) fn introduce_variable(ctx: AssistCtx) -> Option { +pub(crate) fn introduce_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { if ctx.frange.range.is_empty() { return None; } @@ -43,7 +43,7 @@ pub(crate) fn introduce_variable(ctx: AssistCtx) -> Option { return None; } let target = expr.syntax().text_range(); - ctx.add_assist(AssistId("introduce_variable"), "Extract into variable", target, move |edit| { + acc.add(AssistId("introduce_variable"), "Extract into variable", target, move |edit| { let mut buf = String::new(); let cursor_offset = if wrap_in_block { diff --git a/crates/ra_assists/src/handlers/invert_if.rs b/crates/ra_assists/src/handlers/invert_if.rs index b16271443e..527c7caef1 100644 --- a/crates/ra_assists/src/handlers/invert_if.rs +++ b/crates/ra_assists/src/handlers/invert_if.rs @@ -3,7 +3,11 @@ use ra_syntax::{ T, }; -use crate::{utils::invert_boolean_expression, Assist, AssistCtx, AssistId}; +use crate::{ + assist_context::{AssistContext, Assists}, + utils::invert_boolean_expression, + AssistId, +}; // Assist: invert_if // @@ -24,7 +28,7 @@ use crate::{utils::invert_boolean_expression, Assist, AssistCtx, AssistId}; // } // ``` -pub(crate) fn invert_if(ctx: AssistCtx) -> Option { +pub(crate) fn invert_if(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let if_keyword = ctx.find_token_at_offset(T![if])?; let expr = ast::IfExpr::cast(if_keyword.parent())?; let if_range = if_keyword.text_range(); @@ -40,21 +44,21 @@ pub(crate) fn invert_if(ctx: AssistCtx) -> Option { let cond = expr.condition()?.expr()?; let then_node = expr.then_branch()?.syntax().clone(); + let else_block = match expr.else_branch()? { + ast::ElseBranch::Block(it) => it, + ast::ElseBranch::IfExpr(_) => return None, + }; - if let ast::ElseBranch::Block(else_block) = expr.else_branch()? { - let cond_range = cond.syntax().text_range(); - let flip_cond = invert_boolean_expression(cond); - let else_node = else_block.syntax(); - let else_range = else_node.text_range(); - let then_range = then_node.text_range(); - return ctx.add_assist(AssistId("invert_if"), "Invert if", if_range, |edit| { - edit.replace(cond_range, flip_cond.syntax().text()); - edit.replace(else_range, then_node.text()); - edit.replace(then_range, else_node.text()); - }); - } - - None + let cond_range = cond.syntax().text_range(); + let flip_cond = invert_boolean_expression(cond); + let else_node = else_block.syntax(); + let else_range = else_node.text_range(); + let then_range = then_node.text_range(); + acc.add(AssistId("invert_if"), "Invert if", if_range, |edit| { + edit.replace(cond_range, flip_cond.syntax().text()); + edit.replace(else_range, then_node.text()); + edit.replace(then_range, else_node.text()); + }) } #[cfg(test)] diff --git a/crates/ra_assists/src/handlers/merge_imports.rs b/crates/ra_assists/src/handlers/merge_imports.rs index de74d83d85..ac3e53c273 100644 --- a/crates/ra_assists/src/handlers/merge_imports.rs +++ b/crates/ra_assists/src/handlers/merge_imports.rs @@ -6,7 +6,10 @@ use ra_syntax::{ AstNode, Direction, InsertPosition, SyntaxElement, T, }; -use crate::{Assist, AssistCtx, AssistId}; +use crate::{ + assist_context::{AssistContext, Assists}, + AssistId, +}; // Assist: merge_imports // @@ -20,10 +23,10 @@ use crate::{Assist, AssistCtx, AssistId}; // ``` // use std::{fmt::Formatter, io}; // ``` -pub(crate) fn merge_imports(ctx: AssistCtx) -> Option { +pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let tree: ast::UseTree = ctx.find_node_at_offset()?; let mut rewriter = SyntaxRewriter::default(); - let mut offset = ctx.frange.range.start(); + let mut offset = ctx.offset(); if let Some(use_item) = tree.syntax().parent().and_then(ast::UseItem::cast) { let (merged, to_delete) = next_prev() @@ -53,10 +56,10 @@ pub(crate) fn merge_imports(ctx: AssistCtx) -> Option { }; let target = tree.syntax().text_range(); - ctx.add_assist(AssistId("merge_imports"), "Merge imports", target, |edit| { - edit.rewrite(rewriter); + acc.add(AssistId("merge_imports"), "Merge imports", target, |builder| { + builder.rewrite(rewriter); // FIXME: we only need because our diff is imprecise - edit.set_cursor(offset); + builder.set_cursor(offset); }) } diff --git a/crates/ra_assists/src/handlers/merge_match_arms.rs b/crates/ra_assists/src/handlers/merge_match_arms.rs index 7c4d9d55d7..d4e38aa6a5 100644 --- a/crates/ra_assists/src/handlers/merge_match_arms.rs +++ b/crates/ra_assists/src/handlers/merge_match_arms.rs @@ -6,7 +6,7 @@ use ra_syntax::{ Direction, TextSize, }; -use crate::{Assist, AssistCtx, AssistId, TextRange}; +use crate::{AssistContext, AssistId, Assists, TextRange}; // Assist: merge_match_arms // @@ -32,7 +32,7 @@ use crate::{Assist, AssistCtx, AssistId, TextRange}; // } // } // ``` -pub(crate) fn merge_match_arms(ctx: AssistCtx) -> Option { +pub(crate) fn merge_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let current_arm = ctx.find_node_at_offset::()?; // Don't try to handle arms with guards for now - can add support for this later if current_arm.guard().is_some() { @@ -45,7 +45,7 @@ pub(crate) fn merge_match_arms(ctx: AssistCtx) -> Option { InExpr(TextSize), InPat(TextSize), } - let cursor_pos = ctx.frange.range.start(); + let cursor_pos = ctx.offset(); let cursor_pos = if current_expr.syntax().text_range().contains(cursor_pos) { CursorPos::InExpr(current_text_range.end() - cursor_pos) } else { @@ -70,7 +70,7 @@ pub(crate) fn merge_match_arms(ctx: AssistCtx) -> Option { return None; } - ctx.add_assist(AssistId("merge_match_arms"), "Merge match arms", current_text_range, |edit| { + acc.add(AssistId("merge_match_arms"), "Merge match arms", current_text_range, |edit| { let pats = if arms_to_merge.iter().any(contains_placeholder) { "_".into() } else { diff --git a/crates/ra_assists/src/handlers/move_bounds.rs b/crates/ra_assists/src/handlers/move_bounds.rs index 44e50cb6e6..a41aacfc3d 100644 --- a/crates/ra_assists/src/handlers/move_bounds.rs +++ b/crates/ra_assists/src/handlers/move_bounds.rs @@ -5,7 +5,7 @@ use ra_syntax::{ T, }; -use crate::{Assist, AssistCtx, AssistId}; +use crate::{AssistContext, AssistId, Assists}; // Assist: move_bounds_to_where_clause // @@ -22,7 +22,7 @@ use crate::{Assist, AssistCtx, AssistId}; // f(x) // } // ``` -pub(crate) fn move_bounds_to_where_clause(ctx: AssistCtx) -> Option { +pub(crate) fn move_bounds_to_where_clause(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let type_param_list = ctx.find_node_at_offset::()?; let mut type_params = type_param_list.type_params(); @@ -50,36 +50,29 @@ pub(crate) fn move_bounds_to_where_clause(ctx: AssistCtx) -> Option { }; let target = type_param_list.syntax().text_range(); - ctx.add_assist( - AssistId("move_bounds_to_where_clause"), - "Move to where clause", - target, - |edit| { - let new_params = type_param_list - .type_params() - .filter(|it| it.type_bound_list().is_some()) - .map(|type_param| { - let without_bounds = type_param.remove_bounds(); - (type_param, without_bounds) - }); + acc.add(AssistId("move_bounds_to_where_clause"), "Move to where clause", target, |edit| { + let new_params = type_param_list + .type_params() + .filter(|it| it.type_bound_list().is_some()) + .map(|type_param| { + let without_bounds = type_param.remove_bounds(); + (type_param, without_bounds) + }); - let new_type_param_list = type_param_list.replace_descendants(new_params); - edit.replace_ast(type_param_list.clone(), new_type_param_list); + let new_type_param_list = type_param_list.replace_descendants(new_params); + edit.replace_ast(type_param_list.clone(), new_type_param_list); - let where_clause = { - let predicates = type_param_list.type_params().filter_map(build_predicate); - make::where_clause(predicates) - }; + let where_clause = { + let predicates = type_param_list.type_params().filter_map(build_predicate); + make::where_clause(predicates) + }; - let to_insert = match anchor.prev_sibling_or_token() { - Some(ref elem) if elem.kind() == WHITESPACE => { - format!("{} ", where_clause.syntax()) - } - _ => format!(" {}", where_clause.syntax()), - }; - edit.insert(anchor.text_range().start(), to_insert); - }, - ) + let to_insert = match anchor.prev_sibling_or_token() { + Some(ref elem) if elem.kind() == WHITESPACE => format!("{} ", where_clause.syntax()), + _ => format!(" {}", where_clause.syntax()), + }; + edit.insert(anchor.text_range().start(), to_insert); + }) } fn build_predicate(param: ast::TypeParam) -> Option { diff --git a/crates/ra_assists/src/handlers/move_guard.rs b/crates/ra_assists/src/handlers/move_guard.rs index 29bc9a9ffb..fc0335b578 100644 --- a/crates/ra_assists/src/handlers/move_guard.rs +++ b/crates/ra_assists/src/handlers/move_guard.rs @@ -4,7 +4,7 @@ use ra_syntax::{ TextSize, }; -use crate::{Assist, AssistCtx, AssistId}; +use crate::{AssistContext, AssistId, Assists}; // Assist: move_guard_to_arm_body // @@ -31,7 +31,7 @@ use crate::{Assist, AssistCtx, AssistId}; // } // } // ``` -pub(crate) fn move_guard_to_arm_body(ctx: AssistCtx) -> Option { +pub(crate) fn move_guard_to_arm_body(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let match_arm = ctx.find_node_at_offset::()?; let guard = match_arm.guard()?; let space_before_guard = guard.syntax().prev_sibling_or_token(); @@ -41,7 +41,7 @@ pub(crate) fn move_guard_to_arm_body(ctx: AssistCtx) -> Option { let buf = format!("if {} {{ {} }}", guard_conditions.syntax().text(), arm_expr.syntax().text()); let target = guard.syntax().text_range(); - ctx.add_assist(AssistId("move_guard_to_arm_body"), "Move guard to arm body", target, |edit| { + acc.add(AssistId("move_guard_to_arm_body"), "Move guard to arm body", target, |edit| { let offseting_amount = match space_before_guard.and_then(|it| it.into_token()) { Some(tok) => { if ast::Whitespace::cast(tok.clone()).is_some() { @@ -88,7 +88,7 @@ pub(crate) fn move_guard_to_arm_body(ctx: AssistCtx) -> Option { // } // } // ``` -pub(crate) fn move_arm_cond_to_match_guard(ctx: AssistCtx) -> Option { +pub(crate) fn move_arm_cond_to_match_guard(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let match_arm: MatchArm = ctx.find_node_at_offset::()?; let match_pat = match_arm.pat()?; @@ -109,7 +109,7 @@ pub(crate) fn move_arm_cond_to_match_guard(ctx: AssistCtx) -> Option { let buf = format!(" if {}", cond.syntax().text()); let target = if_expr.syntax().text_range(); - ctx.add_assist( + acc.add( AssistId("move_arm_cond_to_match_guard"), "Move condition to match guard", target, diff --git a/crates/ra_assists/src/handlers/raw_string.rs b/crates/ra_assists/src/handlers/raw_string.rs index 155c679b4b..c20ffe0b30 100644 --- a/crates/ra_assists/src/handlers/raw_string.rs +++ b/crates/ra_assists/src/handlers/raw_string.rs @@ -5,7 +5,7 @@ use ra_syntax::{ TextSize, }; -use crate::{Assist, AssistCtx, AssistId}; +use crate::{AssistContext, AssistId, Assists}; // Assist: make_raw_string // @@ -22,11 +22,11 @@ use crate::{Assist, AssistCtx, AssistId}; // r#"Hello, World!"#; // } // ``` -pub(crate) fn make_raw_string(ctx: AssistCtx) -> Option { +pub(crate) fn make_raw_string(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let token = ctx.find_token_at_offset(STRING).and_then(ast::String::cast)?; let value = token.value()?; let target = token.syntax().text_range(); - ctx.add_assist(AssistId("make_raw_string"), "Rewrite as raw string", target, |edit| { + acc.add(AssistId("make_raw_string"), "Rewrite as raw string", target, |edit| { let max_hash_streak = count_hashes(&value); let mut hashes = String::with_capacity(max_hash_streak + 1); for _ in 0..hashes.capacity() { @@ -51,11 +51,11 @@ pub(crate) fn make_raw_string(ctx: AssistCtx) -> Option { // "Hello, \"World!\""; // } // ``` -pub(crate) fn make_usual_string(ctx: AssistCtx) -> Option { +pub(crate) fn make_usual_string(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?; let value = token.value()?; let target = token.syntax().text_range(); - ctx.add_assist(AssistId("make_usual_string"), "Rewrite as regular string", target, |edit| { + acc.add(AssistId("make_usual_string"), "Rewrite as regular string", target, |edit| { // parse inside string to escape `"` let escaped = value.escape_default().to_string(); edit.replace(token.syntax().text_range(), format!("\"{}\"", escaped)); @@ -77,10 +77,10 @@ pub(crate) fn make_usual_string(ctx: AssistCtx) -> Option { // r##"Hello, World!"##; // } // ``` -pub(crate) fn add_hash(ctx: AssistCtx) -> Option { +pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let token = ctx.find_token_at_offset(RAW_STRING)?; let target = token.text_range(); - ctx.add_assist(AssistId("add_hash"), "Add # to raw string", target, |edit| { + acc.add(AssistId("add_hash"), "Add # to raw string", target, |edit| { edit.insert(token.text_range().start() + TextSize::of('r'), "#"); edit.insert(token.text_range().end(), "#"); }) @@ -101,7 +101,7 @@ pub(crate) fn add_hash(ctx: AssistCtx) -> Option { // r"Hello, World!"; // } // ``` -pub(crate) fn remove_hash(ctx: AssistCtx) -> Option { +pub(crate) fn remove_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let token = ctx.find_token_at_offset(RAW_STRING)?; let text = token.text().as_str(); if text.starts_with("r\"") { @@ -109,7 +109,7 @@ pub(crate) fn remove_hash(ctx: AssistCtx) -> Option { return None; } let target = token.text_range(); - ctx.add_assist(AssistId("remove_hash"), "Remove hash from raw string", target, |edit| { + acc.add(AssistId("remove_hash"), "Remove hash from raw string", target, |edit| { let result = &text[2..text.len() - 1]; let result = if result.starts_with('\"') { // FIXME: this logic is wrong, not only the last has has to handled specially diff --git a/crates/ra_assists/src/handlers/remove_dbg.rs b/crates/ra_assists/src/handlers/remove_dbg.rs index e6e02f2aec..8eef578cf4 100644 --- a/crates/ra_assists/src/handlers/remove_dbg.rs +++ b/crates/ra_assists/src/handlers/remove_dbg.rs @@ -3,7 +3,7 @@ use ra_syntax::{ TextSize, T, }; -use crate::{Assist, AssistCtx, AssistId}; +use crate::{AssistContext, AssistId, Assists}; // Assist: remove_dbg // @@ -20,7 +20,7 @@ use crate::{Assist, AssistCtx, AssistId}; // 92; // } // ``` -pub(crate) fn remove_dbg(ctx: AssistCtx) -> Option { +pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let macro_call = ctx.find_node_at_offset::()?; if !is_valid_macrocall(¯o_call, "dbg")? { @@ -58,7 +58,7 @@ pub(crate) fn remove_dbg(ctx: AssistCtx) -> Option { }; let target = macro_call.syntax().text_range(); - ctx.add_assist(AssistId("remove_dbg"), "Remove dbg!()", target, |edit| { + acc.add(AssistId("remove_dbg"), "Remove dbg!()", target, |edit| { edit.replace(macro_range, macro_content); edit.set_cursor(cursor_pos); }) diff --git a/crates/ra_assists/src/handlers/remove_mut.rs b/crates/ra_assists/src/handlers/remove_mut.rs index 9f72f879d5..dce546db79 100644 --- a/crates/ra_assists/src/handlers/remove_mut.rs +++ b/crates/ra_assists/src/handlers/remove_mut.rs @@ -1,6 +1,6 @@ use ra_syntax::{SyntaxKind, TextRange, T}; -use crate::{Assist, AssistCtx, AssistId}; +use crate::{AssistContext, AssistId, Assists}; // Assist: remove_mut // @@ -17,7 +17,7 @@ use crate::{Assist, AssistCtx, AssistId}; // fn feed(&self, amount: u32) {} // } // ``` -pub(crate) fn remove_mut(ctx: AssistCtx) -> Option { +pub(crate) fn remove_mut(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let mut_token = ctx.find_token_at_offset(T![mut])?; let delete_from = mut_token.text_range().start(); let delete_to = match mut_token.next_token() { @@ -26,7 +26,7 @@ pub(crate) fn remove_mut(ctx: AssistCtx) -> Option { }; let target = mut_token.text_range(); - ctx.add_assist(AssistId("remove_mut"), "Remove `mut` keyword", target, |edit| { + acc.add(AssistId("remove_mut"), "Remove `mut` keyword", target, |edit| { edit.set_cursor(delete_from); edit.delete(TextRange::new(delete_from, delete_to)); }) diff --git a/crates/ra_assists/src/handlers/reorder_fields.rs b/crates/ra_assists/src/handlers/reorder_fields.rs index 0b930dea21..757f6406e9 100644 --- a/crates/ra_assists/src/handlers/reorder_fields.rs +++ b/crates/ra_assists/src/handlers/reorder_fields.rs @@ -3,18 +3,9 @@ use std::collections::HashMap; use hir::{Adt, ModuleDef, PathResolution, Semantics, Struct}; use itertools::Itertools; use ra_ide_db::RootDatabase; -use ra_syntax::{ - algo, - ast::{self, Path, RecordLit, RecordPat}, - match_ast, AstNode, SyntaxKind, - SyntaxKind::*, - SyntaxNode, -}; +use ra_syntax::{algo, ast, match_ast, AstNode, SyntaxKind, SyntaxKind::*, SyntaxNode}; -use crate::{ - assist_ctx::{Assist, AssistCtx}, - AssistId, -}; +use crate::{AssistContext, AssistId, Assists}; // Assist: reorder_fields // @@ -31,13 +22,13 @@ use crate::{ // const test: Foo = Foo {foo: 1, bar: 0} // ``` // -pub(crate) fn reorder_fields(ctx: AssistCtx) -> Option { - reorder::(ctx.clone()).or_else(|| reorder::(ctx)) +pub(crate) fn reorder_fields(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { + reorder::(acc, ctx.clone()).or_else(|| reorder::(acc, ctx)) } -fn reorder(ctx: AssistCtx) -> Option { +fn reorder(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let record = ctx.find_node_at_offset::()?; - let path = record.syntax().children().find_map(Path::cast)?; + let path = record.syntax().children().find_map(ast::Path::cast)?; let ranks = compute_fields_ranks(&path, &ctx)?; @@ -51,7 +42,7 @@ fn reorder(ctx: AssistCtx) -> Option { } let target = record.syntax().text_range(); - ctx.add_assist(AssistId("reorder_fields"), "Reorder record fields", target, |edit| { + acc.add(AssistId("reorder_fields"), "Reorder record fields", target, |edit| { for (old, new) in fields.iter().zip(&sorted_fields) { algo::diff(old, new).into_text_edit(edit.text_edit_builder()); } @@ -96,9 +87,9 @@ fn struct_definition(path: &ast::Path, sema: &Semantics) -> Option } } -fn compute_fields_ranks(path: &Path, ctx: &AssistCtx) -> Option> { +fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option> { Some( - struct_definition(path, ctx.sema)? + struct_definition(path, &ctx.sema)? .fields(ctx.db) .iter() .enumerate() diff --git a/crates/ra_assists/src/handlers/replace_if_let_with_match.rs b/crates/ra_assists/src/handlers/replace_if_let_with_match.rs index 2eb8348f82..a59a06efa5 100644 --- a/crates/ra_assists/src/handlers/replace_if_let_with_match.rs +++ b/crates/ra_assists/src/handlers/replace_if_let_with_match.rs @@ -4,7 +4,7 @@ use ra_syntax::{ AstNode, }; -use crate::{utils::TryEnum, Assist, AssistCtx, AssistId}; +use crate::{utils::TryEnum, AssistContext, AssistId, Assists}; // Assist: replace_if_let_with_match // @@ -32,7 +32,7 @@ use crate::{utils::TryEnum, Assist, AssistCtx, AssistId}; // } // } // ``` -pub(crate) fn replace_if_let_with_match(ctx: AssistCtx) -> Option { +pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let if_expr: ast::IfExpr = ctx.find_node_at_offset()?; let cond = if_expr.condition()?; let pat = cond.pat()?; @@ -43,36 +43,31 @@ pub(crate) fn replace_if_let_with_match(ctx: AssistCtx) -> Option { ast::ElseBranch::IfExpr(_) => return None, }; - let sema = ctx.sema; let target = if_expr.syntax().text_range(); - ctx.add_assist( - AssistId("replace_if_let_with_match"), - "Replace with match", - target, - move |edit| { - let match_expr = { - let then_arm = { - let then_expr = unwrap_trivial_block(then_block); - make::match_arm(vec![pat.clone()], then_expr) - }; - let else_arm = { - let pattern = sema - .type_of_pat(&pat) - .and_then(|ty| TryEnum::from_ty(sema, &ty)) - .map(|it| it.sad_pattern()) - .unwrap_or_else(|| make::placeholder_pat().into()); - let else_expr = unwrap_trivial_block(else_block); - make::match_arm(vec![pattern], else_expr) - }; - make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm])) + acc.add(AssistId("replace_if_let_with_match"), "Replace with match", target, move |edit| { + let match_expr = { + let then_arm = { + let then_expr = unwrap_trivial_block(then_block); + make::match_arm(vec![pat.clone()], then_expr) }; + let else_arm = { + let pattern = ctx + .sema + .type_of_pat(&pat) + .and_then(|ty| TryEnum::from_ty(&ctx.sema, &ty)) + .map(|it| it.sad_pattern()) + .unwrap_or_else(|| make::placeholder_pat().into()); + let else_expr = unwrap_trivial_block(else_block); + make::match_arm(vec![pattern], else_expr) + }; + make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm])) + }; - let match_expr = IndentLevel::from_node(if_expr.syntax()).increase_indent(match_expr); + let match_expr = IndentLevel::from_node(if_expr.syntax()).increase_indent(match_expr); - edit.set_cursor(if_expr.syntax().text_range().start()); - edit.replace_ast::(if_expr.into(), match_expr); - }, - ) + edit.set_cursor(if_expr.syntax().text_range().start()); + edit.replace_ast::(if_expr.into(), match_expr); + }) } #[cfg(test)] diff --git a/crates/ra_assists/src/handlers/replace_let_with_if_let.rs b/crates/ra_assists/src/handlers/replace_let_with_if_let.rs index a5509a5673..d3f214591a 100644 --- a/crates/ra_assists/src/handlers/replace_let_with_if_let.rs +++ b/crates/ra_assists/src/handlers/replace_let_with_if_let.rs @@ -9,11 +9,7 @@ use ra_syntax::{ AstNode, T, }; -use crate::{ - assist_ctx::{Assist, AssistCtx}, - utils::TryEnum, - AssistId, -}; +use crate::{utils::TryEnum, AssistContext, AssistId, Assists}; // Assist: replace_let_with_if_let // @@ -39,16 +35,16 @@ use crate::{ // // fn compute() -> Option { None } // ``` -pub(crate) fn replace_let_with_if_let(ctx: AssistCtx) -> Option { +pub(crate) fn replace_let_with_if_let(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let let_kw = ctx.find_token_at_offset(T![let])?; let let_stmt = let_kw.ancestors().find_map(ast::LetStmt::cast)?; let init = let_stmt.initializer()?; let original_pat = let_stmt.pat()?; let ty = ctx.sema.type_of_expr(&init)?; - let happy_variant = TryEnum::from_ty(ctx.sema, &ty).map(|it| it.happy_case()); + let happy_variant = TryEnum::from_ty(&ctx.sema, &ty).map(|it| it.happy_case()); let target = let_kw.text_range(); - ctx.add_assist(AssistId("replace_let_with_if_let"), "Replace with if-let", target, |edit| { + acc.add(AssistId("replace_let_with_if_let"), "Replace with if-let", target, |edit| { let with_placeholder: ast::Pat = match happy_variant { None => make::placeholder_pat().into(), Some(var_name) => make::tuple_struct_pat( diff --git a/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs b/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs index fd41da64b8..1a81d8a0e0 100644 --- a/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs +++ b/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs @@ -1,11 +1,7 @@ use hir; use ra_syntax::{ast, AstNode, SmolStr, TextRange}; -use crate::{ - assist_ctx::{Assist, AssistCtx}, - utils::insert_use_statement, - AssistId, -}; +use crate::{utils::insert_use_statement, AssistContext, AssistId, Assists}; // Assist: replace_qualified_name_with_use // @@ -20,7 +16,10 @@ use crate::{ // // fn process(map: HashMap) {} // ``` -pub(crate) fn replace_qualified_name_with_use(ctx: AssistCtx) -> Option { +pub(crate) fn replace_qualified_name_with_use( + acc: &mut Assists, + ctx: &AssistContext, +) -> Option<()> { let path: ast::Path = ctx.find_node_at_offset()?; // We don't want to mess with use statements if path.syntax().ancestors().find_map(ast::UseItem::cast).is_some() { @@ -34,18 +33,18 @@ pub(crate) fn replace_qualified_name_with_use(ctx: AssistCtx) -> Option } let target = path.syntax().text_range(); - ctx.add_assist( + acc.add( AssistId("replace_qualified_name_with_use"), "Replace qualified path with use", target, - |edit| { + |builder| { let path_to_import = hir_path.mod_path().clone(); - insert_use_statement(path.syntax(), &path_to_import, edit); + insert_use_statement(path.syntax(), &path_to_import, ctx, builder); if let Some(last) = path.segment() { // Here we are assuming the assist will provide a correct use statement // so we can delete the path qualifier - edit.delete(TextRange::new( + builder.delete(TextRange::new( path.syntax().text_range().start(), last.syntax().text_range().start(), )); diff --git a/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs b/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs index c6b73da67b..a46998b8eb 100644 --- a/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs +++ b/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs @@ -5,7 +5,7 @@ use ra_syntax::{ AstNode, }; -use crate::{utils::TryEnum, Assist, AssistCtx, AssistId}; +use crate::{utils::TryEnum, AssistContext, AssistId, Assists}; // Assist: replace_unwrap_with_match // @@ -29,7 +29,7 @@ use crate::{utils::TryEnum, Assist, AssistCtx, AssistId}; // }; // } // ``` -pub(crate) fn replace_unwrap_with_match(ctx: AssistCtx) -> Option { +pub(crate) fn replace_unwrap_with_match(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let method_call: ast::MethodCallExpr = ctx.find_node_at_offset()?; let name = method_call.name_ref()?; if name.text() != "unwrap" { @@ -37,33 +37,26 @@ pub(crate) fn replace_unwrap_with_match(ctx: AssistCtx) -> Option { } let caller = method_call.expr()?; let ty = ctx.sema.type_of_expr(&caller)?; - let happy_variant = TryEnum::from_ty(ctx.sema, &ty)?.happy_case(); + let happy_variant = TryEnum::from_ty(&ctx.sema, &ty)?.happy_case(); let target = method_call.syntax().text_range(); - ctx.add_assist( - AssistId("replace_unwrap_with_match"), - "Replace unwrap with match", - target, - |edit| { - let ok_path = make::path_unqualified(make::path_segment(make::name_ref(happy_variant))); - let it = make::bind_pat(make::name("a")).into(); - let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into(); + acc.add(AssistId("replace_unwrap_with_match"), "Replace unwrap with match", target, |edit| { + let ok_path = make::path_unqualified(make::path_segment(make::name_ref(happy_variant))); + let it = make::bind_pat(make::name("a")).into(); + let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into(); - let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a"))); - let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path)); + let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a"))); + let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path)); - let unreachable_call = make::unreachable_macro_call().into(); - let err_arm = - make::match_arm(iter::once(make::placeholder_pat().into()), unreachable_call); + let unreachable_call = make::unreachable_macro_call().into(); + let err_arm = make::match_arm(iter::once(make::placeholder_pat().into()), unreachable_call); - let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]); - let match_expr = make::expr_match(caller.clone(), match_arm_list); - let match_expr = - IndentLevel::from_node(method_call.syntax()).increase_indent(match_expr); + let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]); + let match_expr = make::expr_match(caller.clone(), match_arm_list); + let match_expr = IndentLevel::from_node(method_call.syntax()).increase_indent(match_expr); - edit.set_cursor(caller.syntax().text_range().start()); - edit.replace_ast::(method_call.into(), match_expr); - }, - ) + edit.set_cursor(caller.syntax().text_range().start()); + edit.replace_ast::(method_call.into(), match_expr); + }) } #[cfg(test)] diff --git a/crates/ra_assists/src/handlers/split_import.rs b/crates/ra_assists/src/handlers/split_import.rs index d495639746..b2757e50ce 100644 --- a/crates/ra_assists/src/handlers/split_import.rs +++ b/crates/ra_assists/src/handlers/split_import.rs @@ -2,7 +2,7 @@ use std::iter::successors; use ra_syntax::{ast, AstNode, T}; -use crate::{Assist, AssistCtx, AssistId}; +use crate::{AssistContext, AssistId, Assists}; // Assist: split_import // @@ -15,7 +15,7 @@ use crate::{Assist, AssistCtx, AssistId}; // ``` // use std::{collections::HashMap}; // ``` -pub(crate) fn split_import(ctx: AssistCtx) -> Option { +pub(crate) fn split_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let colon_colon = ctx.find_token_at_offset(T![::])?; let path = ast::Path::cast(colon_colon.parent())?.qualifier()?; let top_path = successors(Some(path.clone()), |it| it.parent_path()).last()?; @@ -26,10 +26,10 @@ pub(crate) fn split_import(ctx: AssistCtx) -> Option { if new_tree == use_tree { return None; } - let cursor = ctx.frange.range.start(); + let cursor = ctx.offset(); let target = colon_colon.text_range(); - ctx.add_assist(AssistId("split_import"), "Split import", target, |edit| { + acc.add(AssistId("split_import"), "Split import", target, |edit| { edit.replace_ast(use_tree, new_tree); edit.set_cursor(cursor); }) diff --git a/crates/ra_assists/src/handlers/unwrap_block.rs b/crates/ra_assists/src/handlers/unwrap_block.rs index 6df927abbd..eba0631a4c 100644 --- a/crates/ra_assists/src/handlers/unwrap_block.rs +++ b/crates/ra_assists/src/handlers/unwrap_block.rs @@ -1,4 +1,4 @@ -use crate::{Assist, AssistCtx, AssistId}; +use crate::{AssistContext, AssistId, Assists}; use ast::LoopBodyOwner; use ra_fmt::unwrap_trivial_block; @@ -21,7 +21,7 @@ use ra_syntax::{ast, match_ast, AstNode, TextRange, T}; // println!("foo"); // } // ``` -pub(crate) fn unwrap_block(ctx: AssistCtx) -> Option { +pub(crate) fn unwrap_block(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let l_curly_token = ctx.find_token_at_offset(T!['{'])?; let block = ast::BlockExpr::cast(l_curly_token.parent())?; let parent = block.syntax().parent()?; @@ -58,7 +58,7 @@ pub(crate) fn unwrap_block(ctx: AssistCtx) -> Option { }; let target = expr_to_unwrap.syntax().text_range(); - ctx.add_assist(AssistId("unwrap_block"), "Unwrap block", target, |edit| { + acc.add(AssistId("unwrap_block"), "Unwrap block", target, |edit| { edit.set_cursor(expr.syntax().text_range().start()); let pat_start: &[_] = &[' ', '{', '\n']; diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index f4f37614ff..b6dc7cb1bf 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs @@ -10,7 +10,7 @@ macro_rules! eprintln { ($($tt:tt)*) => { stdx::eprintln!($($tt)*) }; } -mod assist_ctx; +mod assist_context; mod marks; #[cfg(test)] mod tests; @@ -18,20 +18,22 @@ 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; +use ra_db::FileRange; +use ra_ide_db::{source_change::SourceChange, RootDatabase}; +use ra_syntax::TextRange; -pub(crate) use crate::assist_ctx::{Assist, AssistCtx}; +pub(crate) use crate::assist_context::{AssistContext, Assists}; /// Unique identifier of the assist, should not be shown to the user /// directly. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct AssistId(pub &'static str); +#[derive(Clone, Debug)] +pub struct GroupLabel(pub String); + #[derive(Debug, Clone)] -pub struct AssistLabel { +pub struct Assist { pub id: AssistId, /// Short description of the assist, as shown in the UI. pub label: String, @@ -41,93 +43,69 @@ pub struct AssistLabel { pub target: TextRange, } -#[derive(Clone, Debug)] -pub struct GroupLabel(pub String); +#[derive(Debug, Clone)] +pub struct ResolvedAssist { + pub assist: Assist, + pub source_change: SourceChange, +} + +impl Assist { + /// Return all the assists applicable at the given position. + /// + /// Assists are returned in the "unresolved" state, that is only labels are + /// returned, without actual edits. + pub fn unresolved(db: &RootDatabase, range: FileRange) -> Vec { + let sema = Semantics::new(db); + let ctx = AssistContext::new(sema, range); + let mut acc = Assists::new_unresolved(&ctx); + handlers::all().iter().for_each(|handler| { + handler(&mut acc, &ctx); + }); + acc.finish_unresolved() + } + + /// Return all the assists applicable at the given position. + /// + /// Assists are returned in the "resolved" state, that is with edit fully + /// computed. + pub fn resolved(db: &RootDatabase, range: FileRange) -> Vec { + let sema = Semantics::new(db); + let ctx = AssistContext::new(sema, range); + let mut acc = Assists::new_resolved(&ctx); + handlers::all().iter().for_each(|handler| { + handler(&mut acc, &ctx); + }); + acc.finish_resolved() + } -impl AssistLabel { pub(crate) fn new( id: AssistId, label: String, group: Option, target: TextRange, - ) -> AssistLabel { + ) -> Assist { // FIXME: make fields private, so that this invariant can't be broken assert!(label.starts_with(|c: char| c.is_uppercase())); - AssistLabel { id, label, group, target } + Assist { id, label, group, target } } } -#[derive(Debug, Clone)] -pub struct AssistAction { - pub edit: TextEdit, - pub cursor_position: Option, - pub file: AssistFile, -} - -#[derive(Debug, Clone)] -pub struct ResolvedAssist { - pub label: AssistLabel, - pub action: AssistAction, -} - -#[derive(Debug, Clone, Copy)] -pub enum AssistFile { - CurrentFile, - TargetFile(FileId), -} - -impl Default for AssistFile { - fn default() -> Self { - Self::CurrentFile - } -} - -/// Return all the assists applicable at the given position. -/// -/// Assists are returned in the "unresolved" state, that is only labels are -/// returned, without actual edits. -pub fn unresolved_assists(db: &RootDatabase, range: FileRange) -> Vec { - let sema = Semantics::new(db); - let ctx = AssistCtx::new(&sema, range, false); - handlers::all() - .iter() - .filter_map(|f| f(ctx.clone())) - .flat_map(|it| it.0) - .map(|a| a.label) - .collect() -} - -/// Return all the assists applicable at the given position. -/// -/// Assists are returned in the "resolved" state, that is with edit fully -/// computed. -pub fn resolved_assists(db: &RootDatabase, range: FileRange) -> Vec { - let sema = Semantics::new(db); - let ctx = AssistCtx::new(&sema, range, true); - let mut a = handlers::all() - .iter() - .filter_map(|f| f(ctx.clone())) - .flat_map(|it| it.0) - .map(|it| it.into_resolved().unwrap()) - .collect::>(); - a.sort_by_key(|it| it.label.target.len()); - a -} - mod handlers { - use crate::{Assist, AssistCtx}; + use crate::{AssistContext, Assists}; - pub(crate) type Handler = fn(AssistCtx) -> Option; + pub(crate) type Handler = fn(&mut Assists, &AssistContext) -> Option<()>; mod add_custom_impl; mod add_derive; mod add_explicit_type; + mod add_from_impl_for_enum; mod add_function; mod add_impl; mod add_missing_impl_members; mod add_new; mod apply_demorgan; mod auto_import; + mod change_return_type_to_result; mod change_visibility; mod early_return; mod fill_match_arms; @@ -144,13 +122,12 @@ mod handlers { mod raw_string; mod remove_dbg; mod remove_mut; + mod reorder_fields; mod replace_if_let_with_match; mod replace_let_with_if_let; mod replace_qualified_name_with_use; mod replace_unwrap_with_match; mod split_import; - mod add_from_impl_for_enum; - mod reorder_fields; mod unwrap_block; pub(crate) fn all() -> &'static [Handler] { @@ -165,6 +142,7 @@ mod handlers { add_new::add_new, apply_demorgan::apply_demorgan, auto_import::auto_import, + change_return_type_to_result::change_return_type_to_result, change_visibility::change_visibility, early_return::convert_to_guarded_return, fill_match_arms::fill_match_arms, diff --git a/crates/ra_assists/src/tests.rs b/crates/ra_assists/src/tests.rs index dd9026df61..a3eacb8f11 100644 --- a/crates/ra_assists/src/tests.rs +++ b/crates/ra_assists/src/tests.rs @@ -11,7 +11,7 @@ use test_utils::{ RangeOrOffset, }; -use crate::{handlers::Handler, resolved_assists, AssistCtx, AssistFile}; +use crate::{handlers::Handler, Assist, AssistContext, Assists}; pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) { let (mut db, file_id) = RootDatabase::with_single_file(text); @@ -41,24 +41,25 @@ fn check_doc_test(assist_id: &str, before: &str, after: &str) { let (db, file_id) = crate::tests::with_single_file(&before); let frange = FileRange { file_id, range: selection.into() }; - let assist = resolved_assists(&db, frange) + let mut assist = Assist::resolved(&db, frange) .into_iter() - .find(|assist| assist.label.id.0 == assist_id) + .find(|assist| assist.assist.id.0 == assist_id) .unwrap_or_else(|| { panic!( "\n\nAssist is not applicable: {}\nAvailable assists: {}", assist_id, - resolved_assists(&db, frange) + Assist::resolved(&db, frange) .into_iter() - .map(|assist| assist.label.id.0) + .map(|assist| assist.assist.id.0) .collect::>() .join(", ") ) }); let actual = { + let change = assist.source_change.source_file_edits.pop().unwrap(); let mut actual = before.clone(); - assist.action.edit.apply(&mut actual); + change.edit.apply(&mut actual); actual }; assert_eq_text!(after, &actual); @@ -70,7 +71,7 @@ enum ExpectedResult<'a> { Target(&'a str), } -fn check(assist: Handler, before: &str, expected: ExpectedResult) { +fn check(handler: 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); db.set_local_roots(Arc::new(vec![db.file_source_root(position.file_id)])); @@ -89,36 +90,36 @@ fn check(assist: Handler, before: &str, expected: ExpectedResult) { let frange = FileRange { file_id: file_with_caret_id, range: range_or_offset.into() }; let sema = Semantics::new(&db); - let assist_ctx = AssistCtx::new(&sema, frange, true); - - match (assist(assist_ctx), expected) { + let ctx = AssistContext::new(sema, frange); + let mut acc = Assists::new_resolved(&ctx); + handler(&mut acc, &ctx); + let mut res = acc.finish_resolved(); + let assist = res.pop(); + match (assist, expected) { (Some(assist), ExpectedResult::After(after)) => { - let action = assist.0[0].action.clone().unwrap(); + let mut source_change = assist.source_change; + let change = source_change.source_file_edits.pop().unwrap(); - 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 = db.file_text(change.file_id).as_ref().to_owned(); + change.edit.apply(&mut actual); - match action.cursor_position { + match source_change.cursor_position { None => { if let RangeOrOffset::Offset(before_cursor_pos) = range_or_offset { - let off = action + let off = change .edit .apply_to_offset(before_cursor_pos) .expect("cursor position is affected by the edit"); actual = add_cursor(&actual, off) } } - Some(off) => actual = add_cursor(&actual, off), + Some(off) => actual = add_cursor(&actual, off.offset), }; assert_eq_text!(after, &actual); } (Some(assist), ExpectedResult::Target(target)) => { - let range = assist.0[0].label.target; + let range = assist.assist.target; assert_eq_text!(&text_without_caret[range], target); } (Some(_), ExpectedResult::NotApplicable) => panic!("assist should not be applicable!"), @@ -135,14 +136,14 @@ fn assist_order_field_struct() { let (before_cursor_pos, before) = extract_offset(before); let (db, file_id) = with_single_file(&before); let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) }; - let assists = resolved_assists(&db, frange); + let assists = Assist::resolved(&db, frange); let mut assists = assists.iter(); assert_eq!( - assists.next().expect("expected assist").label.label, + assists.next().expect("expected assist").assist.label, "Change visibility to pub(crate)" ); - assert_eq!(assists.next().expect("expected assist").label.label, "Add `#[derive]`"); + assert_eq!(assists.next().expect("expected assist").assist.label, "Add `#[derive]`"); } #[test] @@ -158,9 +159,9 @@ fn assist_order_if_expr() { let (range, before) = extract_range(before); let (db, file_id) = with_single_file(&before); let frange = FileRange { file_id, range }; - let assists = resolved_assists(&db, frange); + let assists = Assist::resolved(&db, frange); let mut assists = assists.iter(); - assert_eq!(assists.next().expect("expected assist").label.label, "Extract into variable"); - assert_eq!(assists.next().expect("expected assist").label.label, "Replace with match"); + assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable"); + assert_eq!(assists.next().expect("expected assist").assist.label, "Replace with match"); } diff --git a/crates/ra_assists/src/tests/generated.rs b/crates/ra_assists/src/tests/generated.rs index 7d35fa2846..972dbd251d 100644 --- a/crates/ra_assists/src/tests/generated.rs +++ b/crates/ra_assists/src/tests/generated.rs @@ -249,6 +249,19 @@ pub mod std { pub mod collections { pub struct HashMap { } } } ) } +#[test] +fn doctest_change_return_type_to_result() { + check_doc_test( + "change_return_type_to_result", + r#####" +fn foo() -> i32<|> { 42i32 } +"#####, + r#####" +fn foo() -> Result { Ok(42i32) } +"#####, + ) +} + #[test] fn doctest_change_visibility() { check_doc_test( diff --git a/crates/ra_assists/src/utils/insert_use.rs b/crates/ra_assists/src/utils/insert_use.rs index c1f447efe7..1214e3cd47 100644 --- a/crates/ra_assists/src/utils/insert_use.rs +++ b/crates/ra_assists/src/utils/insert_use.rs @@ -2,7 +2,6 @@ // FIXME: rewrite according to the plan, outlined in // https://github.com/rust-analyzer/rust-analyzer/issues/3301#issuecomment-592931553 -use crate::assist_ctx::ActionBuilder; use hir::{self, ModPath}; use ra_syntax::{ ast::{self, NameOwner}, @@ -12,6 +11,8 @@ use ra_syntax::{ }; use ra_text_edit::TextEditBuilder; +use crate::assist_context::{AssistBuilder, AssistContext}; + /// Creates and inserts a use statement for the given path to import. /// The use statement is inserted in the scope most appropriate to the /// the cursor position given, additionally merged with the existing use imports. @@ -19,10 +20,11 @@ pub(crate) fn insert_use_statement( // Ideally the position of the cursor, used to position: &SyntaxNode, path_to_import: &ModPath, - edit: &mut ActionBuilder, + ctx: &AssistContext, + builder: &mut AssistBuilder, ) { let target = path_to_import.to_string().split("::").map(SmolStr::new).collect::>(); - let container = edit.ctx().sema.ancestors_with_macros(position.clone()).find_map(|n| { + let container = ctx.sema.ancestors_with_macros(position.clone()).find_map(|n| { if let Some(module) = ast::Module::cast(n.clone()) { return module.item_list().map(|it| it.syntax().clone()); } @@ -31,7 +33,7 @@ pub(crate) fn insert_use_statement( if let Some(container) = container { let action = best_action_for_target(container, position.clone(), &target); - make_assist(&action, &target, edit.text_edit_builder()); + make_assist(&action, &target, builder.text_edit_builder()); } } diff --git a/crates/ra_cfg/src/lib.rs b/crates/ra_cfg/src/lib.rs index 51d953f6e8..57feabcb27 100644 --- a/crates/ra_cfg/src/lib.rs +++ b/crates/ra_cfg/src/lib.rs @@ -2,8 +2,6 @@ mod cfg_expr; -use std::iter::IntoIterator; - use ra_syntax::SmolStr; use rustc_hash::FxHashSet; @@ -48,9 +46,4 @@ impl CfgOptions { pub fn insert_key_value(&mut self, key: SmolStr, value: SmolStr) { self.key_values.insert((key, value)); } - - /// Shortcut to set features - pub fn insert_features(&mut self, iter: impl IntoIterator) { - iter.into_iter().for_each(|feat| self.insert_key_value("feature".into(), feat)); - } } diff --git a/crates/ra_db/src/fixture.rs b/crates/ra_db/src/fixture.rs index 8248684eea..51d4c493e3 100644 --- a/crates/ra_db/src/fixture.rs +++ b/crates/ra_db/src/fixture.rs @@ -1,4 +1,48 @@ -//! FIXME: write short doc here +//! Fixtures are strings containing rust source code with optional metadata. +//! A fixture without metadata is parsed into a single source file. +//! Use this to test functionality local to one file. +//! +//! Example: +//! ``` +//! r#" +//! fn main() { +//! println!("Hello World") +//! } +//! "# +//! ``` +//! +//! Metadata can be added to a fixture after a `//-` comment. +//! The basic form is specifying filenames, +//! which is also how to define multiple files in a single test fixture +//! +//! Example: +//! ``` +//! " +//! //- /main.rs +//! mod foo; +//! fn main() { +//! foo::bar(); +//! } +//! +//! //- /foo.rs +//! pub fn bar() {} +//! " +//! ``` +//! +//! Metadata allows specifying all settings and variables +//! that are available in a real rust project: +//! - crate names via `crate:cratename` +//! - dependencies via `deps:dep1,dep2` +//! - configuration settings via `cfg:dbg=false,opt_level=2` +//! - environment variables via `env:PATH=/bin,RUST_LOG=debug` +//! +//! Example: +//! ``` +//! " +//! //- /lib.rs crate:foo deps:bar,baz cfg:foo=a,bar=b env:OUTDIR=path/to,OTHER=foo +//! fn insert_source_code_here() {} +//! " +//! ``` use std::str::FromStr; use std::sync::Arc; diff --git a/crates/ra_flycheck/Cargo.toml b/crates/ra_flycheck/Cargo.toml index 3d5093264e..03e5571484 100644 --- a/crates/ra_flycheck/Cargo.toml +++ b/crates/ra_flycheck/Cargo.toml @@ -14,6 +14,7 @@ log = "0.4.8" cargo_metadata = "0.9.1" serde_json = "1.0.48" jod-thread = "0.1.1" +ra_toolchain = { path = "../ra_toolchain" } [dev-dependencies] insta = "0.16.0" diff --git a/crates/ra_flycheck/src/lib.rs b/crates/ra_flycheck/src/lib.rs index f27252949b..68dcee2851 100644 --- a/crates/ra_flycheck/src/lib.rs +++ b/crates/ra_flycheck/src/lib.rs @@ -4,7 +4,6 @@ mod conv; use std::{ - env, io::{self, BufRead, BufReader}, path::PathBuf, process::{Command, Stdio}, @@ -216,10 +215,10 @@ impl FlycheckThread { let mut cmd = match &self.config { FlycheckConfig::CargoCommand { command, all_targets, all_features, extra_args } => { - let mut cmd = Command::new(cargo_binary()); + let mut cmd = Command::new(ra_toolchain::cargo()); cmd.arg(command); - cmd.args(&["--workspace", "--message-format=json", "--manifest-path"]); - cmd.arg(self.workspace_root.join("Cargo.toml")); + cmd.args(&["--workspace", "--message-format=json", "--manifest-path"]) + .arg(self.workspace_root.join("Cargo.toml")); if *all_targets { cmd.arg("--all-targets"); } @@ -337,7 +336,3 @@ fn run_cargo( Ok(()) } - -fn cargo_binary() -> String { - env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()) -} diff --git a/crates/ra_hir_ty/src/_match.rs b/crates/ra_hir_ty/src/_match.rs index 779e785745..149f650424 100644 --- a/crates/ra_hir_ty/src/_match.rs +++ b/crates/ra_hir_ty/src/_match.rs @@ -573,14 +573,20 @@ pub(crate) fn is_useful( matrix: &Matrix, v: &PatStack, ) -> MatchCheckResult { - // Handle the special case of enums with no variants. In that case, no match - // arm is useful. - if let Ty::Apply(ApplicationTy { ctor: TypeCtor::Adt(AdtId::EnumId(enum_id)), .. }) = - cx.infer[cx.match_expr].strip_references() - { - if cx.db.enum_data(*enum_id).variants.is_empty() { + // Handle two special cases: + // - enum with no variants + // - `!` type + // In those cases, no match arm is useful. + match cx.infer[cx.match_expr].strip_references() { + Ty::Apply(ApplicationTy { ctor: TypeCtor::Adt(AdtId::EnumId(enum_id)), .. }) => { + if cx.db.enum_data(*enum_id).variants.is_empty() { + return Ok(Usefulness::NotUseful); + } + } + Ty::Apply(ApplicationTy { ctor: TypeCtor::Never, .. }) => { return Ok(Usefulness::NotUseful); } + _ => (), } if v.is_empty() { @@ -1917,6 +1923,17 @@ mod tests { check_no_diagnostic(content); } + #[test] + fn type_never() { + let content = r" + fn test_fn(never: !) { + match never {} + } + "; + + check_no_diagnostic(content); + } + #[test] fn enum_never_ref() { let content = r" diff --git a/crates/ra_hir_ty/src/infer/expr.rs b/crates/ra_hir_ty/src/infer/expr.rs index 83f946eeea..614c352a0c 100644 --- a/crates/ra_hir_ty/src/infer/expr.rs +++ b/crates/ra_hir_ty/src/infer/expr.rs @@ -501,8 +501,8 @@ impl<'a> InferenceContext<'a> { } Literal::ByteString(..) => { let byte_type = Ty::simple(TypeCtor::Int(Uncertain::Known(IntTy::u8()))); - let slice_type = Ty::apply_one(TypeCtor::Slice, byte_type); - Ty::apply_one(TypeCtor::Ref(Mutability::Shared), slice_type) + let array_type = Ty::apply_one(TypeCtor::Array, byte_type); + Ty::apply_one(TypeCtor::Ref(Mutability::Shared), array_type) } Literal::Char(..) => Ty::simple(TypeCtor::Char), Literal::Int(_v, ty) => Ty::simple(TypeCtor::Int((*ty).into())), diff --git a/crates/ra_hir_ty/src/tests/method_resolution.rs b/crates/ra_hir_ty/src/tests/method_resolution.rs index ab87f598a6..67f964ab5d 100644 --- a/crates/ra_hir_ty/src/tests/method_resolution.rs +++ b/crates/ra_hir_ty/src/tests/method_resolution.rs @@ -17,8 +17,8 @@ impl [T] { #[lang = "slice_alloc"] impl [T] {} -fn test() { - <[_]>::foo(b"foo"); +fn test(x: &[u8]) { + <[_]>::foo(x); } "#), @r###" @@ -26,10 +26,11 @@ fn test() { 56..79 '{ ... }': T 66..73 'loop {}': ! 71..73 '{}': () - 133..160 '{ ...o"); }': () - 139..149 '<[_]>::foo': fn foo(&[u8]) -> u8 - 139..157 '<[_]>:..."foo")': u8 - 150..156 'b"foo"': &[u8] + 131..132 'x': &[u8] + 141..163 '{ ...(x); }': () + 147..157 '<[_]>::foo': fn foo(&[u8]) -> u8 + 147..160 '<[_]>::foo(x)': u8 + 158..159 'x': &[u8] "### ); } diff --git a/crates/ra_hir_ty/src/tests/simple.rs b/crates/ra_hir_ty/src/tests/simple.rs index 3d3088965d..e17a179004 100644 --- a/crates/ra_hir_ty/src/tests/simple.rs +++ b/crates/ra_hir_ty/src/tests/simple.rs @@ -414,7 +414,7 @@ fn test() { 27..31 '5f32': f32 37..41 '5f64': f64 47..54 '"hello"': &str - 60..68 'b"bytes"': &[u8] + 60..68 'b"bytes"': &[u8; _] 74..77 ''c'': char 83..87 'b'b'': u8 93..97 '3.14': f64 @@ -422,7 +422,7 @@ fn test() { 113..118 'false': bool 124..128 'true': bool 134..202 'r#" ... "#': &str - 208..218 'br#"yolo"#': &[u8] + 208..218 'br#"yolo"#': &[u8; _] "### ); } diff --git a/crates/ra_ide/src/assists.rs b/crates/ra_ide/src/assists.rs deleted file mode 100644 index 389339a034..0000000000 --- a/crates/ra_ide/src/assists.rs +++ /dev/null @@ -1,42 +0,0 @@ -//! FIXME: write short doc here - -use ra_assists::{resolved_assists, AssistAction}; -use ra_db::{FilePosition, FileRange}; -use ra_ide_db::RootDatabase; - -use crate::{FileId, SourceChange, SourceFileEdit}; - -pub use ra_assists::AssistId; - -#[derive(Debug)] -pub struct Assist { - pub id: AssistId, - pub label: String, - pub group_label: Option, - pub source_change: SourceChange, -} - -pub(crate) fn assists(db: &RootDatabase, frange: FileRange) -> Vec { - resolved_assists(db, frange) - .into_iter() - .map(|assist| { - let file_id = frange.file_id; - Assist { - 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, 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(label, file_edit) - .with_cursor_opt(action.cursor_position.map(|offset| FilePosition { offset, file_id })) -} diff --git a/crates/ra_ide/src/completion/completion_item.rs b/crates/ra_ide/src/completion/completion_item.rs index 383b23ac44..6021f7279f 100644 --- a/crates/ra_ide/src/completion/completion_item.rs +++ b/crates/ra_ide/src/completion/completion_item.rs @@ -2,11 +2,12 @@ use std::fmt; -use super::completion_config::SnippetCap; use hir::Documentation; use ra_syntax::TextRange; use ra_text_edit::TextEdit; +use crate::completion::completion_config::SnippetCap; + /// `CompletionItem` describes a single completion variant in the editor pop-up. /// It is basically a POD with various properties. To construct a /// `CompletionItem`, use `new` method and the `Builder` struct. diff --git a/crates/ra_ide/src/display/function_signature.rs b/crates/ra_ide/src/display/function_signature.rs index db3907fe64..f16d422764 100644 --- a/crates/ra_ide/src/display/function_signature.rs +++ b/crates/ra_ide/src/display/function_signature.rs @@ -1,5 +1,7 @@ //! FIXME: write short doc here +// FIXME: this modules relies on strings and AST way too much, and it should be +// rewritten (matklad 2020-05-07) use std::{ convert::From, fmt::{self, Display}, @@ -202,7 +204,11 @@ impl From<&'_ ast::FnDef> for FunctionSignature { res.extend(param_list.params().map(|param| param.syntax().text().to_string())); res_types.extend(param_list.params().map(|param| { - param.syntax().text().to_string().split(':').nth(1).unwrap()[1..].to_string() + let param_text = param.syntax().text().to_string(); + match param_text.split(':').nth(1) { + Some(it) => it[1..].to_string(), + None => param_text, + } })); } (has_self_param, res, res_types) diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index 54d3188582..06d4f1c639 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs @@ -143,7 +143,7 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option from_def_source(db, it, mod_path), ModuleDef::BuiltinType(it) => Some(it.to_string()), }, - Definition::Local(it) => Some(rust_code_markup(&it.ty(db).display_truncated(db, None))), + Definition::Local(it) => Some(rust_code_markup(&it.ty(db).display(db))), Definition::TypeParam(_) | Definition::SelfType(_) => { // FIXME: Hover for generic param None @@ -208,7 +208,7 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option { + a: A, + b: B, + c: C, + } + + struct FakeIter { + inner: I, + } + + struct OtherStruct { + i: T, + } + + enum FakeOption { + Some(T), + None, + } + + fn scan(a: A, b: B, c: C) -> FakeIter, B, C>> { + FakeIter { inner: Scan { a, b, c } } + } + + fn main() { + let num: i32 = 55; + let closure = |memo: &mut u32, value: &u32, _another: &mut u32| -> FakeOption { + FakeOption::Some(*memo + value) + }; + let number = 5u32; + let mut iter<|> = scan(OtherStruct { i: num }, closure, number); + } + "#, + &["FakeIter>, |&mut u32, &u32, &mut u32| -> FakeOption, u32>>"], + ); + } + #[test] fn hover_shows_fn_signature() { // Single file with result @@ -405,7 +446,7 @@ mod tests { } #[test] - fn hover_omits_default_generic_types() { + fn hover_default_generic_types() { check_hover_result( r#" //- /main.rs @@ -417,7 +458,7 @@ struct Test { fn main() { let zz<|> = Test { t: 23, k: 33 }; }"#, - &["Test"], + &["Test"], ); } diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs index 4ed02f60ef..915199bd87 100644 --- a/crates/ra_ide/src/lib.rs +++ b/crates/ra_ide/src/lib.rs @@ -31,7 +31,6 @@ mod syntax_highlighting; mod parent_module; mod references; mod impls; -mod assists; mod diagnostics; mod syntax_tree; mod folding_ranges; @@ -64,7 +63,6 @@ use ra_syntax::{SourceFile, TextRange, TextSize}; use crate::display::ToNav; pub use crate::{ - assists::{Assist, AssistId}, call_hierarchy::CallItem, completion::{ CompletionConfig, CompletionItem, CompletionItemKind, CompletionScore, InsertTextFormat, @@ -84,6 +82,7 @@ pub use crate::{ }; pub use hir::Documentation; +pub use ra_assists::AssistId; pub use ra_db::{ Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRootId, }; @@ -134,10 +133,12 @@ pub struct AnalysisHost { db: RootDatabase, } -impl Default for AnalysisHost { - fn default() -> AnalysisHost { - AnalysisHost::new(None) - } +#[derive(Debug)] +pub struct Assist { + pub id: AssistId, + pub label: String, + pub group_label: Option, + pub source_change: SourceChange, } impl AnalysisHost { @@ -187,6 +188,12 @@ impl AnalysisHost { } } +impl Default for AnalysisHost { + fn default() -> AnalysisHost { + AnalysisHost::new(None) + } +} + /// Analysis is a snapshot of a world state at a moment in time. It is the main /// entry point for asking semantic information about the world. When the world /// state is advanced using `AnalysisHost::apply_change` method, all existing @@ -464,7 +471,17 @@ impl Analysis { /// Computes assists (aka code actions aka intentions) for the given /// position. pub fn assists(&self, frange: FileRange) -> Cancelable> { - self.with_db(|db| assists::assists(db, frange)) + self.with_db(|db| { + ra_assists::Assist::resolved(db, frange) + .into_iter() + .map(|assist| Assist { + id: assist.assist.id, + label: assist.assist.label, + group_label: assist.assist.group.map(|it| it.0), + source_change: assist.source_change, + }) + .collect() + }) } /// Computes the set of diagnostics for the given file. diff --git a/crates/ra_ide/src/references/rename.rs b/crates/ra_ide/src/references/rename.rs index 0398d53bc9..2cbb82c1a4 100644 --- a/crates/ra_ide/src/references/rename.rs +++ b/crates/ra_ide/src/references/rename.rs @@ -712,6 +712,68 @@ mod tests { "###); } + #[test] + fn test_enum_variant_from_module_1() { + test_rename( + r#" + mod foo { + pub enum Foo { + Bar<|>, + } + } + + fn func(f: foo::Foo) { + match f { + foo::Foo::Bar => {} + } + } + "#, + "Baz", + r#" + mod foo { + pub enum Foo { + Baz, + } + } + + fn func(f: foo::Foo) { + match f { + foo::Foo::Baz => {} + } + } + "#, + ); + } + + #[test] + fn test_enum_variant_from_module_2() { + test_rename( + r#" + mod foo { + pub struct Foo { + pub bar<|>: uint, + } + } + + fn foo(f: foo::Foo) { + let _ = f.bar; + } + "#, + "baz", + r#" + mod foo { + pub struct Foo { + pub baz: uint, + } + } + + fn foo(f: foo::Foo) { + let _ = f.baz; + } + "#, + ); + } + fn test_rename(text: &str, new_name: &str, expected: &str) { let (analysis, position) = single_file_with_position(text); let source_change = analysis.rename(position, new_name).unwrap(); diff --git a/crates/ra_ide_db/src/defs.rs b/crates/ra_ide_db/src/defs.rs index 40d0e77b5f..f990e3bb97 100644 --- a/crates/ra_ide_db/src/defs.rs +++ b/crates/ra_ide_db/src/defs.rs @@ -6,7 +6,7 @@ // FIXME: this badly needs rename/rewrite (matklad, 2020-02-06). use hir::{ - Field, HasVisibility, ImplDef, Local, MacroDef, Module, ModuleDef, Name, PathResolution, + Adt, Field, HasVisibility, ImplDef, Local, MacroDef, Module, ModuleDef, Name, PathResolution, Semantics, TypeParam, Visibility, }; use ra_prof::profile; @@ -47,7 +47,13 @@ impl Definition { match self { Definition::Macro(_) => None, Definition::Field(sf) => Some(sf.visibility(db)), - Definition::ModuleDef(def) => module?.visibility_of(db, def), + Definition::ModuleDef(def) => match def { + ModuleDef::EnumVariant(id) => { + let parent = id.parent_enum(db); + module?.visibility_of(db, &ModuleDef::Adt(Adt::Enum(parent))) + } + _ => module?.visibility_of(db, def), + }, Definition::SelfType(_) => None, Definition::Local(_) => None, Definition::TypeParam(_) => None, diff --git a/crates/ra_ide_db/src/source_change.rs b/crates/ra_ide_db/src/source_change.rs index 4dd01b3122..af81a91a4a 100644 --- a/crates/ra_ide_db/src/source_change.rs +++ b/crates/ra_ide_db/src/source_change.rs @@ -6,7 +6,7 @@ use ra_db::{FileId, FilePosition, RelativePathBuf, SourceRootId}; use ra_text_edit::{TextEdit, TextSize}; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct SourceChange { /// For display in the undo log in the editor pub label: String, @@ -90,13 +90,13 @@ impl SourceChange { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct SourceFileEdit { pub file_id: FileId, pub edit: TextEdit, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum FileSystemEdit { CreateFile { source_root: SourceRootId, path: RelativePathBuf }, MoveFile { src: FileId, dst_source_root: SourceRootId, dst_path: RelativePathBuf }, diff --git a/crates/ra_project_model/Cargo.toml b/crates/ra_project_model/Cargo.toml index 5e651fe70d..a32a5daabd 100644 --- a/crates/ra_project_model/Cargo.toml +++ b/crates/ra_project_model/Cargo.toml @@ -14,8 +14,9 @@ rustc-hash = "1.1.0" cargo_metadata = "0.9.1" ra_arena = { path = "../ra_arena" } -ra_db = { path = "../ra_db" } ra_cfg = { path = "../ra_cfg" } +ra_db = { path = "../ra_db" } +ra_toolchain = { path = "../ra_toolchain" } ra_proc_macro = { path = "../ra_proc_macro" } serde = { version = "1.0.106", features = ["derive"] } diff --git a/crates/ra_project_model/src/cargo_workspace.rs b/crates/ra_project_model/src/cargo_workspace.rs index 59f46a2a05..082af4f969 100644 --- a/crates/ra_project_model/src/cargo_workspace.rs +++ b/crates/ra_project_model/src/cargo_workspace.rs @@ -1,7 +1,6 @@ //! FIXME: write short doc here use std::{ - env, ffi::OsStr, ops, path::{Path, PathBuf}, @@ -87,6 +86,7 @@ pub struct PackageData { pub dependencies: Vec, pub edition: Edition, pub features: Vec, + pub cfgs: Vec, pub out_dir: Option, pub proc_macro_dylib_path: Option, } @@ -145,12 +145,8 @@ impl CargoWorkspace { cargo_toml: &Path, cargo_features: &CargoConfig, ) -> Result { - let _ = Command::new(cargo_binary()) - .arg("--version") - .output() - .context("failed to run `cargo --version`, is `cargo` in PATH?")?; - let mut meta = MetadataCommand::new(); + meta.cargo_path(ra_toolchain::cargo()); meta.manifest_path(cargo_toml); if cargo_features.all_features { meta.features(CargoOpt::AllFeatures); @@ -172,10 +168,12 @@ impl CargoWorkspace { })?; let mut out_dir_by_id = FxHashMap::default(); + let mut cfgs = FxHashMap::default(); let mut proc_macro_dylib_paths = FxHashMap::default(); if cargo_features.load_out_dirs_from_check { let resources = load_extern_resources(cargo_toml, cargo_features)?; out_dir_by_id = resources.out_dirs; + cfgs = resources.cfgs; proc_macro_dylib_paths = resources.proc_dylib_paths; } @@ -201,6 +199,7 @@ impl CargoWorkspace { edition, dependencies: Vec::new(), features: Vec::new(), + cfgs: cfgs.get(&id).cloned().unwrap_or_default(), out_dir: out_dir_by_id.get(&id).cloned(), proc_macro_dylib_path: proc_macro_dylib_paths.get(&id).cloned(), }); @@ -282,13 +281,14 @@ impl CargoWorkspace { pub struct ExternResources { out_dirs: FxHashMap, proc_dylib_paths: FxHashMap, + cfgs: FxHashMap>, } pub fn load_extern_resources( cargo_toml: &Path, cargo_features: &CargoConfig, ) -> Result { - let mut cmd = Command::new(cargo_binary()); + let mut cmd = Command::new(ra_toolchain::cargo()); cmd.args(&["check", "--message-format=json", "--manifest-path"]).arg(cargo_toml); if cargo_features.all_features { cmd.arg("--all-features"); @@ -307,8 +307,14 @@ pub fn load_extern_resources( for message in cargo_metadata::parse_messages(output.stdout.as_slice()) { if let Ok(message) = message { match message { - Message::BuildScriptExecuted(BuildScript { package_id, out_dir, .. }) => { - res.out_dirs.insert(package_id, out_dir); + Message::BuildScriptExecuted(BuildScript { package_id, out_dir, cfgs, .. }) => { + res.out_dirs.insert(package_id.clone(), out_dir); + res.cfgs.insert( + package_id, + // FIXME: Current `cargo_metadata` uses `PathBuf` instead of `String`, + // change when https://github.com/oli-obk/cargo_metadata/pulls/112 reaches crates.io + cfgs.iter().filter_map(|c| c.to_str().map(|s| s.to_owned())).collect(), + ); } Message::CompilerArtifact(message) => { @@ -336,7 +342,3 @@ fn is_dylib(path: &Path) -> bool { Some(ext) => matches!(ext.as_str(), "dll" | "dylib" | "so"), } } - -fn cargo_binary() -> String { - env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()) -} diff --git a/crates/ra_project_model/src/lib.rs b/crates/ra_project_model/src/lib.rs index c2b33c1dca..5a0a87ed7d 100644 --- a/crates/ra_project_model/src/lib.rs +++ b/crates/ra_project_model/src/lib.rs @@ -8,7 +8,7 @@ use std::{ fs::{read_dir, File, ReadDir}, io::{self, BufReader}, path::{Path, PathBuf}, - process::Command, + process::{Command, Output}, }; use anyhow::{bail, Context, Result}; @@ -398,7 +398,18 @@ impl ProjectWorkspace { let edition = cargo[pkg].edition; let cfg_options = { let mut opts = default_cfg_options.clone(); - opts.insert_features(cargo[pkg].features.iter().map(Into::into)); + for feature in cargo[pkg].features.iter() { + opts.insert_key_value("feature".into(), feature.into()); + } + for cfg in cargo[pkg].cfgs.iter() { + match cfg.find('=') { + Some(split) => opts.insert_key_value( + cfg[..split].into(), + cfg[split + 1..].trim_matches('"').into(), + ), + None => opts.insert_atom(cfg.into()), + }; + } opts }; let mut env = Env::default(); @@ -556,25 +567,18 @@ pub fn get_rustc_cfg_options(target: Option<&String>) -> CfgOptions { } } - match (|| -> Result { + let rustc_cfgs = || -> Result { // `cfg(test)` and `cfg(debug_assertion)` are handled outside, so we suppress them here. - let mut cmd = Command::new("rustc"); + let mut cmd = Command::new(ra_toolchain::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 ({})", - output - .status - .code() - .map_or(String::from("no exit code"), |code| format!("{}", code)) - ); - } + let output = output(cmd)?; Ok(String::from_utf8(output.stdout)?) - })() { + }(); + + match rustc_cfgs { Ok(rustc_cfgs) => { for line in rustc_cfgs.lines() { match line.find('=') { @@ -587,8 +591,16 @@ pub fn get_rustc_cfg_options(target: Option<&String>) -> CfgOptions { } } } - Err(e) => log::error!("failed to get rustc cfgs: {}", e), + Err(e) => log::error!("failed to get rustc cfgs: {:#}", e), } cfg_options } + +fn output(mut cmd: Command) -> Result { + let output = cmd.output().with_context(|| format!("{:?} failed", cmd))?; + if !output.status.success() { + bail!("{:?} failed, {}", cmd, output.status) + } + Ok(output) +} diff --git a/crates/ra_project_model/src/sysroot.rs b/crates/ra_project_model/src/sysroot.rs index 55ff5ad80b..a8a196e64c 100644 --- a/crates/ra_project_model/src/sysroot.rs +++ b/crates/ra_project_model/src/sysroot.rs @@ -1,14 +1,16 @@ //! FIXME: write short doc here -use anyhow::{bail, Context, Result}; use std::{ env, ops, path::{Path, PathBuf}, - process::{Command, Output}, + process::Command, }; +use anyhow::{bail, Result}; use ra_arena::{Arena, Idx}; +use crate::output; + #[derive(Default, Debug, Clone)] pub struct Sysroot { crates: Arena, @@ -84,43 +86,22 @@ impl Sysroot { } } -fn create_command_text(program: &str, args: &[&str]) -> String { - format!("{} {}", program, args.join(" ")) -} - -fn run_command_in_cargo_dir(cargo_toml: &Path, program: &str, args: &[&str]) -> Result { - let output = Command::new(program) - .current_dir(cargo_toml.parent().unwrap()) - .args(args) - .output() - .context(format!("{} failed", create_command_text(program, args)))?; - if !output.status.success() { - match output.status.code() { - Some(code) => bail!( - "failed to run the command: '{}' exited with code {}", - create_command_text(program, args), - code - ), - None => bail!( - "failed to run the command: '{}' terminated by signal", - create_command_text(program, args) - ), - }; - } - Ok(output) -} - fn get_or_install_rust_src(cargo_toml: &Path) -> Result { if let Ok(path) = env::var("RUST_SRC_PATH") { return Ok(path.into()); } - let rustc_output = run_command_in_cargo_dir(cargo_toml, "rustc", &["--print", "sysroot"])?; + let current_dir = cargo_toml.parent().unwrap(); + let mut rustc = Command::new(ra_toolchain::rustc()); + rustc.current_dir(current_dir).args(&["--print", "sysroot"]); + let rustc_output = output(rustc)?; let stdout = String::from_utf8(rustc_output.stdout)?; let sysroot_path = Path::new(stdout.trim()); let src_path = sysroot_path.join("lib/rustlib/src/rust/src"); if !src_path.exists() { - run_command_in_cargo_dir(cargo_toml, "rustup", &["component", "add", "rust-src"])?; + let mut rustup = Command::new(ra_toolchain::rustup()); + rustup.current_dir(current_dir).args(&["component", "add", "rust-src"]); + let _output = output(rustup)?; } if !src_path.exists() { bail!( diff --git a/crates/ra_toolchain/Cargo.toml b/crates/ra_toolchain/Cargo.toml new file mode 100644 index 0000000000..1873fbe167 --- /dev/null +++ b/crates/ra_toolchain/Cargo.toml @@ -0,0 +1,8 @@ +[package] +edition = "2018" +name = "ra_toolchain" +version = "0.1.0" +authors = ["rust-analyzer developers"] + +[dependencies] +home = "0.5.3" diff --git a/crates/ra_toolchain/src/lib.rs b/crates/ra_toolchain/src/lib.rs new file mode 100644 index 0000000000..3c307a0eac --- /dev/null +++ b/crates/ra_toolchain/src/lib.rs @@ -0,0 +1,64 @@ +//! This crate contains a single public function +//! [`get_path_for_executable`](fn.get_path_for_executable.html). +//! See docs there for more information. +use std::{env, iter, path::PathBuf}; + +pub fn cargo() -> PathBuf { + get_path_for_executable("cargo") +} + +pub fn rustc() -> PathBuf { + get_path_for_executable("rustc") +} + +pub fn rustup() -> PathBuf { + get_path_for_executable("rustup") +} + +/// Return a `PathBuf` to use for the given executable. +/// +/// E.g., `get_path_for_executable("cargo")` may return just `cargo` if that +/// gives a valid Cargo executable; or it may return a full path to a valid +/// Cargo. +fn get_path_for_executable(executable_name: &'static str) -> PathBuf { + // The current implementation checks three places for an executable to use: + // 1) Appropriate environment variable (erroring if this is set but not a usable executable) + // example: for cargo, this checks $CARGO environment variable; for rustc, $RUSTC; etc + // 2) `` + // example: for cargo, this tries just `cargo`, which will succeed if `cargo` is on the $PATH + // 3) `~/.cargo/bin/` + // example: for cargo, this tries ~/.cargo/bin/cargo + // It seems that this is a reasonable place to try for cargo, rustc, and rustup + let env_var = executable_name.to_ascii_uppercase(); + if let Some(path) = env::var_os(&env_var) { + return path.into(); + } + + if lookup_in_path(executable_name) { + return executable_name.into(); + } + + if let Some(mut path) = home::home_dir() { + path.push(".cargo"); + path.push("bin"); + path.push(executable_name); + if path.is_file() { + return path; + } + } + executable_name.into() +} + +fn lookup_in_path(exec: &str) -> bool { + let paths = env::var_os("PATH").unwrap_or_default(); + let mut candidates = env::split_paths(&paths).flat_map(|path| { + let candidate = path.join(&exec); + let with_exe = if env::consts::EXE_EXTENSION == "" { + None + } else { + Some(candidate.with_extension(env::consts::EXE_EXTENSION)) + }; + iter::once(candidate).chain(with_exe) + }); + candidates.any(|it| it.is_file()) +} diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs index 15e8305f88..f4353af647 100644 --- a/crates/rust-analyzer/src/main_loop/handlers.rs +++ b/crates/rust-analyzer/src/main_loop/handlers.rs @@ -42,6 +42,7 @@ use crate::{ world::WorldSnapshot, LspError, Result, }; +use ra_project_model::TargetKind; pub fn handle_analyzer_status(world: WorldSnapshot, _: ()) -> Result { let _p = profile("handle_analyzer_status"); @@ -384,16 +385,27 @@ pub fn handle_runnables( let offset = params.position.map(|it| it.conv_with(&line_index)); let mut res = Vec::new(); let workspace_root = world.workspace_root_for(file_id); + let cargo_spec = CargoTargetSpec::for_file(&world, file_id)?; for runnable in world.analysis().runnables(file_id)? { if let Some(offset) = offset { if !runnable.range.contains_inclusive(offset) { continue; } } + // Do not suggest binary run on other target than binary + if let RunnableKind::Bin = runnable.kind { + if let Some(spec) = &cargo_spec { + match spec.target_kind { + TargetKind::Bin => {} + _ => continue, + } + } + } res.push(to_lsp_runnable(&world, file_id, runnable)?); } + // Add `cargo check` and `cargo test` for the whole package - match CargoTargetSpec::for_file(&world, file_id)? { + match cargo_spec { Some(spec) => { for &cmd in ["check", "test"].iter() { res.push(req::Runnable { @@ -831,13 +843,23 @@ pub fn handle_code_lens( let mut lenses: Vec = Default::default(); + let cargo_spec = CargoTargetSpec::for_file(&world, file_id)?; // Gather runnables 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", + RunnableKind::Bin => { + // Do not suggest binary run on other target than binary + match &cargo_spec { + Some(spec) => match spec.target_kind { + TargetKind::Bin => "Run", + _ => continue, + }, + None => continue, + } + } } .to_string(); let mut r = to_lsp_runnable(&world, file_id, runnable)?; diff --git a/crates/rust-analyzer/tests/heavy_tests/main.rs b/crates/rust-analyzer/tests/heavy_tests/main.rs index 1efa5dd632..e459e3a3ce 100644 --- a/crates/rust-analyzer/tests/heavy_tests/main.rs +++ b/crates/rust-analyzer/tests/heavy_tests/main.rs @@ -9,7 +9,8 @@ use lsp_types::{ }; use rust_analyzer::req::{ CodeActionParams, CodeActionRequest, Completion, CompletionParams, DidOpenTextDocument, - Formatting, GotoDefinition, HoverRequest, OnEnter, Runnables, RunnablesParams, + Formatting, GotoDefinition, GotoTypeDefinition, HoverRequest, OnEnter, Runnables, + RunnablesParams, }; use serde_json::json; use tempfile::TempDir; @@ -574,7 +575,7 @@ version = \"0.0.0\" } #[test] -fn resolve_include_concat_env() { +fn out_dirs_check() { if skip_slow_tests() { return; } @@ -597,11 +598,28 @@ fn main() { r#"pub fn message() -> &'static str { "Hello, World!" }"#, ) .unwrap(); + println!("cargo:rustc-cfg=atom_cfg"); + println!("cargo:rustc-cfg=featlike=\"set\""); println!("cargo:rerun-if-changed=build.rs"); } //- src/main.rs include!(concat!(env!("OUT_DIR"), "/hello.rs")); +#[cfg(atom_cfg)] +struct A; +#[cfg(bad_atom_cfg)] +struct A; +#[cfg(featlike = "set")] +struct B; +#[cfg(featlike = "not_set")] +struct B; + +fn main() { + let va = A; + let vb = B; + message(); +} + fn main() { message(); } "###, ) @@ -613,12 +631,98 @@ fn main() { message(); } let res = server.send_request::(GotoDefinitionParams { text_document_position_params: TextDocumentPositionParams::new( server.doc_id("src/main.rs"), - Position::new(2, 15), + Position::new(14, 8), ), work_done_progress_params: Default::default(), partial_result_params: Default::default(), }); assert!(format!("{}", res).contains("hello.rs")); + server.request::( + GotoDefinitionParams { + text_document_position_params: TextDocumentPositionParams::new( + server.doc_id("src/main.rs"), + Position::new(12, 9), + ), + work_done_progress_params: Default::default(), + partial_result_params: Default::default(), + }, + json!([{ + "originSelectionRange": { + "end": { + "character": 10, + "line": 12 + }, + "start": { + "character": 8, + "line": 12 + } + }, + "targetRange": { + "end": { + "character": 9, + "line": 3 + }, + "start": { + "character": 0, + "line": 2 + } + }, + "targetSelectionRange": { + "end": { + "character": 8, + "line": 3 + }, + "start": { + "character": 7, + "line": 3 + } + }, + "targetUri": "file:///[..]src/main.rs" + }]), + ); + server.request::( + GotoDefinitionParams { + text_document_position_params: TextDocumentPositionParams::new( + server.doc_id("src/main.rs"), + Position::new(13, 9), + ), + work_done_progress_params: Default::default(), + partial_result_params: Default::default(), + }, + json!([{ + "originSelectionRange": { + "end": { + "character": 10, + "line": 13 + }, + "start": { + "character": 8, + "line":13 + } + }, + "targetRange": { + "end": { + "character": 9, + "line": 7 + }, + "start": { + "character": 0, + "line":6 + } + }, + "targetSelectionRange": { + "end": { + "character": 8, + "line": 7 + }, + "start": { + "character": 7, + "line": 7 + } + }, + "targetUri": "file:///[..]src/main.rs" + }]), + ); } #[test] diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs index b1365444a8..b13e13af2a 100644 --- a/crates/test_utils/src/lib.rs +++ b/crates/test_utils/src/lib.rs @@ -155,7 +155,7 @@ pub fn add_cursor(text: &str, offset: TextSize) -> String { res } -#[derive(Debug)] +#[derive(Debug, Eq, PartialEq)] pub struct FixtureEntry { pub meta: String, pub text: String, @@ -170,19 +170,26 @@ pub struct FixtureEntry { /// // - other meta /// ``` pub fn parse_fixture(fixture: &str) -> Vec { - let margin = fixture - .lines() - .filter(|it| it.trim_start().starts_with("//-")) - .map(|it| it.len() - it.trim_start().len()) - .next() - .expect("empty fixture"); + let fixture = indent_first_line(fixture); + let margin = fixture_margin(&fixture); let mut lines = fixture .split('\n') // don't use `.lines` to not drop `\r\n` - .filter_map(|line| { + .enumerate() + .filter_map(|(ix, line)| { if line.len() >= margin { assert!(line[..margin].trim().is_empty()); - Some(&line[margin..]) + let line_content = &line[margin..]; + if !line_content.starts_with("//-") { + assert!( + !line_content.contains("//-"), + r#"Metadata line {} has invalid indentation. All metadata lines need to have the same indentation. +The offending line: {:?}"#, + ix, + line + ); + } + Some(line_content) } else { assert!(line.trim().is_empty()); None @@ -202,6 +209,85 @@ pub fn parse_fixture(fixture: &str) -> Vec { res } +/// Adjusts the indentation of the first line to the minimum indentation of the rest of the lines. +/// This allows fixtures to start off in a different indentation, e.g. to align the first line with +/// the other lines visually: +/// ``` +/// let fixture = "//- /lib.rs +/// mod foo; +/// //- /foo.rs +/// fn bar() {} +/// "; +/// assert_eq!(fixture_margin(fixture), +/// " //- /lib.rs +/// mod foo; +/// //- /foo.rs +/// fn bar() {} +/// ") +/// ``` +fn indent_first_line(fixture: &str) -> String { + if fixture.is_empty() { + return String::new(); + } + let mut lines = fixture.lines(); + let first_line = lines.next().unwrap(); + if first_line.contains("//-") { + let rest = lines.collect::>().join("\n"); + let fixed_margin = fixture_margin(&rest); + let fixed_indent = fixed_margin - indent_len(first_line); + format!("\n{}{}\n{}", " ".repeat(fixed_indent), first_line, rest) + } else { + fixture.to_owned() + } +} + +fn fixture_margin(fixture: &str) -> usize { + fixture + .lines() + .filter(|it| it.trim_start().starts_with("//-")) + .map(indent_len) + .next() + .expect("empty fixture") +} + +fn indent_len(s: &str) -> usize { + s.len() - s.trim_start().len() +} + +#[test] +#[should_panic] +fn parse_fixture_checks_further_indented_metadata() { + parse_fixture( + r" + //- /lib.rs + mod bar; + + fn foo() {} + //- /bar.rs + pub fn baz() {} + ", + ); +} + +#[test] +fn parse_fixture_can_handle_unindented_first_line() { + let fixture = "//- /lib.rs + mod foo; + //- /foo.rs + struct Bar; +"; + assert_eq!( + parse_fixture(fixture), + parse_fixture( + "//- /lib.rs +mod foo; +//- /foo.rs +struct Bar; +" + ) + ) +} + /// Same as `parse_fixture`, except it allow empty fixture pub fn parse_single_fixture(fixture: &str) -> Option { if !fixture.lines().any(|it| it.trim_start().starts_with("//-")) { diff --git a/docs/user/assists.md b/docs/user/assists.md index ee515949e9..692fd4f52b 100644 --- a/docs/user/assists.md +++ b/docs/user/assists.md @@ -241,6 +241,18 @@ fn main() { } ``` +## `change_return_type_to_result` + +Change the function's return type to Result. + +```rust +// BEFORE +fn foo() -> i32┃ { 42i32 } + +// AFTER +fn foo() -> Result { Ok(42i32) } +``` + ## `change_visibility` Adds or changes existing visibility specifier. diff --git a/docs/user/readme.adoc b/docs/user/readme.adoc index 69f5b13d6b..f6ce0accf3 100644 --- a/docs/user/readme.adoc +++ b/docs/user/readme.adoc @@ -61,7 +61,7 @@ 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` +* Windows: `%APPDATA%\Code\User\globalStorage\matklad.rust-analyzer` Note that we only support the latest version of VS Code. @@ -139,17 +139,16 @@ If your editor can't find the binary even though the binary is on your `$PATH`, ==== Arch Linux -The `rust-analyzer` binary can be installed from AUR (Arch User Repository): +The `rust-analyzer` binary can be installed from the repos or AUR (Arch User Repository): -- https://aur.archlinux.org/packages/rust-analyzer-bin[`rust-analyzer-bin`] (binary from GitHub releases) -- https://aur.archlinux.org/packages/rust-analyzer[`rust-analyzer`] (built from latest tagged source) -- https://aur.archlinux.org/packages/rust-analyzer-git[`rust-analyzer-git`] (latest git version) +- https://www.archlinux.org/packages/community/x86_64/rust-analyzer/[`rust-analyzer`] (built from latest tagged source) +- https://aur.archlinux.org/packages/rust-analyzer-git[`rust-analyzer-git`] (latest Git version) -Install it with AUR helper of your choice, for example: +Install it with pacman, for example: [source,bash] ---- -$ yay -S rust-analyzer-bin +$ pacman -S rust-analyzer ---- === Emacs @@ -187,7 +186,7 @@ The are several LSP client implementations for vim or neovim: 1. Install LanguageClient-neovim by following the instructions https://github.com/autozimu/LanguageClient-neovim[here] - * The github project wiki has extra tips on configuration + * The GitHub project wiki has extra tips on configuration 2. Configure by adding this to your vim/neovim config file (replacing the existing Rust-specific line if it exists): + @@ -220,17 +219,11 @@ let g:ycm_language_server = ==== ALE -To add the LSP server to https://github.com/dense-analysis/ale[ale]: +To use the LSP server in https://github.com/dense-analysis/ale[ale]: [source,vim] ---- -call ale#linter#Define('rust', { -\ 'name': 'rust-analyzer', -\ 'lsp': 'stdio', -\ 'executable': 'rust-analyzer', -\ 'command': '%e', -\ 'project_root': '.', -\}) +let g:ale_linters = {'rust': ['analyzer']} ---- ==== nvim-lsp diff --git a/editors/code/package.json b/editors/code/package.json index e750412a73..c6fc13519e 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -300,6 +300,11 @@ "default": true, "markdownDescription": "Check with all features (will be passed as `--all-features`)" }, + "rust-analyzer.inlayHints.enable": { + "type": "boolean", + "default": true, + "description": "Disable all inlay hints" + }, "rust-analyzer.inlayHints.typeHints": { "type": "boolean", "default": true, @@ -405,7 +410,7 @@ "ms-vscode.cpptools" ], "default": "auto", - "description": "Preffered debug engine.", + "description": "Preferred debug engine.", "markdownEnumDescriptions": [ "First try to use [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb), if it's not installed try to use [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools).", "Use [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb)", @@ -599,9 +604,18 @@ "union": [ "entity.name.union" ], + "struct": [ + "entity.name.type.struct" + ], "keyword.unsafe": [ "keyword.other.unsafe" ], + "keyword": [ + "keyword" + ], + "keyword.controlFlow": [ + "keyword.control" + ], "variable.constant": [ "entity.name.constant" ] diff --git a/editors/code/src/cargo.ts b/editors/code/src/cargo.ts index a328ba9bd0..2a2c2e0e1b 100644 --- a/editors/code/src/cargo.ts +++ b/editors/code/src/cargo.ts @@ -1,6 +1,9 @@ import * as cp from 'child_process'; +import * as os from 'os'; +import * as path from 'path'; import * as readline from 'readline'; import { OutputChannel } from 'vscode'; +import { isValidExecutable } from './util'; interface CompilationArtifact { fileName: string; @@ -10,17 +13,9 @@ interface CompilationArtifact { } export class Cargo { - rootFolder: string; - env?: Record; - output: OutputChannel; + constructor(readonly rootFolder: string, readonly output: OutputChannel) { } - public constructor(cargoTomlFolder: string, output: OutputChannel, env: Record | undefined = undefined) { - this.rootFolder = cargoTomlFolder; - this.output = output; - this.env = env; - } - - public async artifactsFromArgs(cargoArgs: string[]): Promise { + private async artifactsFromArgs(cargoArgs: string[]): Promise { const artifacts: CompilationArtifact[] = []; try { @@ -37,17 +32,13 @@ export class Cargo { isTest: message.profile.test }); } - } - else if (message.reason === 'compiler-message') { + } else if (message.reason === 'compiler-message') { this.output.append(message.message.rendered); } }, - stderr => { - this.output.append(stderr); - } + stderr => this.output.append(stderr), ); - } - catch (err) { + } catch (err) { this.output.show(true); throw new Error(`Cargo invocation has failed: ${err}`); } @@ -55,9 +46,8 @@ export class Cargo { return artifacts; } - public async executableFromArgs(args: string[]): Promise { - const cargoArgs = [...args]; // to remain args unchanged - cargoArgs.push("--message-format=json"); + async executableFromArgs(args: readonly string[]): Promise { + const cargoArgs = [...args, "--message-format=json"]; const artifacts = await this.artifactsFromArgs(cargoArgs); @@ -70,24 +60,27 @@ export class Cargo { return artifacts[0].fileName; } - runCargo( + private runCargo( cargoArgs: string[], onStdoutJson: (obj: any) => void, onStderrString: (data: string) => void ): Promise { - return new Promise((resolve, reject) => { - const cargo = cp.spawn('cargo', cargoArgs, { + return new Promise((resolve, reject) => { + let cargoPath; + try { + cargoPath = getCargoPathOrFail(); + } catch (err) { + return reject(err); + } + + const cargo = cp.spawn(cargoPath, cargoArgs, { stdio: ['ignore', 'pipe', 'pipe'], - cwd: this.rootFolder, - env: this.env, + cwd: this.rootFolder }); - cargo.on('error', err => { - reject(new Error(`could not launch cargo: ${err}`)); - }); - cargo.stderr.on('data', chunk => { - onStderrString(chunk.toString()); - }); + cargo.on('error', err => reject(new Error(`could not launch cargo: ${err}`))); + + cargo.stderr.on('data', chunk => onStderrString(chunk.toString())); const rl = readline.createInterface({ input: cargo.stdout }); rl.on('line', line => { @@ -103,4 +96,28 @@ export class Cargo { }); }); } -} \ No newline at end of file +} + +// Mirrors `ra_env::get_path_for_executable` implementation +function getCargoPathOrFail(): string { + const envVar = process.env.CARGO; + const executableName = "cargo"; + + if (envVar) { + if (isValidExecutable(envVar)) return envVar; + + throw new Error(`\`${envVar}\` environment variable points to something that's not a valid executable`); + } + + if (isValidExecutable(executableName)) return executableName; + + const standardLocation = path.join(os.homedir(), '.cargo', 'bin', executableName); + + if (isValidExecutable(standardLocation)) return standardLocation; + + throw new Error( + `Failed to find \`${executableName}\` executable. ` + + `Make sure \`${executableName}\` is in \`$PATH\`, ` + + `or set \`${envVar}\` to point to a valid executable.` + ); +} diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index 533be19134..be2e27aecc 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -94,6 +94,7 @@ export class Config { get inlayHints() { return { + enable: this.get("inlayHints.enable"), typeHints: this.get("inlayHints.typeHints"), parameterHints: this.get("inlayHints.parameterHints"), chainingHints: this.get("inlayHints.chainingHints"), diff --git a/editors/code/src/inlay_hints.ts b/editors/code/src/inlay_hints.ts index a095317970..a2b07d0037 100644 --- a/editors/code/src/inlay_hints.ts +++ b/editors/code/src/inlay_hints.ts @@ -10,13 +10,13 @@ export function activateInlayHints(ctx: Ctx) { const maybeUpdater = { updater: null as null | HintsUpdater, async onConfigChange() { - if ( - !ctx.config.inlayHints.typeHints && - !ctx.config.inlayHints.parameterHints && - !ctx.config.inlayHints.chainingHints - ) { - return this.dispose(); - } + const anyEnabled = ctx.config.inlayHints.typeHints + || ctx.config.inlayHints.parameterHints + || ctx.config.inlayHints.chainingHints; + const enabled = ctx.config.inlayHints.enable && anyEnabled; + + if (!enabled) return this.dispose(); + await sleep(100); if (this.updater) { this.updater.syncCacheAndRenderHints(); diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index efd56a84b5..9b020d0019 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts @@ -8,10 +8,9 @@ import { activateInlayHints } from './inlay_hints'; import { activateStatusDisplay } from './status_display'; import { Ctx } from './ctx'; import { Config, NIGHTLY_TAG } from './config'; -import { log, assert } from './util'; +import { log, assert, isValidExecutable } from './util'; import { PersistentState } from './persistent_state'; import { fetchRelease, download } from './net'; -import { spawnSync } from 'child_process'; import { activateTaskProvider } from './tasks'; let ctx: Ctx | undefined; @@ -179,10 +178,7 @@ async function bootstrapServer(config: Config, state: PersistentState): Promise< log.debug("Using server binary at", path); - const res = spawnSync(path, ["--version"], { encoding: 'utf8' }); - log.debug("Checked binary availability via --version", res); - log.debug(res, "--version output:", res.output); - if (res.status !== 0) { + if (!isValidExecutable(path)) { throw new Error(`Failed to execute ${path} --version`); } diff --git a/editors/code/src/util.ts b/editors/code/src/util.ts index 6f91f81d63..127a9e9112 100644 --- a/editors/code/src/util.ts +++ b/editors/code/src/util.ts @@ -1,6 +1,7 @@ import * as lc from "vscode-languageclient"; import * as vscode from "vscode"; import { strict as nativeAssert } from "assert"; +import { spawnSync } from "child_process"; export function assert(condition: boolean, explanation: string): asserts condition { try { @@ -82,3 +83,13 @@ export function isRustDocument(document: vscode.TextDocument): document is RustD export function isRustEditor(editor: vscode.TextEditor): editor is RustEditor { return isRustDocument(editor.document); } + +export function isValidExecutable(path: string): boolean { + log.debug("Checking availability of a binary at", path); + + const res = spawnSync(path, ["--version"], { encoding: 'utf8' }); + + log.debug(res, "--version output:", res.output); + + return res.status === 0; +} diff --git a/xtask/tests/tidy.rs b/xtask/tests/tidy.rs index c4eac1bc4e..b8e8860ba1 100644 --- a/xtask/tests/tidy.rs +++ b/xtask/tests/tidy.rs @@ -136,7 +136,6 @@ impl TidyDocs { } let whitelist = [ - "ra_db", "ra_hir", "ra_hir_expand", "ra_ide",