From b53e6e7bb6d96f96972fb612fcc159aeaa1e5882 Mon Sep 17 00:00:00 2001 From: Mark Date: Mon, 3 Aug 2026 17:53:07 +0200 Subject: [PATCH] feat: inline code, resource links fixes Text::is_empty() and allows formatted text in the context section of quotes (so you can link to /ref) slight changes to the css of the html generator, this should fix document preview on link hovering and might improve nesting if you edit the generated html in weird ways (should be more robust now). --- example.mharkup | 74 ++++++++++++++++++++++++------------------ src/doc.rs | 19 ++++++----- src/parse.rs | 44 +++++++++++++++++++------ src/to/html.rs | 85 ++++++++++++++++++++++++++++++------------------- src/to/plain.rs | 24 +++++++------- 5 files changed, 151 insertions(+), 95 deletions(-) diff --git a/example.mharkup b/example.mharkup index 49403d6..7d859e5 100644 --- a/example.mharkup +++ b/example.mharkup @@ -15,9 +15,9 @@ in most markdown flavors. as someone using mharkup, you ` can read and write it in your text editor -` can export and `/printable(print) it +` can export and `/printable[print] it ` can easily (re)organize your document -` probably won't apply formatting on accident `/backtick() +` probably won't apply formatting on accident `/backtick[] # the format @@ -46,9 +46,9 @@ for example, here are the sections you just read: each mharkup file starts with a title, the first line of the file, which doesn't have -any leading hashtags. a file `+(should) also have a small +any leading hashtags. a file `+[should] also have a small description of what it will contain, and the file's -actual content `+(should) be in subdocuments. +actual content `+[should] be in subdocuments. for example, here's how this file starts: [[ @@ -68,19 +68,20 @@ features of mharkup. in most cases, a section is just a few lines of text. you've probably already noticed that linebreaks are usually -ignored `-(or, more accurately, replaced with spaces). +ignored `-[or, more accurately, replaced with spaces]. -sections beginning with a hashtag (#) are special. +sections beginning with a hashtag (`![#]) are special. the first line specifies the type of section you want, and the other lines are the section's content. -you've already seen this with #!mharkup in the +you've already seen this with `![#!mharkup] in the first example. the types of sections you can create like this are: -` #@ quotes -` #! code blocks -` #\ various extensions +` `![#@] quotes +` `![#!] code blocks +` `![#:] resource links +` `![#\] various extensions ### backticks /here @@ -90,18 +91,18 @@ with a backtick followed by a space to create a list. here's a list of things the backtick can do: ` create a list -` represent itself ``: ```` -` add a `-(note): ``-(note) -` mark something that's `~(wrong): ``~(wrong) -` use monospace for `!(code): ``!(code) -` mark a non-word or name, like `@(mharkup): ``@(mharkup) -` highlight a `_(special) element: ``_(special) -` add emphasis to `+(something): ``+(something) -` highlight something `*(important): ``*(important) -` insert raw `{`text`}: `<`{`text`}>: `2{`<`{`text`}>}}: `2<`2{`<`{`text`}>}}>>: ... -` link `/here(somewhere): ``/here(somewhere) +` represent itself ``: `!{``} +` add a `-[note]: `!{`-[note]} +` mark something that's `~[wrong]: `!{`~[wrong]} +` include some `![code.f()]: `!{`![code.f()]} +` mark a non-word or name, like `@[mharkup]: ``@[mharkup] +` highlight a `_[special] element: `!{`_[special]} +` add emphasis to `+[something]: `!{`+[something]} +` highlight something `*[important]: `!{`*[important]} +` insert raw `{`text`}: `!<`{`text`}>: `2!{`<`{`text`}>}}: `2!<`2{`<`{`text`}>}}>>: ... +` link `/here[somewhere]: ``/here[somewhere] ` use an extension: ``\spec{...} or ``\spec<...> -` combine any of these: `*_+(whoah): ``*_+(whoah) +` combine any of these: `*_+[whoa`~(h)]: ``*_+[whoa``~(h)] ## @@ -155,21 +156,32 @@ to use the different section types in more detail. ### quotes -#@mark @21st century +#@mark @21st century `/ref[] i've solved many problems that noone's ever had. in this case, the first line -of the section was #@mark @21st century. +of the section was `!{#@mark @21st century `/ref[]}. -### code +### code blocks #!python def do_something_productive(): pass in this case, the first line -of the section was #!python. +of the section was `!{#!python}. + +### resource links /ref + +#:other_doc +A document you should read. + +#https://example.com/hello +Something external. + +The first lines of these sections were `!{#:other_doc} and `!{#https://example.com/hello}. +If the section starts with `!{#:}, the link is relative. ### extensions @@ -198,14 +210,14 @@ or inline: `\texm or `2\texm{ \frac{1}{2} }} i really don't want to think about how many special characters i can't use while writing my plaintext documents. -i know that writing `<`*(word)> instead of **word** is inconvenient, +i know that writing `{`*(word)} instead of **word** is inconvenient, but using *, _, and even \ without any escaping may `-[(?)] already be worth it. -Also, * and _ can't be nested, so if you took `+(this **cool** section) +Also, * and _ can't be nested, so if you took `+[this **cool** section] and copy-pasted it into an already bold section, you would get -`+(**this **cool** section**) which is just... ew. -Meanwhile `{`*(this `*(cool) section)} works just fine -and produces `*(this `*(cool) section). +`+[**this **cool** section**] which is just... ew. +Meanwhile `{`*[this `*(cool) section]} works just fine +and produces `*[this `*(cool) section]. ## why does it need to be printable? /printable @@ -237,7 +249,7 @@ and here's 10 layers of documents in mharkup: ########## whoah, that's a lot -but it's still perfectly readable +but it's still perfectly readable. ## why are there no nested lists? /listnesting diff --git a/src/doc.rs b/src/doc.rs index 952262a..1bfd9ed 100644 --- a/src/doc.rs +++ b/src/doc.rs @@ -11,7 +11,8 @@ pub enum Section<'a> { Paragraph(Text<'a>), List(Vec>), Code(&'a str, Text<'a>), - Quote(&'a str, &'a str, Text<'a>), + Quote(&'a str, Text<'a>, Text<'a>), + Link(&'a str, &'a str, Text<'a>), Ext(&'a str, &'a str), Document(Document<'a>), } @@ -25,34 +26,32 @@ pub enum Component<'a> { Text(&'a str), Note(Text<'a>), Wrong(Text<'a>), + Name(Text<'a>), + Code(Text<'a>), Special(Text<'a>), Emphasis(Text<'a>), Important(Text<'a>), - Name(Text<'a>), - Code(Text<'a>), Link(&'a str, Text<'a>), Ext(&'a str, &'a str), } impl<'a> Text<'a> { pub fn is_empty(&self) -> bool { - self.0.iter().any(|c| c.is_empty()) + self.0.iter().all(|c| c.is_empty()) } } impl<'a> Component<'a> { pub fn is_empty(&self) -> bool { match self { - Component::Char(_) => false, + Component::Char(_) | Component::Link(_, _) | Component::Ext(_, _) => false, Component::Text(t) => t.is_empty(), Component::Note(t) | Component::Wrong(t) + | Component::Name(t) + | Component::Code(t) | Component::Special(t) | Component::Emphasis(t) - | Component::Important(t) - | Component::Name(t) - | Component::Code(t) => t.is_empty(), - Component::Link(target, t) => target.is_empty() && t.is_empty(), - Component::Ext(_, _) => false, + | Component::Important(t) => t.is_empty(), } } } diff --git a/src/parse.rs b/src/parse.rs index 0811209..c09ef93 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -225,6 +225,12 @@ pub fn parse_text_impl<'a>( '~' if opt.is_empty() => { inner = Ok(text(inner, Component::Wrong)); } + '@' if opt.is_empty() => { + inner = Ok(text(inner, Component::Name)); + } + '!' if opt.is_empty() => { + inner = Ok(text(inner, Component::Code)); + } '_' if opt.is_empty() => { inner = Ok(text(inner, Component::Special)); } @@ -234,12 +240,6 @@ pub fn parse_text_impl<'a>( '*' if opt.is_empty() => { inner = Ok(text(inner, Component::Important)); } - '@' if opt.is_empty() => { - inner = Ok(text(inner, Component::Name)); - } - '!' if opt.is_empty() => { - inner = Ok(text(inner, Component::Code)); - } '/' if !opt.is_empty() => { inner = Ok(text(inner, |v| Component::Link(opt, v))); } @@ -254,11 +254,11 @@ pub fn parse_text_impl<'a>( Component::Link(_, text) | Component::Note(text) | Component::Wrong(text) + | Component::Name(text) + | Component::Code(text) | Component::Special(text) | Component::Emphasis(text) - | Component::Important(text) - | Component::Name(text) - | Component::Code(text) => inner = &mut text.0[0], + | Component::Important(text) => inner = &mut text.0[0], Component::Char(_) | Component::Ext(_, _) => { unreachable!() } @@ -426,7 +426,31 @@ fn parse_sections<'a>( attributed.rsplit_once('@').unwrap_or((attributed, "")); sections.push(Section::Quote( attributed.strip_suffix(' ').unwrap_or(attributed), - context.strip_prefix(' ').unwrap_or(context), + parse_text(&mut context.strip_prefix(' ').unwrap_or(context), |o| { + o.remove_linebreaks() + }) + .map_err(|ParseError(e, i)| ParseError(e, i + pos))?, + parse_text(&mut inner, |o| o.remove_linebreaks()) + .map_err(|ParseError(e, i)| ParseError(e, i + pos))?, + )); + } + Some(_) + if posthash + .trim_start_matches(|c: char| !(c == ':' || c.is_whitespace())) + .starts_with(":") => + { + let i = posthash.find(':').unwrap(); + let (mut link, mut inner) = if let Some(j) = posthash.find('\n') { + (&posthash[0..j], &posthash[j + 1..]) + } else { + (posthash, "") + }; + if i == 0 { + link = &link[1..]; + } + sections.push(Section::Link( + &posthash[0..i], + link, parse_text(&mut inner, |o| o.remove_linebreaks()) .map_err(|ParseError(e, i)| ParseError(e, i + pos))?, )); diff --git a/src/to/html.rs b/src/to/html.rs index 46acb1e..b86e854 100644 --- a/src/to/html.rs +++ b/src/to/html.rs @@ -40,7 +40,7 @@ pub fn page_to<'a>(