diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index edf650e3..06300fd2 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "adler" @@ -116,6 +116,31 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +[[package]] +name = "bon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a636f83af97c6946f3f5cf5c268ec02375bf5efd371110292dfd57961f57a509" +dependencies = [ + "bon-macros", + "rustversion", +] + +[[package]] +name = "bon-macros" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7eaf1bfaa5b8d512abfd36d0c432591fef139d3de9ee54f1f839ea109d70d33" +dependencies = [ + "darling", + "ident_case", + "prettyplease", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -198,12 +223,12 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "comrak" -version = "0.29.0" +version = "0.30.0" dependencies = [ "arbitrary", + "bon", "caseless", "clap", - "derive_builder", "emojis", "entities", "memchr", @@ -237,9 +262,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.9" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ "darling_core", "darling_macro", @@ -247,9 +272,9 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.9" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" dependencies = [ "fnv", "ident_case", @@ -261,9 +286,9 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.20.9" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", @@ -290,37 +315,6 @@ dependencies = [ "syn", ] -[[package]] -name = "derive_builder" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0350b5cb0331628a5916d6c5c0b72e97393b8b6b03b47a9284f4e7f5a405ffd7" -dependencies = [ - "derive_builder_macro", -] - -[[package]] -name = "derive_builder_core" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d48cda787f839151732d396ac69e3473923d54312c070ee21e9effcaa8ca0b1d" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "derive_builder_macro" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "206868b8242f27cecce124c19fd88157fbd0dd334df2587f36417bafbc85097b" -dependencies = [ - "derive_builder_core", - "syn", -] - [[package]] name = "deunicode" version = "1.4.4" @@ -561,11 +555,21 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "prettyplease" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro2" -version = "1.0.81" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -630,6 +634,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustversion" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" + [[package]] name = "ryu" version = "1.0.17" @@ -706,9 +716,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.60" +version = "2.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" +checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" dependencies = [ "proc-macro2", "quote", diff --git a/script/cibuild b/script/cibuild index 794441cc..64016bb7 100755 --- a/script/cibuild +++ b/script/cibuild @@ -48,6 +48,8 @@ python3 spec_tests.py --no-normalize --spec ../../../src/tests/fixtures/wikilink || failed=1 python3 spec_tests.py --no-normalize --spec ../../../src/tests/fixtures/wikilinks_title_before_pipe.md "$PROGRAM_ARG -e wikilinks-title-before-pipe" \ || failed=1 +python3 spec_tests.py --no-normalize --spec ../../../src/tests/fixtures/description_lists.md "$PROGRAM_ARG -e description-lists" \ + || failed=1 python3 spec_tests.py --no-normalize --spec regression.txt "$PROGRAM_ARG" \ || failed=1 diff --git a/src/html.rs b/src/html.rs index eb542c5d..0d633c6b 100644 --- a/src/html.rs +++ b/src/html.rs @@ -479,7 +479,7 @@ impl<'o> HtmlFormatter<'o> { self.cr()?; self.output.write_all(b"")?; + self.output.write_all(b">\n")?; } else { self.output.write_all(b"\n")?; } @@ -663,6 +663,7 @@ impl<'o> HtmlFormatter<'o> { .map(|n| n.data.borrow().value.clone()) { Some(NodeValue::List(nl)) => nl.tight, + Some(NodeValue::DescriptionItem(nd)) => nd.tight, _ => false, }; diff --git a/src/nodes.rs b/src/nodes.rs index ec6e549c..85407a7a 100644 --- a/src/nodes.rs +++ b/src/nodes.rs @@ -313,6 +313,10 @@ pub struct NodeDescriptionItem { /// Number of characters between the start of the list marker and the item text (including the list marker(s)). pub padding: usize, + + /// Whether the list is [tight](https://github.github.com/gfm/#tight), i.e. whether the + /// paragraphs are wrapped in `

` tags when formatted as HTML. + pub tight: bool, } /// The type of list. diff --git a/src/parser/mod.rs b/src/parser/mod.rs index e08fdc01..2c233981 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -334,7 +334,7 @@ pub struct ExtensionOptions { /// let mut options = Options::default(); /// options.extension.description_lists = true; /// assert_eq!(markdown_to_html("Term\n\n: Definition", &options), - /// "

Term
\n
\n

Definition

\n
\n
\n"); + /// "
\n
Term
\n
\n

Definition

\n
\n
\n"); /// ``` #[builder(default)] pub description_lists: bool, @@ -1636,10 +1636,13 @@ impl<'a, 'o> Parser<'a, 'o> { container.data.borrow_mut().internal_offset = matched; } else if !indented && self.options.extension.description_lists - && line[self.first_nonspace] == b':' - && self.parse_desc_list_details(container) + && unwrap_into( + scanners::description_item_start(&line[self.first_nonspace..]), + &mut matched, + ) + && self.parse_desc_list_details(container, matched) { - let offset = self.first_nonspace + 1 - self.offset; + let offset = self.first_nonspace + matched - self.offset; self.advance_offset(line, offset, false); if strings::is_space_or_tab(line[self.offset]) { self.advance_offset(line, 1, true); @@ -1881,10 +1884,28 @@ impl<'a, 'o> Parser<'a, 'o> { } } - fn parse_desc_list_details(&mut self, container: &mut &'a AstNode<'a>) -> bool { + fn parse_desc_list_details(&mut self, container: &mut &'a AstNode<'a>, matched: usize) -> bool { + let mut tight = false; let last_child = match container.last_child() { Some(lc) => lc, - None => return false, + None => { + // Happens when the detail line is directly after the term, + // without a blank line between. + if !node_matches!(container, NodeValue::Paragraph) { + // If the container is not a paragraph, then this can't + // be a description list item. + return false; + } + + let parent = container.parent(); + if parent.is_none() { + return false; + } + + tight = true; + *container = parent.unwrap(); + container.last_child().unwrap() + } }; if node_matches!(last_child, NodeValue::Paragraph) { @@ -1908,7 +1929,7 @@ impl<'a, 'o> Parser<'a, 'o> { // All are incorrect; they all give the start line/col of // the DescriptionDetails, and the end line/col is completely off. // - // descriptionDetails: + // DescriptionDetails: // Same as the DescriptionItem. All but last, the end line/col // is (l+1):0. // @@ -1931,7 +1952,8 @@ impl<'a, 'o> Parser<'a, 'o> { let metadata = NodeDescriptionItem { marker_offset: self.indent, - padding: 2, + padding: matched, + tight, }; let item = self.add_child( @@ -1948,6 +1970,31 @@ impl<'a, 'o> Parser<'a, 'o> { *container = details; + true + } else if node_matches!(last_child, NodeValue::DescriptionItem(..)) { + let parent = last_child.parent().unwrap(); + let tight = match last_child.data.borrow().value { + NodeValue::DescriptionItem(ref ndi) => ndi.tight, + _ => false, + }; + + let metadata = NodeDescriptionItem { + marker_offset: self.indent, + padding: matched, + tight, + }; + + let item = self.add_child( + parent, + NodeValue::DescriptionItem(metadata), + self.first_nonspace + 1, + ); + + let details = + self.add_child(item, NodeValue::DescriptionDetails, self.first_nonspace + 1); + + *container = details; + true } else { false diff --git a/src/scanners.re b/src/scanners.re index 35bbc4f6..f665f854 100644 --- a/src/scanners.re +++ b/src/scanners.re @@ -431,4 +431,13 @@ pub fn tasklist(s: &[u8]) -> Option<(usize, u8)> { */ } +pub fn description_item_start(s: &[u8]) -> Option { + let mut cursor = 0; + let len = s.len(); +/*!re2c + [:~] ([ \t]+) { return Some(cursor); } + * { return None; } +*/ +} + // vim: set ft=rust: diff --git a/src/scanners.rs b/src/scanners.rs index 52b2a747..29eca63b 100644 --- a/src/scanners.rs +++ b/src/scanners.rs @@ -1,4 +1,4 @@ -/* Generated by re2c 3.1 */ +/* Generated by re2rust 4.0 */ pub fn atx_heading_start(s: &[u8]) -> Option { let mut cursor = 0; @@ -246,9 +246,7 @@ pub fn atx_heading_start(s: &[u8]) -> Option { } } } - _ => { - panic!("internal lexer error") - } + _ => panic!("internal lexer error"), } } } @@ -2065,9 +2063,7 @@ pub fn html_block_end_1(s: &[u8]) -> bool { } } } - _ => { - panic!("internal lexer error") - } + _ => panic!("internal lexer error"), } } } @@ -2747,9 +2743,7 @@ pub fn html_block_end_2(s: &[u8]) -> bool { 25 => { return true; } - _ => { - panic!("internal lexer error") - } + _ => panic!("internal lexer error"), } } } @@ -3369,9 +3363,7 @@ pub fn html_block_end_3(s: &[u8]) -> bool { 24 => { return true; } - _ => { - panic!("internal lexer error") - } + _ => panic!("internal lexer error"), } } } @@ -3900,9 +3892,7 @@ pub fn html_block_end_4(s: &[u8]) -> bool { } } } - _ => { - panic!("internal lexer error") - } + _ => panic!("internal lexer error"), } } } @@ -4582,9 +4572,7 @@ pub fn html_block_end_5(s: &[u8]) -> bool { 25 => { return true; } - _ => { - panic!("internal lexer error") - } + _ => panic!("internal lexer error"), } } } @@ -5280,9 +5268,7 @@ pub fn open_code_fence(s: &[u8]) -> Option { } } } - _ => { - panic!("internal lexer error") - } + _ => panic!("internal lexer error"), } } } @@ -5544,9 +5530,7 @@ pub fn close_code_fence(s: &[u8]) -> Option { return Some(cursor); } } - _ => { - panic!("internal lexer error") - } + _ => panic!("internal lexer error"), } } } @@ -9164,9 +9148,7 @@ pub fn html_block_start(s: &[u8]) -> Option { } } } - _ => { - panic!("internal lexer error") - } + _ => panic!("internal lexer error"), } } } @@ -10279,9 +10261,7 @@ pub fn html_block_start_7(s: &[u8]) -> Option { } } } - _ => { - panic!("internal lexer error") - } + _ => panic!("internal lexer error"), } } } @@ -10509,9 +10489,7 @@ pub fn setext_heading_line(s: &[u8]) -> Option { } } } - _ => { - panic!("internal lexer error") - } + _ => panic!("internal lexer error"), } } } @@ -10845,9 +10823,7 @@ pub fn footnote_definition(s: &[u8]) -> Option { 17 => { return Some(cursor); } - _ => { - panic!("internal lexer error") - } + _ => panic!("internal lexer error"), } } } @@ -11690,9 +11666,7 @@ pub fn scheme(s: &[u8]) -> Option { } } } - _ => { - panic!("internal lexer error") - } + _ => panic!("internal lexer error"), } } } @@ -12735,9 +12709,7 @@ pub fn autolink_uri(s: &[u8]) -> Option { } } } - _ => { - panic!("internal lexer error") - } + _ => panic!("internal lexer error"), } } } @@ -16586,9 +16558,7 @@ pub fn autolink_email(s: &[u8]) -> Option { } } } - _ => { - panic!("internal lexer error") - } + _ => panic!("internal lexer error"), } } } @@ -17635,9 +17605,7 @@ pub fn html_tag(s: &[u8]) -> Option { } } } - _ => { - panic!("internal lexer error") - } + _ => panic!("internal lexer error"), } } } @@ -18030,9 +17998,7 @@ pub fn html_comment(s: &[u8]) -> Option { 15 => { return Some(cursor); } - _ => { - panic!("internal lexer error") - } + _ => panic!("internal lexer error"), } } } @@ -18559,9 +18525,7 @@ pub fn html_processing_instruction(s: &[u8]) -> Option { } } } - _ => { - panic!("internal lexer error") - } + _ => panic!("internal lexer error"), } } } @@ -18868,9 +18832,7 @@ pub fn html_declaration(s: &[u8]) -> Option { } } } - _ => { - panic!("internal lexer error") - } + _ => panic!("internal lexer error"), } } } @@ -19347,9 +19309,7 @@ pub fn html_cdata(s: &[u8]) -> Option { } } } - _ => { - panic!("internal lexer error") - } + _ => panic!("internal lexer error"), } } } @@ -19411,9 +19371,7 @@ pub fn spacechars(s: &[u8]) -> Option { 3 => { return Some(cursor); } - _ => { - panic!("internal lexer error") - } + _ => panic!("internal lexer error"), } } } @@ -20588,9 +20546,7 @@ pub fn link_title(s: &[u8]) -> Option { } } } - _ => { - panic!("internal lexer error") - } + _ => panic!("internal lexer error"), } } } @@ -21372,9 +21328,7 @@ pub fn dangerous_url(s: &[u8]) -> Option { } } } - _ => { - panic!("internal lexer error") - } + _ => panic!("internal lexer error"), } } } @@ -21703,9 +21657,7 @@ pub fn table_start(s: &[u8]) -> Option { } } } - _ => { - panic!("internal lexer error") - } + _ => panic!("internal lexer error"), } } } @@ -22279,9 +22231,7 @@ pub fn table_cell(s: &[u8], spoiler: bool) -> Option { } } } - _ => { - panic!("internal lexer error") - } + _ => panic!("internal lexer error"), } } } @@ -22781,9 +22731,7 @@ pub fn table_cell(s: &[u8], spoiler: bool) -> Option { } } } - _ => { - panic!("internal lexer error") - } + _ => panic!("internal lexer error"), } } } @@ -22846,9 +22794,7 @@ pub fn table_cell_end(s: &[u8]) -> Option { 3 => { return Some(cursor); } - _ => { - panic!("internal lexer error") - } + _ => panic!("internal lexer error"), } } } @@ -23000,9 +22946,7 @@ pub fn table_row_end(s: &[u8]) -> Option { } } } - _ => { - panic!("internal lexer error") - } + _ => panic!("internal lexer error"), } } } @@ -23102,9 +23046,7 @@ pub fn shortcode(s: &[u8]) -> Option { 7 => { return Some(cursor); } - _ => { - panic!("internal lexer error") - } + _ => panic!("internal lexer error"), } } } @@ -23258,9 +23200,7 @@ pub fn open_multiline_block_quote_fence(s: &[u8]) -> Option { return Some(cursor); } } - _ => { - panic!("internal lexer error") - } + _ => panic!("internal lexer error"), } } } @@ -23414,9 +23354,7 @@ pub fn close_multiline_block_quote_fence(s: &[u8]) -> Option { return Some(cursor); } } - _ => { - panic!("internal lexer error") - } + _ => panic!("internal lexer error"), } } } @@ -23840,9 +23778,93 @@ pub fn tasklist(s: &[u8]) -> Option<(usize, u8)> { return Some((cursor, s[t1])); } } - _ => { - panic!("internal lexer error") + _ => panic!("internal lexer error"), + } + } + } +} + +pub fn description_item_start(s: &[u8]) -> Option { + let mut cursor = 0; + let len = s.len(); + + { + #[allow(unused_assignments)] + let mut yych: u8 = 0; + let mut yystate: usize = 0; + 'yyl: loop { + match yystate { + 0 => { + yych = unsafe { + if cursor < len { + *s.get_unchecked(cursor) + } else { + 0 + } + }; + cursor += 1; + match yych { + 0x3A | 0x7E => { + yystate = 3; + continue 'yyl; + } + _ => { + yystate = 1; + continue 'yyl; + } + } + } + 1 => { + yystate = 2; + continue 'yyl; + } + 2 => { + return None; + } + 3 => { + yych = unsafe { + if cursor < len { + *s.get_unchecked(cursor) + } else { + 0 + } + }; + match yych { + 0x09 | 0x20 => { + cursor += 1; + yystate = 4; + continue 'yyl; + } + _ => { + yystate = 2; + continue 'yyl; + } + } + } + 4 => { + yych = unsafe { + if cursor < len { + *s.get_unchecked(cursor) + } else { + 0 + } + }; + match yych { + 0x09 | 0x20 => { + cursor += 1; + yystate = 4; + continue 'yyl; + } + _ => { + yystate = 5; + continue 'yyl; + } + } + } + 5 => { + return Some(cursor); } + _ => panic!("internal lexer error"), } } } diff --git a/src/tests/description_lists.rs b/src/tests/description_lists.rs index 06970fc0..e2bd23cd 100644 --- a/src/tests/description_lists.rs +++ b/src/tests/description_lists.rs @@ -1,7 +1,7 @@ use super::*; #[test] -fn description_lists() { +fn description_lists_loose() { html_opts!( [extension.description_lists], concat!( @@ -14,7 +14,7 @@ fn description_lists() { ": Definition 2\n" ), concat!( - "
", + "
\n", "
Term 1
\n", "
\n", "

Definition 1

\n", @@ -41,7 +41,7 @@ fn description_lists() { "
    \n", "
  • \n", "

    Nested

    \n", - "
    ", + "
    \n", "
    Term 1
    \n", "
    \n", "

    Definition 1

    \n", @@ -57,6 +57,89 @@ fn description_lists() { ); } +#[test] +fn description_lists_tight() { + html_opts!( + [extension.description_lists], + concat!( + "Term 1\n", + ": Definition 1\n", + "\n", + "Term 2 with *inline markup*\n", + ": Definition 2\n" + ), + concat!( + "
    \n", + "
    Term 1
    \n", + "
    Definition 1
    \n", + "
    Term 2 with inline markup
    \n", + "
    Definition 2
    \n", + "
    \n", + ), + no_roundtrip, + ); + + html_opts!( + [extension.description_lists], + concat!( + "* Nested\n", + "\n", + " Term 1\n", + " : Definition 1\n\n", + " Term 2 with *inline markup*\n", + " : Definition 2\n\n" + ), + concat!( + "
      \n", + "
    • \n", + "

      Nested

      \n", + "
      \n", + "
      Term 1
      \n", + "
      Definition 1
      \n", + "
      Term 2 with inline markup
      \n", + "
      Definition 2
      \n", + "
      \n", + "
    • \n", + "
    \n", + ), + no_roundtrip, + ); +} +#[test] +fn description_lists_edge_cases() { + html_opts!( + [extension.description_lists], + concat!(":"), + concat!("

    :

    \n"), + ); + + html_opts!( + [extension.description_lists], + concat!(": foo"), + concat!("

    : foo

    \n"), + ); + + html_opts!( + [extension.description_lists], + concat!("a\n:"), + concat!("

    a\n:

    \n"), + ); + + html_opts!( + [extension.description_lists], + concat!("- foo\n", "- : bar\n", " - baz\n",), + concat!( + "
      \n", + "
    • foo
    • \n", + "
    • : bar\n", + "
        \n", + "
      • baz
      • \n", + "
      \n", + "
    • \n", + "
    \n", + ), + ); +} #[test] fn sourcepos() { // TODO There's plenty of work to do here still. The test currently represents diff --git a/src/tests/fixtures/description_lists.md b/src/tests/fixtures/description_lists.md new file mode 100644 index 00000000..5f689014 --- /dev/null +++ b/src/tests/fixtures/description_lists.md @@ -0,0 +1,298 @@ +--- +title: Description / defintition lists +based_on: https://github.com/jgm/commonmark-hs/blob/master/commonmark-extensions/test/definition_lists.md +--- + +## Definition lists + +The term is given on a line by itself, followed by +one or more definitions. Each definition must begin +with `:` (after 0-2 spaces); subsequent lines must +be indented unless they are lazy paragraph +continuations. + +The list is tight if there is no blank line between +the term and the first definition, otherwise loose. + +```````````````````````````````` example +apple +: red fruit + +orange +: orange fruit +. +
    +
    apple
    +
    red fruit
    +
    orange
    +
    orange fruit
    +
    +```````````````````````````````` + +Loose: + +```````````````````````````````` example +apple + +: red fruit + +orange + +: orange fruit +. +
    +
    apple
    +
    +

    red fruit

    +
    +
    orange
    +
    +

    orange fruit

    +
    +
    +```````````````````````````````` + +Indented marker: + +```````````````````````````````` example +apple + : red fruit + +orange + : orange fruit +. +
    +
    apple
    +
    red fruit
    +
    orange
    +
    orange fruit
    +
    +```````````````````````````````` + +```````````````````````````````` example +apple + + : red fruit + +orange + + : orange fruit +. +
    +
    apple
    +
    +

    red fruit

    +
    +
    orange
    +
    +

    orange fruit

    +
    +
    +```````````````````````````````` + +Multiple blocks in a definition: + +```````````````````````````````` example +*apple* + +: red fruit + + contains seeds, + crisp, pleasant to taste + +*orange* + +: orange fruit + + { orange code block } + + > orange block quote +. +
    +
    apple
    +
    +

    red fruit

    +

    contains seeds, +crisp, pleasant to taste

    +
    +
    orange
    +
    +

    orange fruit

    +
    { orange code block }
    +
    +
    +

    orange block quote

    +
    +
    +
    +```````````````````````````````` + +Nested lists: + +```````````````````````````````` example +term + +: 1. Para one + + Para two +. +
    +
    term
    +
    +
      +
    1. +

      Para one

      +

      Para two

      +
    2. +
    +
    +
    +```````````````````````````````` + +Multiple definitions, tight: + +```````````````````````````````` example +apple +: red fruit +: computer company + +orange +: orange fruit +: telecom company +. +
    +
    apple
    +
    red fruit
    +
    computer company
    +
    orange
    +
    orange fruit
    +
    telecom company
    +
    +```````````````````````````````` + +Multiple definitions, loose: + +```````````````````````````````` example +apple + +: red fruit + +: computer company + +orange + +: orange fruit +: telecom company +. +
    +
    apple
    +
    +

    red fruit

    +
    +
    +

    computer company

    +
    +
    orange
    +
    +

    orange fruit

    +
    +
    +

    telecom company

    +
    +
    +```````````````````````````````` + +Lazy line continuations: + +```````````````````````````````` example +apple + +: red fruit + +: computer +company + +orange + +: orange +fruit +: telecom company +. +
    +
    apple
    +
    +

    red fruit

    +
    +
    +

    computer +company

    +
    +
    orange
    +
    +

    orange +fruit

    +
    +
    +

    telecom company

    +
    +
    +```````````````````````````````` + + + +`~` may be used as a marker instead of `:`: + +```````````````````````````````` example +apple + ~ red fruit + +orange + ~ orange fruit +. +
    +
    apple
    +
    red fruit
    +
    orange
    +
    orange fruit
    +
    +```````````````````````````````` + +Definition terms may span multiple lines: + +```````````````````````````````` example +a +b\ +c + +: foo +. +
    +
    a +b
    +c
    +
    +

    foo

    +
    +
    +```````````````````````````````` + +Definition list with preceding paragraph +(): + +```````````````````````````````` example +Foo + +bar +: baz + +bim +: bor +. +

    Foo

    +
    +
    bar
    +
    baz
    +
    bim
    +
    bor
    +
    +````````````````````````````````