diff --git a/Cargo.toml b/Cargo.toml index 828ef098..3573b819 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,9 @@ version = "0.10.68" [dependencies.syntect] version = "5.2.0" +[dependencies.cansi] +version = "2.2.1" + # development [patch.crates-io] # ggemini = { path = "ggemini" } diff --git a/README.md b/README.md index e771317a..f79064b1 100644 --- a/README.md +++ b/README.md @@ -68,8 +68,17 @@ GTK 4 / Libadwaita client written in Rust * [x] Inline * [x] Multiline * [x] Alt - * [ ] Terminal emulation* * [x] Syntax highlight* (by [syntect](https://github.com/trishume/syntect)) + * [ ] Terminal emulation* (by [cansi](https://github.com/colored-rs/cansi)) + * [x] foreground + * [x] background + * [ ] intensity + * [x] italic + * [x] underline + * [ ] blink + * [ ] reversed + * [ ] hidden + * [x] strikethrough * [x] Header * [x] H1 * [x] H2 diff --git a/src/app/browser/window/tab/item/page/content/text/gemini/reader.rs b/src/app/browser/window/tab/item/page/content/text/gemini/reader.rs index 2e2c53fe..bce6ef7e 100644 --- a/src/app/browser/window/tab/item/page/content/text/gemini/reader.rs +++ b/src/app/browser/window/tab/item/page/content/text/gemini/reader.rs @@ -1,3 +1,4 @@ +mod ansi; pub mod error; mod syntax; mod tag; @@ -81,7 +82,7 @@ impl Reader { for line in gemtext.lines() { // Is inline code if let Some(code) = Inline::from(line) { - // Try auto-detect code syntax for given `value` + // Try auto-detect code syntax for given `value` @TODO optional match syntax.highlight(&code.value, None) { Ok(highlight) => { for (syntax_tag, entity) in highlight { @@ -98,12 +99,19 @@ impl Reader { } } Err(_) => { - // Nothing match, append default `Code` tag into the main buffer - buffer.insert_with_tags( - &mut buffer.end_iter(), - &code.value, - &[&tag.code.text_tag], - ); + // Try ANSI/SGR format (terminal emulation) @TODO optional + for (syntax_tag, entity) in ansi::format(&code.value) { + // Register new tag + if !tag.text_tag_table.add(&syntax_tag) { + todo!() + } + // Append tag to buffer + buffer.insert_with_tags( + &mut buffer.end_iter(), + &entity, + &[&syntax_tag], + ); + } } // @TODO handle } @@ -151,7 +159,7 @@ impl Reader { }; // Begin code block construction - // Try auto-detect code syntax for given `value` and `alt` + // Try auto-detect code syntax for given `value` and `alt` @TODO optional match syntax.highlight(&this.value, alt) { Ok(highlight) => { for (syntax_tag, entity) in highlight { @@ -168,14 +176,19 @@ impl Reader { } } Err(_) => { - // Try ANSI/SGR highlight (terminal emulation) - - // Nothing match, append default `Code` tag into the main buffer - buffer.insert_with_tags( - &mut buffer.end_iter(), - &this.value, - &[&tag.code.text_tag], - ); + // Try ANSI/SGR format (terminal emulation) @TODO optional + for (syntax_tag, entity) in ansi::format(&this.value) { + // Register new tag + if !tag.text_tag_table.add(&syntax_tag) { + todo!() + } + // Append tag to buffer + buffer.insert_with_tags( + &mut buffer.end_iter(), + &entity, + &[&syntax_tag], + ); + } } // @TODO handle } diff --git a/src/app/browser/window/tab/item/page/content/text/gemini/reader/ansi.rs b/src/app/browser/window/tab/item/page/content/text/gemini/reader/ansi.rs new file mode 100644 index 00000000..652ee5ca --- /dev/null +++ b/src/app/browser/window/tab/item/page/content/text/gemini/reader/ansi.rs @@ -0,0 +1,72 @@ +mod tag; +use tag::Tag; + +use cansi::{v3::categorise_text, Color}; +use gtk::{ + gdk::RGBA, + pango::{Style, Underline}, + prelude::TextTagExt, + TextTag, +}; + +/// Apply `ANSI` format to new buffer +pub fn format(source_code: &str) -> Vec<(TextTag, String)> { + // Init new line buffer + let mut buffer = Vec::new(); + + for entity in categorise_text(&source_code) { + // Create new tag from default preset + let tag = Tag::new(); + + // Apply supported decorations + if let Some(fg) = entity.fg { + tag.text_tag.set_foreground_rgba(Some(&color_to_rgba(fg))); + } + + if let Some(bg) = entity.bg { + tag.text_tag.set_background_rgba(Some(&color_to_rgba(bg))); + } + + if let Some(italic) = entity.italic { + if italic { + tag.text_tag.set_style(Style::Italic); + } + } + + if let Some(underline) = entity.underline { + if underline { + tag.text_tag.set_underline(Underline::Single); + } + } + + if let Some(strikethrough) = entity.strikethrough { + tag.text_tag.set_strikethrough(strikethrough); + } + + // Append + buffer.push((tag.text_tag, entity.text.to_string())); + } + + buffer +} + +fn color_to_rgba(value: Color) -> RGBA { + match value { + Color::Black => RGBA::new(0.0, 0.0, 0.0, 1.0), + Color::Red => RGBA::new(0.8, 0.0, 0.0, 1.0), + Color::Green => RGBA::new(0.0, 0.8, 0.0, 1.0), + Color::Yellow => RGBA::new(0.8, 0.8, 0.0, 1.0), + Color::Blue => RGBA::new(0.0, 0.0, 0.9, 1.0), + Color::Magenta => RGBA::new(0.8, 0.0, 0.8, 1.0), + Color::Cyan => RGBA::new(0.0, 0.8, 0.8, 1.0), + Color::White => RGBA::new(0.9, 0.9, 0.9, 1.0), + Color::BrightBlack => RGBA::new(0.5, 0.5, 0.5, 1.0), + Color::BrightRed => RGBA::new(1.0, 0.0, 0.0, 1.0), + Color::BrightGreen => RGBA::new(0.0, 1.0, 0.0, 1.0), + Color::BrightYellow => RGBA::new(1.0, 1.0, 0.0, 1.0), + Color::BrightBlue => RGBA::new(0.4, 0.4, 1.0, 1.0), + Color::BrightMagenta => RGBA::new(1.0, 0.0, 1.0, 1.0), + Color::BrightCyan => RGBA::new(0.0, 1.0, 1.0, 1.0), + Color::BrightWhite => RGBA::new(1.0, 1.0, 1.0, 1.0), + } +} diff --git a/src/app/browser/window/tab/item/page/content/text/gemini/reader/tag/code.rs b/src/app/browser/window/tab/item/page/content/text/gemini/reader/ansi/tag.rs similarity index 66% rename from src/app/browser/window/tab/item/page/content/text/gemini/reader/tag/code.rs rename to src/app/browser/window/tab/item/page/content/text/gemini/reader/ansi/tag.rs index 7704545c..fe726ce5 100644 --- a/src/app/browser/window/tab/item/page/content/text/gemini/reader/tag/code.rs +++ b/src/app/browser/window/tab/item/page/content/text/gemini/reader/ansi/tag.rs @@ -1,11 +1,15 @@ use gtk::{TextTag, WrapMode}; -pub struct Code { +/// Default [TextTag](https://docs.gtk.org/gtk4/class.TextTag.html) preset +/// for ANSI buffer +pub struct Tag { pub text_tag: TextTag, } -impl Code { - // Construct +impl Tag { + // Constructors + + /// Create new `Self` pub fn new() -> Self { Self { text_tag: TextTag::builder() diff --git a/src/app/browser/window/tab/item/page/content/text/gemini/reader/syntax.rs b/src/app/browser/window/tab/item/page/content/text/gemini/reader/syntax.rs index 8b8e27d1..80dcbf8f 100644 --- a/src/app/browser/window/tab/item/page/content/text/gemini/reader/syntax.rs +++ b/src/app/browser/window/tab/item/page/content/text/gemini/reader/syntax.rs @@ -98,7 +98,7 @@ impl Syntax { Ok(result) => { // Build tags for (style, entity) in result { - // Create new tag preset from source + // Create new tag from default preset let tag = Tag::new(); // Tuneup using syntect conversion diff --git a/src/app/browser/window/tab/item/page/content/text/gemini/reader/tag.rs b/src/app/browser/window/tab/item/page/content/text/gemini/reader/tag.rs index 199f1cad..72b99c29 100644 --- a/src/app/browser/window/tab/item/page/content/text/gemini/reader/tag.rs +++ b/src/app/browser/window/tab/item/page/content/text/gemini/reader/tag.rs @@ -1,4 +1,3 @@ -mod code; mod h1; mod h2; mod h3; @@ -6,7 +5,6 @@ mod list; mod quote; mod title; -use code::Code; use h1::H1; use h2::H2; use h3::H3; @@ -19,7 +17,6 @@ use gtk::TextTagTable; pub struct Tag { pub text_tag_table: TextTagTable, // Tags - pub code: Code, pub h1: H1, pub h2: H2, pub h3: H3, @@ -32,7 +29,6 @@ impl Tag { // Construct pub fn new() -> Self { // Init components - let code = Code::new(); let h1 = H1::new(); let h2 = H2::new(); let h3 = H3::new(); @@ -43,7 +39,6 @@ impl Tag { // Init tag table let text_tag_table = TextTagTable::new(); - text_tag_table.add(&code.text_tag); text_tag_table.add(&h1.text_tag); text_tag_table.add(&h2.text_tag); text_tag_table.add(&h3.text_tag); @@ -54,7 +49,6 @@ impl Tag { Self { text_tag_table, // Tags - code, h1, h2, h3,