From 537ea620bb2a73a5e79872f414af23cf4bf03179 Mon Sep 17 00:00:00 2001
From: Aleksey Kladov <aleksey.kladov@gmail.com>
Date: Tue, 28 Aug 2018 19:23:55 +0300
Subject: complete items from module scope

---
 crates/libeditor/src/completion.rs      | 53 ++++++++++++++++++++++++---------
 crates/libeditor/src/lib.rs             |  6 ++--
 crates/libeditor/src/scope/mod.rs       |  7 ++++-
 crates/libeditor/src/scope/mod_scope.rs | 47 +++++++++++++++++++++++++++++
 crates/libsyntax2/src/ast/generated.rs  |  4 +++
 crates/libsyntax2/src/grammar.ron       |  1 +
 6 files changed, 100 insertions(+), 18 deletions(-)
 create mode 100644 crates/libeditor/src/scope/mod_scope.rs

diff --git a/crates/libeditor/src/completion.rs b/crates/libeditor/src/completion.rs
index c6ce62661..2756e472a 100644
--- a/crates/libeditor/src/completion.rs
+++ b/crates/libeditor/src/completion.rs
@@ -8,7 +8,7 @@ use libsyntax2::{
 
 use {
     AtomEdit, find_node_at_offset,
-    scope::FnScopes,
+    scope::{FnScopes, ModuleScope},
 };
 
 #[derive(Debug)]
@@ -24,18 +24,27 @@ pub fn scope_completion(file: &File, offset: TextUnit) -> Option<Vec<CompletionI
         file.incremental_reparse(&edit)?
     };
     let name_ref = find_node_at_offset::<ast::NameRef>(file.syntax(), offset)?;
-    let fn_def = ancestors(name_ref.syntax()).filter_map(ast::FnDef::cast).next()?;
-    let scopes = FnScopes::new(fn_def);
-    Some(complete(name_ref, &scopes))
+    let mut res = Vec::new();
+    if let Some(fn_def) = ancestors(name_ref.syntax()).filter_map(ast::FnDef::cast).next() {
+        let scopes = FnScopes::new(fn_def);
+        complete_fn(name_ref, &scopes, &mut res);
+    }
+    if let Some(root) = ancestors(name_ref.syntax()).filter_map(ast::Root::cast).next() {
+        let scope = ModuleScope::new(root);
+        res.extend(
+            scope.entries().iter()
+                .map(|entry| CompletionItem { name: entry.name().to_string() })
+        )
+    }
+    Some(res)
 }
 
-fn complete(name_ref: ast::NameRef, scopes: &FnScopes) -> Vec<CompletionItem> {
-    scopes.scope_chain(name_ref.syntax())
-        .flat_map(|scope| scopes.entries(scope).iter())
-        .map(|entry| CompletionItem {
-            name: entry.name().to_string()
-        })
-        .collect()
+fn complete_fn(name_ref: ast::NameRef, scopes: &FnScopes, acc: &mut Vec<CompletionItem>) {
+    acc.extend(
+        scopes.scope_chain(name_ref.syntax())
+            .flat_map(|scope| scopes.entries(scope).iter())
+            .map(|entry| CompletionItem { name: entry.name().to_string() })
+    )
 }
 
 #[cfg(test)]
@@ -59,7 +68,8 @@ mod tests {
                 let z = ();
             }
             ", r#"[CompletionItem { name: "y" },
-                   CompletionItem { name: "x" }]"#);
+                   CompletionItem { name: "x" },
+                   CompletionItem { name: "quux" }]"#);
     }
 
     #[test]
@@ -75,7 +85,8 @@ mod tests {
                 }
             }
             ", r#"[CompletionItem { name: "b" },
-                   CompletionItem { name: "a" }]"#);
+                   CompletionItem { name: "a" },
+                   CompletionItem { name: "quux" }]"#);
     }
 
     #[test]
@@ -86,6 +97,20 @@ mod tests {
                     <|>
                 }
             }
-            ", r#"[CompletionItem { name: "x" }]"#);
+            ", r#"[CompletionItem { name: "x" },
+                   CompletionItem { name: "quux" }]"#);
+    }
+
+    #[test]
+    fn test_completion_mod_scope() {
+        do_check(r"
+            struct Foo;
+            enum Baz {}
+            fn quux() {
+                <|>
+            }
+            ", r#"[CompletionItem { name: "Foo" },
+                   CompletionItem { name: "Baz" },
+                   CompletionItem { name: "quux" }]"#);
     }
 }
