Skip to content

Commit

Permalink
Implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
farmaazon committed Jul 20, 2023
1 parent ed18de8 commit 6e8b1ac
Show file tree
Hide file tree
Showing 17 changed files with 196 additions and 75 deletions.
10 changes: 4 additions & 6 deletions app/gui/controller/double-representation/src/name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,9 @@ pub mod project;
pub enum InvalidQualifiedName {
#[fail(display = "The qualified name is empty.")]
EmptyName,
#[fail(display = "No namespace in project qualified name.")]
NoNamespace,
#[fail(display = "Invalid namespace in project qualified name.")]
InvalidNamespace,
#[fail(display = "Too many segments in project qualified name.")]
#[fail(display = "Too few segments in qualified name.")]
TooFewSegments,
#[fail(display = "Too many segments in qualified name.")]
TooManySegments,
}

Expand Down Expand Up @@ -156,7 +154,7 @@ impl QualifiedName {
let mut iter = segments.into_iter().map(|name| name.into());
let project_name = match (iter.next(), iter.next()) {
(Some(ns), Some(name)) => project::QualifiedName::new(ns, name),
_ => return Err(InvalidQualifiedName::NoNamespace.into()),
_ => return Err(InvalidQualifiedName::TooFewSegments.into()),
};
let without_main = iter.skip_while(|s| *s == PROJECTS_MAIN_MODULE);
Ok(Self::new(project_name, without_main.collect()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ impl QualifiedName {
match all_segments.as_slice() {
[namespace, project] => Ok(Self::new(namespace, project)),
[] => Err(InvalidQualifiedName::EmptyName.into()),
[_] => Err(InvalidQualifiedName::NoNamespace.into()),
[_] => Err(InvalidQualifiedName::TooFewSegments.into()),
_ => Err(InvalidQualifiedName::TooManySegments.into()),
}
}
Expand Down
12 changes: 8 additions & 4 deletions app/gui/src/controller/searcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::model::module::NodeEditStatus;
use crate::model::suggestion_database;
use crate::presenter::searcher;

use crate::model::execution_context::GroupQualifiedName;
use breadcrumbs::Breadcrumbs;
use double_representation::name::project;
use double_representation::name::QualifiedName;
Expand Down Expand Up @@ -39,6 +40,8 @@ pub mod input;
/// needed. Currently enabled to trigger engine's caching of user-added nodes.
/// See: https://github.com/enso-org/ide/issues/1067
pub const ASSIGN_NAMES_FOR_NODES: bool = true;
/// A name of component group containin entries representing literals.
pub const LITERALS_GROUP_NAME: &str = "Literals";


// ==============
Expand Down Expand Up @@ -717,9 +720,9 @@ impl Searcher {

fn add_virtual_entries_to_builder(builder: &mut component::Builder) {
let snippets = component::hardcoded::INPUT_SNIPPETS.with(|s| s.clone());
let group_name = component::hardcoded::INPUT_GROUP_NAME;
let project = project::QualifiedName::standard_base_library();
builder.add_virtual_entries_to_group(group_name, project, snippets);
// Unwrap is safe because conversion from INPUt_GROUP_NAME is tested.
let group_name = GroupQualifiedName::try_from(component::hardcoded::INPUT_GROUP_NAME).unwrap();
builder.add_virtual_entries_to_group(group_name, snippets);
}


Expand Down Expand Up @@ -865,8 +868,9 @@ fn component_list_for_literal(
) -> component::List {
let mut builder = component::builder::Builder::new_empty(db);
let project = project::QualifiedName::standard_base_library();
let group_name = GroupQualifiedName::new(project, LITERALS_GROUP_NAME);
let snippet = component::hardcoded::Snippet::from_literal(literal, db).into();
builder.add_virtual_entries_to_group("Literals", project, vec![snippet]);
builder.add_virtual_entries_to_group(group_name, vec![snippet]);
builder.build()
}

Expand Down
9 changes: 3 additions & 6 deletions app/gui/src/controller/searcher/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ use crate::controller::graph::RequiredImport;
use crate::controller::searcher::Filter;
use crate::model::suggestion_database;

use double_representation::name::project;
use enso_doc_parser::DocSection;
use enso_doc_parser::Tag;
use enso_suggestion_database::entry;
Expand All @@ -25,10 +24,10 @@ use superslice::Ext;
pub mod builder;
pub mod hardcoded;

use crate::model::execution_context::GroupQualifiedName;
pub use builder::Builder;



// =================
// === Constants ===
// =================
Expand All @@ -47,11 +46,9 @@ const ALIAS_MATCH_ATTENUATION_FACTOR: f32 = 0.75;
#[allow(missing_docs)]
#[derive(Clone, Debug, Default)]
pub struct Group {
/// The project where the group is defined.
pub project: project::QualifiedName,
pub name: ImString,
pub name: GroupQualifiedName,
/// Color as defined in project's `package.yaml` file.
pub color: Option<color::Rgb>,
pub color: Option<color::Rgb>,
}


Expand Down
98 changes: 67 additions & 31 deletions app/gui/src/controller/searcher/component/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,13 @@ use crate::controller::searcher::component::Component;
use crate::model::execution_context;
use crate::model::suggestion_database;

use double_representation::name::project;
use crate::model::execution_context::GroupQualifiedName;
use double_representation::name::project::STANDARD_NAMESPACE;
use double_representation::name::QualifiedName;
use double_representation::name::QualifiedNameRef;
use enso_suggestion_database::SuggestionDatabase;



// ===============================
// === Component Ordering Keys ===
// ===============================
Expand Down Expand Up @@ -113,6 +112,7 @@ pub struct Builder<'a> {
built_list: component::List,
/// A mapping from entry id to group index and the cached suggestion database entry.
entry_to_group_map: HashMap<suggestion_database::entry::Id, EntryInGroup>,
group_name_to_id: HashMap<GroupQualifiedName, usize>,
}

impl<'a> Builder<'a> {
Expand All @@ -124,39 +124,45 @@ impl<'a> Builder<'a> {
inside_module: default(),
built_list: default(),
entry_to_group_map: default(),
group_name_to_id: default(),
}
}

/// Create builder for base view (without self type and not inside any module).
pub fn new(db: &'a SuggestionDatabase, groups: &[execution_context::ComponentGroup]) -> Self {
let entry_to_group_entries = groups
.iter()
.enumerate()
.flat_map(|(index, data)| Self::group_data_to_map_entries(db, index, data));
let group_name_to_id =
groups.iter().enumerate().map(|(index, data)| (data.name.clone_ref(), index)).collect();
let entry_to_group_entries = groups.iter().enumerate().flat_map(|(index, data)| {
Self::group_data_to_map_entries(db, index, data, &group_name_to_id)
});
let groups = groups.iter().map(|group_data| component::Group {
project: group_data.project.clone_ref(),
name: group_data.name.clone_ref(),
color: group_data.color,
name: group_data.name.clone_ref(),
color: group_data.color,
});

Self {
db,
this_type: None,
inside_module: None,
built_list: component::List { groups: groups.collect(), ..default() },
entry_to_group_map: entry_to_group_entries.collect(),
group_name_to_id,
}
}

fn group_data_to_map_entries<'b>(
db: &'b SuggestionDatabase,
group_index: usize,
group_data: &'b execution_context::ComponentGroup,
group_name_to_id: &'b HashMap<GroupQualifiedName, usize>,
) -> impl Iterator<Item = (suggestion_database::entry::Id, EntryInGroup)> + 'b {
group_data.components.iter().filter_map(move |component_qn| {
let (id, entry) = db.lookup_by_qualified_name(component_qn).handle_err(|err| {
warn!("Cannot put entry {component_qn} to component groups. {err}")
})?;
Some((id, EntryInGroup { entry, group_index }))
// The group name from documentation tags has precedence.
let group_from_tag = group_index_from_entry_tag(&entry, group_name_to_id);
Some((id, EntryInGroup { entry, group_index: group_from_tag.unwrap_or(group_index) }))
})
}

Expand Down Expand Up @@ -252,17 +258,20 @@ impl<'a> Builder<'a> {
/// It may not be actually added, depending on the mode. See [structure's docs](Builder) for
/// details.
pub fn add_component_from_db(&mut self, id: suggestion_database::entry::Id) -> FallibleResult {
let in_group = self.entry_to_group_map.get(&id);
// If the entry is in group, we already retrieved it from suggestion database.
let entry =
in_group.map_or_else(|| self.db.lookup(id), |group| Ok(group.entry.clone_ref()))?;
// If the entry was specified as in some group, we already retrieved it from suggestion
// database.
let cached_in_group = self.entry_to_group_map.get(&id);
let entry = cached_in_group
.map_or_else(|| self.db.lookup(id), |entry| Ok(entry.entry.clone_ref()))?;
let group_id = cached_in_group
.map(|entry| entry.group_index)
.or_else(|| self.group_index_from_entry_tag(&entry));
let when_displayed = match &self.inside_module {
Some(module) => WhenDisplayed::inside_module(&entry, module.as_ref()),
None if self.this_type.is_some() => WhenDisplayed::with_self_type(),
None => WhenDisplayed::in_base_mode(&entry, in_group.is_some()),
None => WhenDisplayed::in_base_mode(&entry, group_id.is_some()),
};
let component =
Component::new_from_database_entry(id, entry, in_group.map(|e| e.group_index));
let component = Component::new_from_database_entry(id, entry, group_id);
if matches!(when_displayed, WhenDisplayed::Always) {
self.built_list.displayed_by_default.push(component.clone());
}
Expand All @@ -272,19 +281,22 @@ impl<'a> Builder<'a> {
Ok(())
}

fn group_index_from_entry_tag(&self, entry: &suggestion_database::Entry) -> Option<usize> {
group_index_from_entry_tag(entry, &self.group_name_to_id)
}

/// Add virtual entries to a specific group.
///
/// If the groups was not specified in constructor, it will be added.
pub fn add_virtual_entries_to_group(
&mut self,
group_name: &str,
project: project::QualifiedName,
group_name: GroupQualifiedName,
snippets: impl IntoIterator<Item = Rc<hardcoded::Snippet>>,
) {
let groups = &mut self.built_list.groups;
let existing_group_index = groups.iter().position(|grp| grp.name == group_name);
let group_index = existing_group_index.unwrap_or_else(|| {
groups.push(component::Group { project, name: group_name.into(), color: None });
groups.push(component::Group { name: group_name, color: None });
groups.len() - 1
});
for snippet in snippets {
Expand All @@ -299,6 +311,22 @@ impl<'a> Builder<'a> {
}
}

fn group_index_from_entry_tag(
entry: &suggestion_database::Entry,
group_name_to_id: &HashMap<GroupQualifiedName, usize>,
) -> Option<usize> {
entry.group_name.as_ref().and_then(|name| {
// If the group name in tag is not fully qualified, we assume a group defined in the
// same project where entry is defined.
let qn_name =
GroupQualifiedName::try_from(name.as_str()).unwrap_or_else(|_| GroupQualifiedName {
project: entry.defined_in.project().clone_ref(),
name: name.into(),
});
group_name_to_id.get(&qn_name).copied()
})
}



// =============
Expand All @@ -308,27 +336,33 @@ impl<'a> Builder<'a> {
#[cfg(test)]
mod tests {
use super::*;

use crate::controller::searcher::component::tests::check_displayed_components;
use crate::controller::searcher::component::tests::check_groups;

use double_representation::name::project;
use enso_suggestion_database::mock_suggestion_database;
use ide_view::component_browser::component_list_panel::icon;

fn mock_database() -> SuggestionDatabase {
mock_suggestion_database! {
test.Test {
mod TopModule1 {
#[in_group("First Group")]
fn fun2() -> Standard.Base.Any;

mod SubModule1 {
fn fun4() -> Standard.Base.Any;
}
mod SubModule2 {
#[in_group("Second Group")]
fn fun5 -> Standard.Base.Any;
mod SubModule3 {
fn fun6 -> Standard.Base.Any;
}
}

#[in_group("First Group")]
fn fun1() -> Standard.Base.Any;
}
mod TopModule2 {
Expand All @@ -342,20 +376,18 @@ mod tests {
let project = project::QualifiedName::from_text("test.Test").unwrap();
vec![
execution_context::ComponentGroup {
project: project.clone(),
name: "First Group".into(),
name: GroupQualifiedName::new(project.clone_ref(), "First Group"),
color: None,
components: vec![
"test.Test.TopModule2.fun0".try_into().unwrap(),
"test.Test.TopModule1.fun1".try_into().unwrap(),
"test.Test.TopModule1.fun2".try_into().unwrap(),
// Should be overwritten by tag.
"test.Test.TopModule1.SubModule2.fun5".try_into().unwrap(),
],
},
execution_context::ComponentGroup {
project,
name: "Second Group".into(),
color: None,
components: vec!["test.Test.TopModule1.SubModule2.fun5".try_into().unwrap()],
name: GroupQualifiedName::new(project, "Second Group"),
color: None,
components: vec![],
},
]
}
Expand Down Expand Up @@ -444,7 +476,10 @@ mod tests {

// Adding to an empty builder.
let mut builder = Builder::new_empty(&database);
builder.add_virtual_entries_to_group("First Group", project.clone_ref(), snippets.clone());
builder.add_virtual_entries_to_group(
GroupQualifiedName::new(project.clone_ref(), "First Group"),
snippets.clone(),
);
let list = builder.build();
check_displayed_components(&list, vec!["test1", "test2"]);
check_filterable_components(&list, vec!["test1", "test2"]);
Expand All @@ -453,15 +488,16 @@ mod tests {
expected_components: Vec<&str>,
expected_group: Vec<Option<usize>>| {
let mut builder = Builder::new(&database, &groups);
builder.add_virtual_entries_to_group(group_name, project.clone_ref(), snippets.clone());
let group_name = GroupQualifiedName::new(project.clone_ref(), group_name);
builder.add_virtual_entries_to_group(group_name.clone_ref(), snippets.clone());
builder.add_components_from_db(database.keys());
let list = builder.build();
check_displayed_components(&list, expected_components.clone());
check_groups(&list, expected_group.clone());

let mut builder = Builder::new(&database, &groups);
builder.add_components_from_db(database.keys());
builder.add_virtual_entries_to_group(group_name, project.clone_ref(), snippets.clone());
builder.add_virtual_entries_to_group(group_name, snippets.clone());
let list = builder.build();
check_displayed_components(&list, expected_components);
check_groups(&list, expected_group);
Expand Down
5 changes: 4 additions & 1 deletion app/gui/src/controller/searcher/component/hardcoded.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use ide_view::component_browser::component_list_panel::grid::entry::icon::Id as

/// Name of the favorites component group in the `Standard.Base` library where virtual components
/// created from the [`INPUT_SNIPPETS`] should be added.
pub const INPUT_GROUP_NAME: &str = "Input";
pub const INPUT_GROUP_NAME: &str = "Standard.Base.Input";
/// Qualified name of the `Text` type.
const TEXT_ENTRY: &str = "Standard.Base.Main.Data.Text.Text";
/// Qualified name of the `Number` type.
Expand Down Expand Up @@ -139,11 +139,14 @@ impl Snippet {
mod tests {
use super::*;

use crate::model::execution_context::GroupQualifiedName;

/// Test that the qualified names used for hardcoded snippets can be constructed. We don't check
/// if the entries are actually available in the suggestion database.
#[test]
fn test_qualified_names_construction() {
QualifiedName::from_text(TEXT_ENTRY).unwrap();
QualifiedName::from_text(NUMBER_ENTRY).unwrap();
GroupQualifiedName::try_from(INPUT_GROUP_NAME).unwrap();
}
}
Loading

0 comments on commit 6e8b1ac

Please sign in to comment.