mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-28 01:34:21 +00:00
Merge remote-tracking branch 'upstream/master' into uniformed_debug_lens
# Conflicts: # editors/code/src/commands/runnables.ts
This commit is contained in:
commit
0ef17ef1ee
50
Cargo.lock
generated
50
Cargo.lock
generated
@ -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",
|
||||
|
233
crates/ra_assists/src/assist_context.rs
Normal file
233
crates/ra_assists/src/assist_context.rs
Normal file
@ -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<SyntaxToken> {
|
||||
self.source_file.syntax().token_at_offset(self.offset())
|
||||
}
|
||||
pub(crate) fn find_token_at_offset(&self, kind: SyntaxKind) -> Option<SyntaxToken> {
|
||||
self.token_at_offset().find(|it| it.kind() == kind)
|
||||
}
|
||||
pub(crate) fn find_node_at_offset<N: AstNode>(&self) -> Option<N> {
|
||||
find_node_at_offset(self.source_file.syntax(), self.offset())
|
||||
}
|
||||
pub(crate) fn find_node_at_offset_with_descend<N: AstNode>(&self) -> Option<N> {
|
||||
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<SourceChange>)>,
|
||||
}
|
||||
|
||||
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<Assist> {
|
||||
assert!(!self.resolve);
|
||||
self.finish()
|
||||
.into_iter()
|
||||
.map(|(label, edit)| {
|
||||
assert!(edit.is_none());
|
||||
label
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn finish_resolved(self) -> Vec<ResolvedAssist> {
|
||||
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<String>,
|
||||
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<String>,
|
||||
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<SourceChange>)> {
|
||||
self.buf.sort_by_key(|(label, _edit)| label.target.len());
|
||||
self.buf
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct AssistBuilder {
|
||||
edit: TextEditBuilder,
|
||||
cursor_position: Option<TextSize>,
|
||||
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<String>) {
|
||||
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<String>) {
|
||||
self.edit.replace(range, replace_with.into())
|
||||
}
|
||||
pub(crate) fn replace_ast<N: AstNode>(&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<String>,
|
||||
) {
|
||||
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)
|
||||
}
|
||||
}
|
@ -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<AssistInfo>);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct AssistInfo {
|
||||
pub(crate) label: AssistLabel,
|
||||
pub(crate) group_label: Option<GroupLabel>,
|
||||
pub(crate) action: Option<AssistAction>,
|
||||
}
|
||||
|
||||
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<ResolvedAssist> {
|
||||
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<String>,
|
||||
target: TextRange,
|
||||
f: impl FnOnce(&mut ActionBuilder),
|
||||
) -> Option<Assist> {
|
||||
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<String>) -> AssistGroup<'a> {
|
||||
let group = GroupLabel(group_name.into());
|
||||
AssistGroup { ctx: self, group, assists: Vec::new() }
|
||||
}
|
||||
|
||||
pub(crate) fn token_at_offset(&self) -> TokenAtOffset<SyntaxToken> {
|
||||
self.source_file.syntax().token_at_offset(self.frange.range.start())
|
||||
}
|
||||
|
||||
pub(crate) fn find_token_at_offset(&self, kind: SyntaxKind) -> Option<SyntaxToken> {
|
||||
self.token_at_offset().find(|it| it.kind() == kind)
|
||||
}
|
||||
|
||||
pub(crate) fn find_node_at_offset<N: AstNode>(&self) -> Option<N> {
|
||||
find_node_at_offset(self.source_file.syntax(), self.frange.range.start())
|
||||
}
|
||||
|
||||
pub(crate) fn find_node_at_offset_with_descend<N: AstNode>(&self) -> Option<N> {
|
||||
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<AssistInfo>,
|
||||
}
|
||||
|
||||
impl<'a> AssistGroup<'a> {
|
||||
pub(crate) fn add_assist(
|
||||
&mut self,
|
||||
id: AssistId,
|
||||
label: impl Into<String>,
|
||||
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<Assist> {
|
||||
if self.assists.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(Assist(self.assists))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct ActionBuilder<'a, 'b> {
|
||||
edit: TextEditBuilder,
|
||||
cursor_position: Option<TextSize>,
|
||||
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<String>) {
|
||||
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<String>,
|
||||
) {
|
||||
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<String>) {
|
||||
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<N: AstNode>(&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 }
|
||||
}
|
||||
}
|
@ -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<Assist> {
|
||||
pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
let input = ctx.find_node_at_offset::<ast::AttrInput>()?;
|
||||
let attr = input.syntax().parent().and_then(ast::Attr::cast)?;
|
||||
|
||||
@ -49,7 +52,7 @@ pub(crate) fn add_custom_impl(ctx: AssistCtx) -> Option<Assist> {
|
||||
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()
|
||||
|
@ -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<Assist> {
|
||||
pub(crate) fn add_derive(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?;
|
||||
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<TextSize> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::tests::{check_assist, check_assist_target};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn add_derive_new() {
|
||||
check_assist(
|
||||
|
@ -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<Assist> {
|
||||
pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
let stmt = ctx.find_node_at_offset::<LetStmt>()?;
|
||||
let expr = stmt.initializer()?;
|
||||
let pat = stmt.pat()?;
|
||||
@ -59,7 +59,7 @@ pub(crate) fn add_explicit_type(ctx: AssistCtx) -> Option<Assist> {
|
||||
|
||||
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,
|
||||
|
@ -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<Assist> {
|
||||
pub(crate) fn add_from_impl_for_enum(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
let variant = ctx.find_node_at_offset::<ast::EnumVariant>()?;
|
||||
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<Assist> {
|
||||
_ => 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,
|
||||
|
@ -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<Assist> {
|
||||
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<Assist> {
|
||||
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<ast::TypeParamList>,
|
||||
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<hir::InFile<hir::ModuleSource>>,
|
||||
) -> Option<Self> {
|
||||
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<ast::Name> {
|
||||
|
||||
/// Computes the type variables and arguments required for the generated function
|
||||
fn fn_args(
|
||||
ctx: &AssistCtx,
|
||||
ctx: &AssistContext,
|
||||
call: &ast::CallExpr,
|
||||
) -> Option<(Option<ast::TypeParamList>, ast::ParamList)> {
|
||||
let mut arg_names = Vec::new();
|
||||
@ -218,7 +218,7 @@ fn fn_arg_name(fn_arg: &ast::Expr) -> Option<String> {
|
||||
}
|
||||
}
|
||||
|
||||
fn fn_arg_type(ctx: &AssistCtx, fn_arg: &ast::Expr) -> Option<String> {
|
||||
fn fn_arg_type(ctx: &AssistContext, fn_arg: &ast::Expr) -> Option<String> {
|
||||
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<GeneratedFu
|
||||
fn next_space_for_fn_in_module(
|
||||
db: &dyn hir::db::AstDatabase,
|
||||
module: hir::InFile<hir::ModuleSource>,
|
||||
) -> 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)]
|
||||
|
@ -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<Assist> {
|
||||
pub(crate) fn add_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?;
|
||||
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)]
|
||||
|
@ -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<Assist> {
|
||||
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<Assist> {
|
||||
//
|
||||
// }
|
||||
// ```
|
||||
pub(crate) fn add_missing_default_members(ctx: AssistCtx) -> Option<Assist> {
|
||||
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<Assist> {
|
||||
}
|
||||
|
||||
fn add_missing_impl_members_inner(
|
||||
ctx: AssistCtx,
|
||||
acc: &mut Assists,
|
||||
ctx: &AssistContext,
|
||||
mode: AddMissingImplMembersMode,
|
||||
assist_id: &'static str,
|
||||
label: &'static str,
|
||||
) -> Option<Assist> {
|
||||
) -> Option<()> {
|
||||
let _p = ra_prof::profile("add_missing_impl_members_inner");
|
||||
let impl_def = ctx.find_node_at_offset::<ast::ImplDef>()?;
|
||||
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)]
|
||||
|
@ -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<Assist> {
|
||||
pub(crate) fn add_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
let strukt = ctx.find_node_at_offset::<ast::StructDef>()?;
|
||||
|
||||
// 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<Assist> {
|
||||
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<Option<ast::ImplDef>> {
|
||||
fn find_struct_impl(ctx: &AssistContext, strukt: &ast::StructDef) -> Option<Option<ast::ImplDef>> {
|
||||
let db = ctx.db;
|
||||
let module = strukt.syntax().ancestors().find(|node| {
|
||||
ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind())
|
||||
|
@ -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<Assist> {
|
||||
pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
let expr = ctx.find_node_at_offset::<ast::BinExpr>()?;
|
||||
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<Assist> {
|
||||
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()));
|
||||
|
@ -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<Assist> {
|
||||
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<Assist> {
|
||||
}
|
||||
|
||||
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<Self> {
|
||||
fn new(ctx: &AssistContext) -> Option<Self> {
|
||||
if let Some(path_under_caret) = ctx.find_node_at_offset_with_descend::<ast::Path>() {
|
||||
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<Self> {
|
||||
fn for_method_call(method_call: ast::MethodCallExpr, ctx: &AssistContext) -> Option<Self> {
|
||||
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<Self> {
|
||||
fn for_regular_path(path_under_caret: ast::Path, ctx: &AssistContext) -> Option<Self> {
|
||||
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<ModPath> {
|
||||
@ -383,7 +386,7 @@ mod tests {
|
||||
}
|
||||
",
|
||||
r"
|
||||
use PubMod1::PubStruct;
|
||||
use PubMod3::PubStruct;
|
||||
|
||||
PubSt<|>ruct
|
||||
|
||||
|
971
crates/ra_assists/src/handlers/change_return_type_to_result.rs
Normal file
971
crates/ra_assists/src/handlers/change_return_type_to_result.rs
Normal file
@ -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<i32, > { Ok(42i32) }
|
||||
// ```
|
||||
pub(crate) fn change_return_type_to_result(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
let fn_def = ctx.find_node_at_offset::<ast::FnDef>();
|
||||
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<SyntaxNode>,
|
||||
}
|
||||
|
||||
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<Vec<NodeType>> {
|
||||
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<NodeType> = 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<TextSize> {
|
||||
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<i32, <|>> {
|
||||
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<i32, <|>> {
|
||||
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<i32, <|>> {
|
||||
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<i32, <|>> {
|
||||
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<i32, <|>> {
|
||||
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<i32, <|>> {
|
||||
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<i32, <|>> {
|
||||
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<i32, <|>> {
|
||||
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<i32, <|>> {
|
||||
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<i32, <|>> {
|
||||
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<i32, <|>> {
|
||||
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<i32, <|>> {
|
||||
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<i32, <|>> {
|
||||
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<i32, <|>> {
|
||||
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<i32, <|>> {
|
||||
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<i32, <|>> {
|
||||
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<u32, <|>> {
|
||||
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<u32, <|>> {
|
||||
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<i32, <|>> {
|
||||
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<i32, <|>> {
|
||||
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<i32, <|>> {
|
||||
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<u32, <|>> {
|
||||
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<u32, <|>> {
|
||||
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<u32, <|>> {
|
||||
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<u32, <|>> {
|
||||
if the_field < 5 {
|
||||
let mut i = 0;
|
||||
|
||||
if i == 5 {
|
||||
return Ok(99);
|
||||
} else {
|
||||
return Ok(0);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(the_field)
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
}
|
@ -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<Assist> {
|
||||
pub(crate) fn change_visibility(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
if let Some(vis) = ctx.find_node_at_offset::<ast::Visibility>() {
|
||||
return change_vis(ctx, vis);
|
||||
return change_vis(acc, vis);
|
||||
}
|
||||
add_vis(ctx)
|
||||
add_vis(acc, ctx)
|
||||
}
|
||||
|
||||
fn add_vis(ctx: AssistCtx) -> Option<Assist> {
|
||||
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<Assist> {
|
||||
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<Assist> {
|
||||
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<Assist> {
|
||||
}
|
||||
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,
|
||||
|
@ -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<Assist> {
|
||||
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<Assist> {
|
||||
}
|
||||
|
||||
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)]
|
||||
|
@ -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<Assist> {
|
||||
pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
let match_expr = ctx.find_node_at_offset::<ast::MatchExpr>()?;
|
||||
let match_arm_list = match_expr.match_arm_list()?;
|
||||
|
||||
@ -93,7 +93,7 @@ pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option<Assist> {
|
||||
}
|
||||
|
||||
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);
|
||||
|
@ -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<Assist> {
|
||||
pub(crate) fn flip_binexpr(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
let expr = ctx.find_node_at_offset::<BinExpr>()?;
|
||||
let lhs = expr.lhs()?.syntax().clone();
|
||||
let rhs = expr.rhs()?.syntax().clone();
|
||||
@ -33,7 +33,7 @@ pub(crate) fn flip_binexpr(ctx: AssistCtx) -> Option<Assist> {
|
||||
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);
|
||||
}
|
||||
|
@ -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<Assist> {
|
||||
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<Assist> {
|
||||
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());
|
||||
})
|
||||
|
@ -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<T: Copy + Clone>() { }
|
||||
// ```
|
||||
pub(crate) fn flip_trait_bound(ctx: AssistCtx) -> Option<Assist> {
|
||||
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<Assist> {
|
||||
);
|
||||
|
||||
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());
|
||||
})
|
||||
|
@ -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<Assist> {
|
||||
pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
let let_stmt = ctx.find_node_at_offset::<ast::LetStmt>()?;
|
||||
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<Assist> {
|
||||
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<Assist> {
|
||||
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)]
|
||||
|
@ -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<Assist> {
|
||||
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<Assist> {
|
||||
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 {
|
||||
|
@ -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<Assist> {
|
||||
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<Assist> {
|
||||
|
||||
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)]
|
||||
|
@ -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<Assist> {
|
||||
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<Assist> {
|
||||
};
|
||||
|
||||
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);
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -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<Assist> {
|
||||
pub(crate) fn merge_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
let current_arm = ctx.find_node_at_offset::<ast::MatchArm>()?;
|
||||
// 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<Assist> {
|
||||
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<Assist> {
|
||||
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 {
|
||||
|
@ -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<Assist> {
|
||||
pub(crate) fn move_bounds_to_where_clause(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
let type_param_list = ctx.find_node_at_offset::<ast::TypeParamList>()?;
|
||||
|
||||
let mut type_params = type_param_list.type_params();
|
||||
@ -50,36 +50,29 @@ pub(crate) fn move_bounds_to_where_clause(ctx: AssistCtx) -> Option<Assist> {
|
||||
};
|
||||
|
||||
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<ast::WherePred> {
|
||||
|
@ -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<Assist> {
|
||||
pub(crate) fn move_guard_to_arm_body(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
let match_arm = ctx.find_node_at_offset::<MatchArm>()?;
|
||||
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<Assist> {
|
||||
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<Assist> {
|
||||
// }
|
||||
// }
|
||||
// ```
|
||||
pub(crate) fn move_arm_cond_to_match_guard(ctx: AssistCtx) -> Option<Assist> {
|
||||
pub(crate) fn move_arm_cond_to_match_guard(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
let match_arm: MatchArm = ctx.find_node_at_offset::<MatchArm>()?;
|
||||
let match_pat = match_arm.pat()?;
|
||||
|
||||
@ -109,7 +109,7 @@ pub(crate) fn move_arm_cond_to_match_guard(ctx: AssistCtx) -> Option<Assist> {
|
||||
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,
|
||||
|
@ -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<Assist> {
|
||||
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<Assist> {
|
||||
// "Hello, \"World!\"";
|
||||
// }
|
||||
// ```
|
||||
pub(crate) fn make_usual_string(ctx: AssistCtx) -> Option<Assist> {
|
||||
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<Assist> {
|
||||
// r##"Hello, World!"##;
|
||||
// }
|
||||
// ```
|
||||
pub(crate) fn add_hash(ctx: AssistCtx) -> Option<Assist> {
|
||||
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<Assist> {
|
||||
// r"Hello, World!";
|
||||
// }
|
||||
// ```
|
||||
pub(crate) fn remove_hash(ctx: AssistCtx) -> Option<Assist> {
|
||||
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<Assist> {
|
||||
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
|
||||
|
@ -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<Assist> {
|
||||
pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?;
|
||||
|
||||
if !is_valid_macrocall(¯o_call, "dbg")? {
|
||||
@ -58,7 +58,7 @@ pub(crate) fn remove_dbg(ctx: AssistCtx) -> Option<Assist> {
|
||||
};
|
||||
|
||||
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);
|
||||
})
|
||||
|
@ -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<Assist> {
|
||||
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<Assist> {
|
||||
};
|
||||
|
||||
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));
|
||||
})
|
||||
|
@ -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<Assist> {
|
||||
reorder::<RecordLit>(ctx.clone()).or_else(|| reorder::<RecordPat>(ctx))
|
||||
pub(crate) fn reorder_fields(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
reorder::<ast::RecordLit>(acc, ctx.clone()).or_else(|| reorder::<ast::RecordPat>(acc, ctx))
|
||||
}
|
||||
|
||||
fn reorder<R: AstNode>(ctx: AssistCtx) -> Option<Assist> {
|
||||
fn reorder<R: AstNode>(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
|
||||
let record = ctx.find_node_at_offset::<R>()?;
|
||||
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<R: AstNode>(ctx: AssistCtx) -> Option<Assist> {
|
||||
}
|
||||
|
||||
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<RootDatabase>) -> Option
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_fields_ranks(path: &Path, ctx: &AssistCtx) -> Option<HashMap<String, usize>> {
|
||||
fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<HashMap<String, usize>> {
|
||||
Some(
|
||||
struct_definition(path, ctx.sema)?
|
||||
struct_definition(path, &ctx.sema)?
|
||||
.fields(ctx.db)
|
||||
.iter()
|
||||
.enumerate()
|
||||
|
@ -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<Assist> {
|
||||
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<Assist> {
|
||||
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::<ast::Expr>(if_expr.into(), match_expr);
|
||||
},
|
||||
)
|
||||
edit.set_cursor(if_expr.syntax().text_range().start());
|
||||
edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr);
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -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<i32> { None }
|
||||
// ```
|
||||
pub(crate) fn replace_let_with_if_let(ctx: AssistCtx) -> Option<Assist> {
|
||||
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(
|
||||
|
@ -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<String, String>) {}
|
||||
// ```
|
||||
pub(crate) fn replace_qualified_name_with_use(ctx: AssistCtx) -> Option<Assist> {
|
||||
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<Assist>
|
||||
}
|
||||
|
||||
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(),
|
||||
));
|
||||
|
@ -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<Assist> {
|
||||
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<Assist> {
|
||||
}
|
||||
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::<ast::Expr>(method_call.into(), match_expr);
|
||||
},
|
||||
)
|
||||
edit.set_cursor(caller.syntax().text_range().start());
|
||||
edit.replace_ast::<ast::Expr>(method_call.into(), match_expr);
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -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<Assist> {
|
||||
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<Assist> {
|
||||
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);
|
||||
})
|
||||
|
@ -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<Assist> {
|
||||
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<Assist> {
|
||||
};
|
||||
|
||||
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'];
|
||||
|
@ -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<Assist> {
|
||||
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<ResolvedAssist> {
|
||||
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<GroupLabel>,
|
||||
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<TextSize>,
|
||||
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<AssistLabel> {
|
||||
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<ResolvedAssist> {
|
||||
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::<Vec<_>>();
|
||||
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<Assist>;
|
||||
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,
|
||||
|
@ -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::<Vec<_>>()
|
||||
.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");
|
||||
}
|
||||
|
@ -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<i32, > { Ok(42i32) }
|
||||
"#####,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doctest_change_visibility() {
|
||||
check_doc_test(
|
||||
|
@ -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::<Vec<_>>();
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<Item = SmolStr>) {
|
||||
iter.into_iter().for_each(|feat| self.insert_key_value("feature".into(), feat));
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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"
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -573,14 +573,20 @@ pub(crate) fn is_useful(
|
||||
matrix: &Matrix,
|
||||
v: &PatStack,
|
||||
) -> MatchCheckResult<Usefulness> {
|
||||
// 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"
|
||||
|
@ -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())),
|
||||
|
@ -17,8 +17,8 @@ impl<T> [T] {
|
||||
#[lang = "slice_alloc"]
|
||||
impl<T> [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]) -> u8
|
||||
139..157 '<[_]>:..."foo")': u8
|
||||
150..156 'b"foo"': &[u8]
|
||||
131..132 'x': &[u8]
|
||||
141..163 '{ ...(x); }': ()
|
||||
147..157 '<[_]>::foo': fn foo<u8>(&[u8]) -> u8
|
||||
147..160 '<[_]>::foo(x)': u8
|
||||
158..159 'x': &[u8]
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
@ -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; _]
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
@ -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<String>,
|
||||
pub source_change: SourceChange,
|
||||
}
|
||||
|
||||
pub(crate) fn assists(db: &RootDatabase, frange: FileRange) -> Vec<Assist> {
|
||||
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 }))
|
||||
}
|
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -143,7 +143,7 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<Strin
|
||||
ModuleDef::TypeAlias(it) => 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<RangeIn
|
||||
}
|
||||
}?;
|
||||
|
||||
res.extend(Some(rust_code_markup(&ty.display_truncated(db, None))));
|
||||
res.extend(Some(rust_code_markup(&ty.display(db))));
|
||||
let range = sema.original_range(&node).range;
|
||||
Some(RangeInfo::new(range, res))
|
||||
}
|
||||
@ -279,6 +279,47 @@ mod tests {
|
||||
assert_eq!(trim_markup_opt(hover.info.first()), Some("u32"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_shows_long_type_of_an_expression() {
|
||||
check_hover_result(
|
||||
r#"
|
||||
//- /main.rs
|
||||
struct Scan<A, B, C> {
|
||||
a: A,
|
||||
b: B,
|
||||
c: C,
|
||||
}
|
||||
|
||||
struct FakeIter<I> {
|
||||
inner: I,
|
||||
}
|
||||
|
||||
struct OtherStruct<T> {
|
||||
i: T,
|
||||
}
|
||||
|
||||
enum FakeOption<T> {
|
||||
Some(T),
|
||||
None,
|
||||
}
|
||||
|
||||
fn scan<A, B, C>(a: A, b: B, c: C) -> FakeIter<Scan<OtherStruct<A>, 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<u32> {
|
||||
FakeOption::Some(*memo + value)
|
||||
};
|
||||
let number = 5u32;
|
||||
let mut iter<|> = scan(OtherStruct { i: num }, closure, number);
|
||||
}
|
||||
"#,
|
||||
&["FakeIter<Scan<OtherStruct<OtherStruct<i32>>, |&mut u32, &u32, &mut u32| -> FakeOption<u32>, 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<K, T = u8> {
|
||||
fn main() {
|
||||
let zz<|> = Test { t: 23, k: 33 };
|
||||
}"#,
|
||||
&["Test<i32>"],
|
||||
&["Test<i32, u8>"],
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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<String>,
|
||||
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<Vec<Assist>> {
|
||||
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.
|
||||
|
@ -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();
|
||||
|
@ -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,
|
||||
|
@ -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 },
|
||||
|
@ -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"] }
|
||||
|
@ -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<PackageDependency>,
|
||||
pub edition: Edition,
|
||||
pub features: Vec<String>,
|
||||
pub cfgs: Vec<String>,
|
||||
pub out_dir: Option<PathBuf>,
|
||||
pub proc_macro_dylib_path: Option<PathBuf>,
|
||||
}
|
||||
@ -145,12 +145,8 @@ impl CargoWorkspace {
|
||||
cargo_toml: &Path,
|
||||
cargo_features: &CargoConfig,
|
||||
) -> Result<CargoWorkspace> {
|
||||
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<PackageId, PathBuf>,
|
||||
proc_dylib_paths: FxHashMap<PackageId, PathBuf>,
|
||||
cfgs: FxHashMap<PackageId, Vec<String>>,
|
||||
}
|
||||
|
||||
pub fn load_extern_resources(
|
||||
cargo_toml: &Path,
|
||||
cargo_features: &CargoConfig,
|
||||
) -> Result<ExternResources> {
|
||||
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())
|
||||
}
|
||||
|
@ -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<String> {
|
||||
let rustc_cfgs = || -> Result<String> {
|
||||
// `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<Output> {
|
||||
let output = cmd.output().with_context(|| format!("{:?} failed", cmd))?;
|
||||
if !output.status.success() {
|
||||
bail!("{:?} failed, {}", cmd, output.status)
|
||||
}
|
||||
Ok(output)
|
||||
}
|
||||
|
@ -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<SysrootCrateData>,
|
||||
@ -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<Output> {
|
||||
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<PathBuf> {
|
||||
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!(
|
||||
|
8
crates/ra_toolchain/Cargo.toml
Normal file
8
crates/ra_toolchain/Cargo.toml
Normal file
@ -0,0 +1,8 @@
|
||||
[package]
|
||||
edition = "2018"
|
||||
name = "ra_toolchain"
|
||||
version = "0.1.0"
|
||||
authors = ["rust-analyzer developers"]
|
||||
|
||||
[dependencies]
|
||||
home = "0.5.3"
|
64
crates/ra_toolchain/src/lib.rs
Normal file
64
crates/ra_toolchain/src/lib.rs
Normal file
@ -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) `<executable_name>`
|
||||
// example: for cargo, this tries just `cargo`, which will succeed if `cargo` is on the $PATH
|
||||
// 3) `~/.cargo/bin/<executable_name>`
|
||||
// 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())
|
||||
}
|
@ -42,6 +42,7 @@ use crate::{
|
||||
world::WorldSnapshot,
|
||||
LspError, Result,
|
||||
};
|
||||
use ra_project_model::TargetKind;
|
||||
|
||||
pub fn handle_analyzer_status(world: WorldSnapshot, _: ()) -> Result<String> {
|
||||
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<CodeLens> = 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)?;
|
||||
|
@ -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::<GotoDefinition>(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::<GotoTypeDefinition>(
|
||||
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::<GotoTypeDefinition>(
|
||||
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]
|
||||
|
@ -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<FixtureEntry> {
|
||||
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<FixtureEntry> {
|
||||
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::<Vec<_>>().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<FixtureEntry> {
|
||||
if !fixture.lines().any(|it| it.trim_start().starts_with("//-")) {
|
||||
|
@ -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<i32, > { Ok(42i32) }
|
||||
```
|
||||
|
||||
## `change_visibility`
|
||||
|
||||
Adds or changes existing visibility specifier.
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
]
|
||||
|
@ -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<string, string>;
|
||||
output: OutputChannel;
|
||||
constructor(readonly rootFolder: string, readonly output: OutputChannel) { }
|
||||
|
||||
public constructor(cargoTomlFolder: string, output: OutputChannel, env: Record<string, string> | undefined = undefined) {
|
||||
this.rootFolder = cargoTomlFolder;
|
||||
this.output = output;
|
||||
this.env = env;
|
||||
}
|
||||
|
||||
public async artifactsFromArgs(cargoArgs: string[]): Promise<CompilationArtifact[]> {
|
||||
private async artifactsFromArgs(cargoArgs: string[]): Promise<CompilationArtifact[]> {
|
||||
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<string> {
|
||||
const cargoArgs = [...args]; // to remain args unchanged
|
||||
cargoArgs.push("--message-format=json");
|
||||
async executableFromArgs(args: readonly string[]): Promise<string> {
|
||||
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<number> {
|
||||
return new Promise<number>((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 {
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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.`
|
||||
);
|
||||
}
|
||||
|
@ -94,6 +94,7 @@ export class Config {
|
||||
|
||||
get inlayHints() {
|
||||
return {
|
||||
enable: this.get<boolean>("inlayHints.enable"),
|
||||
typeHints: this.get<boolean>("inlayHints.typeHints"),
|
||||
parameterHints: this.get<boolean>("inlayHints.parameterHints"),
|
||||
chainingHints: this.get<boolean>("inlayHints.chainingHints"),
|
||||
|
@ -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();
|
||||
|
@ -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`);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -136,7 +136,6 @@ impl TidyDocs {
|
||||
}
|
||||
|
||||
let whitelist = [
|
||||
"ra_db",
|
||||
"ra_hir",
|
||||
"ra_hir_expand",
|
||||
"ra_ide",
|
||||
|
Loading…
Reference in New Issue
Block a user