diff options
Diffstat (limited to 'crates/ide/src/move_item.rs')
-rw-r--r-- | crates/ide/src/move_item.rs | 348 |
1 files changed, 314 insertions, 34 deletions
diff --git a/crates/ide/src/move_item.rs b/crates/ide/src/move_item.rs index 05fa8fc13..246f10a0a 100644 --- a/crates/ide/src/move_item.rs +++ b/crates/ide/src/move_item.rs | |||
@@ -1,13 +1,15 @@ | |||
1 | use std::iter::once; | 1 | use std::{iter::once, mem}; |
2 | 2 | ||
3 | use hir::Semantics; | 3 | use hir::Semantics; |
4 | use ide_db::{base_db::FileRange, RootDatabase}; | 4 | use ide_db::{base_db::FileRange, RootDatabase}; |
5 | use itertools::Itertools; | 5 | use itertools::Itertools; |
6 | use syntax::{ | 6 | use syntax::{ |
7 | algo, ast, match_ast, AstNode, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode, TextRange, | 7 | algo, ast, match_ast, AstNode, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, |
8 | TokenAtOffset, | ||
8 | }; | 9 | }; |
9 | use text_edit::{TextEdit, TextEditBuilder}; | 10 | use text_edit::{TextEdit, TextEditBuilder}; |
10 | 11 | ||
12 | #[derive(Copy, Clone, Debug)] | ||
11 | pub enum Direction { | 13 | pub enum Direction { |
12 | Up, | 14 | Up, |
13 | Down, | 15 | Down, |
@@ -23,6 +25,8 @@ pub enum Direction { | |||
23 | // | VS Code | **Rust Analyzer: Move item up** | 25 | // | VS Code | **Rust Analyzer: Move item up** |
24 | // | VS Code | **Rust Analyzer: Move item down** | 26 | // | VS Code | **Rust Analyzer: Move item down** |
25 | // |=== | 27 | // |=== |
28 | // | ||
29 | // image::https://user-images.githubusercontent.com/48062697/113065576-04298180-91b1-11eb-91ce-4505e99ed598.gif[] | ||
26 | pub(crate) fn move_item( | 30 | pub(crate) fn move_item( |
27 | db: &RootDatabase, | 31 | db: &RootDatabase, |
28 | range: FileRange, | 32 | range: FileRange, |
@@ -31,14 +35,19 @@ pub(crate) fn move_item( | |||
31 | let sema = Semantics::new(db); | 35 | let sema = Semantics::new(db); |
32 | let file = sema.parse(range.file_id); | 36 | let file = sema.parse(range.file_id); |
33 | 37 | ||
34 | let item = file.syntax().covering_element(range.range); | 38 | let item = if range.range.is_empty() { |
39 | SyntaxElement::Token(pick_best(file.syntax().token_at_offset(range.range.start()))?) | ||
40 | } else { | ||
41 | file.syntax().covering_element(range.range) | ||
42 | }; | ||
43 | |||
35 | find_ancestors(item, direction, range.range) | 44 | find_ancestors(item, direction, range.range) |
36 | } | 45 | } |
37 | 46 | ||
38 | fn find_ancestors(item: SyntaxElement, direction: Direction, range: TextRange) -> Option<TextEdit> { | 47 | fn find_ancestors(item: SyntaxElement, direction: Direction, range: TextRange) -> Option<TextEdit> { |
39 | let root = match item { | 48 | let root = match item { |
40 | NodeOrToken::Node(node) => node, | 49 | SyntaxElement::Node(node) => node, |
41 | NodeOrToken::Token(token) => token.parent()?, | 50 | SyntaxElement::Token(token) => token.parent()?, |
42 | }; | 51 | }; |
43 | 52 | ||
44 | let movable = [ | 53 | let movable = [ |
@@ -51,6 +60,11 @@ fn find_ancestors(item: SyntaxElement, direction: Direction, range: TextRange) - | |||
51 | SyntaxKind::PARAM, | 60 | SyntaxKind::PARAM, |
52 | SyntaxKind::LET_STMT, | 61 | SyntaxKind::LET_STMT, |
53 | SyntaxKind::EXPR_STMT, | 62 | SyntaxKind::EXPR_STMT, |
63 | SyntaxKind::IF_EXPR, | ||
64 | SyntaxKind::FOR_EXPR, | ||
65 | SyntaxKind::LOOP_EXPR, | ||
66 | SyntaxKind::WHILE_EXPR, | ||
67 | SyntaxKind::RETURN_EXPR, | ||
54 | SyntaxKind::MATCH_EXPR, | 68 | SyntaxKind::MATCH_EXPR, |
55 | SyntaxKind::MACRO_CALL, | 69 | SyntaxKind::MACRO_CALL, |
56 | SyntaxKind::TYPE_ALIAS, | 70 | SyntaxKind::TYPE_ALIAS, |
@@ -83,12 +97,12 @@ fn move_in_direction( | |||
83 | ) -> Option<TextEdit> { | 97 | ) -> Option<TextEdit> { |
84 | match_ast! { | 98 | match_ast! { |
85 | match node { | 99 | match node { |
86 | ast::ArgList(it) => swap_sibling_in_list(it.args(), range, direction), | 100 | ast::ArgList(it) => swap_sibling_in_list(node, it.args(), range, direction), |
87 | ast::GenericParamList(it) => swap_sibling_in_list(it.generic_params(), range, direction), | 101 | ast::GenericParamList(it) => swap_sibling_in_list(node, it.generic_params(), range, direction), |
88 | ast::GenericArgList(it) => swap_sibling_in_list(it.generic_args(), range, direction), | 102 | ast::GenericArgList(it) => swap_sibling_in_list(node, it.generic_args(), range, direction), |
89 | ast::VariantList(it) => swap_sibling_in_list(it.variants(), range, direction), | 103 | ast::VariantList(it) => swap_sibling_in_list(node, it.variants(), range, direction), |
90 | ast::TypeBoundList(it) => swap_sibling_in_list(it.bounds(), range, direction), | 104 | ast::TypeBoundList(it) => swap_sibling_in_list(node, it.bounds(), range, direction), |
91 | _ => Some(replace_nodes(node, &match direction { | 105 | _ => Some(replace_nodes(range, node, &match direction { |
92 | Direction::Up => node.prev_sibling(), | 106 | Direction::Up => node.prev_sibling(), |
93 | Direction::Down => node.next_sibling(), | 107 | Direction::Down => node.next_sibling(), |
94 | }?)) | 108 | }?)) |
@@ -97,30 +111,77 @@ fn move_in_direction( | |||
97 | } | 111 | } |
98 | 112 | ||
99 | fn swap_sibling_in_list<A: AstNode + Clone, I: Iterator<Item = A>>( | 113 | fn swap_sibling_in_list<A: AstNode + Clone, I: Iterator<Item = A>>( |
114 | node: &SyntaxNode, | ||
100 | list: I, | 115 | list: I, |
101 | range: TextRange, | 116 | range: TextRange, |
102 | direction: Direction, | 117 | direction: Direction, |
103 | ) -> Option<TextEdit> { | 118 | ) -> Option<TextEdit> { |
104 | let (l, r) = list | 119 | let list_lookup = list |
105 | .tuple_windows() | 120 | .tuple_windows() |
106 | .filter(|(l, r)| match direction { | 121 | .filter(|(l, r)| match direction { |
107 | Direction::Up => r.syntax().text_range().contains_range(range), | 122 | Direction::Up => r.syntax().text_range().contains_range(range), |
108 | Direction::Down => l.syntax().text_range().contains_range(range), | 123 | Direction::Down => l.syntax().text_range().contains_range(range), |
109 | }) | 124 | }) |
110 | .next()?; | 125 | .next(); |
111 | 126 | ||
112 | Some(replace_nodes(l.syntax(), r.syntax())) | 127 | if let Some((l, r)) = list_lookup { |
128 | Some(replace_nodes(range, l.syntax(), r.syntax())) | ||
129 | } else { | ||
130 | // Cursor is beyond any movable list item (for example, on curly brace in enum). | ||
131 | // It's not necessary, that parent of list is movable (arg list's parent is not, for example), | ||
132 | // and we have to continue tree traversal to find suitable node. | ||
133 | find_ancestors(SyntaxElement::Node(node.parent()?), direction, range) | ||
134 | } | ||
113 | } | 135 | } |
114 | 136 | ||
115 | fn replace_nodes(first: &SyntaxNode, second: &SyntaxNode) -> TextEdit { | 137 | fn replace_nodes<'a>( |
138 | range: TextRange, | ||
139 | mut first: &'a SyntaxNode, | ||
140 | mut second: &'a SyntaxNode, | ||
141 | ) -> TextEdit { | ||
142 | let cursor_offset = if range.is_empty() { | ||
143 | // FIXME: `applySnippetTextEdits` does not support non-empty selection ranges | ||
144 | if first.text_range().contains_range(range) { | ||
145 | Some(range.start() - first.text_range().start()) | ||
146 | } else if second.text_range().contains_range(range) { | ||
147 | mem::swap(&mut first, &mut second); | ||
148 | Some(range.start() - first.text_range().start()) | ||
149 | } else { | ||
150 | None | ||
151 | } | ||
152 | } else { | ||
153 | None | ||
154 | }; | ||
155 | |||
156 | let first_with_cursor = match cursor_offset { | ||
157 | Some(offset) => { | ||
158 | let mut item_text = first.text().to_string(); | ||
159 | item_text.insert_str(offset.into(), "$0"); | ||
160 | item_text | ||
161 | } | ||
162 | None => first.text().to_string(), | ||
163 | }; | ||
164 | |||
116 | let mut edit = TextEditBuilder::default(); | 165 | let mut edit = TextEditBuilder::default(); |
117 | 166 | ||
118 | algo::diff(first, second).into_text_edit(&mut edit); | 167 | algo::diff(first, second).into_text_edit(&mut edit); |
119 | algo::diff(second, first).into_text_edit(&mut edit); | 168 | edit.replace(second.text_range(), first_with_cursor); |
120 | 169 | ||
121 | edit.finish() | 170 | edit.finish() |
122 | } | 171 | } |
123 | 172 | ||
173 | fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { | ||
174 | return tokens.max_by_key(priority); | ||
175 | |||
176 | fn priority(n: &SyntaxToken) -> usize { | ||
177 | match n.kind() { | ||
178 | SyntaxKind::IDENT | SyntaxKind::LIFETIME_IDENT => 2, | ||
179 | kind if kind.is_trivia() => 0, | ||
180 | _ => 1, | ||
181 | } | ||
182 | } | ||
183 | } | ||
184 | |||
124 | #[cfg(test)] | 185 | #[cfg(test)] |
125 | mod tests { | 186 | mod tests { |
126 | use crate::fixture; | 187 | use crate::fixture; |
@@ -154,7 +215,7 @@ fn main() { | |||
154 | expect![[r#" | 215 | expect![[r#" |
155 | fn main() { | 216 | fn main() { |
156 | match true { | 217 | match true { |
157 | false => { | 218 | false =>$0 { |
158 | println!("Test"); | 219 | println!("Test"); |
159 | }, | 220 | }, |
160 | true => { | 221 | true => { |
@@ -188,7 +249,7 @@ fn main() { | |||
188 | false => { | 249 | false => { |
189 | println!("Test"); | 250 | println!("Test"); |
190 | }, | 251 | }, |
191 | true => { | 252 | true =>$0 { |
192 | println!("Hello, world"); | 253 | println!("Hello, world"); |
193 | } | 254 | } |
194 | }; | 255 | }; |
@@ -240,7 +301,7 @@ fn main() { | |||
240 | "#, | 301 | "#, |
241 | expect![[r#" | 302 | expect![[r#" |
242 | fn main() { | 303 | fn main() { |
243 | let test2 = 456; | 304 | let test2$0 = 456; |
244 | let test = 123; | 305 | let test = 123; |
245 | } | 306 | } |
246 | "#]], | 307 | "#]], |
@@ -259,7 +320,108 @@ fn main() { | |||
259 | "#, | 320 | "#, |
260 | expect![[r#" | 321 | expect![[r#" |
261 | fn main() { | 322 | fn main() { |
262 | println!("All I want to say is..."); | 323 | println!("All I want to say is...");$0 |
324 | println!("Hello, world"); | ||
325 | } | ||
326 | "#]], | ||
327 | Direction::Up, | ||
328 | ); | ||
329 | check( | ||
330 | r#" | ||
331 | fn main() { | ||
332 | println!("Hello, world"); | ||
333 | |||
334 | if true { | ||
335 | println!("Test"); | ||
336 | }$0$0 | ||
337 | } | ||
338 | "#, | ||
339 | expect![[r#" | ||
340 | fn main() { | ||
341 | if true { | ||
342 | println!("Test"); | ||
343 | }$0 | ||
344 | |||
345 | println!("Hello, world"); | ||
346 | } | ||
347 | "#]], | ||
348 | Direction::Up, | ||
349 | ); | ||
350 | check( | ||
351 | r#" | ||
352 | fn main() { | ||
353 | println!("Hello, world"); | ||
354 | |||
355 | for i in 0..10 { | ||
356 | println!("Test"); | ||
357 | }$0$0 | ||
358 | } | ||
359 | "#, | ||
360 | expect![[r#" | ||
361 | fn main() { | ||
362 | for i in 0..10 { | ||
363 | println!("Test"); | ||
364 | }$0 | ||
365 | |||
366 | println!("Hello, world"); | ||
367 | } | ||
368 | "#]], | ||
369 | Direction::Up, | ||
370 | ); | ||
371 | check( | ||
372 | r#" | ||
373 | fn main() { | ||
374 | println!("Hello, world"); | ||
375 | |||
376 | loop { | ||
377 | println!("Test"); | ||
378 | }$0$0 | ||
379 | } | ||
380 | "#, | ||
381 | expect![[r#" | ||
382 | fn main() { | ||
383 | loop { | ||
384 | println!("Test"); | ||
385 | }$0 | ||
386 | |||
387 | println!("Hello, world"); | ||
388 | } | ||
389 | "#]], | ||
390 | Direction::Up, | ||
391 | ); | ||
392 | check( | ||
393 | r#" | ||
394 | fn main() { | ||
395 | println!("Hello, world"); | ||
396 | |||
397 | while true { | ||
398 | println!("Test"); | ||
399 | }$0$0 | ||
400 | } | ||
401 | "#, | ||
402 | expect![[r#" | ||
403 | fn main() { | ||
404 | while true { | ||
405 | println!("Test"); | ||
406 | }$0 | ||
407 | |||
408 | println!("Hello, world"); | ||
409 | } | ||
410 | "#]], | ||
411 | Direction::Up, | ||
412 | ); | ||
413 | check( | ||
414 | r#" | ||
415 | fn main() { | ||
416 | println!("Hello, world"); | ||
417 | |||
418 | return 123;$0$0 | ||
419 | } | ||
420 | "#, | ||
421 | expect![[r#" | ||
422 | fn main() { | ||
423 | return 123;$0 | ||
424 | |||
263 | println!("Hello, world"); | 425 | println!("Hello, world"); |
264 | } | 426 | } |
265 | "#]], | 427 | "#]], |
@@ -295,7 +457,7 @@ fn main() {} | |||
295 | fn foo() {}$0$0 | 457 | fn foo() {}$0$0 |
296 | "#, | 458 | "#, |
297 | expect![[r#" | 459 | expect![[r#" |
298 | fn foo() {} | 460 | fn foo() {}$0 |
299 | 461 | ||
300 | fn main() {} | 462 | fn main() {} |
301 | "#]], | 463 | "#]], |
@@ -316,7 +478,7 @@ impl Wow for Yay $0$0{} | |||
316 | expect![[r#" | 478 | expect![[r#" |
317 | struct Yay; | 479 | struct Yay; |
318 | 480 | ||
319 | impl Wow for Yay {} | 481 | impl Wow for Yay $0{} |
320 | 482 | ||
321 | trait Wow {} | 483 | trait Wow {} |
322 | "#]], | 484 | "#]], |
@@ -332,7 +494,7 @@ use std::vec::Vec; | |||
332 | use std::collections::HashMap$0$0; | 494 | use std::collections::HashMap$0$0; |
333 | "#, | 495 | "#, |
334 | expect![[r#" | 496 | expect![[r#" |
335 | use std::collections::HashMap; | 497 | use std::collections::HashMap$0; |
336 | use std::vec::Vec; | 498 | use std::vec::Vec; |
337 | "#]], | 499 | "#]], |
338 | Direction::Up, | 500 | Direction::Up, |
@@ -367,7 +529,7 @@ fn main() { | |||
367 | } | 529 | } |
368 | 530 | ||
369 | #[test] | 531 | #[test] |
370 | fn test_moves_param_up() { | 532 | fn test_moves_param() { |
371 | check( | 533 | check( |
372 | r#" | 534 | r#" |
373 | fn test(one: i32, two$0$0: u32) {} | 535 | fn test(one: i32, two$0$0: u32) {} |
@@ -377,7 +539,7 @@ fn main() { | |||
377 | } | 539 | } |
378 | "#, | 540 | "#, |
379 | expect![[r#" | 541 | expect![[r#" |
380 | fn test(two: u32, one: i32) {} | 542 | fn test(two$0: u32, one: i32) {} |
381 | 543 | ||
382 | fn main() { | 544 | fn main() { |
383 | test(123, 456); | 545 | test(123, 456); |
@@ -385,6 +547,15 @@ fn main() { | |||
385 | "#]], | 547 | "#]], |
386 | Direction::Up, | 548 | Direction::Up, |
387 | ); | 549 | ); |
550 | check( | ||
551 | r#" | ||
552 | fn f($0$0arg: u8, arg2: u16) {} | ||
553 | "#, | ||
554 | expect![[r#" | ||
555 | fn f(arg2: u16, $0arg: u8) {} | ||
556 | "#]], | ||
557 | Direction::Down, | ||
558 | ); | ||
388 | } | 559 | } |
389 | 560 | ||
390 | #[test] | 561 | #[test] |
@@ -401,7 +572,7 @@ fn main() { | |||
401 | fn test(one: i32, two: u32) {} | 572 | fn test(one: i32, two: u32) {} |
402 | 573 | ||
403 | fn main() { | 574 | fn main() { |
404 | test(456, 123); | 575 | test(456$0, 123); |
405 | } | 576 | } |
406 | "#]], | 577 | "#]], |
407 | Direction::Up, | 578 | Direction::Up, |
@@ -422,7 +593,7 @@ fn main() { | |||
422 | fn test(one: i32, two: u32) {} | 593 | fn test(one: i32, two: u32) {} |
423 | 594 | ||
424 | fn main() { | 595 | fn main() { |
425 | test(456, 123); | 596 | test(456, 123$0); |
426 | } | 597 | } |
427 | "#]], | 598 | "#]], |
428 | Direction::Down, | 599 | Direction::Down, |
@@ -459,7 +630,7 @@ struct Test<A, B$0$0>(A, B); | |||
459 | fn main() {} | 630 | fn main() {} |
460 | "#, | 631 | "#, |
461 | expect![[r#" | 632 | expect![[r#" |
462 | struct Test<B, A>(A, B); | 633 | struct Test<B$0, A>(A, B); |
463 | 634 | ||
464 | fn main() {} | 635 | fn main() {} |
465 | "#]], | 636 | "#]], |
@@ -481,7 +652,7 @@ fn main() { | |||
481 | struct Test<A, B>(A, B); | 652 | struct Test<A, B>(A, B); |
482 | 653 | ||
483 | fn main() { | 654 | fn main() { |
484 | let t = Test::<&str, i32>(123, "yay"); | 655 | let t = Test::<&str$0, i32>(123, "yay"); |
485 | } | 656 | } |
486 | "#]], | 657 | "#]], |
487 | Direction::Up, | 658 | Direction::Up, |
@@ -501,7 +672,7 @@ fn main() {} | |||
501 | "#, | 672 | "#, |
502 | expect![[r#" | 673 | expect![[r#" |
503 | enum Hello { | 674 | enum Hello { |
504 | Two, | 675 | Two$0, |
505 | One | 676 | One |
506 | } | 677 | } |
507 | 678 | ||
@@ -528,7 +699,7 @@ trait One {} | |||
528 | 699 | ||
529 | trait Two {} | 700 | trait Two {} |
530 | 701 | ||
531 | fn test<T: Two + One>(t: T) {} | 702 | fn test<T: Two$0 + One>(t: T) {} |
532 | 703 | ||
533 | fn main() {} | 704 | fn main() {} |
534 | "#]], | 705 | "#]], |
@@ -574,7 +745,7 @@ trait Yay { | |||
574 | impl Yay for Test { | 745 | impl Yay for Test { |
575 | type One = i32; | 746 | type One = i32; |
576 | 747 | ||
577 | fn inner() { | 748 | fn inner() {$0 |
578 | println!("Mmmm"); | 749 | println!("Mmmm"); |
579 | } | 750 | } |
580 | 751 | ||
@@ -601,7 +772,7 @@ fn test() { | |||
601 | "#, | 772 | "#, |
602 | expect![[r#" | 773 | expect![[r#" |
603 | fn test() { | 774 | fn test() { |
604 | mod hi { | 775 | mod hi {$0 |
605 | fn inner() {} | 776 | fn inner() {} |
606 | } | 777 | } |
607 | 778 | ||
@@ -615,6 +786,115 @@ fn test() { | |||
615 | } | 786 | } |
616 | 787 | ||
617 | #[test] | 788 | #[test] |
789 | fn test_cursor_at_item_start() { | ||
790 | check( | ||
791 | r#" | ||
792 | $0$0#[derive(Debug)] | ||
793 | enum FooBar { | ||
794 | Foo, | ||
795 | Bar, | ||
796 | } | ||
797 | |||
798 | fn main() {} | ||
799 | "#, | ||
800 | expect![[r#" | ||
801 | fn main() {} | ||
802 | |||
803 | $0#[derive(Debug)] | ||
804 | enum FooBar { | ||
805 | Foo, | ||
806 | Bar, | ||
807 | } | ||
808 | "#]], | ||
809 | Direction::Down, | ||
810 | ); | ||
811 | check( | ||
812 | r#" | ||
813 | $0$0enum FooBar { | ||
814 | Foo, | ||
815 | Bar, | ||
816 | } | ||
817 | |||
818 | fn main() {} | ||
819 | "#, | ||
820 | expect![[r#" | ||
821 | fn main() {} | ||
822 | |||
823 | $0enum FooBar { | ||
824 | Foo, | ||
825 | Bar, | ||
826 | } | ||
827 | "#]], | ||
828 | Direction::Down, | ||
829 | ); | ||
830 | check( | ||
831 | r#" | ||
832 | struct Test; | ||
833 | |||
834 | trait SomeTrait {} | ||
835 | |||
836 | $0$0impl SomeTrait for Test {} | ||
837 | |||
838 | fn main() {} | ||
839 | "#, | ||
840 | expect![[r#" | ||
841 | struct Test; | ||
842 | |||
843 | $0impl SomeTrait for Test {} | ||
844 | |||
845 | trait SomeTrait {} | ||
846 | |||
847 | fn main() {} | ||
848 | "#]], | ||
849 | Direction::Up, | ||
850 | ); | ||
851 | } | ||
852 | |||
853 | #[test] | ||
854 | fn test_cursor_at_item_end() { | ||
855 | check( | ||
856 | r#" | ||
857 | enum FooBar { | ||
858 | Foo, | ||
859 | Bar, | ||
860 | }$0$0 | ||
861 | |||
862 | fn main() {} | ||
863 | "#, | ||
864 | expect![[r#" | ||
865 | fn main() {} | ||
866 | |||
867 | enum FooBar { | ||
868 | Foo, | ||
869 | Bar, | ||
870 | }$0 | ||
871 | "#]], | ||
872 | Direction::Down, | ||
873 | ); | ||
874 | check( | ||
875 | r#" | ||
876 | struct Test; | ||
877 | |||
878 | trait SomeTrait {} | ||
879 | |||
880 | impl SomeTrait for Test {}$0$0 | ||
881 | |||
882 | fn main() {} | ||
883 | "#, | ||
884 | expect![[r#" | ||
885 | struct Test; | ||
886 | |||
887 | impl SomeTrait for Test {}$0 | ||
888 | |||
889 | trait SomeTrait {} | ||
890 | |||
891 | fn main() {} | ||
892 | "#]], | ||
893 | Direction::Up, | ||
894 | ); | ||
895 | } | ||
896 | |||
897 | #[test] | ||
618 | fn handles_empty_file() { | 898 | fn handles_empty_file() { |
619 | check(r#"$0$0"#, expect![[r#""#]], Direction::Up); | 899 | check(r#"$0$0"#, expect![[r#""#]], Direction::Up); |
620 | } | 900 | } |