mirror of
https://github.com/enso-org/enso.git
synced 2024-12-24 07:01:33 +03:00
Import Management (https://github.com/enso-org/ide/pull/656)
Original commit: 329534b75c
This commit is contained in:
parent
87c5f28bbb
commit
48b8fea226
1
gui/src/rust/Cargo.lock
generated
1
gui/src/rust/Cargo.lock
generated
@ -1108,6 +1108,7 @@ dependencies = [
|
||||
"flo_stream 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ide-view 0.1.0",
|
||||
"itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"js-sys 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"json-rpc 0.1.0",
|
||||
"logger 0.1.0",
|
||||
|
@ -31,6 +31,7 @@ console_error_panic_hook = { version = "0.1.6"
|
||||
failure = { version = "0.1.6" }
|
||||
flo_stream = { version = "0.4.0" }
|
||||
futures = { version = "0.3.1" }
|
||||
itertools = { version = "0.8.1" }
|
||||
nalgebra = { version = "0.21.1" , features = ["serde-serialize"] }
|
||||
js-sys = { version = "0.3.35" }
|
||||
serde = { version = "1.0" , features = ["derive"] }
|
||||
|
@ -69,12 +69,13 @@ where for<'t> &'t Shape<Ast> : TryInto<&'t T,Error=E> {
|
||||
}
|
||||
|
||||
/// Updated self in place by applying given function on the stored Shape.
|
||||
pub fn update_shape(&mut self, f:impl FnOnce(&mut T))
|
||||
pub fn update_shape<R>(&mut self, f:impl FnOnce(&mut T) -> R) -> R
|
||||
where T : Clone + Into<Shape<Ast>>,
|
||||
E : Debug {
|
||||
let mut shape = self.shape().clone();
|
||||
f(&mut shape);
|
||||
self.ast = self.ast.with_shape(shape)
|
||||
let ret = f(&mut shape);
|
||||
self.ast = self.ast.with_shape(shape);
|
||||
ret
|
||||
}
|
||||
|
||||
/// Create new instance of KnownAst with mapped shape.
|
||||
|
@ -400,6 +400,7 @@ impl<'de> Deserialize<'de> for Ast {
|
||||
#[derive(HasTokens)]
|
||||
pub enum Shape<T> {
|
||||
Unrecognized { str : String },
|
||||
Unexpected { msg : String, stream:Vec<Shifted<T>> },
|
||||
InvalidQuote { quote : Builder },
|
||||
InlineBlock { quote : Builder },
|
||||
|
||||
@ -475,7 +476,7 @@ pub enum Shape<T> {
|
||||
#[macro_export]
|
||||
macro_rules! with_shape_variants {
|
||||
($f:ident) => {
|
||||
$f! { [Unrecognized] [InvalidQuote] [InlineBlock]
|
||||
$f! { [Unrecognized] [Unexpected Ast] [InvalidQuote] [InlineBlock]
|
||||
[Blank] [Var] [Cons] [Opr] [Mod] [InvalidSuffix Ast]
|
||||
[Number] [DanglingBase]
|
||||
[TextLineRaw] [TextLineFmt Ast] [TextBlockRaw] [TextBlockFmt Ast] [TextUnclosed Ast]
|
||||
|
@ -11,6 +11,35 @@ use crate::known;
|
||||
|
||||
|
||||
|
||||
// ===============
|
||||
// === Imports ===
|
||||
// ===============
|
||||
|
||||
/// The keyword introducing an import declaration. See:
|
||||
/// https://dev.enso.org/docs/enso/syntax/imports.html#import-syntax
|
||||
pub const IMPORT_KEYWORD:&str = "import";
|
||||
|
||||
/// If the given AST node is an import declaration, returns it as a Match (which is the only shape
|
||||
/// capable of storing import declarations). Returns `None` otherwise.
|
||||
pub fn ast_as_import_match(ast:&Ast) -> Option<known::Match> {
|
||||
let macro_match = known::Match::try_from(ast).ok()?;
|
||||
is_match_import(¯o_match).then(macro_match)
|
||||
}
|
||||
|
||||
/// Check if the given macro match node is an import declaration.
|
||||
pub fn is_match_import(ast:&known::Match) -> bool {
|
||||
let segment = &ast.segs.head;
|
||||
let keyword = crate::identifier::name(&segment.head);
|
||||
keyword.contains_if(|str| *str == IMPORT_KEYWORD)
|
||||
}
|
||||
|
||||
/// Check if the given ast node is an import declaration.
|
||||
pub fn is_ast_import(ast:&Ast) -> bool {
|
||||
ast_as_import_match(ast).is_some()
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===============
|
||||
// === Lambdas ===
|
||||
// ===============
|
||||
|
@ -175,6 +175,7 @@ has_tokens!(ShiftedVec1<T>, self.head, self.tail);
|
||||
// ===============
|
||||
|
||||
has_tokens!(Unrecognized , self.str);
|
||||
has_tokens!(Unexpected<T> , self.stream);
|
||||
has_tokens!(InvalidQuote , self.quote);
|
||||
has_tokens!(InlineBlock , self.quote);
|
||||
|
||||
|
@ -6,6 +6,38 @@ use wasm_bindgen_test::wasm_bindgen_test_configure;
|
||||
|
||||
wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn import_utilities() {
|
||||
use ast::macros::ast_as_import_match;
|
||||
use ast::macros::is_ast_import;
|
||||
use ast::macros::is_match_import;
|
||||
|
||||
let parser = Parser::new_or_panic();
|
||||
let expect_import = |code:&str| {
|
||||
let ast = parser.parse_line(code).unwrap();
|
||||
assert!(is_ast_import(&ast));
|
||||
let ast_match = ast_as_import_match(&ast).unwrap();
|
||||
assert_eq!(&ast,ast_match.ast());
|
||||
assert!(is_match_import(&ast_match));
|
||||
};
|
||||
|
||||
let expect_not_import = |code:&str| {
|
||||
let ast = parser.parse_line(code).unwrap();
|
||||
assert!(!is_ast_import(&ast));
|
||||
assert!(ast_as_import_match(&ast).is_none());
|
||||
};
|
||||
|
||||
expect_import("import");
|
||||
expect_import("import Foo");
|
||||
expect_import("import Foo.Bar");
|
||||
expect_import("import Foo.Bar.Baz");
|
||||
|
||||
expect_not_import("type Foo");
|
||||
expect_not_import("type Foo as Bar");
|
||||
expect_not_import("if Foo then Bar else Baz");
|
||||
expect_not_import("Foo.Bar.Baz");
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn recognizing_lambdas() {
|
||||
let parser = Parser::new_or_panic();
|
||||
|
@ -1,4 +1,5 @@
|
||||
#![feature(generators, generator_trait)]
|
||||
#![feature(matches_macro)]
|
||||
|
||||
use parser::prelude::*;
|
||||
|
||||
@ -85,6 +86,18 @@ impl Fixture {
|
||||
});
|
||||
}
|
||||
|
||||
fn deserialize_unexpected(&mut self) {
|
||||
let unexpected = "import";
|
||||
let ast = self.parser.parse_line(unexpected).unwrap();
|
||||
// This does not deserialize to "Unexpected" but to a very complex macro match tree that has
|
||||
// Unexpected somewhere within. We just make sure that it is somewhere, and that confirms
|
||||
// that we are able to deserialize such node.
|
||||
let has_unexpected = ast.iter_recursive().find(|ast| {
|
||||
matches!(ast.shape(), Shape::Unexpected(_))
|
||||
});
|
||||
assert!(has_unexpected.is_some());
|
||||
}
|
||||
|
||||
fn deserialize_invalid_quote(&mut self) {
|
||||
let unfinished = "'a''";
|
||||
self.test_shape(unfinished,|shape:&Prefix<Ast>| {
|
||||
@ -383,6 +396,7 @@ impl Fixture {
|
||||
self.blank_line_round_trip();
|
||||
self.deserialize_metadata();
|
||||
self.deserialize_unrecognized();
|
||||
self.deserialize_unexpected();
|
||||
self.deserialize_invalid_quote();
|
||||
self.deserialize_inline_block();
|
||||
self.deserialize_blank();
|
||||
|
@ -3,6 +3,7 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::double_representation::text::apply_code_change_to_id_map;
|
||||
use crate::double_representation::module;
|
||||
use crate::model::module::Path;
|
||||
|
||||
use ast;
|
||||
@ -13,6 +14,7 @@ use enso_protocol::types::Sha3_224;
|
||||
use parser::Parser;
|
||||
|
||||
|
||||
|
||||
// ==============
|
||||
// === Errors ===
|
||||
// ==============
|
||||
@ -129,6 +131,43 @@ impl Handle {
|
||||
})
|
||||
}
|
||||
|
||||
/// Modify module by modifying its `Info` description (which is a wrapper directly over module's
|
||||
/// AST).
|
||||
pub fn modify<R>(&self, f:impl FnOnce(&mut module::Info) -> R) -> R {
|
||||
let mut module = self.module_info();
|
||||
let ret = f(&mut module);
|
||||
self.model.update_ast(module.ast);
|
||||
ret
|
||||
}
|
||||
|
||||
/// Obtains the `Info` value describing this module's AST.
|
||||
pub fn module_info(&self) -> module::Info {
|
||||
let ast = self.model.ast();
|
||||
double_representation::module::Info {ast}
|
||||
}
|
||||
|
||||
/// Adds a new import to the module.
|
||||
///
|
||||
/// May create duplicate entries if such import was already present.
|
||||
pub fn add_import(&self, target:&module::QualifiedName) {
|
||||
let import = module::ImportInfo::from_qualified_name(target);
|
||||
self.modify(|info| info.add_import(&self.parser, import));
|
||||
}
|
||||
|
||||
/// Removes an import declaration that brings given target.
|
||||
///
|
||||
/// Fails, if there was no such declaration found.
|
||||
pub fn remove_import(&self, target:&module::QualifiedName) -> FallibleResult<()> {
|
||||
let import = module::ImportInfo::from_qualified_name(target);
|
||||
self.modify(|info| info.remove_import(&import))
|
||||
}
|
||||
|
||||
/// Retrieve a vector describing all import declarations currently present in the module.
|
||||
pub fn imports(&self) -> Vec<module::ImportInfo> {
|
||||
let module = self.module_info();
|
||||
module.iter_imports().collect()
|
||||
}
|
||||
|
||||
/// Creates a mocked module controller.
|
||||
pub fn new_mock
|
||||
( path : Path
|
||||
|
@ -6,11 +6,204 @@ use crate::double_representation::definition;
|
||||
use crate::double_representation::definition::DefinitionProvider;
|
||||
|
||||
use ast::crumbs::ChildAst;
|
||||
use ast::crumbs::ModuleCrumb;
|
||||
use ast::known;
|
||||
use ast::BlockLine;
|
||||
use enso_protocol::language_server;
|
||||
|
||||
|
||||
|
||||
// =====================
|
||||
// === QualifiedName ===
|
||||
// =====================
|
||||
|
||||
/// Module's qualified name is used in some of the Language Server's APIs, like
|
||||
/// `VisualisationConfiguration`.
|
||||
///
|
||||
/// Qualified name is constructed as follows:
|
||||
/// `ProjectName.<directories_between_src_and_enso_file>.<file_without_ext>`
|
||||
///
|
||||
/// See https://dev.enso.org/docs/distribution/packaging.html for more information about the
|
||||
/// package structure.
|
||||
#[derive(Clone,Debug,Display,Shrinkwrap)]
|
||||
pub struct QualifiedName(String);
|
||||
|
||||
impl QualifiedName {
|
||||
/// Build a module's full qualified name from its name segments and the project name.
|
||||
///
|
||||
/// ```
|
||||
/// use ide::model::module::QualifiedName;
|
||||
///
|
||||
/// let name = QualifiedName::from_segments("Project",&["Main"]);
|
||||
/// assert_eq!(name.to_string(), "Project.Main");
|
||||
/// ```
|
||||
pub fn from_segments
|
||||
(project_name:impl Str, module_segments:impl IntoIterator<Item:AsRef<str>>)
|
||||
-> QualifiedName {
|
||||
let project_name = std::iter::once(project_name.into());
|
||||
let module_segments = module_segments.into_iter();
|
||||
let module_segments = module_segments.map(|segment| segment.as_ref().to_string());
|
||||
let mut all_segments = project_name.chain(module_segments);
|
||||
let name = all_segments.join(".");
|
||||
QualifiedName(name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ==================
|
||||
// === ImportInfo ===
|
||||
// ==================
|
||||
|
||||
/// Representation of a single import declaration.
|
||||
// TODO [mwu]
|
||||
// Currently only supports the unqualified imports like `import Foo.Bar`. Qualified, restricted and
|
||||
// and hiding imports are not supported by the parser yet. In future when parser and engine
|
||||
// supports them, this structure should be adjusted as well.
|
||||
#[derive(Clone,Debug,PartialEq)]
|
||||
pub struct ImportInfo {
|
||||
/// The segments of the qualified name of the imported target.
|
||||
pub target:Vec<String>
|
||||
}
|
||||
|
||||
impl ImportInfo {
|
||||
/// Construct from a string describing an import target, like `"Foo.Bar"`.
|
||||
pub fn from_target_str(name:impl AsRef<str>) -> Self {
|
||||
let name = name.as_ref().trim();
|
||||
let target = if name.is_empty() {
|
||||
Vec::new()
|
||||
} else {
|
||||
name.split(ast::opr::predefined::ACCESS).map(Into::into).collect()
|
||||
};
|
||||
ImportInfo {target}
|
||||
}
|
||||
|
||||
/// Construct from a module qualified name like `"Foo.Bar"` that describes imported target.
|
||||
pub fn from_qualified_name(name:&QualifiedName) -> Self {
|
||||
Self::from_target_str(name.as_str())
|
||||
}
|
||||
|
||||
/// Obtain the qualified name of the imported module.
|
||||
pub fn qualified_name(&self) -> QualifiedName {
|
||||
QualifiedName(self.target.join(ast::opr::predefined::ACCESS))
|
||||
}
|
||||
|
||||
/// Construct from an AST. Fails if the Ast is not an import declaration.
|
||||
pub fn from_ast(ast:&Ast) -> Option<Self> {
|
||||
let macro_match = known::Match::try_from(ast).ok()?;
|
||||
Self::from_match(macro_match)
|
||||
}
|
||||
|
||||
/// Construct from a macro match AST. Fails if the Ast is not an import declaration.
|
||||
pub fn from_match(ast:known::Match) -> Option<Self> {
|
||||
ast::macros::is_match_import(&ast).then_with(|| {
|
||||
ImportInfo::from_target_str(ast.segs.head.body.repr().trim())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ImportInfo {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{} {}",ast::macros::IMPORT_KEYWORD,self.qualified_name())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ==============
|
||||
// === Errors ===
|
||||
// ==============
|
||||
|
||||
#[derive(Clone,Debug,Fail)]
|
||||
#[fail(display="Import `{}` was not found in the module.",_0)]
|
||||
#[allow(missing_docs)]
|
||||
pub struct ImportNotFound(pub ImportInfo);
|
||||
|
||||
#[derive(Clone,Copy,Debug,Fail)]
|
||||
#[fail(display="Line index is out of bounds.")]
|
||||
#[allow(missing_docs)]
|
||||
pub struct LineIndexOutOfBounds;
|
||||
|
||||
|
||||
|
||||
// ============
|
||||
// === Info ===
|
||||
// ============
|
||||
|
||||
/// Wrapper allowing getting information about the module and updating it.
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct Info {
|
||||
#[allow(missing_docs)]
|
||||
pub ast:known::Module,
|
||||
}
|
||||
|
||||
impl Info {
|
||||
/// Iterate over all lines in module that contain an import declaration.
|
||||
pub fn enumerate_imports<'a>(&'a self) -> impl Iterator<Item=(ModuleCrumb, ImportInfo)> + 'a {
|
||||
let children = self.ast.shape().enumerate();
|
||||
children.filter_map(|(crumb,ast)| Some((crumb,ImportInfo::from_ast(ast)?)))
|
||||
}
|
||||
|
||||
/// Iterate over all import declarations in the module.
|
||||
///
|
||||
/// If the caller wants to know *where* the declarations are, use `enumerate_imports`.
|
||||
pub fn iter_imports<'a>(&'a self) -> impl Iterator<Item=ImportInfo> + 'a {
|
||||
self.enumerate_imports().map(|(_,import)| import)
|
||||
}
|
||||
|
||||
/// Add a new line to the module's block.
|
||||
///
|
||||
/// Note that indices are the "module line" indices, which usually are quite different from text
|
||||
/// API line indices (because nested blocks doesn't count as separate "module lines").
|
||||
pub fn add_line(&mut self, index:usize, ast:Option<Ast>) {
|
||||
let line = BlockLine::new(ast);
|
||||
self.ast.update_shape(|shape| shape.lines.insert(index,line))
|
||||
}
|
||||
|
||||
/// Remove line with given index.
|
||||
///
|
||||
/// Returns removed line. Fails if the index is out of bounds.
|
||||
pub fn remove_line(&mut self, index:usize) -> FallibleResult<BlockLine<Option<Ast>>> {
|
||||
self.ast.update_shape(|shape| {
|
||||
shape.lines.try_remove(index).ok_or_else(|| LineIndexOutOfBounds.into())
|
||||
})
|
||||
}
|
||||
|
||||
/// Remove a line that matches given import description.
|
||||
///
|
||||
/// If there is more than one line matching, only the first one will be removed.
|
||||
/// Fails if there is no import matching given argument.
|
||||
pub fn remove_import(&mut self, to_remove:&ImportInfo) -> FallibleResult<()> {
|
||||
let lookup_result = self.enumerate_imports().find(|(_,import)| import == to_remove);
|
||||
let (crumb,_) = lookup_result.ok_or_else(|| ImportNotFound(to_remove.clone()))?;
|
||||
self.remove_line(crumb.line_index)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add a new import declaration to a module.
|
||||
// TODO [mwu]
|
||||
// Ideally we should not require parser but should use some sane way of generating AST from
|
||||
// the `ImportInfo` value.
|
||||
pub fn add_import(&mut self, parser:&parser::Parser, to_add:ImportInfo) -> usize {
|
||||
// Find last import that is not "after" the added one lexicographically.
|
||||
let previous_import = self.enumerate_imports().take_while(|(_,import)| {
|
||||
to_add.target > import.target
|
||||
}).last();
|
||||
|
||||
let index_to_place_at = previous_import.map_or(0,|(crumb,_)| crumb.line_index + 1);
|
||||
let import_ast = parser.parse_line(to_add.to_string()).unwrap();
|
||||
self.add_line(index_to_place_at,Some(import_ast));
|
||||
index_to_place_at
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn expect_code(&self,expected_code:impl AsRef<str>) {
|
||||
assert_eq!(self.ast.repr(),expected_code.as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ==============
|
||||
// === Errors ===
|
||||
// ==============
|
||||
@ -104,6 +297,55 @@ mod tests {
|
||||
|
||||
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn import_listing() {
|
||||
let parser = parser::Parser::new_or_panic();
|
||||
let expect_imports = |code:&str, expected:&[&[&str]]| {
|
||||
let ast = parser.parse_module(code,default()).unwrap();
|
||||
let info = Info {ast};
|
||||
let imports = info.iter_imports().collect_vec();
|
||||
assert_eq!(imports.len(), expected.len());
|
||||
for (import,expected_segments) in imports.iter().zip(expected) {
|
||||
itertools::assert_equal(import.target.iter(),expected_segments.iter());
|
||||
}
|
||||
};
|
||||
|
||||
expect_imports("import", &[&[]]);
|
||||
expect_imports("import Foo", &[&["Foo"]]);
|
||||
expect_imports("import Foo.Bar", &[&["Foo","Bar"]]);
|
||||
expect_imports("foo = bar\nimport Foo.Bar", &[&["Foo","Bar"]]);
|
||||
expect_imports("import Foo.Bar\nfoo=bar\nimport Foo.Bar", &[&["Foo","Bar"],&["Foo","Bar"]]);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn import_adding_and_removing() {
|
||||
let parser = parser::Parser::new_or_panic();
|
||||
let code = "import Foo.Bar.Baz";
|
||||
let ast = parser.parse_module(code,default()).unwrap();
|
||||
let mut info = Info { ast };
|
||||
let import = |code| {
|
||||
let ast = parser.parse_line(code).unwrap();
|
||||
ImportInfo::from_ast(&ast).unwrap()
|
||||
};
|
||||
|
||||
info.add_import(&parser,import("import Bar.Gar"));
|
||||
info.expect_code("import Bar.Gar\nimport Foo.Bar.Baz");
|
||||
info.add_import(&parser,import("import Gar.Bar"));
|
||||
info.expect_code("import Bar.Gar\nimport Foo.Bar.Baz\nimport Gar.Bar");
|
||||
|
||||
info.remove_import(&ImportInfo::from_target_str("Foo.Bar.Baz")).unwrap();
|
||||
info.expect_code("import Bar.Gar\nimport Gar.Bar");
|
||||
info.remove_import(&ImportInfo::from_target_str("Foo.Bar.Baz")).unwrap_err();
|
||||
info.expect_code("import Bar.Gar\nimport Gar.Bar");
|
||||
info.remove_import(&ImportInfo::from_target_str("Gar.Bar")).unwrap();
|
||||
info.expect_code("import Bar.Gar");
|
||||
info.remove_import(&ImportInfo::from_target_str("Bar.Gar")).unwrap();
|
||||
info.expect_code("");
|
||||
|
||||
info.add_import(&parser,import("import Bar.Gar"));
|
||||
info.expect_code("import Bar.Gar");
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn implicit_method_resolution() {
|
||||
let parser = parser::Parser::new_or_panic();
|
||||
|
@ -17,7 +17,7 @@ use parser::Parser;
|
||||
use serde::Serialize;
|
||||
use serde::Deserialize;
|
||||
|
||||
|
||||
pub use double_representation::module::QualifiedName;
|
||||
|
||||
// ============
|
||||
// == Errors ==
|
||||
@ -148,6 +148,26 @@ impl Path {
|
||||
file : self.file_path.deref().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtain a module's full qualified name from the path and the project name.
|
||||
///
|
||||
/// ```
|
||||
/// use ide::prelude::*;
|
||||
/// use ide::model::module::QualifiedName;
|
||||
/// use ide::model::module::Path;
|
||||
///
|
||||
/// let path = Path::from_name_segments(default(),&["Main"]).unwrap();
|
||||
/// assert_eq!(path.to_string(),"//00000000-0000-0000-0000-000000000000/src/Main.enso");
|
||||
/// let name = path.qualified_module_name("Project");
|
||||
/// assert_eq!(name.to_string(),"Project.Main");
|
||||
/// ```
|
||||
pub fn qualified_module_name(&self, project_name:impl Str) -> QualifiedName {
|
||||
let non_src_directories = &self.file_path.segments[1..self.file_path.segments.len()-1];
|
||||
let non_src_directories = non_src_directories.iter().map(|dirname| dirname.as_str());
|
||||
let module_name = std::iter::once(self.module_name());
|
||||
let module_segments = non_src_directories.chain(module_name);
|
||||
QualifiedName::from_segments(project_name,module_segments)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<FilePath> for Path {
|
||||
@ -179,65 +199,6 @@ impl TryFrom<MethodPointer> for Path {
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===========================
|
||||
// === ModuleQualifiedName ===
|
||||
// ===========================
|
||||
|
||||
/// Module's qualified name is used in some of the Language Server's APIs, like
|
||||
/// `VisualisationConfiguration`.
|
||||
///
|
||||
/// Qualified name is constructed as follows:
|
||||
/// `ProjectName.<directories_between_src_and_enso_file>.<file_without_ext>`
|
||||
///
|
||||
/// See https://dev.enso.org/docs/distribution/packaging.html for more information about the
|
||||
/// package structure.
|
||||
#[derive(Clone,Debug,Shrinkwrap)]
|
||||
pub struct QualifiedName(String);
|
||||
|
||||
impl QualifiedName {
|
||||
/// Obtain a module's full qualified name from its path and the project name.
|
||||
///
|
||||
/// ```
|
||||
/// use ide::prelude::*;
|
||||
/// use ide::model::module::QualifiedName;
|
||||
/// use ide::model::module::Path;
|
||||
///
|
||||
/// let path = Path::from_name_segments(default(),&["Main"]).unwrap();
|
||||
/// assert_eq!(path.to_string(),"//00000000-0000-0000-0000-000000000000/src/Main.enso");
|
||||
/// let name = QualifiedName::from_path(&path,"Project");
|
||||
/// assert_eq!(name.to_string(),"Project.Main");
|
||||
/// ```
|
||||
pub fn from_path(path:&Path, project_name:impl Str) -> QualifiedName {
|
||||
let non_src_directories = &path.file_path.segments[1..path.file_path.segments.len()-1];
|
||||
let non_src_directories = non_src_directories.iter().map(|dirname| dirname.as_str());
|
||||
let module_name = std::iter::once(path.module_name());
|
||||
let module_segments = non_src_directories.chain(module_name);
|
||||
Self::from_module_segments(module_segments,project_name)
|
||||
}
|
||||
|
||||
/// Obtain a module's full qualified name from its path and the project name.
|
||||
///
|
||||
/// ```
|
||||
/// use ide::model::module::QualifiedName;
|
||||
///
|
||||
/// let name = QualifiedName::from_module_segments(&["Main"],"Project");
|
||||
/// assert_eq!(name.to_string(), "Project.Main");
|
||||
/// ```
|
||||
pub fn from_module_segments
|
||||
(module_segments:impl IntoIterator<Item:AsRef<str>>, project_name:impl Str)
|
||||
-> QualifiedName {
|
||||
let project_name = std::iter::once(project_name.into());
|
||||
let module_segments = module_segments.into_iter();
|
||||
let module_segments = module_segments.map(|segment| segment.as_ref().to_string());
|
||||
let mut all_segments = project_name.chain(module_segments);
|
||||
let name = all_segments.join(".");
|
||||
QualifiedName(name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ====================
|
||||
// === Notification ===
|
||||
// ====================
|
||||
@ -589,7 +550,7 @@ mod test {
|
||||
let root_id = default();
|
||||
let file_path = FilePath::new(root_id, &["src", "Foo", "Bar.enso"]);
|
||||
let module_path = Path::from_file_path(file_path).unwrap();
|
||||
let qualified = QualifiedName::from_path(&module_path,project_name);
|
||||
let qualified = module_path.qualified_module_name(project_name);
|
||||
assert_eq!(*qualified, "P.Foo.Bar");
|
||||
}
|
||||
}
|
||||
|
@ -279,7 +279,7 @@ impl Project {
|
||||
|
||||
/// Generates full module's qualified name that includes the leading project name segment.
|
||||
pub fn qualified_module_name(&self, path:&model::module::Path) -> ModuleQualifiedName {
|
||||
ModuleQualifiedName::from_path(path,self.name.deref())
|
||||
path.qualified_module_name(self.name.deref())
|
||||
}
|
||||
|
||||
fn load_module(&self, path:ModulePath)
|
||||
|
@ -335,7 +335,7 @@ pub mod tests {
|
||||
}
|
||||
|
||||
pub fn module_qualified_name(&self) -> ModuleQualifiedName {
|
||||
ModuleQualifiedName::from_path(&self.module_path,&self.project_name)
|
||||
self.module_path.qualified_module_name(&self.project_name)
|
||||
}
|
||||
|
||||
pub fn definition_id(&self) -> model::execution_context::DefinitionId {
|
||||
|
@ -588,7 +588,7 @@ impl GraphEditorIntegratedWithControllerModel {
|
||||
// to the customised values.
|
||||
let project_name = self.project.name.as_ref();
|
||||
let module_name = crate::view::project::INITIAL_MODULE_NAME;
|
||||
let visualisation_module = QualifiedName::from_module_segments(&[module_name],project_name);
|
||||
let visualisation_module = QualifiedName::from_segments(project_name,&[module_name]);
|
||||
let id = VisualizationId::new_v4();
|
||||
let expression = crate::constants::SERIALIZE_TO_JSON_EXPRESSION.into();
|
||||
let ast_id = self.get_controller_node_id(*node_id)?;
|
||||
|
Loading…
Reference in New Issue
Block a user