diff --git a/src/configuration/builder.rs b/src/configuration/builder.rs index 81b0275..3f15de3 100644 --- a/src/configuration/builder.rs +++ b/src/configuration/builder.rs @@ -79,6 +79,12 @@ impl ConfigurationBuilder { self.insert("unorderedListKind", value.to_string().into()) } + /// The type of heading to use. + /// Default: `HeadingKind::Atx` + pub fn heading_kind(&mut self, value: HeadingKind) -> &mut Self { + self.insert("headingKind", value.to_string().into()) + } + /// The directive used to ignore a line. /// Default: `dprint-ignore` pub fn ignore_directive(&mut self, value: &str) -> &mut Self { @@ -140,13 +146,14 @@ mod tests { .emphasis_kind(EmphasisKind::Asterisks) .strong_kind(StrongKind::Underscores) .unordered_list_kind(UnorderedListKind::Asterisks) + .heading_kind(HeadingKind::Atx) .ignore_directive("test") .ignore_file_directive("test") .ignore_start_directive("test") .ignore_end_directive("test"); let inner_config = config.get_inner_config(); - assert_eq!(inner_config.len(), 10); + assert_eq!(inner_config.len(), 11); let diagnostics = resolve_config(inner_config, &Default::default()).diagnostics; assert_eq!(diagnostics.len(), 0); } diff --git a/src/configuration/resolve_config.rs b/src/configuration/resolve_config.rs index 6d7fd79..336d5a6 100644 --- a/src/configuration/resolve_config.rs +++ b/src/configuration/resolve_config.rs @@ -60,6 +60,7 @@ pub fn resolve_config( UnorderedListKind::Dashes, &mut diagnostics, ), + heading_kind: get_value(&mut config, "headingKind", HeadingKind::Atx, &mut diagnostics), ignore_directive: get_value( &mut config, "ignoreDirective", diff --git a/src/configuration/types.rs b/src/configuration/types.rs index 13c0bcb..139b8f0 100644 --- a/src/configuration/types.rs +++ b/src/configuration/types.rs @@ -13,6 +13,7 @@ pub struct Configuration { pub emphasis_kind: EmphasisKind, pub strong_kind: StrongKind, pub unordered_list_kind: UnorderedListKind, + pub heading_kind: HeadingKind, pub ignore_directive: String, pub ignore_file_directive: String, pub ignore_start_directive: String, @@ -91,3 +92,20 @@ impl UnorderedListKind { } generate_str_to_from![UnorderedListKind, [Dashes, "dashes"], [Asterisks, "asterisks"]]; + +/// The style of heading to use for level 1 and level headings: +/// [setext](https://spec.commonmark.org/0.31.2/#setext-headings) or +/// [ATX](https://spec.commonmark.org/0.31.2/#atx-headings). Level 3 and +/// higher headings always use ATX headings, since Markdown only supports +/// setext headers for levels 1 and 2. +#[derive(Clone, PartialEq, Copy, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum HeadingKind { + /// Uses an underline of `=` or `-` beneath the heading text for level 1 and + /// 2 headings. + Setext, + /// Uses `#` or `##` before the heading text for level 1 and 2 headings. + Atx, +} + +generate_str_to_from![HeadingKind, [Setext, "setext"], [Atx, "atx"]]; diff --git a/src/generation/generate.rs b/src/generation/generate.rs index 227fe4b..d304948 100644 --- a/src/generation/generate.rs +++ b/src/generation/generate.rs @@ -276,8 +276,33 @@ fn gen_nodes(nodes: &[Node], context: &mut Context) -> PrintItems { fn gen_heading(heading: &Heading, context: &mut Context) -> PrintItems { let mut items = PrintItems::new(); - items.push_string(format!("{} ", "#".repeat(heading.level as usize))); - items.extend(with_no_new_lines(gen_nodes(&heading.children, context))); + if heading.level < 3 && context.configuration.heading_kind == HeadingKind::Setext { + // setext headings only apply to level 1 and level 2. + // + // First, output the heading text. + let heading_children = gen_nodes(&heading.children, context); + items.extend(heading_children); + items.push_item(PrintItem::Signal(Signal::NewLine)); + + // Next, create an underline. To produce a visually appealing underline, + // split the heading text (which may contain more than one line) into + // lines, then determine the length of the longest line. Note that this + // length should be determined **after** formatting, since lines may be + // wrapped during the formatting process. + // + // Incorrect -- need some way to get the text of the heading after's its + // been formatted/line wrapped. How to insert some sort of post-processing + // node? + //let heading_text = heading_children.get_as_text(); + // For now, use a constant width. + let heading_text = "x".repeat(context.configuration.line_width as usize); + // For now, use constant-width dummy text. + items.push_string((if heading.level == 1 { "=" } else { "-" }).repeat(heading_text.len())); + } else { + // atx headings apply to all levels. + items.push_string(format!("{} ", "#".repeat(heading.level as usize))); + items.extend(with_no_new_lines(gen_nodes(&heading.children, context))); + } items } diff --git a/tests/specs/headers/Headers_All.txt b/tests/specs/headers/Headers_All.txt index 1fb24b5..94a3f46 100644 --- a/tests/specs/headers/Headers_All.txt +++ b/tests/specs/headers/Headers_All.txt @@ -1,4 +1,4 @@ -~~ lineWidth: 40 ~~ +~~ lineWidth: 40, textWrap: always ~~ !! should format the headers !! # H1 @@ -37,9 +37,14 @@ Second Header !! should keep a header on a single line even when it goes over the line width !! # Testing this out by going over the line width +Verifying that text is wrapping in other places. + [expect] # Testing this out by going over the line width +Verifying that text is wrapping in other +places. + !! should remove consecutive spaces in the header text !! # Here is a test diff --git a/tests/specs/headers/Headers_All_Setext.txt b/tests/specs/headers/Headers_All_Setext.txt new file mode 100644 index 0000000..bf2f182 --- /dev/null +++ b/tests/specs/headers/Headers_All_Setext.txt @@ -0,0 +1,45 @@ +~~ lineWidth: 40, headingKind: setext ~~ +!! should format the headers !! +# H1 + +## H2 + +### H3 + +#### H4 + +First Header +============ + +Second Header +------------- + +[expect] +H1 +======================================== + +H2 +---------------------------------------- + +### H3 + +#### H4 + +First Header +======================================== + +Second Header +---------------------------------------- + +!! Headers are allowed to flow over multiple lines. +This Header +spans +multiple lines +======================================== + + +[expect] +This Header +spans +multiple lines +========================================