4207: Add unwrap block assist #4156 r=matklad a=bnjjj

close issue #4156 

4253: Remove `workspaceLoaded` setting r=matklad a=eminence

The `workspaceLoaded` notification setting was originally designed to
control the display of a popup message that said:

    "workspace loaded, {} rust packages"

This popup was removed and replaced by a much sleeker message in the
VSCode status bar that provides a real-time status while loading:

    rust-analyzer: {}/{} packages

This was done as part of #3587

The change in this PR simply renames this setting from `workspaceLoaded` to
`progress` to better describe what it actually controls.  At the moment,
the only type of progress message that is controlled by this setting is
the initial load messages, but in theory other messages could also be
controlled by this setting.


Reviewer notes:

* If we didn't like the idea of causing minor breaking to user's config, we could keep the setting name as `workspaceLoaded`
* I think we can now close both #2719 and #3176 since the notification dialog in question no longer exists (actually I think you can close those issues even if you reject this PR 😄 )

Co-authored-by: Benjamin Coenen <5719034+bnjjj@users.noreply.github.com>
Co-authored-by: Andrew Chin <achin@eminence32.net>
This commit is contained in:
bors[bot] 2020-05-02 12:44:55 +00:00 committed by GitHub
commit 89e1f97515
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 390 additions and 14 deletions

View file

@ -728,3 +728,22 @@ use std::{collections::HashMap};
"#####, "#####,
) )
} }
#[test]
fn doctest_unwrap_block() {
check(
"unwrap_block",
r#####"
fn foo() {
if true {<|>
println!("foo");
}
}
"#####,
r#####"
fn foo() {
println!("foo");
}
"#####,
)
}

View file

