draft ANSI/SGR format (terminal emulation)

This commit is contained in:
yggverse 2024-12-05 05:09:50 +02:00
parent 8c9cabfcda
commit 90ddac2033
7 changed files with 122 additions and 27 deletions

View File

@ -38,6 +38,9 @@ version = "0.10.68"
[dependencies.syntect] [dependencies.syntect]
version = "5.2.0" version = "5.2.0"
[dependencies.cansi]
version = "2.2.1"
# development # development
[patch.crates-io] [patch.crates-io]
# ggemini = { path = "ggemini" } # ggemini = { path = "ggemini" }

View File

@ -68,8 +68,17 @@ GTK 4 / Libadwaita client written in Rust
* [x] Inline * [x] Inline
* [x] Multiline * [x] Multiline
* [x] Alt * [x] Alt
* [ ] Terminal emulation*
* [x] Syntax highlight* (by [syntect](https://github.com/trishume/syntect)) * [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] Header
* [x] H1 * [x] H1
* [x] H2 * [x] H2

View File

@ -1,3 +1,4 @@
mod ansi;
pub mod error; pub mod error;
mod syntax; mod syntax;
mod tag; mod tag;
@ -81,7 +82,7 @@ impl Reader {
for line in gemtext.lines() { for line in gemtext.lines() {
// Is inline code // Is inline code
if let Some(code) = Inline::from(line) { 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) { match syntax.highlight(&code.value, None) {
Ok(highlight) => { Ok(highlight) => {
for (syntax_tag, entity) in highlight { for (syntax_tag, entity) in highlight {
@ -98,12 +99,19 @@ impl Reader {
} }
} }
Err(_) => { Err(_) => {
// Nothing match, append default `Code` tag into the main buffer // Try ANSI/SGR format (terminal emulation) @TODO optional
buffer.insert_with_tags( for (syntax_tag, entity) in ansi::format(&code.value) {
&mut buffer.end_iter(), // Register new tag
&code.value, if !tag.text_tag_table.add(&syntax_tag) {
&[&tag.code.text_tag], todo!()
); }
// Append tag to buffer
buffer.insert_with_tags(
&mut buffer.end_iter(),
&entity,
&[&syntax_tag],
);
}
} // @TODO handle } // @TODO handle
} }
@ -151,7 +159,7 @@ impl Reader {
}; };
// Begin code block construction // 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) { match syntax.highlight(&this.value, alt) {
Ok(highlight) => { Ok(highlight) => {
for (syntax_tag, entity) in highlight { for (syntax_tag, entity) in highlight {
@ -168,14 +176,19 @@ impl Reader {
} }
} }
Err(_) => { Err(_) => {
// Try ANSI/SGR highlight (terminal emulation) // Try ANSI/SGR format (terminal emulation) @TODO optional
for (syntax_tag, entity) in ansi::format(&this.value) {
// Nothing match, append default `Code` tag into the main buffer // Register new tag
buffer.insert_with_tags( if !tag.text_tag_table.add(&syntax_tag) {
&mut buffer.end_iter(), todo!()
&this.value, }
&[&tag.code.text_tag], // Append tag to buffer
); buffer.insert_with_tags(
&mut buffer.end_iter(),
&entity,
&[&syntax_tag],
);
}
} // @TODO handle } // @TODO handle
} }

View File

@ -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),
}
}

View File

@ -1,11 +1,15 @@
use gtk::{TextTag, WrapMode}; 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, pub text_tag: TextTag,
} }
impl Code { impl Tag {
// Construct // Constructors
/// Create new `Self`
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
text_tag: TextTag::builder() text_tag: TextTag::builder()

View File

@ -98,7 +98,7 @@ impl Syntax {
Ok(result) => { Ok(result) => {
// Build tags // Build tags
for (style, entity) in result { for (style, entity) in result {
// Create new tag preset from source // Create new tag from default preset
let tag = Tag::new(); let tag = Tag::new();
// Tuneup using syntect conversion // Tuneup using syntect conversion

View File

@ -1,4 +1,3 @@
mod code;
mod h1; mod h1;
mod h2; mod h2;
mod h3; mod h3;
@ -6,7 +5,6 @@ mod list;
mod quote; mod quote;
mod title; mod title;
use code::Code;
use h1::H1; use h1::H1;
use h2::H2; use h2::H2;
use h3::H3; use h3::H3;
@ -19,7 +17,6 @@ use gtk::TextTagTable;
pub struct Tag { pub struct Tag {
pub text_tag_table: TextTagTable, pub text_tag_table: TextTagTable,
// Tags // Tags
pub code: Code,
pub h1: H1, pub h1: H1,
pub h2: H2, pub h2: H2,
pub h3: H3, pub h3: H3,
@ -32,7 +29,6 @@ impl Tag {
// Construct // Construct
pub fn new() -> Self { pub fn new() -> Self {
// Init components // Init components
let code = Code::new();
let h1 = H1::new(); let h1 = H1::new();
let h2 = H2::new(); let h2 = H2::new();
let h3 = H3::new(); let h3 = H3::new();
@ -43,7 +39,6 @@ impl Tag {
// Init tag table // Init tag table
let text_tag_table = TextTagTable::new(); let text_tag_table = TextTagTable::new();
text_tag_table.add(&code.text_tag);
text_tag_table.add(&h1.text_tag); text_tag_table.add(&h1.text_tag);
text_tag_table.add(&h2.text_tag); text_tag_table.add(&h2.text_tag);
text_tag_table.add(&h3.text_tag); text_tag_table.add(&h3.text_tag);
@ -54,7 +49,6 @@ impl Tag {
Self { Self {
text_tag_table, text_tag_table,
// Tags // Tags
code,
h1, h1,
h2, h2,
h3, h3,