diff --git a/cursive-core/Cargo.toml b/cursive-core/Cargo.toml index 0ce23831..48fcf655 100644 --- a/cursive-core/Cargo.toml +++ b/cursive-core/Cargo.toml @@ -60,6 +60,10 @@ version = "0.8" default-features = false version = "0.4" +[dependencies.regex] +optional = true +version = "1" + [dependencies.pulldown-cmark] default-features = false optional = true @@ -69,6 +73,7 @@ version = "0.11" default = [] doc-cfg = [] builder = ["inventory", "cursive-macros/builder"] +cursup = ["regex"] markdown = ["pulldown-cmark"] ansi = ["ansi-parser"] unstable_scroll = [] # Deprecated feature, remove in next version diff --git a/cursive-core/src/theme/style.rs b/cursive-core/src/theme/style.rs index eced6505..2c6dc52f 100644 --- a/cursive-core/src/theme/style.rs +++ b/cursive-core/src/theme/style.rs @@ -1,4 +1,5 @@ use std::iter::FromIterator; +use std::str::FromStr; use super::{ Color, ColorPair, ColorStyle, ColorType, ConcreteEffects, Effect, Effects, Palette, @@ -164,6 +165,22 @@ impl Style { } } +impl FromStr for Style { + type Err = super::NoSuchColor; + + fn from_str(s: &str) -> Result { + if let Ok(front) = s.parse::() { + return Ok(front.into()); + } + + if let Ok(effect) = s.parse::() { + return Ok(effect.into()); + } + + Err(super::NoSuchColor) + } +} + impl From for Style { fn from(effect: Effect) -> Self { Style { diff --git a/cursive-core/src/utils/markup/cursup.rs b/cursive-core/src/utils/markup/cursup.rs new file mode 100644 index 00000000..18783140 --- /dev/null +++ b/cursive-core/src/utils/markup/cursup.rs @@ -0,0 +1,96 @@ +//! A simple markup format. +//! +//! # Examples +//! +//! ``` +//! /red{This} /green{text} /blue{is} /bold{very} /underline{styled}! +//! /red+bold{This too!} +//! ``` +#![cfg(feature = "cursup")] +#![cfg_attr(feature = "doc-cfg", doc(cfg(feature = "cursup")))] +use crate::theme::Style; +use crate::utils::markup::{StyledIndexedSpan, StyledString}; +use crate::utils::span::IndexedCow; + +use unicode_width::UnicodeWidthStr; + +/// Parse spans for the given text. +pub fn parse_spans(input: &str) -> Vec { + let mut result = Vec::new(); + + let re = regex::Regex::new(r"\/(?:(\w+)(?:\+(\w+))*)\{(.+)\}").unwrap(); + + let mut offset = 0; + let mut content = input; + while let Some(c) = re.captures(content) { + let m = c.get(0).unwrap(); + let start = m.start(); + let end = m.end(); + + // First, append the entire content up to here. + if start != 0 { + result.push(StyledIndexedSpan { + content: IndexedCow::Borrowed { + start: offset, + end: offset + start, + }, + attr: Style::default(), + width: content[..start].width(), + }); + } + + let len = c.len(); + assert!( + len > 2, + "The regex should always yield at least 2 groups (+ the entire match)." + ); + let body = c.get(len - 1).unwrap(); + + let mut style = Style::default(); + + for i in 1..len - 1 { + let Some(action) = c.get(i) else { + continue; + }; + + style = style.combine(action.as_str().parse::