@ -0,0 +1,348 @@
use crate::{Assist, AssistCtx, AssistId};
use ast::{BlockExpr, Expr, ForExpr, IfExpr, LoopBodyOwner, LoopExpr, WhileExpr};
use ra_fmt::unwrap_trivial_block;
use ra_syntax::{ast, AstNode, TextRange, T};
// Assist: unwrap_block
//
// This assist removes if...else, for, while and loop control statements to just keep the body.
//
// ```
// fn foo() {
// if true {<|>
// println!("foo");
// }
// }
// ```
// ->
// ```
// fn foo() {
// println!("foo");
// }
// ```
pub(crate) fn unwrap_block(ctx: AssistCtx) -> Option<Assist> {
let l_curly_token = ctx.find_token_at_offset(T!['{'])?;
let res = if let Some(if_expr) = l_curly_token.ancestors().find_map(IfExpr::cast) {
// if expression
let expr_to_unwrap = if_expr.blocks().find_map(|expr| extract_expr(ctx.frange.range, expr));
let expr_to_unwrap = expr_to_unwrap?;
// Find if we are in a else if block
let ancestor = if_expr.syntax().ancestors().skip(1).find_map(ast::IfExpr::cast);
if let Some(ancestor) = ancestor {
Some((ast::Expr::IfExpr(ancestor), expr_to_unwrap))
} else {
Some((ast::Expr::IfExpr(if_expr), expr_to_unwrap))
}
} else if let Some(for_expr) = l_curly_token.ancestors().find_map(ForExpr::cast) {
// for expression
let block_expr = for_expr.loop_body()?;
extract_expr(ctx.frange.range, block_expr)
.map(|expr_to_unwrap| (ast::Expr::ForExpr(for_expr), expr_to_unwrap))
} else if let Some(while_expr) = l_curly_token.ancestors().find_map(WhileExpr::cast) {
// while expression
let block_expr = while_expr.loop_body()?;
extract_expr(ctx.frange.range, block_expr)
.map(|expr_to_unwrap| (ast::Expr::WhileExpr(while_expr), expr_to_unwrap))
} else if let Some(loop_expr) = l_curly_token.ancestors().find_map(LoopExpr::cast) {
// loop expression
let block_expr = loop_expr.loop_body()?;
extract_expr(ctx.frange.range, block_expr)
.map(|expr_to_unwrap| (ast::Expr::LoopExpr(loop_expr), expr_to_unwrap))
} else {
None
};
let (expr, expr_to_unwrap) = res?;
ctx.add_assist(AssistId("unwrap_block"), "Unwrap block", |edit| {
edit.set_cursor(expr.syntax().text_range().start());
edit.target(expr_to_unwrap.syntax().text_range());
let pat_start: &[_] = &[' ', '{', '\n'];
let expr_to_unwrap = expr_to_unwrap.to_string();
let expr_string = expr_to_unwrap.trim_start_matches(pat_start);
let mut expr_string_lines: Vec<&str> = expr_string.lines().collect();
expr_string_lines.pop(); // Delete last line
let expr_string = expr_string_lines
.into_iter()
.map(|line| line.replacen(" ", "", 1)) // Delete indentation
.collect::<Vec<String>>()
.join("\n");
edit.replace(expr.syntax().text_range(), expr_string);
})
}
fn extract_expr(cursor_range: TextRange, block: BlockExpr) -> Option<Expr> {
let cursor_in_range = block.l_curly_token()?.text_range().contains_range(cursor_range);
if cursor_in_range {
Some(unwrap_trivial_block(block))
} else {
None
}
}
#[cfg(test)]
mod tests {
use crate::helpers::{check_assist, check_assist_not_applicable};
use super::*;
#[test]
fn simple_if() {
check_assist(
unwrap_block,
r#"
fn main() {
bar();
if true {<|>
foo();
//comment
bar();
} else {
println!("bar");
}
}
"#,
r#"
fn main() {
bar();
<|>foo();
//comment
bar();
}
"#,
);
}
#[test]
fn simple_if_else() {
check_assist(
unwrap_block,
r#"
fn main() {
bar();
if true {
foo();
//comment
bar();
} else {<|>
println!("bar");
}
}
"#,
r#"
fn main() {
bar();
<|>println!("bar");
}
"#,
);
}
#[test]
fn simple_if_else_if() {
check_assist(
unwrap_block,
r#"
fn main() {
//bar();
if true {
println!("true");
//comment
//bar();
} else if false {<|>
println!("bar");
} else {
println!("foo");
}
}
"#,
r#"
fn main() {
//bar();
<|>println!("bar");
}
"#,
);
}
#[test]
fn simple_if_bad_cursor_position() {
check_assist_not_applicable(
unwrap_block,
r#"
fn main() {
bar();<|>
if true {
foo();
//comment
bar();
} else {
println!("bar");
}
}
"#,
);
}
#[test]
fn simple_for() {
check_assist(
unwrap_block,
r#"
fn main() {
for i in 0..5 {<|>
if true {
foo();
//comment
bar();
} else {
println!("bar");
}
}
}
"#,
r#"
fn main() {
<|>if true {
foo();
//comment
bar();
} else {
println!("bar");
}
}
"#,
);
}
#[test]
fn simple_if_in_for() {
check_assist(
unwrap_block,
r#"
fn main() {
for i in 0..5 {
if true {<|>
foo();
//comment
bar();
} else {
println!("bar");
}
}
}
"#,
r#"
fn main() {
for i in 0..5 {
<|>foo();
//comment
bar();
}
}
"#,
);
}
#[test]
fn simple_loop() {
check_assist(
unwrap_block,
r#"
fn main() {
loop {<|>
if true {
foo();
//comment
bar();
} else {
println!("bar");
}
}
}
"#,
r#"
fn main() {
<|>if true {
foo();
//comment
bar();
} else {
println!("bar");
}
}
"#,
);
}
#[test]
fn simple_while() {
check_assist(
unwrap_block,
r#"
fn main() {
while true {<|>
if true {
foo();
//comment
bar();
} else {
println!("bar");
}
}
}
"#,
r#"
fn main() {
<|>if true {
foo();
//comment
bar();
} else {
println!("bar");
}
}
"#,
);
}
#[test]
fn simple_if_in_while_bad_cursor_position() {
check_assist_not_applicable(
unwrap_block,
r#"
fn main() {
while true {
if true {
foo();<|>
//comment
bar();
} else {
println!("bar");
}
}
}
"#,
);
}
}

View file

