]> git.lizzy.rs Git - rust.git/commitdiff
docs
authorAleksey Kladov <aleksey.kladov@gmail.com>
Fri, 21 Dec 2018 22:01:40 +0000 (01:01 +0300)
committerAleksey Kladov <aleksey.kladov@gmail.com>
Fri, 21 Dec 2018 22:01:40 +0000 (01:01 +0300)
crates/ra_analysis/src/completion.rs
crates/ra_analysis/src/completion/complete_fn_param.rs
crates/ra_analysis/src/completion/complete_keyword.rs
crates/ra_analysis/src/completion/complete_path.rs
crates/ra_analysis/src/completion/complete_scope.rs
crates/ra_analysis/src/completion/complete_snippet.rs
crates/ra_analysis/src/completion/completion_context.rs [new file with mode: 0644]

index 93edcc4c267b94d859177ff2cc7a9f61945f902d..2d61a3aef53e9abe91300f5059c434779644ac1e 100644 (file)
@@ -1,4 +1,5 @@
 mod completion_item;
+mod completion_context;
 
 mod complete_fn_param;
 mod complete_keyword;
@@ -6,34 +7,33 @@
 mod complete_path;
 mod complete_scope;
 
-use ra_editor::find_node_at_offset;
-use ra_text_edit::AtomTextEdit;
-use ra_syntax::{
-    algo::find_leaf_at_offset,
-    ast,
-    AstNode,
-    SyntaxNodeRef,
-    SourceFileNode,
-    TextUnit,
-    SyntaxKind::*,
-};
 use ra_db::SyntaxDatabase;
-use hir::source_binder;
 
 use crate::{
     db,
     Cancelable, FilePosition,
-    completion::completion_item::{Completions, CompletionKind},
+    completion::{
+        completion_item::{Completions, CompletionKind},
+        completion_context::CompletionContext,
+    },
 };
 
 pub use crate::completion::completion_item::{CompletionItem, InsertText};
 
