diff options
Diffstat (limited to 'crates')
-rw-r--r-- | crates/ra_db/src/cancellation.rs | 6 | ||||
-rw-r--r-- | crates/ra_db/src/lib.rs | 22 | ||||
-rw-r--r-- | crates/ra_db/src/loc2id.rs | 11 | ||||
-rw-r--r-- | crates/ra_hir/src/mock.rs | 4 | ||||
-rw-r--r-- | crates/ra_ide_api/src/db.rs | 5 | ||||
-rw-r--r-- | crates/ra_ide_api/src/lib.rs | 51 | ||||
-rw-r--r-- | crates/ra_ide_api_light/src/extend_selection.rs | 131 |
7 files changed, 199 insertions, 31 deletions
diff --git a/crates/ra_db/src/cancellation.rs b/crates/ra_db/src/cancellation.rs index 8d38eebfb..32a268553 100644 --- a/crates/ra_db/src/cancellation.rs +++ b/crates/ra_db/src/cancellation.rs | |||
@@ -27,6 +27,12 @@ impl Canceled { | |||
27 | pub(crate) fn new() -> Canceled { | 27 | pub(crate) fn new() -> Canceled { |
28 | Canceled { _private: () } | 28 | Canceled { _private: () } |
29 | } | 29 | } |
30 | |||
31 | pub fn throw() -> ! { | ||
32 | // We use resume and not panic here to avoid running the panic | ||
33 | // hook (that is, to avoid collecting and printing backtrace). | ||
34 | std::panic::resume_unwind(Box::new(Canceled::new())) | ||
35 | } | ||
30 | } | 36 | } |
31 | 37 | ||
32 | impl std::fmt::Display for Canceled { | 38 | impl std::fmt::Display for Canceled { |
diff --git a/crates/ra_db/src/lib.rs b/crates/ra_db/src/lib.rs index fb8ea2496..20e712afe 100644 --- a/crates/ra_db/src/lib.rs +++ b/crates/ra_db/src/lib.rs | |||
@@ -5,6 +5,8 @@ mod input; | |||
5 | mod loc2id; | 5 | mod loc2id; |
6 | pub mod mock; | 6 | pub mod mock; |
7 | 7 | ||
8 | use std::panic; | ||
9 | |||
8 | use ra_syntax::{TextUnit, TextRange, SourceFile, TreePtr}; | 10 | use ra_syntax::{TextUnit, TextRange, SourceFile, TreePtr}; |
9 | 11 | ||
10 | pub use crate::{ | 12 | pub use crate::{ |
@@ -18,13 +20,21 @@ pub use crate::{ | |||
18 | loc2id::LocationIntener, | 20 | loc2id::LocationIntener, |
19 | }; | 21 | }; |
20 | 22 | ||
21 | pub trait BaseDatabase: salsa::Database { | 23 | pub trait BaseDatabase: salsa::Database + panic::RefUnwindSafe { |
22 | fn check_canceled(&self) -> Cancelable<()> { | 24 | fn check_canceled(&self) -> Cancelable<()> { |
23 | if self.salsa_runtime().is_current_revision_canceled() { | 25 | self.salsa_runtime() |
24 | Err(Canceled::new()) | 26 | .if_current_revision_is_canceled(Canceled::throw); |
25 | } else { | 27 | Ok(()) |
26 | Ok(()) | 28 | } |
27 | } | 29 | |
30 | fn catch_canceled<F: FnOnce(&Self) -> T + panic::UnwindSafe, T>( | ||
31 | &self, | ||
32 | f: F, | ||
33 | ) -> Result<T, Canceled> { | ||
34 | panic::catch_unwind(|| f(self)).map_err(|err| match err.downcast::<Canceled>() { | ||
35 | Ok(canceled) => *canceled, | ||
36 | Err(payload) => panic::resume_unwind(payload), | ||
37 | }) | ||
28 | } | 38 | } |
29 | } | 39 | } |
30 | 40 | ||
diff --git a/crates/ra_db/src/loc2id.rs b/crates/ra_db/src/loc2id.rs index 254c52629..359cd893d 100644 --- a/crates/ra_db/src/loc2id.rs +++ b/crates/ra_db/src/loc2id.rs | |||
@@ -1,4 +1,4 @@ | |||
1 | use std::hash::Hash; | 1 | use std::{panic, hash::Hash}; |
2 | 2 | ||
3 | use parking_lot::Mutex; | 3 | use parking_lot::Mutex; |
4 | use rustc_hash::FxHashMap; | 4 | use rustc_hash::FxHashMap; |
@@ -70,6 +70,15 @@ where | |||
70 | map: Mutex<Loc2IdMap<LOC, ID>>, | 70 | map: Mutex<Loc2IdMap<LOC, ID>>, |
71 | } | 71 | } |
72 | 72 | ||
73 | impl<LOC, ID> panic::RefUnwindSafe for LocationIntener<LOC, ID> | ||
74 | where | ||
75 | ID: ArenaId + Clone, | ||
76 | LOC: Clone + Eq + Hash, | ||
77 | ID: panic::RefUnwindSafe, | ||
78 | LOC: panic::RefUnwindSafe, | ||
79 | { | ||
80 | } | ||
81 | |||
73 | impl<LOC, ID> Default for LocationIntener<LOC, ID> | 82 | impl<LOC, ID> Default for LocationIntener<LOC, ID> |
74 | where | 83 | where |
75 | ID: ArenaId + Clone, | 84 | ID: ArenaId + Clone, |
diff --git a/crates/ra_hir/src/mock.rs b/crates/ra_hir/src/mock.rs index 0fae7de82..7a0301648 100644 --- a/crates/ra_hir/src/mock.rs +++ b/crates/ra_hir/src/mock.rs | |||
@@ -1,4 +1,4 @@ | |||
1 | use std::sync::Arc; | 1 | use std::{sync::Arc, panic}; |
2 | 2 | ||
3 | use parking_lot::Mutex; | 3 | use parking_lot::Mutex; |
4 | use salsa::{self, Database}; | 4 | use salsa::{self, Database}; |
@@ -18,6 +18,8 @@ pub(crate) struct MockDatabase { | |||
18 | file_counter: u32, | 18 | file_counter: u32, |
19 | } | 19 | } |
20 | 20 | ||
21 | impl panic::RefUnwindSafe for MockDatabase {} | ||
22 | |||
21 | impl MockDatabase { | 23 | impl MockDatabase { |
22 | pub(crate) fn with_files(fixture: &str) -> (MockDatabase, SourceRoot) { | 24 | pub(crate) fn with_files(fixture: &str) -> (MockDatabase, SourceRoot) { |
23 | let (db, source_root, position) = MockDatabase::from_fixture(fixture); | 25 | let (db, source_root, position) = MockDatabase::from_fixture(fixture); |
diff --git a/crates/ra_ide_api/src/db.rs b/crates/ra_ide_api/src/db.rs index 9d46609ec..a2e06f5db 100644 --- a/crates/ra_ide_api/src/db.rs +++ b/crates/ra_ide_api/src/db.rs | |||
@@ -1,7 +1,7 @@ | |||
1 | use std::{fmt, sync::Arc}; | 1 | use std::{fmt, sync::Arc}; |
2 | 2 | ||
3 | use salsa::{self, Database}; | 3 | use salsa::{self, Database}; |
4 | use ra_db::{LocationIntener, BaseDatabase, FileId}; | 4 | use ra_db::{LocationIntener, BaseDatabase, FileId, Canceled}; |
5 | 5 | ||
6 | use crate::{symbol_index, LineIndex}; | 6 | use crate::{symbol_index, LineIndex}; |
7 | 7 | ||
@@ -29,6 +29,9 @@ impl salsa::Database for RootDatabase { | |||
29 | fn salsa_runtime(&self) -> &salsa::Runtime<RootDatabase> { | 29 | fn salsa_runtime(&self) -> &salsa::Runtime<RootDatabase> { |
30 | &self.runtime | 30 | &self.runtime |
31 | } | 31 | } |
32 | fn on_propagated_panic(&self) -> ! { | ||
33 | Canceled::throw() | ||
34 | } | ||
32 | } | 35 | } |
33 | 36 | ||
34 | impl Default for RootDatabase { | 37 | impl Default for RootDatabase { |
diff --git a/crates/ra_ide_api/src/lib.rs b/crates/ra_ide_api/src/lib.rs index fbe1421a4..f505959ce 100644 --- a/crates/ra_ide_api/src/lib.rs +++ b/crates/ra_ide_api/src/lib.rs | |||
@@ -35,7 +35,7 @@ use std::{fmt, sync::Arc}; | |||
35 | 35 | ||
36 | use ra_syntax::{SmolStr, SourceFile, TreePtr, SyntaxKind, TextRange, TextUnit}; | 36 | use ra_syntax::{SmolStr, SourceFile, TreePtr, SyntaxKind, TextRange, TextUnit}; |
37 | use ra_text_edit::TextEdit; | 37 | use ra_text_edit::TextEdit; |
38 | use ra_db::{SyntaxDatabase, FilesDatabase, LocalSyntaxPtr}; | 38 | use ra_db::{SyntaxDatabase, FilesDatabase, LocalSyntaxPtr, BaseDatabase}; |
39 | use rayon::prelude::*; | 39 | use rayon::prelude::*; |
40 | use relative_path::RelativePathBuf; | 40 | use relative_path::RelativePathBuf; |
41 | use rustc_hash::FxHashMap; | 41 | use rustc_hash::FxHashMap; |
@@ -420,43 +420,47 @@ impl Analysis { | |||
420 | 420 | ||
421 | /// Fuzzy searches for a symbol. | 421 | /// Fuzzy searches for a symbol. |
422 | pub fn symbol_search(&self, query: Query) -> Cancelable<Vec<NavigationTarget>> { | 422 | pub fn symbol_search(&self, query: Query) -> Cancelable<Vec<NavigationTarget>> { |
423 | let res = symbol_index::world_symbols(&*self.db, query)? | 423 | self.with_db(|db| { |
424 | .into_iter() | 424 | let res = symbol_index::world_symbols(db, query)? |
425 | .map(NavigationTarget::from_symbol) | 425 | .into_iter() |
426 | .collect(); | 426 | .map(NavigationTarget::from_symbol) |
427 | Ok(res) | 427 | .collect::<Vec<_>>(); |
428 | Ok(res) | ||
429 | })? | ||
428 | } | 430 | } |
429 | 431 | ||
430 | pub fn goto_definition( | 432 | pub fn goto_definition( |
431 | &self, | 433 | &self, |
432 | position: FilePosition, | 434 | position: FilePosition, |
433 | ) -> Cancelable<Option<Vec<NavigationTarget>>> { | 435 | ) -> Cancelable<Option<Vec<NavigationTarget>>> { |
434 | goto_definition::goto_definition(&*self.db, position) | 436 | self.db |
437 | .catch_canceled(|db| goto_definition::goto_definition(db, position))? | ||
435 | } | 438 | } |
436 | 439 | ||
437 | /// Finds all usages of the reference at point. | 440 | /// Finds all usages of the reference at point. |
438 | pub fn find_all_refs(&self, position: FilePosition) -> Cancelable<Vec<(FileId, TextRange)>> { | 441 | pub fn find_all_refs(&self, position: FilePosition) -> Cancelable<Vec<(FileId, TextRange)>> { |
439 | self.db.find_all_refs(position) | 442 | self.with_db(|db| db.find_all_refs(position))? |
440 | } | 443 | } |
441 | 444 | ||
442 | /// Returns a short text descrbing element at position. | 445 | /// Returns a short text descrbing element at position. |
443 | pub fn hover(&self, position: FilePosition) -> Cancelable<Option<RangeInfo<String>>> { | 446 | pub fn hover(&self, position: FilePosition) -> Cancelable<Option<RangeInfo<String>>> { |
444 | hover::hover(&*self.db, position) | 447 | self.with_db(|db| hover::hover(db, position))? |
445 | } | 448 | } |
446 | 449 | ||
447 | /// Computes parameter information for the given call expression. | 450 | /// Computes parameter information for the given call expression. |
448 | pub fn call_info(&self, position: FilePosition) -> Cancelable<Option<CallInfo>> { | 451 | pub fn call_info(&self, position: FilePosition) -> Cancelable<Option<CallInfo>> { |
449 | call_info::call_info(&*self.db, position) | 452 | self.db |
453 | .catch_canceled(|db| call_info::call_info(db, position))? | ||
450 | } | 454 | } |
451 | 455 | ||
452 | /// Returns a `mod name;` declaration which created the current module. | 456 | /// Returns a `mod name;` declaration which created the current module. |
453 | pub fn parent_module(&self, position: FilePosition) -> Cancelable<Vec<NavigationTarget>> { | 457 | pub fn parent_module(&self, position: FilePosition) -> Cancelable<Vec<NavigationTarget>> { |
454 | self.db.parent_module(position) | 458 | self.with_db(|db| db.parent_module(position))? |
455 | } | 459 | } |
456 | 460 | ||
457 | /// Returns crates this file belongs too. | 461 | /// Returns crates this file belongs too. |
458 | pub fn crate_for(&self, file_id: FileId) -> Cancelable<Vec<CrateId>> { | 462 | pub fn crate_for(&self, file_id: FileId) -> Cancelable<Vec<CrateId>> { |
459 | self.db.crate_for(file_id) | 463 | self.with_db(|db| db.crate_for(file_id))? |
460 | } | 464 | } |
461 | 465 | ||
462 | /// Returns the root file of the given crate. | 466 | /// Returns the root file of the given crate. |
@@ -466,17 +470,21 @@ impl Analysis { | |||
466 | 470 | ||
467 | /// Returns the set of possible targets to run for the current file. | 471 | /// Returns the set of possible targets to run for the current file. |
468 | pub fn runnables(&self, file_id: FileId) -> Cancelable<Vec<Runnable>> { | 472 | pub fn runnables(&self, file_id: FileId) -> Cancelable<Vec<Runnable>> { |
469 | runnables::runnables(&*self.db, file_id) | 473 | self.db |
474 | .catch_canceled(|db| runnables::runnables(db, file_id))? | ||
470 | } | 475 | } |
471 | 476 | ||
472 | /// Computes syntax highlighting for the given file. | 477 | /// Computes syntax highlighting for the given file. |
473 | pub fn highlight(&self, file_id: FileId) -> Cancelable<Vec<HighlightedRange>> { | 478 | pub fn highlight(&self, file_id: FileId) -> Cancelable<Vec<HighlightedRange>> { |
474 | syntax_highlighting::highlight(&*self.db, file_id) | 479 | self.db |
480 | .catch_canceled(|db| syntax_highlighting::highlight(db, file_id))? | ||
475 | } | 481 | } |
476 | 482 | ||
477 | /// Computes completions at the given position. | 483 | /// Computes completions at the given position. |
478 | pub fn completions(&self, position: FilePosition) -> Cancelable<Option<Vec<CompletionItem>>> { | 484 | pub fn completions(&self, position: FilePosition) -> Cancelable<Option<Vec<CompletionItem>>> { |
479 | let completions = completion::completions(&self.db, position)?; | 485 | let completions = self |
486 | .db | ||
487 | .catch_canceled(|db| completion::completions(db, position))??; | ||
480 | Ok(completions.map(|it| it.into())) | 488 | Ok(completions.map(|it| it.into())) |
481 | } | 489 | } |
482 | 490 | ||
@@ -488,12 +496,12 @@ impl Analysis { | |||
488 | 496 | ||
489 | /// Computes the set of diagnostics for the given file. | 497 | /// Computes the set of diagnostics for the given file. |
490 | pub fn diagnostics(&self, file_id: FileId) -> Cancelable<Vec<Diagnostic>> { | 498 | pub fn diagnostics(&self, file_id: FileId) -> Cancelable<Vec<Diagnostic>> { |
491 | self.db.diagnostics(file_id) | 499 | self.with_db(|db| db.diagnostics(file_id))? |
492 | } | 500 | } |
493 | 501 | ||
494 | /// Computes the type of the expression at the given position. | 502 | /// Computes the type of the expression at the given position. |
495 | pub fn type_of(&self, frange: FileRange) -> Cancelable<Option<String>> { | 503 | pub fn type_of(&self, frange: FileRange) -> Cancelable<Option<String>> { |
496 | hover::type_of(&*self.db, frange) | 504 | self.with_db(|db| hover::type_of(db, frange))? |
497 | } | 505 | } |
498 | 506 | ||
499 | /// Returns the edit required to rename reference at the position to the new | 507 | /// Returns the edit required to rename reference at the position to the new |
@@ -503,7 +511,14 @@ impl Analysis { | |||
503 | position: FilePosition, | 511 | position: FilePosition, |
504 | new_name: &str, | 512 | new_name: &str, |
505 | ) -> Cancelable<Vec<SourceFileEdit>> { | 513 | ) -> Cancelable<Vec<SourceFileEdit>> { |
506 | self.db.rename(position, new_name) | 514 | self.with_db(|db| db.rename(position, new_name))? |
515 | } | ||
516 | |||
517 | fn with_db<F: FnOnce(&db::RootDatabase) -> T + std::panic::UnwindSafe, T>( | ||
518 | &self, | ||
519 | f: F, | ||
520 | ) -> Cancelable<T> { | ||
521 | self.db.catch_canceled(f) | ||
507 | } | 522 | } |
508 | } | 523 | } |
509 | 524 | ||
diff --git a/crates/ra_ide_api_light/src/extend_selection.rs b/crates/ra_ide_api_light/src/extend_selection.rs index 08cae5a51..db93db208 100644 --- a/crates/ra_ide_api_light/src/extend_selection.rs +++ b/crates/ra_ide_api_light/src/extend_selection.rs | |||
@@ -6,6 +6,21 @@ use ra_syntax::{ | |||
6 | 6 | ||
7 | pub fn extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange> { | 7 | pub fn extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange> { |
8 | let string_kinds = [COMMENT, STRING, RAW_STRING, BYTE_STRING, RAW_BYTE_STRING]; | 8 | let string_kinds = [COMMENT, STRING, RAW_STRING, BYTE_STRING, RAW_BYTE_STRING]; |
9 | let list_kinds = [ | ||
10 | FIELD_PAT_LIST, | ||
11 | MATCH_ARM_LIST, | ||
12 | NAMED_FIELD_LIST, | ||
13 | NAMED_FIELD_DEF_LIST, | ||
14 | POS_FIELD_LIST, | ||
15 | ENUM_VARIANT_LIST, | ||
16 | USE_TREE_LIST, | ||
17 | TYPE_PARAM_LIST, | ||
18 | TYPE_ARG_LIST, | ||
19 | PARAM_LIST, | ||
20 | ARG_LIST, | ||
21 | ARRAY_EXPR, | ||
22 | ]; | ||
23 | |||
9 | if range.is_empty() { | 24 | if range.is_empty() { |
10 | let offset = range.start(); | 25 | let offset = range.start(); |
11 | let mut leaves = find_leaf_at_offset(root, offset); | 26 | let mut leaves = find_leaf_at_offset(root, offset); |
@@ -26,9 +41,25 @@ pub fn extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange | |||
26 | return Some(leaf_range); | 41 | return Some(leaf_range); |
27 | }; | 42 | }; |
28 | let node = find_covering_node(root, range); | 43 | let node = find_covering_node(root, range); |
29 | if string_kinds.contains(&node.kind()) && range == node.range() { | 44 | |
30 | if let Some(range) = extend_comments(node) { | 45 | // Using shallowest node with same range allows us to traverse siblings. |
31 | return Some(range); | 46 | let node = node |
47 | .ancestors() | ||
48 | .take_while(|n| n.range() == node.range()) | ||
49 | .last() | ||
50 | .unwrap(); | ||
51 | |||
52 | if range == node.range() { | ||
53 | if string_kinds.contains(&node.kind()) { | ||
54 | if let Some(range) = extend_comments(node) { | ||
55 | return Some(range); | ||
56 | } | ||
57 | } | ||
58 | |||
59 | if node.parent().map(|n| list_kinds.contains(&n.kind())) == Some(true) { | ||
60 | if let Some(range) = extend_list_item(node) { | ||
61 | return Some(range); | ||
62 | } | ||
32 | } | 63 | } |
33 | } | 64 | } |
34 | 65 | ||
@@ -99,6 +130,45 @@ fn pick_best<'a>(l: &'a SyntaxNode, r: &'a SyntaxNode) -> &'a SyntaxNode { | |||
99 | } | 130 | } |
100 | } | 131 | } |
101 | 132 | ||
133 | /// Extend list item selection to include nearby comma and whitespace. | ||
134 | fn extend_list_item(node: &SyntaxNode) -> Option<TextRange> { | ||
135 | fn is_single_line_ws(node: &SyntaxNode) -> bool { | ||
136 | node.kind() == WHITESPACE && !node.leaf_text().unwrap().contains('\n') | ||
137 | } | ||
138 | |||
139 | fn nearby_comma(node: &SyntaxNode, dir: Direction) -> Option<&SyntaxNode> { | ||
140 | node.siblings(dir) | ||
141 | .skip(1) | ||
142 | .skip_while(|node| is_single_line_ws(node)) | ||
143 | .next() | ||
144 | .filter(|node| node.kind() == COMMA) | ||
145 | } | ||
146 | |||
147 | if let Some(comma_node) = nearby_comma(node, Direction::Prev) { | ||
148 | return Some(TextRange::from_to( | ||
149 | comma_node.range().start(), | ||
150 | node.range().end(), | ||
151 | )); | ||
152 | } | ||
153 | |||
154 | if let Some(comma_node) = nearby_comma(node, Direction::Next) { | ||
155 | // Include any following whitespace when comma if after list item. | ||
156 | let final_node = comma_node | ||
157 | .siblings(Direction::Next) | ||
158 | .skip(1) | ||
159 | .next() | ||
160 | .filter(|node| is_single_line_ws(node)) | ||
161 | .unwrap_or(comma_node); | ||
162 | |||
163 | return Some(TextRange::from_to( | ||
164 | node.range().start(), | ||
165 | final_node.range().end(), | ||
166 | )); | ||
167 | } | ||
168 | |||
169 | return None; | ||
170 | } | ||
171 | |||
102 | fn extend_comments(node: &SyntaxNode) -> Option<TextRange> { | 172 | fn extend_comments(node: &SyntaxNode) -> Option<TextRange> { |
103 | let prev = adj_comments(node, Direction::Prev); | 173 | let prev = adj_comments(node, Direction::Prev); |
104 | let next = adj_comments(node, Direction::Next); | 174 | let next = adj_comments(node, Direction::Next); |
@@ -145,7 +215,60 @@ mod tests { | |||
145 | } | 215 | } |
146 | 216 | ||
147 | #[test] | 217 | #[test] |
148 | fn test_extend_selection_start_of_the_lind() { | 218 | fn test_extend_selection_list() { |
219 | do_check(r#"fn foo(<|>x: i32) {}"#, &["x", "x: i32"]); | ||
220 | do_check( | ||
221 | r#"fn foo(<|>x: i32, y: i32) {}"#, | ||
222 | &["x", "x: i32", "x: i32, "], | ||
223 | ); | ||
224 | do_check( | ||
225 | r#"fn foo(<|>x: i32,y: i32) {}"#, | ||
226 | &["x", "x: i32", "x: i32,"], | ||
227 | ); | ||
228 | do_check( | ||
229 | r#"fn foo(x: i32, <|>y: i32) {}"#, | ||
230 | &["y", "y: i32", ", y: i32"], | ||
231 | ); | ||
232 | do_check( | ||
233 | r#"fn foo(x: i32, <|>y: i32, ) {}"#, | ||
234 | &["y", "y: i32", ", y: i32"], | ||
235 | ); | ||
236 | do_check( | ||
237 | r#"fn foo(x: i32,<|>y: i32) {}"#, | ||
238 | &["y", "y: i32", ",y: i32"], | ||
239 | ); | ||
240 | |||
241 | do_check( | ||
242 | r#"const FOO: [usize; 2] = [ 22<|> , 33];"#, | ||
243 | &["22", "22 , "], | ||
244 | ); | ||
245 | do_check(r#"const FOO: [usize; 2] = [ 22 , 33<|>];"#, &["33", ", 33"]); | ||
246 | do_check( | ||
247 | r#"const FOO: [usize; 2] = [ 22 , 33<|> ,];"#, | ||
248 | &["33", ", 33"], | ||
249 | ); | ||
250 | |||
251 | do_check( | ||
252 | r#" | ||
253 | const FOO: [usize; 2] = [ | ||
254 | 22, | ||
255 | <|>33, | ||
256 | ]"#, | ||
257 | &["33", "33,"], | ||
258 | ); | ||
259 | |||
260 | do_check( | ||
261 | r#" | ||
262 | const FOO: [usize; 2] = [ | ||
263 | 22 | ||
264 | , 33<|>, | ||
265 | ]"#, | ||
266 | &["33", ", 33"], | ||
267 | ); | ||
268 | } | ||
269 | |||
270 | #[test] | ||
271 | fn test_extend_selection_start_of_the_line() { | ||
149 | do_check( | 272 | do_check( |
150 | r#" | 273 | r#" |
151 | impl S { | 274 | impl S { |