diff --git a/crates/treehouse/src/cli/regenerate.rs b/crates/treehouse/src/cli/regenerate.rs
index 5711d59..8ae097d 100644
--- a/crates/treehouse/src/cli/regenerate.rs
+++ b/crates/treehouse/src/cli/regenerate.rs
@@ -17,7 +17,9 @@ use tower_http::services::ServeDir;
use tower_livereload::LiveReloadLayer;
use walkdir::WalkDir;
-use crate::{cli::parse::parse_tree_with_diagnostics, html::tree::branches_to_html};
+use crate::{
+ cli::parse::parse_tree_with_diagnostics, html::tree::branches_to_html, tree::SemaRoots,
+};
use crate::state::{FileId, Treehouse};
@@ -132,6 +134,8 @@ impl Generator {
);
if let Ok(roots) = parse_tree_with_diagnostics(&mut treehouse, file_id) {
+ let roots = SemaRoots::from_roots(&mut treehouse, file_id, roots);
+
let mut tree = String::new();
branches_to_html(&mut tree, &mut treehouse, file_id, &roots.branches);
diff --git a/crates/treehouse/src/html/mod.rs b/crates/treehouse/src/html/mod.rs
index eec01a6..f9cd40e 100644
--- a/crates/treehouse/src/html/mod.rs
+++ b/crates/treehouse/src/html/mod.rs
@@ -1,6 +1,5 @@
use std::fmt::{self, Display, Write};
-pub mod attributes;
mod markdown;
pub mod tree;
diff --git a/crates/treehouse/src/html/tree.rs b/crates/treehouse/src/html/tree.rs
index 95a8835..4892cfc 100644
--- a/crates/treehouse/src/html/tree.rs
+++ b/crates/treehouse/src/html/tree.rs
@@ -1,43 +1,37 @@
use std::fmt::Write;
-use codespan_reporting::diagnostic::{Diagnostic, Label, LabelStyle, Severity};
-use treehouse_format::{ast::Branch, pull::BranchKind};
+use pulldown_cmark::{BrokenLink, LinkType};
+use treehouse_format::pull::BranchKind;
use crate::{
html::EscapeAttribute,
- state::{toml_error_to_diagnostic, FileId, TomlError, Treehouse},
+ state::{FileId, Treehouse},
+ tree::{attributes::Content, SemaBranchId},
};
-use super::{
- attributes::{Attributes, Content},
- markdown, EscapeHtml,
-};
-
-pub fn branch_to_html(s: &mut String, treehouse: &mut Treehouse, file_id: FileId, branch: &Branch) {
- let attributes = parse_attributes(treehouse, file_id, branch);
+use super::{markdown, EscapeHtml};
+pub fn branch_to_html(
+ s: &mut String,
+ treehouse: &mut Treehouse,
+ file_id: FileId,
+ branch_id: SemaBranchId,
+) {
// Reborrow because the closure requires unique access (it adds a new diagnostic.)
let source = treehouse.source(file_id);
+ let branch = treehouse.tree.branch(branch_id);
let has_children =
- !branch.children.is_empty() || matches!(attributes.content, Content::Link(_));
-
- let id = format!(
- "{}:{}",
- treehouse
- .tree_path(file_id)
- .expect("file should have a tree path"),
- attributes.id
- );
+ !branch.children.is_empty() || matches!(branch.attributes.content, Content::Link(_));
let class = if has_children { "branch" } else { "leaf" };
- let component = if let Content::Link(_) = attributes.content {
+ let component = if let Content::Link(_) = branch.attributes.content {
"th-b-linked"
} else {
"th-b"
};
- let linked_branch = if let Content::Link(link) = &attributes.content {
+ let linked_branch = if let Content::Link(link) = &branch.attributes.content {
format!(" data-th-link=\"{}\"", EscapeHtml(link))
} else {
String::new()
@@ -46,7 +40,7 @@ pub fn branch_to_html(s: &mut String, treehouse: &mut Treehouse, file_id: FileId
write!(
s,
"
",
- EscapeAttribute(&id)
+ EscapeAttribute(&branch.html_id)
)
.unwrap();
{
@@ -77,13 +71,38 @@ pub fn branch_to_html(s: &mut String, treehouse: &mut Treehouse, file_id: FileId
unindented_block_content.push('\n');
}
- let markdown_parser = pulldown_cmark::Parser::new_ext(&unindented_block_content, {
- use pulldown_cmark::Options;
- Options::ENABLE_STRIKETHROUGH | Options::ENABLE_TABLES
- });
+ let broken_link_callback = &mut |broken_link: BrokenLink<'_>| {
+ if let LinkType::Reference | LinkType::Shortcut = broken_link.link_type {
+ broken_link
+ .reference
+ .split_once(':')
+ .and_then(|(kind, linked)| match kind {
+ "branch" => treehouse
+ .branches_by_named_id
+ .get(linked)
+ .map(|&branch_id| {
+ (
+ format!("#{}", treehouse.tree.branch(branch_id).html_id).into(),
+ "".into(),
+ )
+ }),
+ _ => None,
+ })
+ } else {
+ None
+ }
+ };
+ let markdown_parser = pulldown_cmark::Parser::new_with_broken_link_callback(
+ &unindented_block_content,
+ {
+ use pulldown_cmark::Options;
+ Options::ENABLE_STRIKETHROUGH | Options::ENABLE_TABLES
+ },
+ Some(broken_link_callback),
+ );
markdown::push_html(s, markdown_parser);
- if let Content::Link(link) = &attributes.content {
+ if let Content::Link(link) = &branch.attributes.content {
write!(
s,
"",
@@ -95,7 +114,7 @@ pub fn branch_to_html(s: &mut String, treehouse: &mut Treehouse, file_id: FileId
s.push_str("");
{
- if let Content::Link(link) = &attributes.content {
+ if let Content::Link(link) = &branch.attributes.content {
write!(
s,
"",
@@ -107,7 +126,7 @@ pub fn branch_to_html(s: &mut String, treehouse: &mut Treehouse, file_id: FileId
write!(
s,
"",
- EscapeAttribute(&id)
+ EscapeAttribute(&branch.html_id)
)
.unwrap();
}
@@ -115,7 +134,15 @@ pub fn branch_to_html(s: &mut String, treehouse: &mut Treehouse, file_id: FileId
if has_children {
s.push_str("");
- branches_to_html(s, treehouse, file_id, &branch.children);
+ {
+ s.push_str("");
+ let num_children = branch.children.len();
+ for i in 0..num_children {
+ let child_id = treehouse.tree.branch(branch_id).children[i];
+ branch_to_html(s, treehouse, file_id, child_id);
+ }
+ s.push_str("
");
+ }
s.push_str("");
} else {
s.push_str("");
@@ -124,90 +151,14 @@ pub fn branch_to_html(s: &mut String, treehouse: &mut Treehouse, file_id: FileId
s.push_str("");
}
-fn parse_attributes(treehouse: &mut Treehouse, file_id: usize, branch: &Branch) -> Attributes {
- let source = treehouse.source(file_id);
-
- let mut successfully_parsed = true;
- let mut attributes = if let Some(attributes) = &branch.attributes {
- toml_edit::de::from_str(&source[attributes.data.clone()]).unwrap_or_else(|error| {
- treehouse
- .diagnostics
- .push(toml_error_to_diagnostic(TomlError {
- message: error.message().to_owned(),
- span: error.span(),
- file_id,
- input_range: attributes.data.clone(),
- }));
- successfully_parsed = false;
- Attributes::default()
- })
- } else {
- Attributes::default()
- };
- let successfully_parsed = successfully_parsed;
-
- // Only check for attribute validity if the attributes were parsed successfully.
- if successfully_parsed {
- let attribute_warning_span = branch
- .attributes
- .as_ref()
- .map(|attributes| attributes.percent.clone())
- .unwrap_or(branch.kind_span.clone());
-
- // Check that every block has an ID.
- if attributes.id.is_empty() {
- attributes.id = format!("treehouse-missingno-{}", treehouse.next_missingno());
- treehouse.diagnostics.push(Diagnostic {
- severity: Severity::Warning,
- code: Some("attr".into()),
- message: "branch does not have an `id` attribute".into(),
- labels: vec![Label {
- style: LabelStyle::Primary,
- file_id,
- range: attribute_warning_span.clone(),
- message: String::new(),
- }],
- notes: vec![
- format!(
- "note: a generated id `{}` will be used, but this id is unstable and will not persist across generations",
- attributes.id
- ),
- format!("help: run `treehouse fix {}` to add missing ids to branches", treehouse.filename(file_id)),
- ],
- });
- }
-
- // Check that link-type blocks are `+`-type to facilitate lazy loading.
- if let Content::Link(_) = &attributes.content {
- if branch.kind == BranchKind::Expanded {
- treehouse.diagnostics.push(Diagnostic {
- severity: Severity::Warning,
- code: Some("attr".into()),
- message: "`content.link` branch is expanded by default".into(),
- labels: vec![Label {
- style: LabelStyle::Primary,
- file_id,
- range: branch.kind_span.clone(),
- message: String::new(),
- }],
- notes: vec![
- "note: `content.link` branches should normally be collapsed to allow for lazy loading".into(),
- ],
- });
- }
- }
- }
- attributes
-}
-
pub fn branches_to_html(
s: &mut String,
treehouse: &mut Treehouse,
file_id: FileId,
- branches: &[Branch],
+ branches: &[SemaBranchId],
) {
s.push_str("");
- for child in branches {
+ for &child in branches {
branch_to_html(s, treehouse, file_id, child);
}
s.push_str("
");
diff --git a/crates/treehouse/src/main.rs b/crates/treehouse/src/main.rs
index 324758a..c74243e 100644
--- a/crates/treehouse/src/main.rs
+++ b/crates/treehouse/src/main.rs
@@ -12,6 +12,7 @@ mod cli;
mod html;
mod paths;
mod state;
+mod tree;
async fn fallible_main() -> anyhow::Result<()> {
let args = ProgramArgs::parse();
diff --git a/crates/treehouse/src/state.rs b/crates/treehouse/src/state.rs
index f739862..fb9bc36 100644
--- a/crates/treehouse/src/state.rs
+++ b/crates/treehouse/src/state.rs
@@ -1,4 +1,4 @@
-use std::ops::Range;
+use std::{collections::HashMap, ops::Range};
use anyhow::Context;
use codespan_reporting::{
@@ -8,6 +8,8 @@ use codespan_reporting::{
};
use ulid::Ulid;
+use crate::tree::{SemaBranchId, SemaTree};
+
pub type Files = SimpleFiles;
pub type FileId = >::FileId;
@@ -16,18 +18,31 @@ pub struct Treehouse {
pub files: Files,
pub diagnostics: Vec>,
+ pub tree: SemaTree,
+ pub branches_by_named_id: HashMap,
+
// Bit of a hack because I don't wanna write my own `Files`.
tree_paths: Vec