diff options
author | Aleksey Kladov <[email protected]> | 2020-03-11 11:46:36 +0000 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2020-03-11 11:58:16 +0000 |
commit | 52da9e90a6002eb712175f7c8d76a472cf966d8e (patch) | |
tree | f2a08835519d35693e1c8661581ce1593c3c2a91 /crates/ra_ide/src/typing | |
parent | c48dcf74118b6e0df747f036a9b66701037f3fc7 (diff) |
Move on enter to a separate module
Diffstat (limited to 'crates/ra_ide/src/typing')
-rw-r--r-- | crates/ra_ide/src/typing/on_enter.rs | 171 |
1 files changed, 171 insertions, 0 deletions
diff --git a/crates/ra_ide/src/typing/on_enter.rs b/crates/ra_ide/src/typing/on_enter.rs new file mode 100644 index 000000000..359794f67 --- /dev/null +++ b/crates/ra_ide/src/typing/on_enter.rs | |||
@@ -0,0 +1,171 @@ | |||
1 | //! Handles the `Enter` key press. At the momently, this only continues | ||
2 | //! comments, but should handle indent some time in the future as well. | ||
3 | |||
4 | use ra_db::{FilePosition, SourceDatabase}; | ||
5 | use ra_ide_db::RootDatabase; | ||
6 | use ra_syntax::{ | ||
7 | ast::{self, AstToken}, | ||
8 | AstNode, SmolStr, SourceFile, | ||
9 | SyntaxKind::*, | ||
10 | SyntaxToken, TextUnit, TokenAtOffset, | ||
11 | }; | ||
12 | use ra_text_edit::TextEdit; | ||
13 | |||
14 | use crate::{SourceChange, SourceFileEdit}; | ||
15 | |||
16 | pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<SourceChange> { | ||
17 | let parse = db.parse(position.file_id); | ||
18 | let file = parse.tree(); | ||
19 | let comment = file | ||
20 | .syntax() | ||
21 | .token_at_offset(position.offset) | ||
22 | .left_biased() | ||
23 | .and_then(ast::Comment::cast)?; | ||
24 | |||
25 | if comment.kind().shape.is_block() { | ||
26 | return None; | ||
27 | } | ||
28 | |||
29 | let prefix = comment.prefix(); | ||
30 | let comment_range = comment.syntax().text_range(); | ||
31 | if position.offset < comment_range.start() + TextUnit::of_str(prefix) { | ||
32 | return None; | ||
33 | } | ||
34 | |||
35 | // Continuing non-doc line comments (like this one :) ) is annoying | ||
36 | if prefix == "//" && comment_range.end() == position.offset { | ||
37 | return None; | ||
38 | } | ||
39 | |||
40 | let indent = node_indent(&file, comment.syntax())?; | ||
41 | let inserted = format!("\n{}{} ", indent, prefix); | ||
42 | let cursor_position = position.offset + TextUnit::of_str(&inserted); | ||
43 | let edit = TextEdit::insert(position.offset, inserted); | ||
44 | |||
45 | Some( | ||
46 | SourceChange::source_file_edit( | ||
47 | "on enter", | ||
48 | SourceFileEdit { edit, file_id: position.file_id }, | ||
49 | ) | ||
50 | .with_cursor(FilePosition { offset: cursor_position, file_id: position.file_id }), | ||
51 | ) | ||
52 | } | ||
53 | |||
54 | fn node_indent(file: &SourceFile, token: &SyntaxToken) -> Option<SmolStr> { | ||
55 | let ws = match file.syntax().token_at_offset(token.text_range().start()) { | ||
56 | TokenAtOffset::Between(l, r) => { | ||
57 | assert!(r == *token); | ||
58 | l | ||
59 | } | ||
60 | TokenAtOffset::Single(n) => { | ||
61 | assert!(n == *token); | ||
62 | return Some("".into()); | ||
63 | } | ||
64 | TokenAtOffset::None => unreachable!(), | ||
65 | }; | ||
66 | if ws.kind() != WHITESPACE { | ||
67 | return None; | ||
68 | } | ||
69 | let text = ws.text(); | ||
70 | let pos = text.rfind('\n').map(|it| it + 1).unwrap_or(0); | ||
71 | Some(text[pos..].into()) | ||
72 | } | ||
73 | |||
74 | #[cfg(test)] | ||
75 | mod tests { | ||
76 | use test_utils::{add_cursor, assert_eq_text, extract_offset}; | ||
77 | |||
78 | use crate::mock_analysis::single_file; | ||
79 | |||
80 | use super::*; | ||
81 | |||
82 | fn apply_on_enter(before: &str) -> Option<String> { | ||
83 | let (offset, before) = extract_offset(before); | ||
84 | let (analysis, file_id) = single_file(&before); | ||
85 | let result = analysis.on_enter(FilePosition { offset, file_id }).unwrap()?; | ||
86 | |||
87 | assert_eq!(result.source_file_edits.len(), 1); | ||
88 | let actual = result.source_file_edits[0].edit.apply(&before); | ||
89 | let actual = add_cursor(&actual, result.cursor_position.unwrap().offset); | ||
90 | Some(actual) | ||
91 | } | ||
92 | |||
93 | fn do_check(ra_fixture_before: &str, ra_fixture_after: &str) { | ||
94 | let actual = apply_on_enter(ra_fixture_before).unwrap(); | ||
95 | assert_eq_text!(ra_fixture_after, &actual); | ||
96 | } | ||
97 | |||
98 | fn do_check_noop(ra_fixture_text: &str) { | ||
99 | assert!(apply_on_enter(ra_fixture_text).is_none()) | ||
100 | } | ||
101 | |||
102 | #[test] | ||
103 | fn test_on_enter() { | ||
104 | do_check( | ||
105 | r" | ||
106 | /// Some docs<|> | ||
107 | fn foo() { | ||
108 | } | ||
109 | ", | ||
110 | r" | ||
111 | /// Some docs | ||
112 | /// <|> | ||
113 | fn foo() { | ||
114 | } | ||
115 | ", | ||
116 | ); | ||
117 | do_check( | ||
118 | r" | ||
119 | impl S { | ||
120 | /// Some<|> docs. | ||
121 | fn foo() {} | ||
122 | } | ||
123 | ", | ||
124 | r" | ||
125 | impl S { | ||
126 | /// Some | ||
127 | /// <|> docs. | ||
128 | fn foo() {} | ||
129 | } | ||
130 | ", | ||
131 | ); | ||
132 | do_check( | ||
133 | r" | ||
134 | fn main() { | ||
135 | // Fix<|> me | ||
136 | let x = 1 + 1; | ||
137 | } | ||
138 | ", | ||
139 | r" | ||
140 | fn main() { | ||
141 | // Fix | ||
142 | // <|> me | ||
143 | let x = 1 + 1; | ||
144 | } | ||
145 | ", | ||
146 | ); | ||
147 | do_check( | ||
148 | r" | ||
149 | ///<|> Some docs | ||
150 | fn foo() { | ||
151 | } | ||
152 | ", | ||
153 | r" | ||
154 | /// | ||
155 | /// <|> Some docs | ||
156 | fn foo() { | ||
157 | } | ||
158 | ", | ||
159 | ); | ||
160 | do_check_noop( | ||
161 | r" | ||
162 | fn main() { | ||
163 | // Fix me<|> | ||
164 | let x = 1 + 1; | ||
165 | } | ||
166 | ", | ||
167 | ); | ||
168 | |||
169 | do_check_noop(r"<|>//! docz"); | ||
170 | } | ||
171 | } | ||