+/// Main entry point for copmletion. We run comletion as a two-phase process.
+///
+/// First, we look at the position and collect a so-called `CompletionContext.
+/// This is a somewhat messy process, because, during completion, syntax tree is
+/// incomplete and can look readlly weired.
+///
+/// Once the context is collected, we run a series of completion routines whihc
+/// look at the context and produce completion items.
 pub(crate) fn completions(
     db: &db::RootDatabase,
     position: FilePosition,
 ) -> Cancelable<Option<Completions>> {
     let original_file = db.source_file(position.file_id);
-    let ctx = ctry!(SyntaxContext::new(db, &original_file, position)?);
+    let ctx = ctry!(CompletionContext::new(db, &original_file, position)?);
 
     let mut acc = Completions::default();
 
@@ -47,148 +47,6 @@ pub(crate) fn completions(
     Ok(Some(acc))
 }
 
-/// `SyntaxContext` is created early during completion to figure out, where
-/// exactly is the cursor, syntax-wise.
-#[derive(Debug)]
-pub(super) struct SyntaxContext<'a> {
-    db: &'a db::RootDatabase,
-    offset: TextUnit,
-    leaf: SyntaxNodeRef<'a>,
-    module: Option<hir::Module>,
-    enclosing_fn: Option<ast::FnDef<'a>>,
-    is_param: bool,
-    /// A single-indent path, like `foo`.
-    is_trivial_path: bool,
-    /// If not a trivial, path, the prefix (qualifier).
-    path_prefix: Option<hir::Path>,
-    after_if: bool,
-    is_stmt: bool,
-    /// Something is typed at the "top" level, in module or impl/trait.
-    is_new_item: bool,
-}
-
-impl<'a> SyntaxContext<'a> {
-    pub(super) fn new(
-        db: &'a db::RootDatabase,
-        original_file: &'a SourceFileNode,
-        position: FilePosition,
-    ) -> Cancelable<Option<SyntaxContext<'a>>> {
-        let module = source_binder::module_from_position(db, position)?;
-        let leaf =
-            ctry!(find_leaf_at_offset(original_file.syntax(), position.offset).left_biased());
-        let mut ctx = SyntaxContext {
-            db,
-            leaf,
-            offset: position.offset,
-            module,
-            enclosing_fn: None,
-            is_param: false,
-            is_trivial_path: false,
-            path_prefix: None,
-            after_if: false,
-            is_stmt: false,
-            is_new_item: false,
-        };
-        ctx.fill(original_file, position.offset);
-        Ok(Some(ctx))
-    }
-
-    fn fill(&mut self, original_file: &SourceFileNode, offset: TextUnit) {
-        // Insert a fake ident to get a valid parse tree. We will use this file
-        // to determine context, though the original_file will be used for
-        // actual completion.
-        let file = {
-            let edit = AtomTextEdit::insert(offset, "intellijRulezz".to_string());
-            original_file.reparse(&edit)
-        };
-
-        // First, let's try to complete a reference to some declaration.
-        if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), offset) {
-            // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`.
-            // See RFC#1685.
-            if is_node::<ast::Param>(name_ref.syntax()) {
-                self.is_param = true;
-                return;
-            }
-            self.classify_name_ref(&file, name_ref);
-        }
-
-        // Otherwise, see if this is a declaration. We can use heuristics to
-        // suggest declaration names, see `CompletionKind::Magic`.
-        if let Some(name) = find_node_at_offset::<ast::Name>(file.syntax(), offset) {
-            if is_node::<ast::Param>(name.syntax()) {
-                self.is_param = true;
-                return;
-            }
-        }
-    }
-    fn classify_name_ref(&mut self, file: &SourceFileNode, name_ref: ast::NameRef) {
-        let name_range = name_ref.syntax().range();
-        let top_node = name_ref
-            .syntax()
-            .ancestors()
-            .take_while(|it| it.range() == name_range)
-            .last()
-            .unwrap();
-
-        match top_node.parent().map(|it| it.kind()) {
-            Some(SOURCE_FILE) | Some(ITEM_LIST) => {
-                self.is_new_item = true;
-                return;
-            }
-            _ => (),
-        }
-
-        let parent = match name_ref.syntax().parent() {
-            Some(it) => it,
-            None => return,
-        };
-        if let Some(segment) = ast::PathSegment::cast(parent) {
-            let path = segment.parent_path();
-            if let Some(mut path) = hir::Path::from_ast(path) {
-                if !path.is_ident() {
-                    path.segments.pop().unwrap();
-                    self.path_prefix = Some(path);
-                    return;
-                }
-            }
-            if path.qualifier().is_none() {
-                self.is_trivial_path = true;
-                self.enclosing_fn = self
-                    .leaf
-                    .ancestors()
-                    .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
-                    .find_map(ast::FnDef::cast);
-
-                self.is_stmt = match name_ref
-                    .syntax()
-                    .ancestors()
-                    .filter_map(ast::ExprStmt::cast)
-                    .next()
-                {
-                    None => false,
-                    Some(expr_stmt) => expr_stmt.syntax().range() == name_ref.syntax().range(),
-                };
-
-                if let Some(off) = name_ref.syntax().range().start().checked_sub(2.into()) {
-                    if let Some(if_expr) = find_node_at_offset::<ast::IfExpr>(file.syntax(), off) {
-                        if if_expr.syntax().range().end() < name_ref.syntax().range().start() {
-                            self.after_if = true;
-                        }
-                    }
-                }
-            }
-        }
-    }
-}
-
-fn is_node<'a, N: AstNode<'a>>(node: SyntaxNodeRef<'a>) -> bool {
-    match node.ancestors().filter_map(N::cast).next() {
-        None => false,
-        Some(n) => n.syntax().range() == node.range(),
-    }
-}
-
 #[cfg(test)]
 fn check_completion(code: &str, expected_completions: &str, kind: CompletionKind) {
     use crate::mock_analysis::{single_file_with_position, analysis_and_position};
index d05a5e3cf5c03e765e4d1cf8b7013f17c180bc11..3ec507fdf54488bae474440967940a4715dc6975 100644 (file)
@@ -8,14 +8,14 @@
 use rustc_hash::{FxHashMap};
 
 use crate::{
-    completion::{SyntaxContext, Completions, CompletionKind, CompletionItem},
+    completion::{CompletionContext, Completions, CompletionKind, CompletionItem},
 };
 
 /// Complete repeated parametes, both name and type. For example, if all
 /// functions in a file have a `spam: &mut Spam` parameter, a completion with
 /// `spam: &mut Spam` insert text/label and `spam` lookup string will be
 /// suggested.
-pub(super) fn complete_fn_param(acc: &mut Completions, ctx: &SyntaxContext) {
+pub(super) fn complete_fn_param(acc: &mut Completions, ctx: &CompletionContext) {
     if !ctx.is_param {
         return;
     }
index d0a6ec19e3c3a5350168756bb818c4650b3d258d..2ee36430ec6842520cc00cb3065a3243554c5c32 100644 (file)
@@ -6,10 +6,10 @@
 };
 
 use crate::{
-    completion::{SyntaxContext, CompletionItem, Completions, CompletionKind::*},
+    completion::{CompletionContext, CompletionItem, Completions, CompletionKind::*},
 };
 
-pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &SyntaxContext) {
+pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) {
     if !ctx.is_trivial_path {
         return;
     }
index 8374ec34612b7f06e6881eb662d19f6f335ad6f3..41e439b1bab93e5d4b1eeb305d010118567c1de6 100644 (file)
@@ -1,9 +1,9 @@
 use crate::{
-    completion::{CompletionItem, Completions, CompletionKind::*, SyntaxContext},
+    completion::{CompletionItem, Completions, CompletionKind::*, CompletionContext},
     Cancelable,
 };
 
-pub(super) fn complete_path(acc: &mut Completions, ctx: &SyntaxContext) -> Cancelable<()> {
+pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) -> Cancelable<()> {
     let (path, module) = match (&ctx.path_prefix, &ctx.module) {
         (Some(path), Some(module)) => (path.clone(), module),
         _ => return Ok(()),
index ddaf13b884aabdf886c80f1a5d9f5ba441b7ca7d..c1ab19d5bcef6392ca15077af84a385ddaefedff 100644 (file)
@@ -2,11 +2,11 @@
 use ra_syntax::TextUnit;
 
 use crate::{
-    completion::{CompletionItem, Completions, CompletionKind::*, SyntaxContext},
+    completion::{CompletionItem, Completions, CompletionKind::*, CompletionContext},
     Cancelable
 };
 
-pub(super) fn complete_scope(acc: &mut Completions, ctx: &SyntaxContext) -> Cancelable<()> {
+pub(super) fn complete_scope(acc: &mut Completions, ctx: &CompletionContext) -> Cancelable<()> {
     if !ctx.is_trivial_path {
         return Ok(());
     }
index 5d6cc5dc9a7056437d131650bc7636fd42e58b77..6816ae6959d646971e0fc9688b8d93019c9c5607 100644 (file)
@@ -1,8 +1,8 @@
 use crate::{
-    completion::{CompletionItem, Completions, CompletionKind::*, SyntaxContext},
+    completion::{CompletionItem, Completions, CompletionKind::*, CompletionContext},
 };
 
-pub(super) fn complete_expr_snippet(acc: &mut Completions, ctx: &SyntaxContext) {
+pub(super) fn complete_expr_snippet(acc: &mut Completions, ctx: &CompletionContext) {
     if !(ctx.is_trivial_path && ctx.enclosing_fn.is_some()) {
         return;
     }
@@ -16,7 +16,7 @@ pub(super) fn complete_expr_snippet(acc: &mut Completions, ctx: &SyntaxContext)
         .add_to(acc);
 }
 
-pub(super) fn complete_item_snippet(acc: &mut Completions, ctx: &SyntaxContext) {
+pub(super) fn complete_item_snippet(acc: &mut Completions, ctx: &CompletionContext) {
     if !ctx.is_new_item {
         return;
     }
diff --git a/crates/ra_analysis/src/completion/completion_context.rs b/crates/ra_analysis/src/completion/completion_context.rs
new file mode 100644 (file)
index 0000000..064fbc6
--- /dev/null
@@ -0,0 +1,156 @@
+use ra_editor::find_node_at_offset;
+use ra_text_edit::AtomTextEdit;
+use ra_syntax::{
+    algo::find_leaf_at_offset,
+    ast,
+    AstNode,
+    SyntaxNodeRef,
+    SourceFileNode,
+    TextUnit,
+    SyntaxKind::*,
+};
+use hir::source_binder;
+
+use crate::{db, FilePosition, Cancelable};
+
+/// `CompletionContext` is created early during completion to figure out, where
+/// exactly is the cursor, syntax-wise.
+#[derive(Debug)]
+pub(super) struct CompletionContext<'a> {
+    pub(super) db: &'a db::RootDatabase,
+    pub(super) offset: TextUnit,
+    pub(super) leaf: SyntaxNodeRef<'a>,
+    pub(super) module: Option<hir::Module>,
+    pub(super) enclosing_fn: Option<ast::FnDef<'a>>,
+    pub(super) is_param: bool,
+    /// A single-indent path, like `foo`.
+    pub(super) is_trivial_path: bool,
+    /// If not a trivial, path, the prefix (qualifier).
+    pub(super) path_prefix: Option<hir::Path>,
+    pub(super) after_if: bool,
+    pub(super) is_stmt: bool,
+    /// Something is typed at the "top" level, in module or impl/trait.
+    pub(super) is_new_item: bool,
+}
+
+impl<'a> CompletionContext<'a> {
+    pub(super) fn new(
+        db: &'a db::RootDatabase,
+        original_file: &'a SourceFileNode,
+        position: FilePosition,
+    ) -> Cancelable<Option<CompletionContext<'a>>> {
+        let module = source_binder::module_from_position(db, position)?;
+        let leaf =
+            ctry!(find_leaf_at_offset(original_file.syntax(), position.offset).left_biased());
+        let mut ctx = CompletionContext {
+            db,
+            leaf,
+            offset: position.offset,
+            module,
+            enclosing_fn: None,
+            is_param: false,
+            is_trivial_path: false,
+            path_prefix: None,
+            after_if: false,
+            is_stmt: false,
+            is_new_item: false,
+        };
+        ctx.fill(original_file, position.offset);
+        Ok(Some(ctx))
+    }
+
+    fn fill(&mut self, original_file: &SourceFileNode, offset: TextUnit) {
+        // Insert a fake ident to get a valid parse tree. We will use this file
+        // to determine context, though the original_file will be used for
+        // actual completion.
+        let file = {
+            let edit = AtomTextEdit::insert(offset, "intellijRulezz".to_string());
+            original_file.reparse(&edit)
+        };
+
+        // First, let's try to complete a reference to some declaration.
+        if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), offset) {
+            // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`.
+            // See RFC#1685.
+            if is_node::<ast::Param>(name_ref.syntax()) {
+                self.is_param = true;
+                return;
+            }
+            self.classify_name_ref(&file, name_ref);
+        }
+
+        // Otherwise, see if this is a declaration. We can use heuristics to
+        // suggest declaration names, see `CompletionKind::Magic`.
+        if let Some(name) = find_node_at_offset::<ast::Name>(file.syntax(), offset) {
+            if is_node::<ast::Param>(name.syntax()) {
+                self.is_param = true;
+                return;
+            }
+        }
+    }
+    fn classify_name_ref(&mut self, file: &SourceFileNode, name_ref: ast::NameRef) {
+        let name_range = name_ref.syntax().range();
+        let top_node = name_ref
+            .syntax()
+            .ancestors()
+            .take_while(|it| it.range() == name_range)
+            .last()
+            .unwrap();
+
+        match top_node.parent().map(|it| it.kind()) {
+            Some(SOURCE_FILE) | Some(ITEM_LIST) => {
+                self.is_new_item = true;
+                return;
+            }
+            _ => (),
+        }
+
+        let parent = match name_ref.syntax().parent() {
+            Some(it) => it,
+            None => return,
+        };
+        if let Some(segment) = ast::PathSegment::cast(parent) {
+            let path = segment.parent_path();
+            if let Some(mut path) = hir::Path::from_ast(path) {
+                if !path.is_ident() {
+                    path.segments.pop().unwrap();
+                    self.path_prefix = Some(path);
+                    return;
+                }
+            }
+            if path.qualifier().is_none() {
+                self.is_trivial_path = true;
+                self.enclosing_fn = self
+                    .leaf
+                    .ancestors()
+                    .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE)
+                    .find_map(ast::FnDef::cast);
+
+                self.is_stmt = match name_ref
+                    .syntax()
+                    .ancestors()
+                    .filter_map(ast::ExprStmt::cast)
+                    .next()
+                {
+                    None => false,
+                    Some(expr_stmt) => expr_stmt.syntax().range() == name_ref.syntax().range(),
+                };
+
+                if let Some(off) = name_ref.syntax().range().start().checked_sub(2.into()) {
+                    if let Some(if_expr) = find_node_at_offset::<ast::IfExpr>(file.syntax(), off) {
+                        if if_expr.syntax().range().end() < name_ref.syntax().range().start() {
+                            self.after_if = true;
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+fn is_node<'a, N: AstNode<'a>>(node: SyntaxNodeRef<'a>) -> bool {
+    match node.ancestors().filter_map(N::cast).next() {
+        None => false,
+        Some(n) => n.syntax().range() == node.range(),
+    }
+}