aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_analysis/src/lib.rs30
-rw-r--r--crates/ra_editor/src/lib.rs2
-rw-r--r--crates/ra_editor/src/typing.rs169
-rw-r--r--crates/ra_lsp_server/src/caps.rs2
-rw-r--r--crates/ra_lsp_server/src/main_loop.rs14
-rw-r--r--crates/ra_lsp_server/src/main_loop/handlers.rs65
6 files changed, 225 insertions, 57 deletions
diff --git a/crates/ra_analysis/src/lib.rs b/crates/ra_analysis/src/lib.rs
index 77f77e9a8..c8f846c56 100644
--- a/crates/ra_analysis/src/lib.rs
+++ b/crates/ra_analysis/src/lib.rs
@@ -12,41 +12,39 @@ macro_rules! ctry {
12 }; 12 };
13} 13}
14 14
15mod db;
16mod imp;
17mod completion; 15mod completion;
16mod db;
18mod goto_defenition; 17mod goto_defenition;
19mod symbol_index; 18mod imp;
20pub mod mock_analysis; 19pub mod mock_analysis;
21mod runnables; 20mod runnables;
21mod symbol_index;
22 22
23mod extend_selection; 23mod extend_selection;
24mod syntax_highlighting;
25mod hover; 24mod hover;
25mod syntax_highlighting;
26 26
27use std::{fmt, sync::Arc}; 27use std::{fmt, sync::Arc};
28 28
29use rustc_hash::FxHashMap; 29use ra_syntax::{SmolStr, SourceFileNode, SyntaxKind, TextRange, TextUnit};
30use ra_syntax::{SourceFileNode, TextRange, TextUnit, SmolStr, SyntaxKind};
31use ra_text_edit::TextEdit; 30use ra_text_edit::TextEdit;
32use rayon::prelude::*; 31use rayon::prelude::*;
33use relative_path::RelativePathBuf; 32use relative_path::RelativePathBuf;
33use rustc_hash::FxHashMap;
34use salsa::ParallelDatabase; 34use salsa::ParallelDatabase;
35 35
36use crate::symbol_index::{SymbolIndex, FileSymbol}; 36use crate::symbol_index::{FileSymbol, SymbolIndex};
37 37
38pub use crate::{ 38pub use crate::{
39 completion::{CompletionItem, CompletionItemKind, InsertText}, 39 completion::{CompletionItem, CompletionItemKind, InsertText},
40 runnables::{Runnable, RunnableKind}, 40 runnables::{Runnable, RunnableKind},
41}; 41};
42pub use ra_editor::{
43 Fold, FoldKind, HighlightedRange, LineIndex, StructureNode, Severity
44};
45pub use hir::FnSignatureInfo; 42pub use hir::FnSignatureInfo;
43pub use ra_editor::{Fold, FoldKind, HighlightedRange, LineIndex, Severity, StructureNode};
46 44
47pub use ra_db::{ 45pub use ra_db::{
48 Canceled, Cancelable, FilePosition, FileRange, LocalSyntaxPtr, 46 Cancelable, Canceled, CrateGraph, CrateId, FileId, FilePosition, FileRange, FilesDatabase,
49 CrateGraph, CrateId, SourceRootId, FileId, SyntaxDatabase, FilesDatabase 47 LocalSyntaxPtr, SourceRootId, SyntaxDatabase,
50}; 48};
51 49
52#[derive(Default)] 50#[derive(Default)]
@@ -346,7 +344,7 @@ impl Analysis {
346 let edit = ra_editor::on_enter(&file, position.offset)?; 344 let edit = ra_editor::on_enter(&file, position.offset)?;
347 Some(SourceChange::from_local_edit(position.file_id, edit)) 345 Some(SourceChange::from_local_edit(position.file_id, edit))
348 } 346 }
349 /// Returns an edit which should be applied after `=` was typed. Primaraly, 347 /// Returns an edit which should be applied after `=` was typed. Primarily,
350 /// this works when adding `let =`. 348 /// this works when adding `let =`.
351 // FIXME: use a snippet completion instead of this hack here. 349 // FIXME: use a snippet completion instead of this hack here.
352 pub fn on_eq_typed(&self, position: FilePosition) -> Option<SourceChange> { 350 pub fn on_eq_typed(&self, position: FilePosition) -> Option<SourceChange> {
@@ -354,6 +352,12 @@ impl Analysis {
354 let edit = ra_editor::on_eq_typed(&file, position.offset)?; 352 let edit = ra_editor::on_eq_typed(&file, position.offset)?;
355 Some(SourceChange::from_local_edit(position.file_id, edit)) 353 Some(SourceChange::from_local_edit(position.file_id, edit))
356 } 354 }
355 /// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately.
356 pub fn on_dot_typed(&self, position: FilePosition) -> Option<SourceChange> {
357 let file = self.db.source_file(position.file_id);
358 let edit = ra_editor::on_dot_typed(&file, position.offset)?;
359 Some(SourceChange::from_local_edit(position.file_id, edit))
360 }
357 /// Returns a tree representation of symbols in the file. Useful to draw a 361 /// Returns a tree representation of symbols in the file. Useful to draw a
358 /// file outline. 362 /// file outline.
359 pub fn file_structure(&self, file_id: FileId) -> Vec<StructureNode> { 363 pub fn file_structure(&self, file_id: FileId) -> Vec<StructureNode> {
diff --git a/crates/ra_editor/src/lib.rs b/crates/ra_editor/src/lib.rs
index ac283e2e0..a3c85ed5d 100644
--- a/crates/ra_editor/src/lib.rs
+++ b/crates/ra_editor/src/lib.rs
@@ -16,7 +16,7 @@ pub use self::{
16 line_index::{LineCol, LineIndex}, 16 line_index::{LineCol, LineIndex},
17 line_index_utils::translate_offset_with_edit, 17 line_index_utils::translate_offset_with_edit,
18 structure::{file_structure, StructureNode}, 18 structure::{file_structure, StructureNode},
19 typing::{join_lines, on_enter, on_eq_typed}, 19 typing::{join_lines, on_enter, on_dot_typed, on_eq_typed},
20 diagnostics::diagnostics 20 diagnostics::diagnostics
21}; 21};
22use ra_text_edit::TextEditBuilder; 22use ra_text_edit::TextEditBuilder;
diff --git a/crates/ra_editor/src/typing.rs b/crates/ra_editor/src/typing.rs
index 1b568e96c..12500854c 100644
--- a/crates/ra_editor/src/typing.rs
+++ b/crates/ra_editor/src/typing.rs
@@ -1,17 +1,17 @@
1use std::mem; 1use std::mem;
2 2
3use itertools::Itertools;
3use ra_syntax::{ 4use ra_syntax::{
4 algo::{find_covering_node, find_leaf_at_offset, LeafAtOffset}, 5 algo::{find_covering_node, find_leaf_at_offset, LeafAtOffset},
5 ast, 6 ast,
6 text_utils::intersect, 7 text_utils::intersect,
7 AstNode, SourceFileNode, SyntaxKind, 8 AstNode, Direction, SourceFileNode, SyntaxKind,
8 SyntaxKind::*, 9 SyntaxKind::*,
9 SyntaxNodeRef, TextRange, TextUnit, 10 SyntaxNodeRef, TextRange, TextUnit,
10}; 11};
11use ra_text_edit::text_utils::contains_offset_nonstrict; 12use ra_text_edit::text_utils::contains_offset_nonstrict;
12use itertools::Itertools;
13 13
14use crate::{find_node_at_offset, TextEditBuilder, LocalEdit}; 14use crate::{find_node_at_offset, LocalEdit, TextEditBuilder};
15 15
16pub fn join_lines(file: &SourceFileNode, range: TextRange) -> LocalEdit { 16pub fn join_lines(file: &SourceFileNode, range: TextRange) -> LocalEdit {
17 let range = if range.is_empty() { 17 let range = if range.is_empty() {
@@ -136,6 +136,56 @@ pub fn on_eq_typed(file: &SourceFileNode, offset: TextUnit) -> Option<LocalEdit>
136 }) 136 })
137} 137}
138 138
139pub fn on_dot_typed(file: &SourceFileNode, offset: TextUnit) -> Option<LocalEdit> {
140 let before_dot_offset = offset - TextUnit::of_char('.');
141
142 let whitespace = find_leaf_at_offset(file.syntax(), before_dot_offset).left_biased()?;
143
144 // find whitespace just left of the dot
145 ast::Whitespace::cast(whitespace)?;
146
147 // make sure there is a method call
148 let method_call = whitespace
149 .siblings(Direction::Prev)
150 // first is whitespace
151 .skip(1)
152 .next()?;
153
154 ast::MethodCallExprNode::cast(method_call)?;
155
156 // find how much the _method call is indented
157 let method_chain_indent = method_call
158 .parent()?
159 .siblings(Direction::Prev)
160 .skip(1)
161 .next()?
162 .leaf_text()
163 .map(|x| last_line_indent_in_whitespace(x))?;
164
165 let current_indent = TextUnit::of_str(last_line_indent_in_whitespace(whitespace.leaf_text()?));
166 // TODO: indent is always 4 spaces now. A better heuristic could look on the previous line(s)
167
168 let target_indent = TextUnit::of_str(method_chain_indent) + TextUnit::from_usize(4);
169
170 let diff = target_indent - current_indent;
171
172 let indent = "".repeat(diff.to_usize());
173
174 let cursor_position = offset + diff;
175 let mut edit = TextEditBuilder::default();
176 edit.insert(before_dot_offset, indent);
177 Some(LocalEdit {
178 label: "indent dot".to_string(),
179 edit: edit.finish(),
180 cursor_position: Some(cursor_position),
181 })
182}
183
184/// Finds the last line in the whitespace
185fn last_line_indent_in_whitespace(ws: &str) -> &str {
186 ws.split('\n').last().unwrap_or("")
187}
188
139fn remove_newline( 189fn remove_newline(
140 edit: &mut TextEditBuilder, 190 edit: &mut TextEditBuilder,
141 node: SyntaxNodeRef, 191 node: SyntaxNodeRef,
@@ -283,7 +333,9 @@ fn compute_ws(left: SyntaxNodeRef, right: SyntaxNodeRef) -> &'static str {
283#[cfg(test)] 333#[cfg(test)]
284mod tests { 334mod tests {
285 use super::*; 335 use super::*;
286 use crate::test_utils::{add_cursor, check_action, extract_offset, extract_range, assert_eq_text}; 336 use crate::test_utils::{
337 add_cursor, assert_eq_text, check_action, extract_offset, extract_range,
338};
287 339
288 fn check_join_lines(before: &str, after: &str) { 340 fn check_join_lines(before: &str, after: &str) {
289 check_action(before, after, |file, offset| { 341 check_action(before, after, |file, offset| {
@@ -615,6 +667,115 @@ fn foo() {
615 } 667 }
616 668
617 #[test] 669 #[test]
670 fn test_on_dot_typed() {
671 fn do_check(before: &str, after: &str) {
672 let (offset, before) = extract_offset(before);
673 let file = SourceFileNode::parse(&before);
674 if let Some(result) = on_eq_typed(&file, offset) {
675 let actual = result.edit.apply(&before);
676 assert_eq_text!(after, &actual);
677 };
678 }
679 // indent if continuing chain call
680 do_check(
681 r"
682 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
683 self.child_impl(db, name)
684 .<|>
685 }
686",
687 r"
688 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
689 self.child_impl(db, name)
690 .
691 }
692",
693 );
694
695 // do not indent if already indented
696 do_check(
697 r"
698 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
699 self.child_impl(db, name)
700 .<|>
701 }
702",
703 r"
704 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
705 self.child_impl(db, name)
706 .
707 }
708",
709 );
710
711 // indent if the previous line is already indented
712 do_check(
713 r"
714 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
715 self.child_impl(db, name)
716 .first()
717 .<|>
718 }
719",
720 r"
721 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
722 self.child_impl(db, name)
723 .first()
724 .
725 }
726",
727 );
728
729 // don't indent if indent matches previous line
730 do_check(
731 r"
732 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
733 self.child_impl(db, name)
734 .first()
735 .<|>
736 }
737",
738 r"
739 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
740 self.child_impl(db, name)
741 .first()
742 .
743 }
744",
745 );
746
747 // don't indent if there is no method call on previous line
748 do_check(
749 r"
750 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
751 .<|>
752 }
753",
754 r"
755 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
756 .
757 }
758",
759 );
760
761 // indent to match previous expr
762 do_check(
763 r"
764 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
765 self.child_impl(db, name)
766.<|>
767 }
768",
769 r"
770 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
771 self.child_impl(db, name)
772 .
773 }
774",
775 );
776 }
777
778 #[test]
618 fn test_on_enter() { 779 fn test_on_enter() {
619 fn apply_on_enter(before: &str) -> Option<String> { 780 fn apply_on_enter(before: &str) -> Option<String> {
620 let (offset, before) = extract_offset(before); 781 let (offset, before) = extract_offset(before);
diff --git a/crates/ra_lsp_server/src/caps.rs b/crates/ra_lsp_server/src/caps.rs
index a74f9f27b..2599a4ca6 100644
--- a/crates/ra_lsp_server/src/caps.rs
+++ b/crates/ra_lsp_server/src/caps.rs
@@ -37,7 +37,7 @@ pub fn server_capabilities() -> ServerCapabilities {
37 document_range_formatting_provider: None, 37 document_range_formatting_provider: None,
38 document_on_type_formatting_provider: Some(DocumentOnTypeFormattingOptions { 38 document_on_type_formatting_provider: Some(DocumentOnTypeFormattingOptions {
39 first_trigger_character: "=".to_string(), 39 first_trigger_character: "=".to_string(),
40 more_trigger_character: None, 40 more_trigger_character: Some(vec![".".to_string()]),
41 }), 41 }),
42 folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)), 42 folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)),
43 rename_provider: Some(RenameProviderCapability::Options(RenameOptions { 43 rename_provider: Some(RenameProviderCapability::Options(RenameOptions {
diff --git a/crates/ra_lsp_server/src/main_loop.rs b/crates/ra_lsp_server/src/main_loop.rs
index 60d9671de..2dc1be26a 100644
--- a/crates/ra_lsp_server/src/main_loop.rs
+++ b/crates/ra_lsp_server/src/main_loop.rs
@@ -1,13 +1,11 @@
1mod handlers; 1mod handlers;
2mod subscriptions; 2mod subscriptions;
3 3
4use std::{ 4use std::{fmt, path::PathBuf, sync::Arc};
5 fmt,
6 path::PathBuf,
7 sync::Arc,
8};
9 5
10use crossbeam_channel::{unbounded, select, Receiver, Sender, RecvError}; 6use crossbeam_channel::{select, unbounded, Receiver, RecvError, Sender};
7use failure::{bail, format_err};
8use failure_derive::Fail;
11use gen_lsp_server::{ 9use gen_lsp_server::{
12 handle_shutdown, ErrorCode, RawMessage, RawNotification, RawRequest, RawResponse, 10 handle_shutdown, ErrorCode, RawMessage, RawNotification, RawRequest, RawResponse,
13}; 11};
@@ -15,11 +13,9 @@ use languageserver_types::NumberOrString;
15use ra_analysis::{Canceled, FileId, LibraryData}; 13use ra_analysis::{Canceled, FileId, LibraryData};
16use ra_vfs::VfsTask; 14use ra_vfs::VfsTask;
17use rayon; 15use rayon;
18use threadpool::ThreadPool;
19use rustc_hash::FxHashSet; 16use rustc_hash::FxHashSet;
20use serde::{de::DeserializeOwned, Serialize}; 17use serde::{de::DeserializeOwned, Serialize};
21use failure::{format_err, bail}; 18use threadpool::ThreadPool;
22use failure_derive::Fail;
23 19
24use crate::{ 20use crate::{
25 main_loop::subscriptions::Subscriptions, 21 main_loop::subscriptions::Subscriptions,
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs
index 1baed73ad..51f134e8a 100644
--- a/crates/ra_lsp_server/src/main_loop/handlers.rs
+++ b/crates/ra_lsp_server/src/main_loop/handlers.rs
@@ -2,15 +2,16 @@ use std::collections::HashMap;
2 2
3use gen_lsp_server::ErrorCode; 3use gen_lsp_server::ErrorCode;
4use languageserver_types::{ 4use languageserver_types::{
5 CodeActionResponse, Command, Diagnostic, 5 CodeActionResponse, Command, Diagnostic, DiagnosticSeverity, DocumentFormattingParams,
6 DiagnosticSeverity, DocumentSymbol, Documentation, FoldingRange, FoldingRangeKind, 6 DocumentHighlight, DocumentSymbol, Documentation, FoldingRange, FoldingRangeKind,
7 FoldingRangeParams, Location, MarkupContent, MarkupKind, MarkedString, Position, 7 FoldingRangeParams, Hover, HoverContents, Location, MarkedString, MarkupContent, MarkupKind,
8 PrepareRenameResponse, RenameParams, SymbolInformation, TextDocumentIdentifier, TextEdit, 8 ParameterInformation, ParameterLabel, Position, PrepareRenameResponse, Range, RenameParams,
9 Range, WorkspaceEdit, ParameterInformation, ParameterLabel, SignatureInformation, Hover, 9 SignatureInformation, SymbolInformation, TextDocumentIdentifier, TextEdit, WorkspaceEdit,
10 HoverContents, DocumentFormattingParams, DocumentHighlight,
11}; 10};
12use ra_analysis::{FileId, FoldKind, Query, RunnableKind, FileRange, FilePosition, Severity}; 11use ra_analysis::{
13use ra_syntax::{TextUnit, text_utils::intersect}; 12 FileId, FilePosition, FileRange, FoldKind, Query, RunnableKind, Severity, SourceChange,
13};
14use ra_syntax::{text_utils::intersect, TextUnit};
14use ra_text_edit::text_utils::contains_offset_nonstrict; 15use ra_text_edit::text_utils::contains_offset_nonstrict;
15use rustc_hash::FxHashMap; 16use rustc_hash::FxHashMap;
16use serde_json::to_value; 17use serde_json::to_value;
@@ -92,29 +93,35 @@ pub fn handle_on_type_formatting(
92 world: ServerWorld, 93 world: ServerWorld,
93 params: req::DocumentOnTypeFormattingParams, 94 params: req::DocumentOnTypeFormattingParams,
94) -> Result<Option<Vec<TextEdit>>> { 95) -> Result<Option<Vec<TextEdit>>> {
95 if params.ch != "=" { 96 let analysis: Option<Box<Fn(FilePosition) -> Option<SourceChange>>> = match params.ch.as_str() {
96 return Ok(None); 97 "=" => Some(Box::new(|pos| world.analysis().on_eq_typed(pos))),
98 "." => Some(Box::new(|pos| world.analysis().on_dot_typed(pos))),
99 _ => None,
100 };
101
102 if let Some(ana) = analysis {
103 let file_id = params.text_document.try_conv_with(&world)?;
104 let line_index = world.analysis().file_line_index(file_id);
105 let position = FilePosition {
106 file_id,
107 offset: params.position.conv_with(&line_index),
108 };
109
110 if let Some(mut action) = ana(position) {
111 let change: Vec<TextEdit> = action
112 .source_file_edits
113 .pop()
114 .unwrap()
115 .edit
116 .as_atoms()
117 .iter()
118 .map_conv_with(&line_index)
119 .collect();
120 return Ok(Some(change));
121 }
97 } 122 }
98 123
99 let file_id = params.text_document.try_conv_with(&world)?; 124 return Ok(None);
100 let line_index = world.analysis().file_line_index(file_id);
101 let position = FilePosition {
102 file_id,
103 offset: params.position.conv_with(&line_index),
104 };
105 let edits = match world.analysis().on_eq_typed(position) {
106 None => return Ok(None),
107 Some(mut action) => action
108 .source_file_edits
109 .pop()
110 .unwrap()
111 .edit
112 .as_atoms()
113 .iter()
114 .map_conv_with(&line_index)
115 .collect(),
116 };
117 Ok(Some(edits))
118} 125}
119 126
120pub fn handle_document_symbol( 127pub fn handle_document_symbol(