diff --git a/crates/libeditor/src/lib.rs b/crates/libeditor/src/lib.rs
index b2e2c4782..06dac9d6d 100644
--- a/crates/libeditor/src/lib.rs
+++ b/crates/libeditor/src/lib.rs
@@ -18,7 +18,7 @@ mod test_utils;
 
 use libsyntax2::{
     File, TextUnit, TextRange, SyntaxNodeRef,
-    ast::{AstNode, NameOwner},
+    ast::{self, AstNode, NameOwner},
     algo::{walk, find_leaf_at_offset, ancestors},
     SyntaxKind::{self, *},
 };
@@ -126,8 +126,8 @@ pub fn syntax_tree(file: &File) -> String {
 }
 
 pub fn runnables(file: &File) -> Vec<Runnable> {
-    file.ast()
-        .functions()
+    walk::preorder(file.syntax())
+        .filter_map(ast::FnDef::cast)
         .filter_map(|f| {
             let name = f.name()?.text();
             let kind = if name == "main" {
diff --git a/crates/libeditor/src/scope/mod.rs b/crates/libeditor/src/scope/mod.rs
index 1a77a8b6e..2f25230f8 100644
--- a/crates/libeditor/src/scope/mod.rs
+++ b/crates/libeditor/src/scope/mod.rs
@@ -1,3 +1,8 @@
 mod fn_scope;
+mod mod_scope;
+
+pub use self::{
+    fn_scope::FnScopes,
+    mod_scope::ModuleScope,
+};
 
-pub use self::fn_scope::FnScopes;
diff --git a/crates/libeditor/src/scope/mod_scope.rs b/crates/libeditor/src/scope/mod_scope.rs
new file mode 100644
index 000000000..0e51108d9
--- /dev/null
+++ b/crates/libeditor/src/scope/mod_scope.rs
@@ -0,0 +1,47 @@
+use libsyntax2::{
+    AstNode, SyntaxNode, SmolStr, ast
+};
+
+pub struct ModuleScope {
+    entries: Vec<Entry>,
+}
+
+impl ModuleScope {
+    pub fn new(m: ast::Root) -> ModuleScope {
+        let entries = m.items().filter_map(|item| {
+            match item {
+                ast::ModuleItem::StructDef(item) => Entry::new(item),
+                ast::ModuleItem::EnumDef(item) => Entry::new(item),
+                ast::ModuleItem::FnDef(item) => Entry::new(item),
+                ast::ModuleItem::TraitDef(item) => Entry::new(item),
+                ast::ModuleItem::ExternCrateItem(_) |
+                ast::ModuleItem::ImplItem(_) |
+                ast::ModuleItem::UseItem(_) => None
+            }
+        }).collect();
+
+        ModuleScope { entries }
+    }
+
+    pub fn entries(&self) -> &[Entry] {
+        self.entries.as_slice()
+    }
+}
+
+pub struct Entry {
+    name: SyntaxNode,
+}
+
+impl Entry {
+    fn new<'a>(item: impl ast::NameOwner<'a>) -> Option<Entry> {
+        let name = item.name()?;
+        Some(Entry { name: name.syntax().owned() })
+    }
+    pub fn name(&self) -> SmolStr {
+        self.ast().text()
+    }
+    fn ast(&self) -> ast::Name {
+        ast::Name::cast(self.name.borrowed()).unwrap()
+    }
+}
+
diff --git a/crates/libsyntax2/src/ast/generated.rs b/crates/libsyntax2/src/ast/generated.rs
index b24fd2aba..2b400b847 100644
--- a/crates/libsyntax2/src/ast/generated.rs
+++ b/crates/libsyntax2/src/ast/generated.rs
@@ -1441,6 +1441,10 @@ impl<'a> AstNode<'a> for Root<'a> {
 }
 
 impl<'a> Root<'a> {
+    pub fn items(self) -> impl Iterator<Item = ModuleItem<'a>> + 'a {
+        super::children(self)
+    }
+
     pub fn functions(self) -> impl Iterator<Item = FnDef<'a>> + 'a {
         super::children(self)
     }
diff --git a/crates/libsyntax2/src/grammar.ron b/crates/libsyntax2/src/grammar.ron
index c3a29c10c..8055a4687 100644
--- a/crates/libsyntax2/src/grammar.ron
+++ b/crates/libsyntax2/src/grammar.ron
@@ -238,6 +238,7 @@ Grammar(
     ast: {
         "Root": (
             collections: [
+                ["items", "ModuleItem"],
                 ["functions", "FnDef"],
                 ["modules", "Module"],
             ]
-- 
cgit v1.2.3