Skip to content

Commit

Permalink
feat: LSP autocomplete module declaration
Browse files Browse the repository at this point in the history
  • Loading branch information
asterite committed Jan 22, 2025
1 parent 02056d6 commit 7e5b0f3
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 7 deletions.
11 changes: 10 additions & 1 deletion compiler/fm/src/file_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<PathBuf> for PathString {
fn from(pb: PathBuf) -> PathString {
Expand Down Expand Up @@ -82,7 +86,7 @@ impl FileMap {
}

pub fn get_name(&self, file_id: FileId) -> Result<PathString, Error> {
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 {
Expand All @@ -93,6 +97,11 @@ impl FileMap {

Ok(name)
}

pub fn get_absolute_name(&self, file_id: FileId) -> Result<PathString, Error> {
let name = self.files.get(file_id.as_usize())?.name().clone();
Ok(name)
}
}
impl Default for FileMap {
fn default() -> Self {
Expand Down
1 change: 1 addition & 0 deletions compiler/noirc_frontend/src/ast/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ pub struct ModuleDeclaration {
pub visibility: ItemVisibility,
pub ident: Ident,
pub outer_attributes: Vec<SecondaryAttribute>,
pub has_semicolon: bool,
}

impl std::fmt::Display for ModuleDeclaration {
Expand Down
11 changes: 9 additions & 2 deletions compiler/noirc_frontend/src/parser/parser/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ impl<'a> Parser<'a> {
visibility,
ident: Ident::default(),
outer_attributes,
has_semicolon: false,
});
};

Expand All @@ -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,
})
}
}
}
Expand Down
64 changes: 60 additions & 4 deletions tooling/lsp/src/requests/completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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<()> {

Check warning on line 1116 in tooling/lsp/src/requests/completion.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (delcaration)
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<String> =
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
Expand Down Expand Up @@ -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);

Check warning on line 1852 in tooling/lsp/src/requests/completion.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (delcaration)
}
}

fn get_field_type(typ: &Type, name: &str) -> Option<Type> {
Expand Down

0 comments on commit 7e5b0f3

Please sign in to comment.