Skip to content

Commit

Permalink
Add Alerts (#32)
Browse files Browse the repository at this point in the history
* Alerts

* Fix extra space

* Tweak alert colors

So that they work with both dark and light mode

* Add alerts to changelog
  • Loading branch information
lampsitter committed Feb 19, 2024
1 parent 2b6cd2e commit 024b2d5
Show file tree
Hide file tree
Showing 5 changed files with 251 additions and 9 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,17 @@

## Unreleased

### Added

- Alerts ([#32](https://github.com/lampsitter/egui_commonmark/pull/32))

> [!TIP]
> Alerts like this can be used
### Changed

- Prettier blockquotes

Before two simple horizontal lines were rendered. Now it's a single horizonal
line in front of the elements.

Expand Down
40 changes: 40 additions & 0 deletions examples/markdown/blockquotes.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Block quotes

> This is a simple block quote
> A block quote with more other blocks inside it
Expand All @@ -7,5 +9,43 @@
> println!("Hello, World!");
> }
> ```
## Alerts
Alerts build upon block quotes.
```markdown
> [!NOTE]
> note alert
```
or

```markdown
> [!NOTE]
>
> note alert
```

will be displayed as:

> [!NOTE]
> note alert
> [!TIP]
> tip alert
<!-- The trailing whitespaces are deliberate on important and warning -->

> [!IMPORTANT]
> important alert
> [!WARNING]
> warning alert
> [!CAUTION]
>
> caution alert
The alerts are completely customizable. An arbitrary amount of alerts can be
added
101 changes: 101 additions & 0 deletions src/alerts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
use crate::elements::{blockquote, newline};
use egui::Ui;
use std::collections::HashMap;

#[derive(Debug, Clone)]
pub struct Alert {
/// The color that will be used to put emphasis to the alert
pub accent_color: egui::Color32,
/// The icon that will be displayed
pub icon: char,
/// The identifier that will be used to look for the blockquote such as NOTE and TIP
pub identifier: String,
/// The identifier that will be shown when rendering. E.g: Note and Tip
pub identifier_rendered: String,
}

impl Alert {
pub(crate) fn ui(&self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui)) {
blockquote(ui, self.accent_color, |ui| {
newline(ui);
ui.colored_label(self.accent_color, self.icon.to_string());
ui.add_space(3.0);
ui.colored_label(self.accent_color, &self.identifier_rendered);
// end line
newline(ui);
add_contents(ui);
})
}
}

#[derive(Debug, Clone)]
pub struct AlertBundle {
/// the key is `[!identifier]`
alerts: HashMap<String, Alert>,
}

impl AlertBundle {
fn from_alerts(alerts: Vec<Alert>) -> Self {
let mut map = HashMap::with_capacity(alerts.len());
for alert in alerts {
// Store it the way it will be in text to make lookup easier
map.insert(format!("[!{}]", alert.identifier), alert);
}

Self { alerts: map }
}

pub(crate) fn try_get_alert(&self, text: &str) -> Option<&Alert> {
self.alerts.get(text)
}

pub fn empty() -> Self {
AlertBundle {
alerts: Default::default(),
}
}

/// github flavoured markdown alerts
/// `[!NOTE]`, `[!TIP]`, `[!IMPORTANT]`, `[!WARNING]` and `[!CAUTION]`.
///
/// This is used by default
pub fn gfm() -> Self {
Self::from_alerts(vec![
Alert {
accent_color: egui::Color32::from_rgb(10, 80, 210),
icon: '❕',
identifier: "NOTE".to_owned(),
identifier_rendered: "Note".to_owned(),
},
Alert {
accent_color: egui::Color32::from_rgb(0, 130, 20),
icon: '💡',
identifier: "TIP".to_owned(),
identifier_rendered: "Tip".to_owned(),
},
Alert {
accent_color: egui::Color32::from_rgb(150, 30, 140),
icon: '💬',
identifier: "IMPORTANT".to_owned(),
identifier_rendered: "Important".to_owned(),
},
Alert {
accent_color: egui::Color32::from_rgb(200, 120, 0),
icon: '⚠',
identifier: "WARNING".to_owned(),
identifier_rendered: "Warning".to_owned(),
},
Alert {
accent_color: egui::Color32::from_rgb(220, 0, 0),
icon: '🔴',
identifier: "CAUTION".to_owned(),
identifier_rendered: "Caution".to_owned(),
},
])
}

/// See if the bundle contains no alerts
pub fn is_empty(&self) -> bool {
self.alerts.is_empty()
}
}
15 changes: 15 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,12 @@ use std::collections::HashMap;

use egui::{self, text::LayoutJob, Id, RichText, TextStyle, Ui};

mod alerts;
mod elements;
mod parsers;

pub use alerts::*;

#[cfg(all(feature = "comrak", feature = "pulldown_cmark"))]
compile_error!("Cannot have multiple different parsing backends enabled at the same time");

Expand Down Expand Up @@ -248,6 +251,7 @@ struct CommonMarkOptions {
theme_dark: String,
use_explicit_uri_scheme: bool,
default_implicit_uri_scheme: String,
alerts: AlertBundle,
}

impl Default for CommonMarkOptions {
Expand All @@ -263,6 +267,7 @@ impl Default for CommonMarkOptions {
theme_dark: DEFAULT_THEME_DARK.to_owned(),
use_explicit_uri_scheme: false,
default_implicit_uri_scheme: "file://".to_owned(),
alerts: AlertBundle::gfm(),
}
}
}
Expand Down Expand Up @@ -379,6 +384,16 @@ impl CommonMarkViewer {
self
}

#[cfg(not(feature = "comrak"))] // not supported by the backend atm.
/// Specify what kind of alerts are supported. This can also be used to localize alerts.
///
/// By default [github flavoured markdown style alerts](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts)
/// are used
pub fn alerts(mut self, alerts: AlertBundle) -> Self {
self.options.alerts = alerts;
self
}

/// Shows rendered markdown
pub fn show(self, ui: &mut egui::Ui, cache: &mut CommonMarkCache, text: &str) {
cache.prepare_show(ui.ctx());
Expand Down
96 changes: 87 additions & 9 deletions src/parsers/pulldown.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Duplicates a lot of stuff for now.

use crate::elements::*;
use crate::{elements::*, Alert, AlertBundle};
use crate::{CommonMarkCache, CommonMarkOptions};

use egui::{self, Id, Pos2, TextStyle, Ui, Vec2};
Expand Down Expand Up @@ -38,6 +38,75 @@ fn delayed_events<'e>(
}
}

/// try to parse events as an alert quote block. This ill modify the events
/// to remove the parsed text that should not be rendered.
/// Assumes that the first element is a Paragraph
fn parse_alerts<'a>(
alerts: &'a AlertBundle,
events: &mut Vec<pulldown_cmark::Event<'_>>,
) -> Option<&'a Alert> {
// no point in parsing if there are no alerts to render
if !alerts.is_empty() {
let mut alert_ident = "".to_owned();
let mut alert_ident_ends_at = 0;
let mut has_extra_line = false;

for (i, e) in events.iter().enumerate() {
if let pulldown_cmark::Event::End(_) = e {
// > [!TIP]
// >
// > Detect the first paragraph
// In this case the next text will be within a paragraph so it is better to remove
// the entire paragraph
alert_ident_ends_at = i;
has_extra_line = true;
break;
}

if let pulldown_cmark::Event::SoftBreak = e {
// > [!NOTE]
// > this is valid and will produce a soft break
alert_ident_ends_at = i;
break;
}

if let pulldown_cmark::Event::HardBreak = e {
// > [!NOTE]<whitespace>
// > this is valid and will produce a hard break
alert_ident_ends_at = i;
break;
}

if let pulldown_cmark::Event::Text(text) = e {
alert_ident += text;
}
}

let alert = alerts.try_get_alert(&alert_ident);

if alert.is_some() {
// remove the text that identifies it as an alert so that it won't end up in the
// render
//
// FIMXE: performance improvement potential
if has_extra_line {
for _ in 0..=alert_ident_ends_at {
events.remove(0);
}
} else {
for _ in 0..alert_ident_ends_at {
// the first element must be kept as it _should_ be Paragraph
events.remove(1);
}
}
}

alert
} else {
None
}
}

/// Supported pulldown_cmark options
fn parser_options() -> Options {
Options::ENABLE_TABLES
Expand Down Expand Up @@ -231,14 +300,23 @@ impl CommonMarkViewerInternal {
ui: &mut Ui,
) {
if self.is_blockquote {
let collected_events = delayed_events(events, pulldown_cmark::TagEnd::BlockQuote);
blockquote(ui, ui.visuals().weak_text_color(), |ui| {
self.text_style.quote = true;
for event in collected_events {
self.event(ui, event, cache, options, max_width);
}
self.text_style.quote = false;
});
let mut collected_events = delayed_events(events, pulldown_cmark::TagEnd::BlockQuote);

if let Some(alert) = parse_alerts(&options.alerts, &mut collected_events) {
alert.ui(ui, |ui| {
for event in collected_events.into_iter() {
self.event(ui, event, cache, options, max_width);
}
})
} else {
blockquote(ui, ui.visuals().weak_text_color(), |ui| {
self.text_style.quote = true;
for event in collected_events {
self.event(ui, event, cache, options, max_width);
}
self.text_style.quote = false;
});
}

newline(ui);

Expand Down

0 comments on commit 024b2d5

Please sign in to comment.