aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <bors[bot]@users.noreply.github.com>2018-10-11 16:43:34 +0100
committerbors[bot] <bors[bot]@users.noreply.github.com>2018-10-11 16:43:34 +0100
commit77e9bf9b5fdcd4262e2d9badb42b912f5728d90b (patch)
tree3f318ed358723e229dc6101404d7b607bbeabd51
parent9b155c89764b8413df6b32edfde94fce1d9c15ec (diff)
parent6fe77db41307da8ead8a0b0355488221b61c0349 (diff)
Merge #116
116: Collapse comments upon join r=matklad a=aochagavia Todo: - [x] Write tests - [x] Resolve fixmes - [x] Implement `comment_start_length` using the parser I left a bunch of questions as fixmes. Can someone take a look at them? Also, I would love to use the parser to calculate the length of the leading characters in a comment (`//`, `///`, `//!`, `/*`), so any hints are greatly appreciated. Co-authored-by: Adolfo OchagavĂ­a <[email protected]> Co-authored-by: Adolfo OchagavĂ­a <[email protected]>
-rw-r--r--crates/ra_editor/src/typing.rs195
-rw-r--r--crates/ra_syntax/src/ast/generated.rs18
-rw-r--r--crates/ra_syntax/src/ast/mod.rs43
-rw-r--r--crates/ra_syntax/src/grammar.ron1
4 files changed, 199 insertions, 58 deletions
diff --git a/crates/ra_editor/src/typing.rs b/crates/ra_editor/src/typing.rs
index 3384389d1..1dc658f9b 100644
--- a/crates/ra_editor/src/typing.rs
+++ b/crates/ra_editor/src/typing.rs
@@ -30,6 +30,7 @@ pub fn join_lines(file: &File, range: TextRange) -> LocalEdit {
30 } else { 30 } else {
31 range 31 range
32 }; 32 };
33
33 let node = find_covering_node(file.syntax(), range); 34 let node = find_covering_node(file.syntax(), range);
34 let mut edit = EditBuilder::new(); 35 let mut edit = EditBuilder::new();
35 for node in node.descendants() { 36 for node in node.descendants() {
@@ -57,14 +58,19 @@ pub fn join_lines(file: &File, range: TextRange) -> LocalEdit {
57} 58}
58 59
59pub fn on_enter(file: &File, offset: TextUnit) -> Option<LocalEdit> { 60pub fn on_enter(file: &File, offset: TextUnit) -> Option<LocalEdit> {
60 let comment = find_leaf_at_offset(file.syntax(), offset).left_biased().filter(|it| it.kind() == COMMENT)?; 61 let comment = find_leaf_at_offset(file.syntax(), offset).left_biased().and_then(|it| ast::Comment::cast(it))?;
61 let prefix = comment_preffix(comment)?; 62
62 if offset < comment.range().start() + TextUnit::of_str(prefix) { 63 if let ast::CommentFlavor::Multiline = comment.flavor() {
64 return None;
65 }
66
67 let prefix = comment.prefix();
68 if offset < comment.syntax().range().start() + TextUnit::of_str(prefix) + TextUnit::from(1) {
63 return None; 69 return None;
64 } 70 }
65 71
66 let indent = node_indent(file, comment)?; 72 let indent = node_indent(file, comment.syntax())?;
67 let inserted = format!("\n{}{}", indent, prefix); 73 let inserted = format!("\n{}{} ", indent, prefix);
68 let cursor_position = offset + TextUnit::of_str(&inserted); 74 let cursor_position = offset + TextUnit::of_str(&inserted);
69 let mut edit = EditBuilder::new(); 75 let mut edit = EditBuilder::new();
70 edit.insert(offset, inserted); 76 edit.insert(offset, inserted);
@@ -74,20 +80,6 @@ pub fn on_enter(file: &File, offset: TextUnit) -> Option<LocalEdit> {
74 }) 80 })
75} 81}
76 82
77fn comment_preffix(comment: SyntaxNodeRef) -> Option<&'static str> {
78 let text = comment.leaf_text().unwrap();
79 let res = if text.starts_with("///") {
80 "/// "
81 } else if text.starts_with("//!") {
82 "//! "
83 } else if text.starts_with("//") {
84 "// "
85 } else {
86 return None;
87 };
88 Some(res)
89}
90
91fn node_indent<'a>(file: &'a File, node: SyntaxNodeRef) -> Option<&'a str> { 83fn node_indent<'a>(file: &'a File, node: SyntaxNodeRef) -> Option<&'a str> {
92 let ws = match find_leaf_at_offset(file.syntax(), node.range().start()) { 84 let ws = match find_leaf_at_offset(file.syntax(), node.range().start()) {
93 LeafAtOffset::Between(l, r) => { 85 LeafAtOffset::Between(l, r) => {
@@ -139,41 +131,60 @@ fn remove_newline(
139 node_text: &str, 131 node_text: &str,
140 offset: TextUnit, 132 offset: TextUnit,
141) { 133) {
142 if node.kind() == WHITESPACE && node_text.bytes().filter(|&b| b == b'\n').count() == 1 { 134 if node.kind() != WHITESPACE || node_text.bytes().filter(|&b| b == b'\n').count() != 1 {
143 if join_single_expr_block(edit, node).is_some() { 135 // The node is either the first or the last in the file
144 return 136 let suff = &node_text[TextRange::from_to(
145 } 137 offset - node.range().start() + TextUnit::of_char('\n'),
146 match (node.prev_sibling(), node.next_sibling()) { 138 TextUnit::of_str(node_text),
147 (Some(prev), Some(next)) => { 139 )];
148 let range = TextRange::from_to(prev.range().start(), node.range().end()); 140 let spaces = suff.bytes().take_while(|&b| b == b' ').count();
149 if is_trailing_comma(prev.kind(), next.kind()) { 141
150 edit.delete(range); 142 edit.replace(
151 } else if no_space_required(prev.kind(), next.kind()) { 143 TextRange::offset_len(offset, ((spaces + 1) as u32).into()),
152 edit.delete(node.range()); 144 " ".to_string(),
153 } else if prev.kind() == COMMA && next.kind() == R_CURLY { 145 );
154 edit.replace(range, " ".to_string()); 146 return;
155 } else {
156 edit.replace(
157 node.range(),
158 compute_ws(prev, next).to_string(),
159 );
160 }
161 return;
162 }
163 _ => (),
164 }
165 } 147 }
166 148
167 let suff = &node_text[TextRange::from_to( 149 // Special case that turns something like:
168 offset - node.range().start() + TextUnit::of_char('\n'), 150 //
169 TextUnit::of_str(node_text), 151 // ```
170 )]; 152 // my_function({<|>
171 let spaces = suff.bytes().take_while(|&b| b == b' ').count(); 153 // <some-expr>
154 // })
155 // ```
156 //
157 // into `my_function(<some-expr>)`
158 if join_single_expr_block(edit, node).is_some() {
159 return
160 }
172 161
173 edit.replace( 162 // The node is between two other nodes
174 TextRange::offset_len(offset, ((spaces + 1) as u32).into()), 163 let prev = node.prev_sibling().unwrap();
175 " ".to_string(), 164 let next = node.next_sibling().unwrap();
176 ); 165 if is_trailing_comma(prev.kind(), next.kind()) {
166 // Removes: trailing comma, newline (incl. surrounding whitespace)
167 edit.delete(TextRange::from_to(prev.range().start(), node.range().end()));
168 } else if prev.kind() == COMMA && next.kind() == R_CURLY {
169 // Removes: comma, newline (incl. surrounding whitespace)
170 // Adds: a single whitespace
171 edit.replace(
172 TextRange::from_to(prev.range().start(), node.range().end()),
173 " ".to_string()
174 );
175 } else if let (Some(_), Some(next)) = (ast::Comment::cast(prev), ast::Comment::cast(next)) {
176 // Removes: newline (incl. surrounding whitespace), start of the next comment
177 edit.delete(TextRange::from_to(
178 node.range().start(),
179 next.syntax().range().start() + TextUnit::of_str(next.prefix())
180 ));
181 } else {
182 // Remove newline but add a computed amount of whitespace characters
183 edit.replace(
184 node.range(),
185 compute_ws(prev, next).to_string(),
186 );
187 }
177} 188}
178 189
179fn is_trailing_comma(left: SyntaxKind, right: SyntaxKind) -> bool { 190fn is_trailing_comma(left: SyntaxKind, right: SyntaxKind) -> bool {
@@ -183,13 +194,6 @@ fn is_trailing_comma(left: SyntaxKind, right: SyntaxKind) -> bool {
183 } 194 }
184} 195}
185 196
186fn no_space_required(left: SyntaxKind, right: SyntaxKind) -> bool {
187 match (left, right) {
188 (_, DOT) => true,
189 _ => false
190 }
191}
192
193fn join_single_expr_block( 197fn join_single_expr_block(
194 edit: &mut EditBuilder, 198 edit: &mut EditBuilder,
195 node: SyntaxNodeRef, 199 node: SyntaxNodeRef,
@@ -231,6 +235,7 @@ fn compute_ws(left: SyntaxNodeRef, right: SyntaxNodeRef) -> &'static str {
231 } 235 }
232 match right.kind() { 236 match right.kind() {
233 R_PAREN | R_BRACK => return "", 237 R_PAREN | R_BRACK => return "",
238 DOT => return "",
234 _ => (), 239 _ => (),
235 } 240 }
236 " " 241 " "
@@ -291,6 +296,80 @@ fn foo() {
291}"); 296}");
292 } 297 }
293 298
299 #[test]
300 fn test_join_lines_normal_comments() {
301 check_join_lines(r"
302fn foo() {
303 // Hello<|>
304 // world!
305}
306", r"
307fn foo() {
308 // Hello<|> world!
309}
310");
311 }
312
313 #[test]
314 fn test_join_lines_doc_comments() {
315 check_join_lines(r"
316fn foo() {
317 /// Hello<|>
318 /// world!
319}
320", r"
321fn foo() {
322 /// Hello<|> world!
323}
324");
325 }
326
327 #[test]
328 fn test_join_lines_mod_comments() {
329 check_join_lines(r"
330fn foo() {
331 //! Hello<|>
332 //! world!
333}
334", r"
335fn foo() {
336 //! Hello<|> world!
337}
338");
339 }
340
341 #[test]
342 fn test_join_lines_multiline_comments_1() {
343 check_join_lines(r"
344fn foo() {
345 // Hello<|>
346 /* world! */
347}
348", r"
349fn foo() {
350 // Hello<|> world! */
351}
352");
353 }
354
355 #[test]
356 fn test_join_lines_multiline_comments_2() {
357 check_join_lines(r"
358fn foo() {
359 // The<|>
360 /* quick
361 brown
362 fox! */
363}
364", r"
365fn foo() {
366 // The<|> quick
367 brown
368 fox! */
369}
370");
371 }
372
294 fn check_join_lines_sel(before: &str, after: &str) { 373 fn check_join_lines_sel(before: &str, after: &str) {
295 let (sel, before) = extract_range(before); 374 let (sel, before) = extract_range(before);
296 let file = File::parse(&before); 375 let file = File::parse(&before);
diff --git a/crates/ra_syntax/src/ast/generated.rs b/crates/ra_syntax/src/ast/generated.rs
index f27093291..ef7b5b1a1 100644
--- a/crates/ra_syntax/src/ast/generated.rs
+++ b/crates/ra_syntax/src/ast/generated.rs
@@ -231,6 +231,24 @@ impl<'a> AstNode<'a> for CastExpr<'a> {
231 231
232impl<'a> CastExpr<'a> {} 232impl<'a> CastExpr<'a> {}
233 233
234// Comment
235#[derive(Debug, Clone, Copy)]
236pub struct Comment<'a> {
237 syntax: SyntaxNodeRef<'a>,
238}
239
240impl<'a> AstNode<'a> for Comment<'a> {
241 fn cast(syntax: SyntaxNodeRef<'a>) -> Option<Self> {
242 match syntax.kind() {
243 COMMENT => Some(Comment { syntax }),
244 _ => None,
245 }
246 }
247 fn syntax(self) -> SyntaxNodeRef<'a> { self.syntax }
248}
249
250impl<'a> Comment<'a> {}
251
234// Condition 252// Condition
235#[derive(Debug, Clone, Copy)] 253#[derive(Debug, Clone, Copy)]
236pub struct Condition<'a> { 254pub struct Condition<'a> {
diff --git a/crates/ra_syntax/src/ast/mod.rs b/crates/ra_syntax/src/ast/mod.rs
index c1570b868..10dac72e5 100644
--- a/crates/ra_syntax/src/ast/mod.rs
+++ b/crates/ra_syntax/src/ast/mod.rs
@@ -99,6 +99,49 @@ impl<'a> Lifetime<'a> {
99 } 99 }
100} 100}
101 101
102impl<'a> Comment<'a> {
103 pub fn text(&self) -> SmolStr {
104 self.syntax().leaf_text().unwrap().clone()
105 }
106
107 pub fn flavor(&self) -> CommentFlavor {
108 let text = self.text();
109 if text.starts_with("///") {
110 CommentFlavor::Doc
111 } else if text.starts_with("//!") {
112 CommentFlavor::ModuleDoc
113 } else if text.starts_with("//") {
114 CommentFlavor::Line
115 } else {
116 CommentFlavor::Multiline
117 }
118 }
119
120 pub fn prefix(&self) -> &'static str {
121 self.flavor().prefix()
122 }
123}
124
125#[derive(Debug)]
126pub enum CommentFlavor {
127 Line,
128 Doc,
129 ModuleDoc,
130 Multiline
131}
132
133impl CommentFlavor {
134 pub fn prefix(&self) -> &'static str {
135 use self::CommentFlavor::*;
136 match *self {
137 Line => "//",
138 Doc => "///",
139 ModuleDoc => "//!",
140 Multiline => "/*"
141 }
142 }
143}
144
102impl<'a> Name<'a> { 145impl<'a> Name<'a> {
103 pub fn text(&self) -> SmolStr { 146 pub fn text(&self) -> SmolStr {
104 let ident = self.syntax().first_child() 147 let ident = self.syntax().first_child()
diff --git a/crates/ra_syntax/src/grammar.ron b/crates/ra_syntax/src/grammar.ron
index 4b990fd8d..9da0c2c13 100644
--- a/crates/ra_syntax/src/grammar.ron
+++ b/crates/ra_syntax/src/grammar.ron
@@ -537,5 +537,6 @@ Grammar(
537 "PathSegment": ( 537 "PathSegment": (
538 options: [ "NameRef" ] 538 options: [ "NameRef" ]
539 ), 539 ),
540 "Comment": (),
540 }, 541 },
541) 542)