diff --git a/compiler/fm/src/file_map.rs b/compiler/fm/src/file_map.rs index 857c7460fb9..f078ecb8545 100644 --- a/compiler/fm/src/file_map.rs +++ b/compiler/fm/src/file_map.rs @@ -19,6 +19,10 @@ impl PathString { pub fn from_path(p: PathBuf) -> Self { PathString(p) } + + pub fn into_path_buf(self) -> PathBuf { + self.0 + } } impl From for PathString { fn from(pb: PathBuf) -> PathString { @@ -82,7 +86,7 @@ impl FileMap { } pub fn get_name(&self, file_id: FileId) -> Result { - let name = self.files.get(file_id.as_usize())?.name().clone(); + let name = self.get_absolute_name(file_id)?; // See if we can make the file name a bit shorter/easier to read if it starts with the current directory if let Some(current_dir) = &self.current_dir { @@ -93,6 +97,11 @@ impl FileMap { Ok(name) } + + pub fn get_absolute_name(&self, file_id: FileId) -> Result { + let name = self.files.get(file_id.as_usize())?.name().clone(); + Ok(name) + } } impl Default for FileMap { fn default() -> Self { diff --git a/compiler/noirc_frontend/src/ast/statement.rs b/compiler/noirc_frontend/src/ast/statement.rs index 7695de14d69..02715e8c2d3 100644 --- a/compiler/noirc_frontend/src/ast/statement.rs +++ b/compiler/noirc_frontend/src/ast/statement.rs @@ -308,6 +308,7 @@ pub struct ModuleDeclaration { pub visibility: ItemVisibility, pub ident: Ident, pub outer_attributes: Vec, + pub has_semicolon: bool, } impl std::fmt::Display for ModuleDeclaration { diff --git a/compiler/noirc_frontend/src/parser/parser/module.rs b/compiler/noirc_frontend/src/parser/parser/module.rs index da733168099..1bc3d7b5beb 100644 --- a/compiler/noirc_frontend/src/parser/parser/module.rs +++ b/compiler/noirc_frontend/src/parser/parser/module.rs @@ -25,6 +25,7 @@ impl<'a> Parser<'a> { visibility, ident: Ident::default(), outer_attributes, + has_semicolon: false, }); }; @@ -41,10 +42,16 @@ impl<'a> Parser<'a> { is_contract, }) } else { - if !self.eat_semicolons() { + let has_semicolon = self.eat_semicolons(); + if !has_semicolon { self.expected_token(Token::Semicolon); } - ItemKind::ModuleDecl(ModuleDeclaration { visibility, ident, outer_attributes }) + ItemKind::ModuleDecl(ModuleDeclaration { + visibility, + ident, + outer_attributes, + has_semicolon, + }) } } } diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 597b778260a..9948a29691e 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -18,9 +18,9 @@ use noirc_frontend::{ AsTraitPath, AttributeTarget, BlockExpression, CallExpression, ConstructorExpression, Expression, ExpressionKind, ForLoopStatement, GenericTypeArgs, Ident, IfExpression, IntegerBitSize, ItemVisibility, LValue, Lambda, LetStatement, MemberAccessExpression, - MethodCallExpression, NoirFunction, NoirStruct, NoirTraitImpl, Path, PathKind, Pattern, - Signedness, Statement, TraitBound, TraitImplItemKind, TypeImpl, TypePath, - UnresolvedGeneric, UnresolvedGenerics, UnresolvedType, UnresolvedTypeData, + MethodCallExpression, ModuleDeclaration, NoirFunction, NoirStruct, NoirTraitImpl, Path, + PathKind, Pattern, Signedness, Statement, TraitBound, TraitImplItemKind, TypeImpl, + TypePath, UnresolvedGeneric, UnresolvedGenerics, UnresolvedType, UnresolvedTypeData, UnresolvedTypeExpression, UseTree, UseTreeKind, Visitor, }, graph::{CrateId, Dependency}, @@ -1111,7 +1111,55 @@ impl<'a> NodeFinder<'a> { } } - /// Determine where each segment in a `use` statement is located. + /// Try to suggest the name of a module to declare based on which + /// files exist in the filesystem, excluding modules that are already declared. + fn complete_module_delcaration(&mut self, module: &ModuleDeclaration) -> Option<()> { + let filename = self.files.get_absolute_name(self.file).ok()?.into_path_buf(); + + let is_main_lib_or_mod = filename.ends_with("main.nr") + || filename.ends_with("lib.nr") + || filename.ends_with("mod.nr"); + + let paths = if is_main_lib_or_mod { + // For a "main" file we list sibling files + std::fs::read_dir(filename.parent()?) + } else { + // For a non-main files we list directory children + std::fs::read_dir(filename.with_extension("")) + }; + let paths = paths.ok()?; + + // See which modules are already defined via `mod ...;` + let module_data = + &self.def_maps[&self.module_id.krate].modules()[self.module_id.local_id.0]; + let existing_children: HashSet = + module_data.children.keys().map(|ident| ident.to_string()).collect(); + + for path in paths { + let Ok(path) = path else { + continue; + }; + let file_name = path.file_name().to_string_lossy().to_string(); + let Some(name) = file_name.strip_suffix(".nr") else { + continue; + }; + if name == "main" || name == "mod" || name == "lib" { + continue; + } + if existing_children.contains(name) { + continue; + } + + let label = if module.has_semicolon { name.to_string() } else { format!("{};", name) }; + self.completion_items.push(simple_completion_item( + label, + CompletionItemKind::MODULE, + None, + )); + } + + Some(()) + } fn includes_span(&self, span: Span) -> bool { span.start() as usize <= self.byte_index && self.byte_index <= span.end() as usize @@ -1795,6 +1843,14 @@ impl<'a> Visitor for NodeFinder<'a> { trait_bound.trait_generics.accept(self); false } + + fn visit_module_declaration(&mut self, module: &ModuleDeclaration, _: Span) { + if !self.includes_span(module.ident.span()) { + return; + } + + self.complete_module_delcaration(module); + } } fn get_field_type(typ: &Type, name: &str) -> Option {