mirror of
https://github.com/enso-org/enso.git
synced 2024-11-22 22:10:15 +03:00
Groups in DocTags (#7337)
Fixes #7336 in a quick way. Next to the old way of defining groups, the library can just add `GROUP` tag to some entities, and it will be added to the group specified in tag's description. The group name may be qualified (with project name, like `Standard.Base.Input/Output`) or just name - in the latter case, IDE will assume a group defined in the same library as the entity. Also moved some entities from "export" list in package.yaml to GROUP tag to give an example. I didn't move all of those, as I assume the library team will reorganize those groups anyway. ### Important Notes @jdunkerley @radeusgd @GregoryTravis When you will start specifying groups in tags, remember that: * The groups still belongs to a concrete project; if some entity outside a project wants to be added to its group, the "qualified" name should be specified. See `Table.new` example in this PR. * If the group name does not reflect any group in package.yaml **the tag is ignored**. * A single entity may be only in a single group. If it's specified in both package.yaml and in tag, the tag takes precedence. --------- Co-authored-by: Ilya Bogdanov <fumlead@gmail.com>
This commit is contained in:
parent
04ed4c1868
commit
1d2371f986
@ -205,6 +205,9 @@
|
||||
- [Help chat][7151]. The link to the Discord server is replaced with a chat
|
||||
bridge to the Discord server. This is intended to have the chat visible at the
|
||||
same time as the IDE, so that help can be much more interactive.
|
||||
- [The libraries' authors may put entities to groups by adding GROUP tag in the
|
||||
docstring]. It was requested as more convenient way than specifying full names
|
||||
in package.yaml.
|
||||
|
||||
[5910]: https://github.com/enso-org/enso/pull/5910
|
||||
[6279]: https://github.com/enso-org/enso/pull/6279
|
||||
@ -226,6 +229,7 @@
|
||||
[7151]: https://github.com/enso-org/enso/pull/7151
|
||||
[7164]: https://github.com/enso-org/enso/pull/7164
|
||||
[7372]: https://github.com/enso-org/enso/pull/7372
|
||||
[7337]: https://github.com/enso-org/enso/pull/7337
|
||||
|
||||
#### EnsoGL (rendering engine)
|
||||
|
||||
|
@ -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,
|
||||
}
|
||||
|
||||
@ -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()))
|
||||
|
@ -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()),
|
||||
}
|
||||
}
|
||||
|
@ -1177,6 +1177,7 @@ pub struct LibraryComponentGroup {
|
||||
pub color: Option<String>,
|
||||
pub icon: Option<String>,
|
||||
/// The list of components provided by this component group.
|
||||
#[serde(default)]
|
||||
pub exports: Vec<LibraryComponent>,
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ use crate::prelude::*;
|
||||
|
||||
use crate::controller::graph::ImportType;
|
||||
use crate::controller::graph::RequiredImport;
|
||||
use crate::model::execution_context::GroupQualifiedName;
|
||||
use crate::model::module::NodeEditStatus;
|
||||
use crate::model::suggestion_database;
|
||||
use crate::presenter::searcher;
|
||||
@ -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 a component group containing entries representing literals.
|
||||
pub const LITERALS_GROUP_NAME: &str = "Literals";
|
||||
|
||||
|
||||
// ==============
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
|
@ -6,9 +6,9 @@ use crate::prelude::*;
|
||||
|
||||
use crate::controller::graph::RequiredImport;
|
||||
use crate::controller::searcher::Filter;
|
||||
use crate::model::execution_context::GroupQualifiedName;
|
||||
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;
|
||||
@ -47,11 +47,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>,
|
||||
}
|
||||
|
||||
|
||||
|
@ -7,9 +7,9 @@ use crate::controller::searcher::component;
|
||||
use crate::controller::searcher::component::hardcoded;
|
||||
use crate::controller::searcher::component::Component;
|
||||
use crate::model::execution_context;
|
||||
use crate::model::execution_context::GroupQualifiedName;
|
||||
use crate::model::suggestion_database;
|
||||
|
||||
use double_representation::name::project;
|
||||
use double_representation::name::project::STANDARD_NAMESPACE;
|
||||
use double_representation::name::QualifiedName;
|
||||
use double_representation::name::QualifiedNameRef;
|
||||
@ -113,6 +113,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> {
|
||||
@ -124,26 +125,29 @@ 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 groups = groups.iter().map(|group_data| component::Group {
|
||||
project: group_data.project.clone_ref(),
|
||||
name: group_data.name.clone_ref(),
|
||||
color: group_data.color,
|
||||
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 {
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
@ -151,12 +155,15 @@ impl<'a> Builder<'a> {
|
||||
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) }))
|
||||
})
|
||||
}
|
||||
|
||||
@ -252,17 +259,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());
|
||||
}
|
||||
@ -272,19 +282,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 {
|
||||
@ -299,6 +312,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()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
@ -308,8 +337,11 @@ 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;
|
||||
|
||||
@ -317,18 +349,21 @@ mod tests {
|
||||
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 {
|
||||
@ -342,20 +377,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![],
|
||||
},
|
||||
]
|
||||
}
|
||||
@ -444,7 +477,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"]);
|
||||
@ -453,7 +489,8 @@ 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());
|
||||
@ -461,7 +498,7 @@ mod tests {
|
||||
|
||||
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);
|
||||
|
@ -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.
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,9 @@
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use ast::opr::predefined::ACCESS;
|
||||
use double_representation::identifier::Identifier;
|
||||
use double_representation::name;
|
||||
use double_representation::name::project;
|
||||
use double_representation::name::QualifiedName;
|
||||
use engine_protocol::language_server;
|
||||
@ -380,6 +382,42 @@ pub struct AttachedVisualization {
|
||||
// === ComponentGroup ===
|
||||
// ======================
|
||||
|
||||
// === GroupQualifiedName ===
|
||||
|
||||
/// A Component Group name containing project name part.
|
||||
#[derive(Clone, CloneRef, Debug, Default, Eq, Hash, PartialEq)]
|
||||
pub struct GroupQualifiedName {
|
||||
/// The fully qualified name of the library project.
|
||||
pub project: project::QualifiedName,
|
||||
/// The group name without the library project name prefix. E.g. given the `Standard.Base.Group
|
||||
/// 1` group reference, the `name` field contains `Group 1`.
|
||||
pub name: ImString,
|
||||
}
|
||||
|
||||
impl GroupQualifiedName {
|
||||
/// Contructor.
|
||||
pub fn new(project: project::QualifiedName, name: impl Into<ImString>) -> Self {
|
||||
Self { project, name: name.into() }
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for GroupQualifiedName {
|
||||
type Error = failure::Error;
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
if let Some((namespace, project, group)) = value.splitn(3, ACCESS).collect_tuple() {
|
||||
Ok(Self {
|
||||
project: project::QualifiedName::new(namespace, project),
|
||||
name: group.into(),
|
||||
})
|
||||
} else {
|
||||
Err(name::InvalidQualifiedName::TooFewSegments.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// === ComponentGroup ===
|
||||
|
||||
/// A named group of components which is defined in a library imported into an execution context.
|
||||
///
|
||||
/// Components are language elements displayed by the Component Browser. The Component Browser
|
||||
@ -389,11 +427,7 @@ pub struct AttachedVisualization {
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct ComponentGroup {
|
||||
/// The fully qualified name of the library project.
|
||||
pub project: project::QualifiedName,
|
||||
/// The group name without the library project name prefix. E.g. given the `Standard.Base.Group
|
||||
/// 1` group reference, the `name` field contains `Group 1`.
|
||||
pub name: ImString,
|
||||
pub name: GroupQualifiedName,
|
||||
/// An optional color to use when displaying the component group.
|
||||
pub color: Option<color::Rgb>,
|
||||
pub components: Vec<QualifiedName>,
|
||||
@ -404,12 +438,12 @@ impl ComponentGroup {
|
||||
pub fn from_language_server_protocol_struct(
|
||||
group: language_server::LibraryComponentGroup,
|
||||
) -> FallibleResult<Self> {
|
||||
let project = group.library.try_into()?;
|
||||
let name = group.name.into();
|
||||
let name =
|
||||
GroupQualifiedName { project: group.library.try_into()?, name: group.name.into() };
|
||||
let color = group.color.as_ref().and_then(|c| color::Rgb::from_css_hex(c));
|
||||
let components: FallibleResult<Vec<_>> =
|
||||
group.exports.into_iter().map(|e| e.name.try_into()).collect();
|
||||
Ok(ComponentGroup { project, name, color, components: components? })
|
||||
Ok(ComponentGroup { name, color, components: components? })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -395,6 +395,7 @@ pub mod test {
|
||||
use crate::executor::test_utils::TestWithLocalPoolExecutor;
|
||||
use crate::model::execution_context::plain::test::MockData;
|
||||
use crate::model::execution_context::ComponentGroup;
|
||||
use crate::model::execution_context::GroupQualifiedName;
|
||||
use crate::model::traits::*;
|
||||
|
||||
use double_representation::name::project;
|
||||
@ -704,7 +705,7 @@ pub mod test {
|
||||
|
||||
// Verify that the first component group was parsed and has expected contents.
|
||||
let first_group = &groups[0];
|
||||
assert_eq!(first_group.name, "Test Group 1".to_string());
|
||||
assert_eq!(first_group.name.name, "Test Group 1".to_string());
|
||||
let color = first_group.color.unwrap();
|
||||
assert_eq!((color.red * 255.0) as u8, 0xC0);
|
||||
assert_eq!((color.green * 255.0) as u8, 0x47);
|
||||
@ -717,8 +718,10 @@ pub mod test {
|
||||
|
||||
// Verify that the second component group was parsed and has expected contents.
|
||||
assert_eq!(groups[1], ComponentGroup {
|
||||
project: project::QualifiedName::standard_base_library(),
|
||||
name: "Input".into(),
|
||||
name: GroupQualifiedName::new(
|
||||
project::QualifiedName::standard_base_library(),
|
||||
"Input"
|
||||
),
|
||||
color: None,
|
||||
components: vec!["Standard.Base.System.File.new".try_into().unwrap(),],
|
||||
});
|
||||
|
@ -15,6 +15,7 @@ use double_representation::name::QualifiedNameRef;
|
||||
use engine_protocol::language_server;
|
||||
use engine_protocol::language_server::FieldUpdate;
|
||||
use engine_protocol::language_server::SuggestionsDatabaseModification;
|
||||
use enso_doc_parser::doc_sections::HtmlString;
|
||||
use enso_doc_parser::DocSection;
|
||||
use enso_doc_parser::Tag;
|
||||
use enso_text::Location;
|
||||
@ -237,6 +238,8 @@ pub struct Entry {
|
||||
pub scope: Scope,
|
||||
/// A name of a custom icon to use when displaying the entry.
|
||||
pub icon_name: Option<IconName>,
|
||||
/// A name of a group this entry belongs to.
|
||||
pub group_name: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
@ -265,6 +268,7 @@ impl Entry {
|
||||
self_type: None,
|
||||
scope: Scope::Everywhere,
|
||||
icon_name: None,
|
||||
group_name: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -395,6 +399,12 @@ impl Entry {
|
||||
self.icon_name = Some(icon_name);
|
||||
self
|
||||
}
|
||||
|
||||
/// Takes self and returns it with new `group_name` value.
|
||||
pub fn in_group(mut self, group_name: impl Into<String>) -> Self {
|
||||
self.group_name = Some(group_name.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -633,6 +643,7 @@ impl Entry {
|
||||
};
|
||||
let doc_sections = enso_doc_parser::parse(documentation);
|
||||
let icon_name = find_icon_name_in_doc_sections(&doc_sections);
|
||||
let group_name = find_group_name_in_doc_sections(&doc_sections);
|
||||
let reexported_in: Option<QualifiedName> = match &mut entry {
|
||||
Type { reexport: Some(reexport), .. }
|
||||
| Constructor { reexport: Some(reexport), .. }
|
||||
@ -672,6 +683,7 @@ impl Entry {
|
||||
};
|
||||
this.documentation = doc_sections;
|
||||
this.icon_name = icon_name;
|
||||
this.group_name = group_name;
|
||||
this.reexported_in = reexported_in;
|
||||
this
|
||||
}
|
||||
@ -964,9 +976,19 @@ where
|
||||
// === Entry helpers ===
|
||||
|
||||
fn find_icon_name_in_doc_sections<'a, I>(doc_sections: I) -> Option<IconName>
|
||||
where I: IntoIterator<Item = &'a DocSection> {
|
||||
find_tag_in_doc_sections(Tag::Icon, doc_sections).map(|body| IconName::from_tag_body(body))
|
||||
}
|
||||
|
||||
fn find_group_name_in_doc_sections<'a, I>(doc_sections: I) -> Option<String>
|
||||
where I: IntoIterator<Item = &'a DocSection> {
|
||||
find_tag_in_doc_sections(Tag::Group, doc_sections).cloned()
|
||||
}
|
||||
|
||||
fn find_tag_in_doc_sections<'a, I>(tag: Tag, doc_sections: I) -> Option<&'a HtmlString>
|
||||
where I: IntoIterator<Item = &'a DocSection> {
|
||||
doc_sections.into_iter().find_map(|section| match section {
|
||||
DocSection::Tag { tag: Tag::Icon, body } => Some(IconName::from_tag_body(body)),
|
||||
DocSection::Tag { tag: current_tag, body } if *current_tag == tag => Some(body),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
@ -10,16 +10,8 @@ maintainers:
|
||||
email: contact@enso.org
|
||||
component-groups:
|
||||
new:
|
||||
- Input:
|
||||
exports:
|
||||
- Standard.Base.System.File.File.new
|
||||
- Standard.Base.Data.read
|
||||
- Standard.Base.Data.read_text
|
||||
- Standard.Base.Data.list_directory
|
||||
- Web:
|
||||
exports:
|
||||
- Standard.Base.Network.HTTP.HTTP.new
|
||||
- Standard.Base.Data.fetch
|
||||
- Input: {}
|
||||
- Web: {}
|
||||
- Parse:
|
||||
exports:
|
||||
- Standard.Base.Data.Json.Json.parse
|
||||
|
@ -20,6 +20,7 @@ from project.Data.Boolean import Boolean, False, True
|
||||
from project.System.File_Format import Auto_Detect, File_Format
|
||||
|
||||
## ALIAS Load, Open
|
||||
GROUP Input
|
||||
Reads a file into Enso.
|
||||
Uses the specified file format to parse the file into an Enso type. If not
|
||||
specified will use the file's extension to determine the file format.
|
||||
@ -60,6 +61,7 @@ read path format=Auto_Detect (on_problems=Problem_Behavior.Report_Warning) =
|
||||
File.new path . read format on_problems
|
||||
|
||||
## ALIAS Load Text, Open Text
|
||||
GROUP Input
|
||||
Open and read the file at the provided `path`.
|
||||
|
||||
Arguments:
|
||||
@ -85,7 +87,8 @@ read_text : (Text | File) -> Encoding -> Problem_Behavior -> Text
|
||||
read_text path (encoding=Encoding.utf_8) (on_problems=Problem_Behavior.Report_Warning) =
|
||||
File.new path . read_text encoding on_problems
|
||||
|
||||
## Lists files contained in the provided directory.
|
||||
## GROUP Input
|
||||
Lists files contained in the provided directory.
|
||||
|
||||
Arguments:
|
||||
- name_filter: A glob pattern that can be used to filter the returned files.
|
||||
@ -140,6 +143,7 @@ list_directory directory name_filter=Nothing recursive=False =
|
||||
File.new directory . list name_filter=name_filter recursive=recursive
|
||||
|
||||
## ALIAS Download, HTTP Get
|
||||
GROUP Web
|
||||
Fetches from the provided URI and returns the response, parsing the body if
|
||||
the content-type is recognised. Returns an error if the status code does not
|
||||
represent a successful response.
|
||||
|
@ -28,6 +28,7 @@ polyglot java import org.enso.base.Http_Utils
|
||||
|
||||
type HTTP
|
||||
## ADVANCED
|
||||
GROUP Web
|
||||
Create a new instance of the HTTP client.
|
||||
|
||||
Arguments:
|
||||
|
@ -46,6 +46,7 @@ polyglot java import org.enso.base.Encoding_Utils
|
||||
@Builtin_Type
|
||||
type File
|
||||
## ALIAS New File
|
||||
GROUP Input
|
||||
|
||||
Creates a new file object, pointing to the given path.
|
||||
|
||||
|
@ -10,12 +10,6 @@ maintainers:
|
||||
email: contact@enso.org
|
||||
component-groups:
|
||||
extends:
|
||||
- Standard.Base.Input:
|
||||
exports:
|
||||
- Standard.Table.Data.Table.Table.new
|
||||
- Standard.Table.Data.Table.Table.from_rows
|
||||
- Standard.Table.Data.Table.Table.from_objects
|
||||
- Standard.Table.Data.Column.Column.from_vector
|
||||
- Standard.Base.Select:
|
||||
exports:
|
||||
- Standard.Table.Data.Table.Table.at
|
||||
|
@ -32,7 +32,8 @@ polyglot java import org.enso.table.data.table.Column as Java_Column
|
||||
polyglot java import org.enso.table.operations.OrderBuilder
|
||||
|
||||
type Column
|
||||
## Creates a new column given a name and a vector of elements.
|
||||
## GROUP Standard.Base.Input
|
||||
Creates a new column given a name and a vector of elements.
|
||||
|
||||
Arguments:
|
||||
- name: The name of the column to create.
|
||||
|
@ -61,7 +61,8 @@ polyglot java import org.enso.table.operations.OrderBuilder
|
||||
|
||||
## Represents a column-oriented table data structure.
|
||||
type Table
|
||||
## Creates a new table from a vector of `[name, items]` pairs.
|
||||
## GROUP Standard.Base.Input
|
||||
Creates a new table from a vector of `[name, items]` pairs.
|
||||
|
||||
Arguments:
|
||||
- columns: The `[name, items]` pairs to construct a new table from.
|
||||
@ -91,7 +92,8 @@ type Table
|
||||
if cols.distinct .getName . length != cols.length then Error.throw (Illegal_Argument.Error "Column names must be distinct.") else
|
||||
Table.Value (Java_Table.new cols)
|
||||
|
||||
## Creates a new table from a vector of column names and a vector of vectors
|
||||
## GROUP Standard.Base.Input
|
||||
Creates a new table from a vector of column names and a vector of vectors
|
||||
specifying row contents.
|
||||
|
||||
Arguments:
|
||||
|
@ -63,6 +63,7 @@ pub enum Tag {
|
||||
Alias,
|
||||
Deprecated,
|
||||
Icon,
|
||||
Group,
|
||||
Modified,
|
||||
Private,
|
||||
Removed,
|
||||
@ -92,6 +93,7 @@ impl Tag {
|
||||
"ALIAS" => Some(Alias),
|
||||
"DEPRECATED" => Some(Deprecated),
|
||||
"ICON" => Some(Icon),
|
||||
"GROUP" => Some(Group),
|
||||
"MODIFIED" => Some(Modified),
|
||||
"PRIVATE" => Some(Private),
|
||||
"REMOVED" => Some(Removed),
|
||||
@ -110,6 +112,7 @@ impl Tag {
|
||||
Tag::Alias => "ALIAS",
|
||||
Tag::Deprecated => "DEPRECATED",
|
||||
Tag::Icon => "ICON",
|
||||
Tag::Group => "GROUP",
|
||||
Tag::Modified => "MODIFIED",
|
||||
Tag::Private => "PRIVATE",
|
||||
Tag::Removed => "REMOVED",
|
||||
|
@ -9,6 +9,7 @@ use crate::impls;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::borrow::Borrow;
|
||||
use std::borrow::Cow;
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
@ -215,6 +216,18 @@ impl AsRef<str> for ImString {
|
||||
}
|
||||
}
|
||||
|
||||
impl Borrow<str> for ImString {
|
||||
fn borrow(&self) -> &str {
|
||||
&self.content
|
||||
}
|
||||
}
|
||||
|
||||
impl Borrow<String> for ImString {
|
||||
fn borrow(&self) -> &String {
|
||||
&self.content
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for ImString {
|
||||
fn from(t: String) -> Self {
|
||||
Self::new(t)
|
||||
|
Loading…
Reference in New Issue
Block a user