Skip to content

Commit

Permalink
move method icon definition to documentation tag (#7123)
Browse files Browse the repository at this point in the history
  • Loading branch information
Frizi authored Jun 29, 2023
1 parent 26bd95c commit cb9d4c4
Show file tree
Hide file tree
Showing 14 changed files with 95 additions and 82 deletions.
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 0 additions & 6 deletions app/gui/language/ast/impl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,6 @@ pub mod constants {
/// The "void" atom returned by function meant to not return any argument.
pub const NOTHING: &str = "Nothing";
}

/// The tag name of documentation sections marked as "PRIVATE"
pub const PRIVATE_DOC_SECTION_TAG_NAME: &str = "PRIVATE";

/// The tag name of a documentation section with a method alias.
pub const ALIAS_DOC_SECTION_TAG_NAME: &str = "ALIAS";
}

pub use crumbs::Crumb;
Expand Down
13 changes: 6 additions & 7 deletions app/gui/src/controller/searcher/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use convert_case::Case;
use convert_case::Casing;
use double_representation::name::QualifiedName;
use enso_doc_parser::DocSection;
use enso_doc_parser::Tag;
use ordered_float::OrderedFloat;


Expand Down Expand Up @@ -237,11 +238,10 @@ impl Component {
/// Check whether the component contains the "PRIVATE" tag.
pub fn is_private(&self) -> bool {
match &self.data {
Data::FromDatabase { entry, .. } => entry.documentation.iter().any(|doc| match doc {
DocSection::Tag { name, .. } =>
name == &ast::constants::PRIVATE_DOC_SECTION_TAG_NAME,
_ => false,
}),
Data::FromDatabase { entry, .. } => entry
.documentation
.iter()
.any(|doc| matches!(doc, DocSection::Tag { tag: Tag::Private, .. })),
_ => false,
}
}
Expand All @@ -252,8 +252,7 @@ impl Component {
let aliases = match &self.data {
Data::FromDatabase { entry, .. } => {
let aliases = entry.documentation.iter().filter_map(|doc| match doc {
DocSection::Tag { name, body }
if name == &ast::constants::ALIAS_DOC_SECTION_TAG_NAME =>
DocSection::Tag { tag: Tag::Alias, body } =>
Some(body.as_str().split(',').map(|s| s.trim())),
_ => None,
});
Expand Down
3 changes: 1 addition & 2 deletions app/gui/src/controller/searcher/component/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -627,8 +627,7 @@ mod tests {
/// excluded from the list.
#[test]
fn building_component_list_with_private_component() {
use ast::constants::PRIVATE_DOC_SECTION_TAG_NAME as PRIVATE_TAG;
let private_doc_section = enso_suggestion_database::doc_section!(@ PRIVATE_TAG, "");
let private_doc_section = enso_suggestion_database::doc_section!(@ Private, "");
let suggestion_db = enso_suggestion_database::mock_suggestion_database! {
test.Test {
mod LocalModule {
Expand Down
14 changes: 7 additions & 7 deletions app/gui/suggestion-database/src/documentation_ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -723,7 +723,7 @@ impl FilteredDocSections {
let mut examples = Vec::new();
for section in doc_sections {
match section {
DocSection::Tag { name, body } => tags.push(Tag::new(name, body)),
DocSection::Tag { tag, body } => tags.push(Tag::new(tag.to_str(), body)),
DocSection::Marked { mark: Mark::Example, .. } => examples.push(section.clone()),
section => synopsis.push(section.clone()),
}
Expand Down Expand Up @@ -838,21 +838,21 @@ mod tests {
Bar (b);

#[with_doc_section(doc_section!("Documentation of method A.baz."))]
#[with_doc_section(doc_section!(@ "Tag", "Tag body."))]
#[with_doc_section(doc_section!(@ Deprecated, "Tag body."))]
fn baz() -> Standard.Base.B;
}

#[with_doc_section(doc_section!("Documentation of type B."))]
type B {
#[with_doc_section(doc_section!("Documentation of constructor B.New."))]
#[with_doc_section(doc_section!(@ "AnotherTag", "Tag body."))]
#[with_doc_section(doc_section!(@ Alias, "Tag body."))]
#[with_doc_section(doc_section!(! "Important", "Important note."))]
#[with_doc_section(doc_section!(> "Example", "Example of constructor B.New usage."))]
New;
}

#[with_doc_section(doc_section!("Documentation of module method."))]
#[with_doc_section(doc_section!(@ "Deprecated", ""))]
#[with_doc_section(doc_section!(@ Deprecated, ""))]
#[with_doc_section(doc_section!(> "Example", "Example of module method usage."))]
fn module_method() -> Standard.Base.A;
}
Expand All @@ -875,7 +875,7 @@ mod tests {
name: QualifiedName::from_text("Standard.Base.module_method").unwrap().into(),
tags: Tags {
list: SortedVec::new([Tag {
name: "Deprecated".to_im_string(),
name: "DEPRECATED".to_im_string(),
body: "".to_im_string(),
}]),
},
Expand Down Expand Up @@ -936,7 +936,7 @@ mod tests {
fn a_baz_method() -> Function {
Function {
name: QualifiedName::from_text("Standard.Base.A.baz").unwrap().into(),
tags: Tags { list: vec![Tag::new("Tag", "Tag body.")].into() },
tags: Tags { list: vec![Tag::new("DEPRECATED", "Tag body.")].into() },
arguments: default(),
synopsis: Synopsis::from_doc_sections([doc_section!(
"Documentation of method A.baz."
Expand All @@ -960,7 +960,7 @@ mod tests {
fn b_new_constructor() -> Function {
Function {
name: QualifiedName::from_text("Standard.Base.B.New").unwrap().into(),
tags: Tags { list: vec![Tag::new("AnotherTag", "Tag body.")].into() },
tags: Tags { list: vec![Tag::new("ALIAS", "Tag body.")].into() },
arguments: default(),
synopsis: Synopsis::from_doc_sections([
doc_section!("Documentation of constructor B.New."),
Expand Down
51 changes: 14 additions & 37 deletions app/gui/suggestion-database/src/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use engine_protocol::language_server;
use engine_protocol::language_server::FieldUpdate;
use engine_protocol::language_server::SuggestionsDatabaseModification;
use enso_doc_parser::DocSection;
use enso_doc_parser::Tag;
use enso_text::Location;
use language_server::types::FieldAction;

Expand All @@ -30,16 +31,6 @@ pub use language_server::types::SuggestionsDatabaseUpdate as Update;



// =================
// === Constants ===
// =================

/// Key of the keyed [`language_server::types::DocSection`] containing a name of an icon in its
/// body.
const ICON_DOC_SECTION_KEY: &str = "Icon";



// ==============
// === Errors ===
// ==============
Expand Down Expand Up @@ -91,25 +82,23 @@ pub struct ModuleSpan {
// ================

/// Name of an icon. The name is composed of words with unspecified casing.
///
/// In order to make icon definitions more readable for non-programmer users, the builtin icon name
/// is allowed to be formatted in arbitrary casing. Either `SNAKE_case`,`camelCase`, `Pascal_Case`
/// etc. is allowed.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IconName {
/// Internally the name is kept in PascalCase to optimize converting into
/// [`component_group_view::icon::Id`].
/// The name is kept in `PascalCase` to allow easy conversion into builtin icon ID.
pascal_cased: ImString,
}

impl IconName {
/// Construct from a name formatted in snake_case.
pub fn from_snake_case(s: impl AsRef<str>) -> Self {
let pascal_cased = s.as_ref().from_case(Case::Snake).to_case(Case::Pascal).into();
/// Parse arbitrary tag body with any casing into an `PascalCase` icon name.
pub fn from_tag_body(s: &str) -> Self {
let pascal_cased = s.to_case(Case::Pascal).into();
Self { pascal_cased }
}

/// Convert to a name formatted in snake_case.
pub fn to_snake_case(&self) -> ImString {
self.pascal_cased.from_case(Case::Pascal).to_case(Case::Snake).into()
}

/// Convert to a name formatted in PascalCase.
pub fn to_pascal_case(&self) -> ImString {
self.pascal_cased.clone()
Expand Down Expand Up @@ -977,19 +966,7 @@ where
fn find_icon_name_in_doc_sections<'a, I>(doc_sections: I) -> Option<IconName>
where I: IntoIterator<Item = &'a DocSection> {
doc_sections.into_iter().find_map(|section| match section {
DocSection::Keyed { key, body } if key == ICON_DOC_SECTION_KEY => {
let icon_name = IconName::from_snake_case(body);
let as_snake_case = icon_name.to_snake_case();
if as_snake_case.as_str() != body.as_str() || !body.is_case(Case::Snake) {
let msg = format!(
"The icon name {body} used in the {ICON_DOC_SECTION_KEY} section of the \
documentation of a component is not a valid, losslessly-convertible snake_case \
identifier. The component may be displayed with a different icon than expected."
);
warn!("{msg}");
}
Some(icon_name)
}
DocSection::Tag { tag: Tag::Icon, body } => Some(IconName::from_tag_body(body)),
_ => None,
})
}
Expand Down Expand Up @@ -1274,8 +1251,8 @@ mod test {
use enso_doc_parser::DocSection;
let doc_sections = [
DocSection::Paragraph { body: "Some paragraph.".into() },
DocSection::Keyed { key: "NotIcon".into(), body: "example_not_icon_body".into() },
DocSection::Keyed { key: "Icon".into(), body: "example_icon_name".into() },
DocSection::Tag { tag: Tag::Advanced, body: "example_not_icon_body".into() },
DocSection::Tag { tag: Tag::Icon, body: "ExampleIconName".into() },
DocSection::Paragraph { body: "Another paragraph.".into() },
];
let icon_name = find_icon_name_in_doc_sections(&doc_sections).unwrap();
Expand All @@ -1286,8 +1263,8 @@ mod test {
/// converting [`IconName`] values to PascalCase.
#[test]
fn icon_name_case_insensitiveness() {
let name_from_small_snake_case = IconName::from_snake_case("an_example_name");
let name_from_mixed_snake_case = IconName::from_snake_case("aN_EXAMPLE_name");
let name_from_small_snake_case = IconName::from_tag_body("an_example_name");
let name_from_mixed_snake_case = IconName::from_tag_body("An_EXAMPLE_name");
const PASCAL_CASE_NAME: &str = "AnExampleName";
assert_eq!(name_from_small_snake_case, name_from_mixed_snake_case);
assert_eq!(name_from_small_snake_case.to_pascal_case(), PASCAL_CASE_NAME);
Expand Down
16 changes: 9 additions & 7 deletions app/gui/suggestion-database/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,7 @@ pub const DEFAULT_TYPE: &str = "Standard.Base.Any";
///
/// let mut builder = Builder::new();
/// builder.add_and_enter_module("local.Project", |e| e);
/// builder
/// .add_and_enter_type("Type", vec![], |e| e.with_icon(IconName::from_snake_case("an_icon")));
/// builder.add_and_enter_type("Type", vec![], |e| e.with_icon(IconName::from_tag_body("an_icon")));
/// builder.add_constructor("Constructor", vec![], |e| e);
/// builder.leave();
/// builder.add_method("module_method", vec![], "local.Project.Type", true, |e| e);
Expand Down Expand Up @@ -320,7 +319,7 @@ macro_rules! mock_suggestion_database_entries {
/// static fn static_method(x) -> Standard.Base.Number;
/// }
///
/// #[with_icon(entry::IconName::from_snake_case("TestIcon"))]
/// #[with_icon(entry::IconName::from_tag_body("TestIcon"))]
/// static fn module_method() -> local.Project.Submodule.TestType;
/// }
/// }
Expand Down Expand Up @@ -405,7 +404,7 @@ macro_rules! doc_section_mark {
/// ### [`DocSection::Tag`]
/// ```
/// # use enso_suggestion_database::doc_section;
/// doc_section!(@ "Tag name", "Tag body.");
/// doc_section!(@ Deprecated, "Tag body.");
/// ```
///
/// ### [`DocSection::Keyed`]
Expand All @@ -426,8 +425,11 @@ macro_rules! doc_section_mark {
/// ```
#[macro_export]
macro_rules! doc_section {
(@ $tag:expr, $body:expr) => {
$crate::mock::enso_doc_parser::DocSection::Tag { name: $tag.into(), body: $body.into() }
(@ $tag:ident, $body:expr) => {
$crate::mock::enso_doc_parser::DocSection::Tag {
tag: $crate::mock::enso_doc_parser::Tag::$tag,
body: $body.into(),
}
};
($mark:tt $body:expr) => {
$crate::mock::enso_doc_parser::DocSection::Marked {
Expand Down Expand Up @@ -478,7 +480,7 @@ pub fn standard_db_mock() -> SuggestionDatabase {
static fn static_method(x) -> Standard.Base.Number;
}

#[with_icon(entry::IconName::from_snake_case("TestIcon"))]
#[with_icon(entry::IconName::from_tag_body("TestIcon"))]
static fn module_method() -> local.Project.Submodule.TestType;
}
}
Expand Down
7 changes: 4 additions & 3 deletions app/gui/view/examples/documentation/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use ensogl::prelude::*;

use enso_doc_parser::DocSection;
use enso_doc_parser::Mark;
use enso_doc_parser::Tag;
use enso_suggestion_database as suggestion_database;
use enso_suggestion_database::doc_section;
use enso_suggestion_database::documentation_ir::EntryDocumentation;
Expand Down Expand Up @@ -94,7 +95,7 @@ fn database() -> SuggestionDatabase {
#[with_doc_section(doc_section!(? "Info", "Info sections provide some insights."))]
Standard.Base {
#[with_doc_section(doc_section!("Maybe type."))]
#[with_doc_section(doc_section!(@ "Annotated", ""))]
#[with_doc_section(doc_section!(@ Advanced, ""))]
type Delimited_Format (a) {
#[with_doc_section(doc_section!("Some constructor."))]
#[with_doc_section(doc_section!(> "Example", "Some 1"))]
Expand Down Expand Up @@ -135,7 +136,7 @@ fn database() -> SuggestionDatabase {
builder.add_function("bar", args, "Standard.Base.Boolean", scope.clone(), |e| {
e.with_doc_sections(vec![
DocSection::Paragraph { body: "Documentation for the bar function.".into() },
DocSection::Tag { name: "DEPRECATED", body: default() },
DocSection::Tag { tag: Tag::Deprecated, body: default() },
DocSection::Marked {
mark: Mark::Example,
header: None,
Expand All @@ -147,7 +148,7 @@ fn database() -> SuggestionDatabase {
builder.add_local("local1", "Standard.Base.Boolean", scope, |e| {
e.with_doc_sections(vec![
DocSection::Paragraph { body: "Documentation for the local1 variable.".into() },
DocSection::Tag { name: "SOMETAG", body: default() },
DocSection::Tag { tag: Tag::Advanced, body: default() },
])
});

Expand Down
3 changes: 3 additions & 0 deletions app/gui/view/examples/icons/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ edition = "2021"
crate-type = ["cdylib", "rlib"]

[dependencies]
convert_case = { workspace = true }
ensogl = { path = "../../../../../lib/rust/ensogl" }
ensogl-hardcoded-theme = { path = "../../../../../lib/rust/ensogl/app/theme/hardcoded" }
ensogl-text-msdf = { path = "../../../../../lib/rust/ensogl/component/text/src/font/msdf" }
ensogl-tooltip = { path = "../../../../../lib/rust/ensogl/component/tooltip/" }
ide-view-component-list-panel-grid = { path = "../../component-browser/component-list-panel/grid" }
ide-view-component-list-panel-icons = { path = "../../component-browser/component-list-panel/icons" }
ide-view-graph-editor = { path = "../../graph-editor" }
Expand Down
Loading

0 comments on commit cb9d4c4

Please sign in to comment.