diff --git a/crates/aiken-project/Cargo.toml b/crates/aiken-project/Cargo.toml index 257bb6a7..5515fc16 100644 --- a/crates/aiken-project/Cargo.toml +++ b/crates/aiken-project/Cargo.toml @@ -16,7 +16,7 @@ build = "build.rs" [dependencies] aiken-lang = { path = "../aiken-lang", version = "1.0.31-alpha" } -askama = "0.12.0" +askama = { version = "0.12.0", features = ["urlencode"] } ciborium = "0.2.2" cryptoxide = "0.4.4" dirs = "4.0.0" diff --git a/crates/aiken-project/src/docs.rs b/crates/aiken-project/src/docs.rs index 2e523397..7f41249f 100644 --- a/crates/aiken-project/src/docs.rs +++ b/crates/aiken-project/src/docs.rs @@ -4,10 +4,11 @@ use crate::{ }; use aiken_lang::{ ast::{ - DataType, Definition, Function, ModuleConstant, RecordConstructor, TypeAlias, + DataType, Definition, Function, ModuleConstant, RecordConstructor, Span, TypeAlias, TypedDefinition, }, format, + parser::extra::Comment, tipo::Type, }; use askama::Template; @@ -42,7 +43,7 @@ struct ModuleTemplate<'a> { project_name: &'a str, project_version: &'a str, modules: &'a [DocLink], - functions: Vec, + functions: Vec, types: Vec, constants: Vec, documentation: String, @@ -154,16 +155,62 @@ fn generate_module( ) -> (Vec, DocFile) { let mut search_indexes = vec![]; + // Section headers + let mut section_headers = module + .extra + .comments + .iter() + .filter_map(|span| { + let comment = Comment::from((span, module.code.as_str())) + .content + .trim_start(); + if comment.starts_with("#") { + let trimmed = comment.trim_start_matches("#"); + let heading = comment.len() - trimmed.len(); + Some(( + span, + DocSection { + heading, + title: trimmed.trim_start().to_string(), + }, + )) + } else { + None + } + }) + .collect_vec(); + // Functions - let functions: Vec = module + let functions: Vec<(Span, DocFunction)> = module .ast .definitions .iter() .flat_map(DocFunction::from_definition) .collect(); - functions - .iter() - .for_each(|function| search_indexes.push(SearchIndex::from_function(module, function))); + + functions.iter().for_each(|(_, function)| { + search_indexes.push(SearchIndex::from_function(module, function)) + }); + + let no_functions = functions.is_empty(); + + let mut functions_and_headers = Vec::new(); + + for (span_fn, function) in functions { + let mut to_remove = vec![]; + for (ix, (span_h, header)) in section_headers.iter().enumerate() { + if span_h.start < span_fn.start { + functions_and_headers.push(Interspersed::Section(header.clone())); + to_remove.push(ix); + } + } + + for ix in to_remove.iter().rev() { + section_headers.remove(*ix); + } + + functions_and_headers.push(Interspersed::Function(function)) + } // Types let types: Vec = module @@ -187,7 +234,7 @@ fn generate_module( .iter() .for_each(|constant| search_indexes.push(SearchIndex::from_constant(module, constant))); - let is_empty = functions.is_empty() && types.is_empty() && constants.is_empty(); + let is_empty = no_functions && types.is_empty() && constants.is_empty(); // Module if !is_empty { @@ -203,7 +250,7 @@ fn generate_module( page_title: &format!("{} - {}", module.name, config.name), module_name: module.name.clone(), project_version: &config.version.to_string(), - functions, + functions: functions_and_headers, types, constants, source, @@ -391,7 +438,19 @@ impl SearchIndex { } } -#[derive(PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +enum Interspersed { + Section(DocSection), + Function(DocFunction), +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] +struct DocSection { + heading: usize, + title: String, +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] struct DocFunction { name: String, signature: String, @@ -401,26 +460,29 @@ struct DocFunction { } impl DocFunction { - fn from_definition(def: &TypedDefinition) -> Option { + fn from_definition(def: &TypedDefinition) -> Option<(Span, Self)> { match def { - Definition::Fn(func_def) if func_def.public => Some(DocFunction { - name: func_def.name.clone(), - documentation: func_def - .doc - .as_deref() - .map(render_markdown) - .unwrap_or_default(), - raw_documentation: func_def.doc.as_deref().unwrap_or_default().to_string(), - signature: format::Formatter::new() - .docs_fn_signature( - &func_def.name, - &func_def.arguments, - &func_def.return_annotation, - func_def.return_type.clone(), - ) - .to_pretty_string(MAX_COLUMNS), - source_url: "#todo".to_string(), - }), + Definition::Fn(func_def) if func_def.public => Some(( + func_def.location, + DocFunction { + name: func_def.name.clone(), + documentation: func_def + .doc + .as_deref() + .map(render_markdown) + .unwrap_or_default(), + raw_documentation: func_def.doc.as_deref().unwrap_or_default().to_string(), + signature: format::Formatter::new() + .docs_fn_signature( + &func_def.name, + &func_def.arguments, + &func_def.return_annotation, + func_def.return_type.clone(), + ) + .to_pretty_string(MAX_COLUMNS), + source_url: "#todo".to_string(), + }, + )), _ => None, } } diff --git a/crates/aiken-project/templates/css/index.css b/crates/aiken-project/templates/css/index.css index 14126e18..eeb94a50 100644 --- a/crates/aiken-project/templates/css/index.css +++ b/crates/aiken-project/templates/css/index.css @@ -290,11 +290,13 @@ p code { /* Module doc */ .module-name > a, +.module-heading > a, .module-member-kind > a { color: inherit; } .module-name > a:hover, +.module-heading > a:hover, .module-member-kind > a:hover { text-decoration: none; } @@ -328,6 +330,7 @@ p code { .sidebar h2 { margin: 0; color: var(--color-link-accent); + font-size: 1.75em; } .sidebar h3.modules-prefix { @@ -407,6 +410,10 @@ body.drawer-open .label-closed { .module-member-kind { font-size: 2rem; +} + +.module-heading, +.module-member-kind { color: var(--color-text); } @@ -1113,3 +1120,29 @@ body.theme-dark { .sidebar li[data-indent="1"]::before { padding-left: 1rem; } .sidebar li[data-indent="2"]::before { padding-left: 2rem; } .sidebar li[data-indent="3"]::before { padding-left: 3rem; } + +.sidebar li[data-heading] { + font-weight: bold; + font-size: 1em; + color: var(--color-link-accent); +} + +.sidebar li[data-heading="1"] { + margin-top: 1.5rem; + font-size: 1.4em; +} + +.sidebar li[data-heading="2"] { + margin-top: 1rem; + font-size: 1.2em; +} + +.sidebar li[data-heading="3"] { + margin-top: 0.5rem; + font-size: 1.1em; +} + +.sidebar li[data-heading]:first-child, +.sidebar li[data-heading] + li[data-heading] { + margin-top: 0; +} diff --git a/crates/aiken-project/templates/module.html b/crates/aiken-project/templates/module.html index 8a695650..17c8f0b2 100644 --- a/crates/aiken-project/templates/module.html +++ b/crates/aiken-project/templates/module.html @@ -22,8 +22,14 @@ {% if !functions.is_empty() %}

Functions

    - {% for function in functions %} -
  • {{ function.name }}
  • + {% for function_or_section in functions %} + {% match function_or_section %} + {% when Interspersed::Function with (function) %} +
  • {{ function.name }}
  • + + {% when Interspersed::Section with (section) %} +
  • {{ section.title }}
  • + {% endmatch %} {% endfor %}
{% endif %} @@ -118,20 +124,33 @@

Functions

- {% for function in functions %} -
-
-

{{ function.signature }}

- {% if !function.source_url.is_empty() %} - + {% for function_or_section in functions %} + {% match function_or_section %} + {% when Interspersed::Function with (function) %} +
+
+

{{ function.signature }}

+ {% if !function.source_url.is_empty() %} + + {% endif %} +
+
{{ function.documentation|safe }}
+
+ {% when Interspersed::Section with (section) %} + {% if section.heading == 1 %} +

{{ section.title }}

+ {% else if section.heading == 2 %} +

{{ section.title }}

+ {% else if section.heading == 3 %} +

{{ section.title }}

+ {% else %} +

{{ section.title }}

{% endif %} -
-
{{ function.documentation|safe }}
-
+ {% endmatch %} {% endfor %} {% endif %}