aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock1
-rw-r--r--crates/ra_analysis/src/imp.rs61
-rw-r--r--crates/ra_analysis/src/lib.rs5
-rw-r--r--crates/ra_db/Cargo.toml1
-rw-r--r--crates/ra_db/src/cancelation.rs85
-rw-r--r--crates/ra_db/src/lib.rs18
-rw-r--r--crates/ra_editor/src/code_actions.rs7
-rw-r--r--crates/ra_editor/src/lib.rs58
-rw-r--r--crates/ra_editor/src/typing.rs29
-rw-r--r--crates/ra_lsp_server/src/main_loop.rs2
-rw-r--r--crates/ra_lsp_server/tests/heavy_tests/main.rs4
-rw-r--r--crates/tools/src/lib.rs2
12 files changed, 194 insertions, 79 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 51cf1825d..69134b434 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -668,6 +668,7 @@ dependencies = [
668name = "ra_db" 668name = "ra_db"
669version = "0.1.0" 669version = "0.1.0"
670dependencies = [ 670dependencies = [
671 "backtrace 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
671 "parking_lot 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", 672 "parking_lot 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
672 "ra_editor 0.1.0", 673 "ra_editor 0.1.0",
673 "ra_syntax 0.1.0", 674 "ra_syntax 0.1.0",
diff --git a/crates/ra_analysis/src/imp.rs b/crates/ra_analysis/src/imp.rs
index a547c5a20..38a5c1a7d 100644
--- a/crates/ra_analysis/src/imp.rs
+++ b/crates/ra_analysis/src/imp.rs
@@ -3,31 +3,32 @@ use std::{
3 sync::Arc, 3 sync::Arc,
4}; 4};
5 5
6use ra_editor::{self, find_node_at_offset, FileSymbol, LineIndex, LocalEdit, Severity};
7use ra_syntax::{
8 ast::{self, ArgListOwner, Expr, NameOwner, FnDef},
9 algo::find_covering_node,
10 AstNode, SourceFileNode,
11 SyntaxKind::*,
12 SyntaxNodeRef, TextRange, TextUnit,
13};
14use ra_db::{FilesDatabase, SourceRoot, SourceRootId, SyntaxDatabase};
15use rayon::prelude::*; 6use rayon::prelude::*;
16use salsa::{Database, ParallelDatabase}; 7use salsa::{Database, ParallelDatabase};
8
17use hir::{ 9use hir::{
18 self, 10 self,
19 source_binder,
20 FnSignatureInfo, 11 FnSignatureInfo,
21 Problem, 12 Problem,
13 source_binder,
14};
15use ra_db::{FilesDatabase, SourceRoot, SourceRootId, SyntaxDatabase};
16use ra_editor::{self, FileSymbol, find_node_at_offset, LineIndex, LocalEdit, Severity};
17use ra_syntax::{
18 algo::find_covering_node,
19 ast::{self, ArgListOwner, Expr, FnDef, NameOwner},
20 AstNode, SourceFileNode,
21 SyntaxKind::*,
22 SyntaxNodeRef, TextRange, TextUnit,
22}; 23};
23 24
24use crate::{ 25use crate::{
25 completion::{completions, CompletionItem}, 26 AnalysisChange,
26 db, 27 Cancelable,
27 symbol_index::{SymbolIndex, SymbolsDatabase, LibrarySymbolsQuery}, 28 completion::{CompletionItem, completions},
28 AnalysisChange, RootChange, Cancelable, CrateId, Diagnostic, FileId, 29 CrateId, db, Diagnostic, FileId, FilePosition, FileSystemEdit,
29 FileSystemEdit, FilePosition, Query, SourceChange, SourceFileEdit, 30 Query, ReferenceResolution, RootChange, SourceChange, SourceFileEdit,
30 ReferenceResolution, 31 symbol_index::{LibrarySymbolsQuery, SymbolIndex, SymbolsDatabase},
31}; 32};
32 33
33#[derive(Debug, Default)] 34#[derive(Debug, Default)]
@@ -366,7 +367,7 @@ impl AnalysisImpl {
366 range: d.range, 367 range: d.range,
367 message: d.msg, 368 message: d.msg,
368 severity: d.severity, 369 severity: d.severity,
369 fix: None, 370 fix: d.fix.map(|fix| SourceChange::from_local_edit(file_id, fix)),
370 }) 371 })
371 .collect::<Vec<_>>(); 372 .collect::<Vec<_>>();
372 if let Some(m) = source_binder::module_from_file_id(&*self.db, file_id)? { 373 if let Some(m) = source_binder::module_from_file_id(&*self.db, file_id)? {
@@ -425,25 +426,15 @@ impl AnalysisImpl {
425 let file = self.file_syntax(file_id); 426 let file = self.file_syntax(file_id);
426 let offset = range.start(); 427 let offset = range.start();
427 let actions = vec![ 428 let actions = vec![
428 ( 429 ra_editor::flip_comma(&file, offset).map(|f| f()),
429 "flip comma", 430 ra_editor::add_derive(&file, offset).map(|f| f()),
430 ra_editor::flip_comma(&file, offset).map(|f| f()), 431 ra_editor::add_impl(&file, offset).map(|f| f()),
431 ), 432 ra_editor::make_pub_crate(&file, offset).map(|f| f()),
432 ( 433 ra_editor::introduce_variable(&file, range).map(|f| f()),
433 "add `#[derive]`",
434 ra_editor::add_derive(&file, offset).map(|f| f()),
435 ),
436 ("add impl", ra_editor::add_impl(&file, offset).map(|f| f())),
437 (
438 "introduce variable",
439 ra_editor::introduce_variable(&file, range).map(|f| f()),
440 ),
441 ]; 434 ];
442 actions 435 actions
443 .into_iter() 436 .into_iter()
444 .filter_map(|(name, local_edit)| { 437 .filter_map(|local_edit| Some(SourceChange::from_local_edit(file_id, local_edit?)))
445 Some(SourceChange::from_local_edit(file_id, name, local_edit?))
446 })
447 .collect() 438 .collect()
448 } 439 }
449 440
@@ -541,13 +532,13 @@ impl AnalysisImpl {
541} 532}
542 533
543impl SourceChange { 534impl SourceChange {
544 pub(crate) fn from_local_edit(file_id: FileId, label: &str, edit: LocalEdit) -> SourceChange { 535 pub(crate) fn from_local_edit(file_id: FileId, edit: LocalEdit) -> SourceChange {
545 let file_edit = SourceFileEdit { 536 let file_edit = SourceFileEdit {
546 file_id, 537 file_id,
547 edit: edit.edit, 538 edit: edit.edit,
548 }; 539 };
549 SourceChange { 540 SourceChange {
550 label: label.to_string(), 541 label: edit.label,
551 source_file_edits: vec![file_edit], 542 source_file_edits: vec![file_edit],
552 file_system_edits: vec![], 543 file_system_edits: vec![],
553 cursor_position: edit 544 cursor_position: edit
diff --git a/crates/ra_analysis/src/lib.rs b/crates/ra_analysis/src/lib.rs
index a029f66b4..476d1b438 100644
--- a/crates/ra_analysis/src/lib.rs
+++ b/crates/ra_analysis/src/lib.rs
@@ -288,19 +288,18 @@ impl Analysis {
288 } 288 }
289 pub fn join_lines(&self, file_id: FileId, range: TextRange) -> SourceChange { 289 pub fn join_lines(&self, file_id: FileId, range: TextRange) -> SourceChange {
290 let file = self.imp.file_syntax(file_id); 290 let file = self.imp.file_syntax(file_id);
291 SourceChange::from_local_edit(file_id, "join lines", ra_editor::join_lines(&file, range)) 291 SourceChange::from_local_edit(file_id, ra_editor::join_lines(&file, range))
292 } 292 }
293 pub fn on_enter(&self, position: FilePosition) -> Option<SourceChange> { 293 pub fn on_enter(&self, position: FilePosition) -> Option<SourceChange> {
294 let file = self.imp.file_syntax(position.file_id); 294 let file = self.imp.file_syntax(position.file_id);
295 let edit = ra_editor::on_enter(&file, position.offset)?; 295 let edit = ra_editor::on_enter(&file, position.offset)?;
296 let res = SourceChange::from_local_edit(position.file_id, "on enter", edit); 296 let res = SourceChange::from_local_edit(position.file_id, edit);
297 Some(res) 297 Some(res)
298 } 298 }
299 pub fn on_eq_typed(&self, position: FilePosition) -> Option<SourceChange> { 299 pub fn on_eq_typed(&self, position: FilePosition) -> Option<SourceChange> {
300 let file = self.imp.file_syntax(position.file_id); 300 let file = self.imp.file_syntax(position.file_id);
301 Some(SourceChange::from_local_edit( 301 Some(SourceChange::from_local_edit(
302 position.file_id, 302 position.file_id,
303 "add semicolon",
304 ra_editor::on_eq_typed(&file, position.offset)?, 303 ra_editor::on_eq_typed(&file, position.offset)?,
305 )) 304 ))
306 } 305 }
diff --git a/crates/ra_db/Cargo.toml b/crates/ra_db/Cargo.toml
index f316c0ab2..4be32b5f3 100644
--- a/crates/ra_db/Cargo.toml
+++ b/crates/ra_db/Cargo.toml
@@ -5,6 +5,7 @@ version = "0.1.0"
5authors = ["Aleksey Kladov <[email protected]>"] 5authors = ["Aleksey Kladov <[email protected]>"]
6 6
7[dependencies] 7[dependencies]
8backtrace = "0.3.1"
8relative-path = "0.4.0" 9relative-path = "0.4.0"
9salsa = "0.8.0" 10salsa = "0.8.0"
10rustc-hash = "1.0" 11rustc-hash = "1.0"
diff --git a/crates/ra_db/src/cancelation.rs b/crates/ra_db/src/cancelation.rs
new file mode 100644
index 000000000..73444b015
--- /dev/null
+++ b/crates/ra_db/src/cancelation.rs
@@ -0,0 +1,85 @@
1//! Utility types to support cancellation.
2//!
3//! In a typical IDE use-case, requests and modification happen concurrently, as
4//! in the following scenario:
5//!
6//! * user types a character,
7//! * a syntax highlighting process is started
8//! * user types next character, while syntax highlighting *is still in
9//! progress*.
10//!
11//! In this situation, we want to react to modification as quckly as possible.
12//! At the same time, in-progress results are not very interesting, because they
13//! are invalidated by the edit anyway. So, we first cancel all in-flight
14//! requests, and then apply modification knowing that it won't intrfere with
15//! any background processing (this bit is handled by salsa, see
16//! `BaseDatabase::check_canceled` method).
17
18use std::{
19 cmp,
20 hash::{Hash, Hasher},
21 sync::Arc,
22};
23
24use backtrace::Backtrace;
25use parking_lot::Mutex;
26
27/// An "error" signifing that the operation was canceled.
28#[derive(Clone)]
29pub struct Canceled {
30 backtrace: Arc<Mutex<Backtrace>>,
31}
32
33pub type Cancelable<T> = Result<T, Canceled>;
34
35impl Canceled {
36 pub(crate) fn new() -> Canceled {
37 let bt = Backtrace::new_unresolved();
38 Canceled {
39 backtrace: Arc::new(Mutex::new(bt)),
40 }
41 }
42}
43
44impl std::fmt::Display for Canceled {
45 fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46 fmt.write_str("canceled")
47 }
48}
49
50impl std::fmt::Debug for Canceled {
51 fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52 let mut bt = self.backtrace.lock();
53 let bt: &mut Backtrace = &mut *bt;
54 bt.resolve();
55 write!(fmt, "canceled at:\n{:?}", bt)
56 }
57}
58
59impl std::error::Error for Canceled {}
60
61impl PartialEq for Canceled {
62 fn eq(&self, _: &Canceled) -> bool {
63 true
64 }
65}
66
67impl Eq for Canceled {}
68
69impl Hash for Canceled {
70 fn hash<H: Hasher>(&self, hasher: &mut H) {
71 ().hash(hasher)
72 }
73}
74
75impl cmp::Ord for Canceled {
76 fn cmp(&self, _: &Canceled) -> cmp::Ordering {
77 cmp::Ordering::Equal
78 }
79}
80
81impl cmp::PartialOrd for Canceled {
82 fn partial_cmp(&self, other: &Canceled) -> Option<cmp::Ordering> {
83 Some(self.cmp(other))
84 }
85}
diff --git a/crates/ra_db/src/lib.rs b/crates/ra_db/src/lib.rs
index 78f2cbf12..1f7c9187b 100644
--- a/crates/ra_db/src/lib.rs
+++ b/crates/ra_db/src/lib.rs
@@ -1,27 +1,17 @@
1//! ra_db defines basic database traits. Concrete DB is defined by ra_analysis. 1//! ra_db defines basic database traits. Concrete DB is defined by ra_analysis.
2mod cancelation;
2mod syntax_ptr; 3mod syntax_ptr;
3mod input; 4mod input;
4mod loc2id; 5mod loc2id;
5pub mod mock; 6pub mod mock;
6 7
7use std::sync::Arc; 8use std::sync::Arc;
9
8use ra_editor::LineIndex; 10use ra_editor::LineIndex;
9use ra_syntax::{TextUnit, SourceFileNode}; 11use ra_syntax::{TextUnit, SourceFileNode};
10 12
11#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
12pub struct Canceled;
13
14pub type Cancelable<T> = Result<T, Canceled>;
15
16impl std::fmt::Display for Canceled {
17 fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18 fmt.write_str("canceled")
19 }
20}
21
22impl std::error::Error for Canceled {}
23
24pub use crate::{ 13pub use crate::{
14 cancelation::{Canceled, Cancelable},
25 syntax_ptr::LocalSyntaxPtr, 15 syntax_ptr::LocalSyntaxPtr,
26 input::{ 16 input::{
27 FilesDatabase, FileId, CrateId, SourceRoot, SourceRootId, CrateGraph, 17 FilesDatabase, FileId, CrateId, SourceRoot, SourceRootId, CrateGraph,
@@ -48,7 +38,7 @@ macro_rules! impl_numeric_id {
48pub trait BaseDatabase: salsa::Database { 38pub trait BaseDatabase: salsa::Database {
49 fn check_canceled(&self) -> Cancelable<()> { 39 fn check_canceled(&self) -> Cancelable<()> {
50 if self.salsa_runtime().is_current_revision_canceled() { 40 if self.salsa_runtime().is_current_revision_canceled() {
51 Err(Canceled) 41 Err(Canceled::new())
52 } else { 42 } else {
53 Ok(()) 43 Ok(())
54 } 44 }
diff --git a/crates/ra_editor/src/code_actions.rs b/crates/ra_editor/src/code_actions.rs
index 1d78cb7e8..7615f37a6 100644
--- a/crates/ra_editor/src/code_actions.rs
+++ b/crates/ra_editor/src/code_actions.rs
@@ -12,6 +12,7 @@ use crate::{find_node_at_offset, TextEdit, TextEditBuilder};
12 12
13#[derive(Debug)] 13#[derive(Debug)]
14pub struct LocalEdit { 14pub struct LocalEdit {
15 pub label: String,
15 pub edit: TextEdit, 16 pub edit: TextEdit,
16 pub cursor_position: Option<TextUnit>, 17 pub cursor_position: Option<TextUnit>,
17} 18}
@@ -30,6 +31,7 @@ pub fn flip_comma<'a>(
30 edit.replace(prev.range(), next.text().to_string()); 31 edit.replace(prev.range(), next.text().to_string());
31 edit.replace(next.range(), prev.text().to_string()); 32 edit.replace(next.range(), prev.text().to_string());
32 LocalEdit { 33 LocalEdit {
34 label: "flip comma".to_string(),
33 edit: edit.finish(), 35 edit: edit.finish(),
34 cursor_position: None, 36 cursor_position: None,
35 } 37 }
@@ -58,6 +60,7 @@ pub fn add_derive<'a>(
58 Some(tt) => tt.syntax().range().end() - TextUnit::of_char(')'), 60 Some(tt) => tt.syntax().range().end() - TextUnit::of_char(')'),
59 }; 61 };
60 LocalEdit { 62 LocalEdit {
63 label: "add `#[derive]`".to_string(),
61 edit: edit.finish(), 64 edit: edit.finish(),
62 cursor_position: Some(offset), 65 cursor_position: Some(offset),
63 } 66 }
@@ -109,6 +112,7 @@ pub fn add_impl<'a>(
109 buf.push_str("\n}"); 112 buf.push_str("\n}");
110 edit.insert(start_offset, buf); 113 edit.insert(start_offset, buf);
111 LocalEdit { 114 LocalEdit {
115 label: "add impl".to_string(),
112 edit: edit.finish(), 116 edit: edit.finish(),
113 cursor_position: Some(offset), 117 cursor_position: Some(offset),
114 } 118 }
@@ -148,6 +152,7 @@ pub fn introduce_variable<'a>(
148 } 152 }
149 let cursor_position = anchor_stmt.range().start() + TextUnit::of_str("let "); 153 let cursor_position = anchor_stmt.range().start() + TextUnit::of_str("let ");
150 LocalEdit { 154 LocalEdit {
155 label: "introduce variable".to_string(),
151 edit: edit.finish(), 156 edit: edit.finish(),
152 cursor_position: Some(cursor_position), 157 cursor_position: Some(cursor_position),
153 } 158 }
@@ -194,6 +199,7 @@ pub fn make_pub_crate<'a>(
194 || parent.children().any(|child| child.kind() == VISIBILITY) 199 || parent.children().any(|child| child.kind() == VISIBILITY)
195 { 200 {
196 return LocalEdit { 201 return LocalEdit {
202 label: "make pub crate".to_string(),
197 edit: edit.finish(), 203 edit: edit.finish(),
198 cursor_position: Some(offset), 204 cursor_position: Some(offset),
199 }; 205 };
@@ -201,6 +207,7 @@ pub fn make_pub_crate<'a>(
201 207
202 edit.insert(node_start, "pub(crate) ".to_string()); 208 edit.insert(node_start, "pub(crate) ".to_string());
203 LocalEdit { 209 LocalEdit {
210 label: "make pub crate".to_string(),
204 edit: edit.finish(), 211 edit: edit.finish(),
205 cursor_position: Some(node_start), 212 cursor_position: Some(node_start),
206 } 213 }
diff --git a/crates/ra_editor/src/lib.rs b/crates/ra_editor/src/lib.rs
index 7a689b0f2..dcf8be5a7 100644
--- a/crates/ra_editor/src/lib.rs
+++ b/crates/ra_editor/src/lib.rs
@@ -42,6 +42,7 @@ pub struct Diagnostic {
42 pub range: TextRange, 42 pub range: TextRange,
43 pub msg: String, 43 pub msg: String,
44 pub severity: Severity, 44 pub severity: Severity,
45 pub fix: Option<LocalEdit>,
45} 46}
46 47
47#[derive(Debug)] 48#[derive(Debug)]
@@ -111,6 +112,7 @@ pub fn diagnostics(file: &SourceFileNode) -> Vec<Diagnostic> {
111 range: location_to_range(err.location()), 112 range: location_to_range(err.location()),
112 msg: format!("Syntax Error: {}", err), 113 msg: format!("Syntax Error: {}", err),
113 severity: Severity::Error, 114 severity: Severity::Error,
115 fix: None,
114 }) 116 })
115 .collect(); 117 .collect();
116 118
@@ -124,11 +126,30 @@ fn check_unnecessary_braces_in_use_statement(file: &SourceFileNode) -> Vec<Diagn
124 let mut diagnostics = Vec::new(); 126 let mut diagnostics = Vec::new();
125 for node in file.syntax().descendants() { 127 for node in file.syntax().descendants() {
126 if let Some(use_tree_list) = ast::UseTreeList::cast(node) { 128 if let Some(use_tree_list) = ast::UseTreeList::cast(node) {
127 if use_tree_list.use_trees().count() <= 1 { 129 if use_tree_list.use_trees().count() == 1 {
130 let range = use_tree_list.syntax().range();
131 // use_tree_list always has one child, so we use unwrap directly here.
132 let single_use_tree: ast::UseTree = use_tree_list.use_trees().next().unwrap();
133 let edit = text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(
134 single_use_tree,
135 )
136 .unwrap_or_else(|| {
137 let to_replace = single_use_tree.syntax().text().to_string();
138 let mut edit_builder = TextEditBuilder::new();
139 edit_builder.delete(range);
140 edit_builder.insert(range.start(), to_replace);
141 edit_builder.finish()
142 });
143
128 diagnostics.push(Diagnostic { 144 diagnostics.push(Diagnostic {
129 range: use_tree_list.syntax().range(), 145 range: range,
130 msg: format!("Unnecessary braces in use statement"), 146 msg: format!("Unnecessary braces in use statement"),
131 severity: Severity::WeakWarning, 147 severity: Severity::WeakWarning,
148 fix: Some(LocalEdit {
149 label: "Remove unnecessary braces".to_string(),
150 edit: edit,
151 cursor_position: None,
152 }),
132 }) 153 })
133 } 154 }
134 } 155 }
@@ -137,6 +158,28 @@ fn check_unnecessary_braces_in_use_statement(file: &SourceFileNode) -> Vec<Diagn
137 diagnostics 158 diagnostics
138} 159}
139 160
161fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(
162 single_use_tree: ast::UseTree,
163) -> Option<TextEdit> {
164 let use_tree_list_node = single_use_tree.syntax().parent()?;
165 if single_use_tree
166 .path()?
167 .segment()?
168 .syntax()
169 .first_child()?
170 .kind()
171 == SyntaxKind::SELF_KW
172 {
173 let start = use_tree_list_node.prev_sibling()?.range().start();
174 let end = use_tree_list_node.range().end();
175 let range = TextRange::from_to(start, end);
176 let mut edit_builder = TextEditBuilder::new();
177 edit_builder.delete(range);
178 return Some(edit_builder.finish());
179 }
180 None
181}
182
140pub fn syntax_tree(file: &SourceFileNode) -> String { 183pub fn syntax_tree(file: &SourceFileNode) -> String {
141 ::ra_syntax::utils::dump_tree(file.syntax()) 184 ::ra_syntax::utils::dump_tree(file.syntax())
142} 185}
@@ -173,8 +216,9 @@ pub fn find_node_at_offset<'a, N: AstNode<'a>>(
173 216
174#[cfg(test)] 217#[cfg(test)]
175mod tests { 218mod tests {
219 use crate::test_utils::{add_cursor, assert_eq_dbg, assert_eq_text, extract_offset};
220
176 use super::*; 221 use super::*;
177 use crate::test_utils::{add_cursor, assert_eq_dbg, extract_offset, assert_eq_text};
178 222
179 #[test] 223 #[test]
180 fn test_highlighting() { 224 fn test_highlighting() {
@@ -243,6 +287,7 @@ fn test_foo() {}
243use a; 287use a;
244use {b}; 288use {b};
245use a::{c}; 289use a::{c};
290use a::{self};
246use a::{c, d::e}; 291use a::{c, d::e};
247use a::{c, d::{e}}; 292use a::{c, d::{e}};
248fn main() {} 293fn main() {}
@@ -250,9 +295,10 @@ fn main() {}
250 ); 295 );
251 let diagnostics = check_unnecessary_braces_in_use_statement(&file); 296 let diagnostics = check_unnecessary_braces_in_use_statement(&file);
252 assert_eq_dbg( 297 assert_eq_dbg(
253 r#"[Diagnostic { range: [12; 15), msg: "Unnecessary braces in use statement", severity: WeakWarning }, 298 r#"[Diagnostic { range: [12; 15), msg: "Unnecessary braces in use statement", severity: WeakWarning, fix: Some(LocalEdit { label: "Remove unnecessary braces", edit: TextEdit { atoms: [AtomTextEdit { delete: [12; 12), insert: "b" }, AtomTextEdit { delete: [12; 15), insert: "" }] }, cursor_position: None }) },
254 Diagnostic { range: [24; 27), msg: "Unnecessary braces in use statement", severity: WeakWarning }, 299 Diagnostic { range: [24; 27), msg: "Unnecessary braces in use statement", severity: WeakWarning, fix: Some(LocalEdit { label: "Remove unnecessary braces", edit: TextEdit { atoms: [AtomTextEdit { delete: [24; 24), insert: "c" }, AtomTextEdit { delete: [24; 27), insert: "" }] }, cursor_position: None }) },
255 Diagnostic { range: [61; 64), msg: "Unnecessary braces in use statement", severity: WeakWarning }]"#, 300 Diagnostic { range: [36; 42), msg: "Unnecessary braces in use statement", severity: WeakWarning, fix: Some(LocalEdit { label: "Remove unnecessary braces", edit: TextEdit { atoms: [AtomTextEdit { delete: [34; 42), insert: "" }] }, cursor_position: None }) },
301 Diagnostic { range: [76; 79), msg: "Unnecessary braces in use statement", severity: WeakWarning, fix: Some(LocalEdit { label: "Remove unnecessary braces", edit: TextEdit { atoms: [AtomTextEdit { delete: [76; 76), insert: "e" }, AtomTextEdit { delete: [76; 79), insert: "" }] }, cursor_position: None }) }]"#,
256 &diagnostics, 302 &diagnostics,
257 ) 303 )
258 } 304 }
diff --git a/crates/ra_editor/src/typing.rs b/crates/ra_editor/src/typing.rs
index 5e412bcfa..f0d8dc7bb 100644
--- a/crates/ra_editor/src/typing.rs
+++ b/crates/ra_editor/src/typing.rs
@@ -8,7 +8,9 @@ use ra_syntax::{
8 SyntaxKind::*, 8 SyntaxKind::*,
9 SyntaxNodeRef, TextRange, TextUnit, 9 SyntaxNodeRef, TextRange, TextUnit,
10}; 10};
11use ra_text_edit::text_utils::contains_offset_nonstrict; 11use ra_text_edit::text_utils::{
12 contains_offset_nonstrict
13};
12 14
13use crate::{find_node_at_offset, TextEditBuilder, LocalEdit}; 15use crate::{find_node_at_offset, TextEditBuilder, LocalEdit};
14 16
@@ -19,6 +21,7 @@ pub fn join_lines(file: &SourceFileNode, range: TextRange) -> LocalEdit {
19 let pos = match text.find('\n') { 21 let pos = match text.find('\n') {
20 None => { 22 None => {
21 return LocalEdit { 23 return LocalEdit {
24 label: "join lines".to_string(),
22 edit: TextEditBuilder::new().finish(), 25 edit: TextEditBuilder::new().finish(),
23 cursor_position: None, 26 cursor_position: None,
24 }; 27 };
@@ -51,6 +54,7 @@ pub fn join_lines(file: &SourceFileNode, range: TextRange) -> LocalEdit {
51 } 54 }
52 55
53 LocalEdit { 56 LocalEdit {
57 label: "join lines".to_string(),
54 edit: edit.finish(), 58 edit: edit.finish(),
55 cursor_position: None, 59 cursor_position: None,
56 } 60 }
@@ -76,6 +80,7 @@ pub fn on_enter(file: &SourceFileNode, offset: TextUnit) -> Option<LocalEdit> {
76 let mut edit = TextEditBuilder::new(); 80 let mut edit = TextEditBuilder::new();
77 edit.insert(offset, inserted); 81 edit.insert(offset, inserted);
78 Some(LocalEdit { 82 Some(LocalEdit {
83 label: "on enter".to_string(),
79 edit: edit.finish(), 84 edit: edit.finish(),
80 cursor_position: Some(cursor_position), 85 cursor_position: Some(cursor_position),
81 }) 86 })
@@ -126,6 +131,7 @@ pub fn on_eq_typed(file: &SourceFileNode, offset: TextUnit) -> Option<LocalEdit>
126 let mut edit = TextEditBuilder::new(); 131 let mut edit = TextEditBuilder::new();
127 edit.insert(offset, ";".to_string()); 132 edit.insert(offset, ";".to_string());
128 Some(LocalEdit { 133 Some(LocalEdit {
134 label: "add semicolon".to_string(),
129 edit: edit.finish(), 135 edit: edit.finish(),
130 cursor_position: None, 136 cursor_position: None,
131 }) 137 })
@@ -249,23 +255,12 @@ fn join_single_use_tree(edit: &mut TextEditBuilder, node: SyntaxNodeRef) -> Opti
249} 255}
250 256
251fn single_use_tree(tree_list: ast::UseTreeList) -> Option<ast::UseTree> { 257fn single_use_tree(tree_list: ast::UseTreeList) -> Option<ast::UseTree> {
252 let mut res = None; 258 let sub_use_trees = tree_list.use_trees().count();
253 for child in tree_list.syntax().children() { 259 if sub_use_trees != 1 {
254 if let Some(tree) = ast::UseTree::cast(child) { 260 return None;
255 if tree.syntax().text().contains('\n') {
256 return None;
257 }
258 if mem::replace(&mut res, Some(tree)).is_some() {
259 return None;
260 }
261 } else {
262 match child.kind() {
263 WHITESPACE | L_CURLY | R_CURLY | COMMA => (),
264 _ => return None,
265 }
266 }
267 } 261 }
268 res 262
263 tree_list.use_trees().next()
269} 264}
270 265
271fn compute_ws(left: SyntaxNodeRef, right: SyntaxNodeRef) -> &'static str { 266fn compute_ws(left: SyntaxNodeRef, right: SyntaxNodeRef) -> &'static str {
diff --git a/crates/ra_lsp_server/src/main_loop.rs b/crates/ra_lsp_server/src/main_loop.rs
index 60f13267c..1edb9fae4 100644
--- a/crates/ra_lsp_server/src/main_loop.rs
+++ b/crates/ra_lsp_server/src/main_loop.rs
@@ -427,7 +427,7 @@ impl<'a> PoolDispatcher<'a> {
427 RawResponse::err( 427 RawResponse::err(
428 id, 428 id,
429 ErrorCode::ContentModified as i32, 429 ErrorCode::ContentModified as i32,
430 format!("content modified: {}", e), 430 format!("content modified: {:?}", e),
431 ) 431 )
432 } else { 432 } else {
433 RawResponse::err( 433 RawResponse::err(
diff --git a/crates/ra_lsp_server/tests/heavy_tests/main.rs b/crates/ra_lsp_server/tests/heavy_tests/main.rs
index 029a55d40..1f5cc5e8b 100644
--- a/crates/ra_lsp_server/tests/heavy_tests/main.rs
+++ b/crates/ra_lsp_server/tests/heavy_tests/main.rs
@@ -141,7 +141,7 @@ fn main() {}
141 server.request::<CodeActionRequest>( 141 server.request::<CodeActionRequest>(
142 CodeActionParams { 142 CodeActionParams {
143 text_document: server.doc_id("src/lib.rs"), 143 text_document: server.doc_id("src/lib.rs"),
144 range: Range::new(Position::new(0, 0), Position::new(0, 7)), 144 range: Range::new(Position::new(0, 4), Position::new(0, 7)),
145 context: empty_context(), 145 context: empty_context(),
146 }, 146 },
147 json!([ 147 json!([
@@ -168,7 +168,7 @@ fn main() {}
168 server.request::<CodeActionRequest>( 168 server.request::<CodeActionRequest>(
169 CodeActionParams { 169 CodeActionParams {
170 text_document: server.doc_id("src/lib.rs"), 170 text_document: server.doc_id("src/lib.rs"),
171 range: Range::new(Position::new(2, 0), Position::new(2, 7)), 171 range: Range::new(Position::new(2, 4), Position::new(2, 7)),
172 context: empty_context(), 172 context: empty_context(),
173 }, 173 },
174 json!([]), 174 json!([]),
diff --git a/crates/tools/src/lib.rs b/crates/tools/src/lib.rs
index 2795afe0b..6f96b8120 100644
--- a/crates/tools/src/lib.rs
+++ b/crates/tools/src/lib.rs
@@ -29,7 +29,7 @@ pub fn collect_tests(s: &str) -> Vec<(usize, Test)> {
29 let prefix = "// "; 29 let prefix = "// ";
30 let comment_blocks = s 30 let comment_blocks = s
31 .lines() 31 .lines()
32 .map(str::trim_left) 32 .map(str::trim_start)
33 .enumerate() 33 .enumerate()
34 .group_by(|(_idx, line)| line.starts_with(prefix)); 34 .group_by(|(_idx, line)| line.starts_with(prefix));
35 35