diff options
Diffstat (limited to 'crates')
-rw-r--r-- | crates/ra_analysis/src/lib.rs | 6 | ||||
-rw-r--r-- | crates/ra_editor/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/ra_editor/src/typing.rs | 101 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/conv.rs | 41 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/main_loop/handlers.rs | 14 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/main_loop/mod.rs | 1 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/req.rs | 8 |
7 files changed, 169 insertions, 4 deletions
diff --git a/crates/ra_analysis/src/lib.rs b/crates/ra_analysis/src/lib.rs index b4c7db476..f6ceb7eb2 100644 --- a/crates/ra_analysis/src/lib.rs +++ b/crates/ra_analysis/src/lib.rs | |||
@@ -184,6 +184,12 @@ impl Analysis { | |||
184 | let file = self.imp.file_syntax(file_id); | 184 | let file = self.imp.file_syntax(file_id); |
185 | SourceChange::from_local_edit(file_id, "join lines", ra_editor::join_lines(&file, range)) | 185 | SourceChange::from_local_edit(file_id, "join lines", ra_editor::join_lines(&file, range)) |
186 | } | 186 | } |
187 | pub fn on_enter(&self, file_id: FileId, offset: TextUnit) -> Option<SourceChange> { | ||
188 | let file = self.imp.file_syntax(file_id); | ||
189 | let edit = ra_editor::on_enter(&file, offset)?; | ||
190 | let res = SourceChange::from_local_edit(file_id, "on enter", edit); | ||
191 | Some(res) | ||
192 | } | ||
187 | pub fn on_eq_typed(&self, file_id: FileId, offset: TextUnit) -> Option<SourceChange> { | 193 | pub fn on_eq_typed(&self, file_id: FileId, offset: TextUnit) -> Option<SourceChange> { |
188 | let file = self.imp.file_syntax(file_id); | 194 | let file = self.imp.file_syntax(file_id); |
189 | Some(SourceChange::from_local_edit(file_id, "add semicolon", ra_editor::on_eq_typed(&file, offset)?)) | 195 | Some(SourceChange::from_local_edit(file_id, "add semicolon", ra_editor::on_eq_typed(&file, offset)?)) |
diff --git a/crates/ra_editor/src/lib.rs b/crates/ra_editor/src/lib.rs index 2a801f7da..fe0045378 100644 --- a/crates/ra_editor/src/lib.rs +++ b/crates/ra_editor/src/lib.rs | |||
@@ -35,7 +35,7 @@ pub use self::{ | |||
35 | flip_comma, add_derive, add_impl, | 35 | flip_comma, add_derive, add_impl, |
36 | introduce_variable, | 36 | introduce_variable, |
37 | }, | 37 | }, |
38 | typing::{join_lines, on_eq_typed}, | 38 | typing::{join_lines, on_eq_typed, on_enter}, |
39 | completion::{scope_completion, CompletionItem}, | 39 | completion::{scope_completion, CompletionItem}, |
40 | folding_ranges::{Fold, FoldKind, folding_ranges} | 40 | folding_ranges::{Fold, FoldKind, folding_ranges} |
41 | }; | 41 | }; |
diff --git a/crates/ra_editor/src/typing.rs b/crates/ra_editor/src/typing.rs index 512076941..3384389d1 100644 --- a/crates/ra_editor/src/typing.rs +++ b/crates/ra_editor/src/typing.rs | |||
@@ -4,7 +4,7 @@ use ra_syntax::{ | |||
4 | TextUnit, TextRange, SyntaxNodeRef, File, AstNode, SyntaxKind, | 4 | TextUnit, TextRange, SyntaxNodeRef, File, AstNode, SyntaxKind, |
5 | ast, | 5 | ast, |
6 | algo::{ | 6 | algo::{ |
7 | find_covering_node, | 7 | find_covering_node, find_leaf_at_offset, LeafAtOffset, |
8 | }, | 8 | }, |
9 | text_utils::{intersect, contains_offset_nonstrict}, | 9 | text_utils::{intersect, contains_offset_nonstrict}, |
10 | SyntaxKind::*, | 10 | SyntaxKind::*, |
@@ -56,6 +56,58 @@ pub fn join_lines(file: &File, range: TextRange) -> LocalEdit { | |||
56 | } | 56 | } |
57 | } | 57 | } |
58 | 58 | ||
59 | pub 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 prefix = comment_preffix(comment)?; | ||
62 | if offset < comment.range().start() + TextUnit::of_str(prefix) { | ||
63 | return None; | ||
64 | } | ||
65 | |||
66 | let indent = node_indent(file, comment)?; | ||
67 | let inserted = format!("\n{}{}", indent, prefix); | ||
68 | let cursor_position = offset + TextUnit::of_str(&inserted); | ||
69 | let mut edit = EditBuilder::new(); | ||
70 | edit.insert(offset, inserted); | ||
71 | Some(LocalEdit { | ||
72 | edit: edit.finish(), | ||
73 | cursor_position: Some(cursor_position), | ||
74 | }) | ||
75 | } | ||
76 | |||
77 | fn 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 | |||
91 | fn node_indent<'a>(file: &'a File, node: SyntaxNodeRef) -> Option<&'a str> { | ||
92 | let ws = match find_leaf_at_offset(file.syntax(), node.range().start()) { | ||
93 | LeafAtOffset::Between(l, r) => { | ||
94 | assert!(r == node); | ||
95 | l | ||
96 | } | ||
97 | LeafAtOffset::Single(n) => { | ||
98 | assert!(n == node); | ||
99 | return Some("") | ||
100 | } | ||
101 | LeafAtOffset::None => unreachable!(), | ||
102 | }; | ||
103 | if ws.kind() != WHITESPACE { | ||
104 | return None; | ||
105 | } | ||
106 | let text = ws.leaf_text().unwrap(); | ||
107 | let pos = text.as_str().rfind('\n').map(|it| it + 1).unwrap_or(0); | ||
108 | Some(&text[pos..]) | ||
109 | } | ||
110 | |||
59 | pub fn on_eq_typed(file: &File, offset: TextUnit) -> Option<LocalEdit> { | 111 | pub fn on_eq_typed(file: &File, offset: TextUnit) -> Option<LocalEdit> { |
60 | let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?; | 112 | let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?; |
61 | if let_stmt.has_semi() { | 113 | if let_stmt.has_semi() { |
@@ -187,7 +239,7 @@ fn compute_ws(left: SyntaxNodeRef, right: SyntaxNodeRef) -> &'static str { | |||
187 | #[cfg(test)] | 239 | #[cfg(test)] |
188 | mod tests { | 240 | mod tests { |
189 | use super::*; | 241 | use super::*; |
190 | use test_utils::{check_action, extract_range, extract_offset}; | 242 | use test_utils::{check_action, extract_range, extract_offset, add_cursor}; |
191 | 243 | ||
192 | fn check_join_lines(before: &str, after: &str) { | 244 | fn check_join_lines(before: &str, after: &str) { |
193 | check_action(before, after, |file, offset| { | 245 | check_action(before, after, |file, offset| { |
@@ -344,4 +396,49 @@ fn foo() { | |||
344 | // } | 396 | // } |
345 | // "); | 397 | // "); |
346 | } | 398 | } |
399 | |||
400 | #[test] | ||
401 | fn test_on_enter() { | ||
402 | fn apply_on_enter(before: &str) -> Option<String> { | ||
403 | let (offset, before) = extract_offset(before); | ||
404 | let file = File::parse(&before); | ||
405 | let result = on_enter(&file, offset)?; | ||
406 | let actual = result.edit.apply(&before); | ||
407 | let actual = add_cursor(&actual, result.cursor_position.unwrap()); | ||
408 | Some(actual) | ||
409 | } | ||
410 | |||
411 | fn do_check(before: &str, after: &str) { | ||
412 | let actual = apply_on_enter(before).unwrap(); | ||
413 | assert_eq_text!(after, &actual); | ||
414 | } | ||
415 | |||
416 | fn do_check_noop(text: &str) { | ||
417 | assert!(apply_on_enter(text).is_none()) | ||
418 | } | ||
419 | |||
420 | do_check(r" | ||
421 | /// Some docs<|> | ||
422 | fn foo() { | ||
423 | } | ||
424 | ", r" | ||
425 | /// Some docs | ||
426 | /// <|> | ||
427 | fn foo() { | ||
428 | } | ||
429 | "); | ||
430 | do_check(r" | ||
431 | impl S { | ||
432 | /// Some<|> docs. | ||
433 | fn foo() {} | ||
434 | } | ||
435 | ", r" | ||
436 | impl S { | ||
437 | /// Some | ||
438 | /// <|> docs. | ||
439 | fn foo() {} | ||
440 | } | ||
441 | "); | ||
442 | do_check_noop(r"<|>//! docz"); | ||
443 | } | ||
347 | } | 444 | } |
diff --git a/crates/ra_lsp_server/src/conv.rs b/crates/ra_lsp_server/src/conv.rs index 759e5e914..08a656569 100644 --- a/crates/ra_lsp_server/src/conv.rs +++ b/crates/ra_lsp_server/src/conv.rs | |||
@@ -190,9 +190,13 @@ impl TryConvWith for SourceChange { | |||
190 | None => None, | 190 | None => None, |
191 | Some(pos) => { | 191 | Some(pos) => { |
192 | let line_index = world.analysis().file_line_index(pos.file_id); | 192 | let line_index = world.analysis().file_line_index(pos.file_id); |
193 | let edits = self.source_file_edits.iter().find(|it| it.file_id == pos.file_id) | ||
194 | .map(|it| it.edits.as_slice()).unwrap_or(&[]); | ||
195 | let line_col = translate_offset_with_edit(&*line_index, pos.offset, edits); | ||
196 | let position = Position::new(line_col.line as u64, u32::from(line_col.col) as u64); | ||
193 | Some(TextDocumentPositionParams { | 197 | Some(TextDocumentPositionParams { |
194 | text_document: TextDocumentIdentifier::new(pos.file_id.try_conv_with(world)?), | 198 | text_document: TextDocumentIdentifier::new(pos.file_id.try_conv_with(world)?), |
195 | position: pos.offset.conv_with(&line_index), | 199 | position, |
196 | }) | 200 | }) |
197 | } | 201 | } |
198 | }; | 202 | }; |
@@ -207,6 +211,41 @@ impl TryConvWith for SourceChange { | |||
207 | } | 211 | } |
208 | } | 212 | } |
209 | 213 | ||
214 | // HACK: we should translate offset to line/column using linde_index *with edits applied*. | ||
215 | // A naive version of this function would be to apply `edits` to the original text, | ||
216 | // construct a new line index and use that, but it would be slow. | ||
217 | // | ||
218 | // Writing fast & correct version is issue #105, let's use a quick hack in the meantime | ||
219 | fn translate_offset_with_edit( | ||
220 | pre_edit_index: &LineIndex, | ||
221 | offset: TextUnit, | ||
222 | edits: &[AtomEdit], | ||
223 | ) -> LineCol { | ||
224 | let fallback = pre_edit_index.line_col(offset); | ||
225 | let edit = match edits.first() { | ||
226 | None => return fallback, | ||
227 | Some(edit) => edit | ||
228 | }; | ||
229 | let end_offset = edit.delete.start() + TextUnit::of_str(&edit.insert); | ||
230 | if !(edit.delete.start() <= offset && offset <= end_offset) { | ||
231 | return fallback | ||
232 | } | ||
233 | let rel_offset = offset - edit.delete.start(); | ||
234 | let in_edit_line_col = LineIndex::new(&edit.insert).line_col(rel_offset); | ||
235 | let edit_line_col = pre_edit_index.line_col(edit.delete.start()); | ||
236 | if in_edit_line_col.line == 0 { | ||
237 | LineCol { | ||
238 | line: edit_line_col.line, | ||
239 | col: edit_line_col.col + in_edit_line_col.col, | ||
240 | } | ||
241 | } else { | ||
242 | LineCol { | ||
243 | line: edit_line_col.line + in_edit_line_col.line, | ||
244 | col: in_edit_line_col.col, | ||
245 | } | ||
246 | } | ||
247 | } | ||
248 | |||
210 | impl TryConvWith for SourceFileEdit { | 249 | impl TryConvWith for SourceFileEdit { |
211 | type Ctx = ServerWorld; | 250 | type Ctx = ServerWorld; |
212 | type Output = TextDocumentEdit; | 251 | type Output = TextDocumentEdit; |
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs index 79a54183e..725036cc7 100644 --- a/crates/ra_lsp_server/src/main_loop/handlers.rs +++ b/crates/ra_lsp_server/src/main_loop/handlers.rs | |||
@@ -77,6 +77,20 @@ pub fn handle_join_lines( | |||
77 | .try_conv_with(&world) | 77 | .try_conv_with(&world) |
78 | } | 78 | } |
79 | 79 | ||
80 | pub fn handle_on_enter( | ||
81 | world: ServerWorld, | ||
82 | params: req::TextDocumentPositionParams, | ||
83 | _token: JobToken, | ||
84 | ) -> Result<Option<req::SourceChange>> { | ||
85 | let file_id = params.text_document.try_conv_with(&world)?; | ||
86 | let line_index = world.analysis().file_line_index(file_id); | ||
87 | let offset = params.position.conv_with(&line_index); | ||
88 | match world.analysis().on_enter(file_id, offset) { | ||
89 | None => Ok(None), | ||
90 | Some(edit) => Ok(Some(edit.try_conv_with(&world)?)) | ||
91 | } | ||
92 | } | ||
93 | |||
80 | pub fn handle_on_type_formatting( | 94 | pub fn handle_on_type_formatting( |
81 | world: ServerWorld, | 95 | world: ServerWorld, |
82 | params: req::DocumentOnTypeFormattingParams, | 96 | params: req::DocumentOnTypeFormattingParams, |
diff --git a/crates/ra_lsp_server/src/main_loop/mod.rs b/crates/ra_lsp_server/src/main_loop/mod.rs index 47a9b202e..53c6f1dff 100644 --- a/crates/ra_lsp_server/src/main_loop/mod.rs +++ b/crates/ra_lsp_server/src/main_loop/mod.rs | |||
@@ -244,6 +244,7 @@ fn on_request( | |||
244 | .on::<req::ExtendSelection>(handlers::handle_extend_selection)? | 244 | .on::<req::ExtendSelection>(handlers::handle_extend_selection)? |
245 | .on::<req::FindMatchingBrace>(handlers::handle_find_matching_brace)? | 245 | .on::<req::FindMatchingBrace>(handlers::handle_find_matching_brace)? |
246 | .on::<req::JoinLines>(handlers::handle_join_lines)? | 246 | .on::<req::JoinLines>(handlers::handle_join_lines)? |
247 | .on::<req::OnEnter>(handlers::handle_on_enter)? | ||
247 | .on::<req::OnTypeFormatting>(handlers::handle_on_type_formatting)? | 248 | .on::<req::OnTypeFormatting>(handlers::handle_on_type_formatting)? |
248 | .on::<req::DocumentSymbolRequest>(handlers::handle_document_symbol)? | 249 | .on::<req::DocumentSymbolRequest>(handlers::handle_document_symbol)? |
249 | .on::<req::WorkspaceSymbol>(handlers::handle_workspace_symbol)? | 250 | .on::<req::WorkspaceSymbol>(handlers::handle_workspace_symbol)? |
diff --git a/crates/ra_lsp_server/src/req.rs b/crates/ra_lsp_server/src/req.rs index 4af61dbbd..458c79ea9 100644 --- a/crates/ra_lsp_server/src/req.rs +++ b/crates/ra_lsp_server/src/req.rs | |||
@@ -119,6 +119,14 @@ pub struct JoinLinesParams { | |||
119 | pub range: Range, | 119 | pub range: Range, |
120 | } | 120 | } |
121 | 121 | ||
122 | pub enum OnEnter {} | ||
123 | |||
124 | impl Request for OnEnter { | ||
125 | type Params = TextDocumentPositionParams; | ||
126 | type Result = Option<SourceChange>; | ||
127 | const METHOD: &'static str = "m/onEnter"; | ||
128 | } | ||
129 | |||
122 | pub enum Runnables {} | 130 | pub enum Runnables {} |
123 | 131 | ||
124 | impl Request for Runnables { | 132 | impl Request for Runnables { |