@ -143,6 +143,7 @@ mod handlers {
mod split_import; mod split_import;
mod add_from_impl_for_enum; mod add_from_impl_for_enum;
mod reorder_fields; mod reorder_fields;
mod unwrap_block;
pub(crate) fn all() -> &'static [AssistHandler] { pub(crate) fn all() -> &'static [AssistHandler] {
&[ &[
@ -181,6 +182,7 @@ mod handlers {
replace_unwrap_with_match::replace_unwrap_with_match, replace_unwrap_with_match::replace_unwrap_with_match,
split_import::split_import, split_import::split_import,
add_from_impl_for_enum::add_from_impl_for_enum, add_from_impl_for_enum::add_from_impl_for_enum,
unwrap_block::unwrap_block,
// These are manually sorted for better priorities // These are manually sorted for better priorities
add_missing_impl_members::add_missing_impl_members, add_missing_impl_members::add_missing_impl_members,
add_missing_impl_members::add_missing_default_members, add_missing_impl_members::add_missing_default_members,

View file

@ -43,7 +43,7 @@ impl ast::IfExpr {
Some(res) Some(res)
} }
fn blocks(&self) -> AstChildren<ast::BlockExpr> { pub fn blocks(&self) -> AstChildren<ast::BlockExpr> {
support::children(self.syntax()) support::children(self.syntax())
} }
} }

View file

@ -49,7 +49,6 @@ pub enum FilesWatcher {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct NotificationsConfig { pub struct NotificationsConfig {
pub workspace_loaded: bool,
pub cargo_toml_not_found: bool, pub cargo_toml_not_found: bool,
} }
@ -83,10 +82,7 @@ impl Default for Config {
lru_capacity: None, lru_capacity: None,
proc_macro_srv: None, proc_macro_srv: None,
files: FilesConfig { watcher: FilesWatcher::Notify, exclude: Vec::new() }, files: FilesConfig { watcher: FilesWatcher::Notify, exclude: Vec::new() },
notifications: NotificationsConfig { notifications: NotificationsConfig { cargo_toml_not_found: true },
workspace_loaded: true,
cargo_toml_not_found: true,
},
cargo: CargoConfig::default(), cargo: CargoConfig::default(),
rustfmt: RustfmtConfig::Rustfmt { extra_args: Vec::new() }, rustfmt: RustfmtConfig::Rustfmt { extra_args: Vec::new() },
@ -129,7 +125,6 @@ impl Config {
Some("client") => FilesWatcher::Client, Some("client") => FilesWatcher::Client,
Some("notify") | _ => FilesWatcher::Notify Some("notify") | _ => FilesWatcher::Notify
}; };
set(value, "/notifications/workspaceLoaded", &mut self.notifications.workspace_loaded);
set(value, "/notifications/cargoTomlNotFound", &mut self.notifications.cargo_toml_not_found); set(value, "/notifications/cargoTomlNotFound", &mut self.notifications.cargo_toml_not_found);
set(value, "/cargo/noDefaultFeatures", &mut self.cargo.no_default_features); set(value, "/cargo/noDefaultFeatures", &mut self.cargo.no_default_features);

View file

@ -415,8 +415,7 @@ fn loop_turn(
}); });
} }
let show_progress = let show_progress = !loop_state.workspace_loaded;
!loop_state.workspace_loaded && world_state.config.notifications.workspace_loaded;
if !loop_state.workspace_loaded if !loop_state.workspace_loaded
&& loop_state.roots_scanned == loop_state.roots_total && loop_state.roots_scanned == loop_state.roots_total

View file

@ -697,3 +697,21 @@ use std::┃collections::HashMap;
// AFTER // AFTER
use std::{collections::HashMap}; use std::{collections::HashMap};
``` ```
## `unwrap_block`
This assist removes if...else, for, while and loop control statements to just keep the body.
```rust
// BEFORE
fn foo() {
if true {┃
println!("foo");
}
}
// AFTER
fn foo() {
println!("foo");
}
```

View file

@ -205,11 +205,6 @@
"default": [], "default": [],
"description": "Paths to exclude from analysis." "description": "Paths to exclude from analysis."
}, },
"rust-analyzer.notifications.workspaceLoaded": {
"type": "boolean",
"default": true,
"markdownDescription": "Whether to show `workspace loaded` message."
},
"rust-analyzer.notifications.cargoTomlNotFound": { "rust-analyzer.notifications.cargoTomlNotFound": {
"type": "boolean", "type": "boolean",
"default": true, "default": true,