use shared code widget, remove duplicated components (used in gemtext and markdown renderers)

This commit is contained in:
yggverse 2026-03-18 14:34:33 +02:00
parent 45e8824a2e
commit f4b1aa9ecc
19 changed files with 166 additions and 741 deletions

View file

@ -1,12 +1,12 @@
mod common;
mod gemini;
mod markdown;
mod nex;
mod plain;
mod source;
use crate::{app::browser::window::tab::item::page::Page, profile::Profile};
use super::{ItemAction, WindowAction};
use crate::{app::browser::window::tab::item::page::Page, profile::Profile};
use adw::ClampScrollable;
use gemini::Gemini;
use gtk::{ScrolledWindow, TextView, glib::Uri};

View file

@ -0,0 +1,4 @@
/// Shared widgets:
/// e.g. Gemtext and Markdown renderers have single implementation for the code blocks
pub mod code;
pub use code::Code;

View file

@ -0,0 +1,139 @@
mod ansi;
mod syntax;
use gtk::{
Align, Box, Button, Label, Orientation, PolicyType, ScrolledWindow, Separator, TextBuffer,
TextTagTable, TextView, WrapMode,
gdk::Display,
glib::{ControlFlow, idle_add_local},
prelude::{BoxExt, ButtonExt, DisplayExt, TextBufferExt, TextBufferExtManual, WidgetExt},
};
use std::{cell::Cell, rc::Rc};
use syntax::Syntax;
pub struct Code {
pub widget: Box,
}
impl Code {
pub fn init(parent: &TextView, source: &str, alt: Option<&String>) -> Self {
let syntax = Syntax::new();
let copied = Rc::new(Cell::new(None));
let widget = Box::builder()
.css_classes(["card"])
.halign(Align::Fill)
.hexpand(true)
.margin_bottom(MARGIN / 2)
.orientation(Orientation::Vertical)
.build();
widget.append(&{
let header = Box::builder()
.halign(Align::Fill)
.hexpand(true)
.orientation(Orientation::Horizontal)
.build();
if let Some(label) = alt {
header.append(
&Label::builder()
.halign(Align::Start)
.hexpand(true)
.label(label)
.margin_bottom(MARGIN)
.margin_end(MARGIN)
.margin_start(MARGIN)
.margin_top(MARGIN)
.selectable(true)
.build(),
);
}
header.append(&{
const TOGGLE_BUTTON_CLASS: &str = "dimmed";
const TOGGLE_BUTTON_TOOLTIP: (&str, &str) = ("Copy", "Copied");
let copy = Button::builder()
.css_classes(["circular", "flat", TOGGLE_BUTTON_CLASS])
.halign(Align::End)
.icon_name("edit-copy-symbolic")
.margin_bottom(MARGIN / 2)
.margin_end(MARGIN / 2)
.margin_start(MARGIN / 2)
.margin_top(MARGIN / 2)
.tooltip_text(TOGGLE_BUTTON_TOOLTIP.0)
.valign(Align::Center)
.build();
copy.set_cursor_from_name(Some("pointer"));
copy.connect_clicked({
let source = String::from(source);
let copied = copied.clone();
move |this| {
if let Some(prev) = copied.replace(Some(this.clone())) {
prev.set_tooltip_text(Some(TOGGLE_BUTTON_TOOLTIP.0));
prev.add_css_class(TOGGLE_BUTTON_CLASS)
}
this.set_tooltip_text(Some(TOGGLE_BUTTON_TOOLTIP.1));
this.remove_css_class(TOGGLE_BUTTON_CLASS);
Display::default().unwrap().clipboard().set_text(&source)
}
});
copy
});
header
});
widget.append(
&Separator::builder()
.orientation(Orientation::Horizontal)
.build(),
);
widget.append(&{
ScrolledWindow::builder()
.child(
&TextView::builder()
.buffer(&{
let b = TextBuffer::new(Some(&TextTagTable::new()));
let mut start = b.start_iter();
match syntax.highlight(source, alt) {
Ok(highlight) => {
for (syntax_tag, entity) in highlight {
assert!(b.tag_table().add(&syntax_tag));
b.insert_with_tags(&mut start, &entity, &[&syntax_tag])
}
}
Err(_) => {
// Try ANSI/SGR format (terminal emulation) @TODO optional
for (syntax_tag, entity) in ansi::format(source) {
assert!(b.tag_table().add(&syntax_tag));
b.insert_with_tags(&mut start, &entity, &[&syntax_tag])
}
}
}
b
})
.css_classes(["code-block"])
.cursor_visible(false)
.editable(false)
.wrap_mode(WrapMode::None)
.build(),
)
.margin_bottom(MARGIN)
.margin_end(MARGIN)
.margin_start(MARGIN)
.margin_top(MARGIN)
.vscrollbar_policy(PolicyType::Never)
.hscrollbar_policy(PolicyType::Automatic)
.propagate_natural_height(true)
.build()
});
idle_add_local({
let widget = widget.clone();
let parent = parent.clone();
move || {
widget.set_width_request(parent.width() - 22);
ControlFlow::Break
}
});
Self { widget }
}
}
const MARGIN: i32 = 16;

View file

@ -1,8 +1,6 @@
mod ansi;
pub mod error;
mod gutter;
mod icon;
mod syntax;
mod tag;
use super::{ItemAction, WindowAction};
@ -20,7 +18,6 @@ use gutter::Gutter;
use icon::Icon;
use sourceview::prelude::{ActionExt, ActionMapExt, DisplayExt, ToVariant};
use std::{cell::Cell, collections::HashMap, rc::Rc};
use syntax::Syntax;
use tag::Tag;
pub const NEW_LINE: &str = "\n";
@ -63,9 +60,6 @@ impl Gemini {
RGBA::new(0.208, 0.518, 0.894, 0.9),
);
// Init syntect highlight features
let syntax = Syntax::new();
// Init icons
let icon = Icon::new();
@ -125,61 +119,18 @@ impl Gemini {
Some(ref mut c) => {
match c.continue_from(line) {
Ok(()) => {
// Close tag found:
// Closing tag found:
if c.is_completed {
// Is alt provided
let alt = match c.alt {
Some(ref alt) => {
// Insert alt value to the main buffer
buffer.insert_with_tags(
&mut buffer.end_iter(),
alt.as_str(),
&[&tag.title],
text_view.add_child_at_anchor(
&super::common::Code::init(
&text_view,
c.value.trim_end(),
c.alt.as_ref(),
)
.widget,
&buffer.create_child_anchor(&mut buffer.end_iter()),
);
// Append new line after alt text
buffer.insert(&mut buffer.end_iter(), NEW_LINE);
// Return value as wanted also for syntax highlight detection
Some(alt)
}
None => None,
};
// Begin code block construction
// Try auto-detect code syntax for given `value` and `alt` @TODO optional
match syntax.highlight(&c.value, alt) {
Ok(highlight) => {
for (syntax_tag, entity) in highlight {
// 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],
);
}
}
Err(_) => {
// Try ANSI/SGR format (terminal emulation) @TODO optional
for (syntax_tag, entity) in ansi::format(&c.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
}
// Reset
code = None;
}

View file

@ -1,29 +0,0 @@
use gtk::{TextTag, WrapMode};
/// Default [TextTag](https://docs.gtk.org/gtk4/class.TextTag.html) preset
/// for ANSI buffer
pub struct Tag {
pub text_tag: TextTag,
}
impl Default for Tag {
fn default() -> Self {
Self::new()
}
}
impl Tag {
// Constructors
/// Create new `Self`
pub fn new() -> Self {
Self {
text_tag: TextTag::builder()
.family("monospace") // @TODO
.left_margin(28)
.scale(0.81) // * the rounded `0.8` value crops text for some reason @TODO
.wrap_mode(WrapMode::None)
.build(),
}
}
}

View file

@ -1,29 +0,0 @@
use gtk::{TextTag, WrapMode};
/// Default [TextTag](https://docs.gtk.org/gtk4/class.TextTag.html) preset
/// for syntax highlight buffer
pub struct Tag {
pub text_tag: TextTag,
}
impl Default for Tag {
fn default() -> Self {
Self::new()
}
}
impl Tag {
// Constructors
/// Create new `Self`
pub fn new() -> Self {
Self {
text_tag: TextTag::builder()
.family("monospace") // @TODO
.left_margin(28)
.scale(0.81) // * the rounded `0.8` value crops text for some reason @TODO
.wrap_mode(WrapMode::None)
.build(),
}
}
}

View file

@ -2,14 +2,12 @@ mod header;
mod list;
mod plain;
mod quote;
mod title;
use gtk::{TextTag, TextTagTable};
use header::Header;
use list::List;
use plain::Plain;
use quote::Quote;
use title::Title;
pub struct Tag {
pub text_tag_table: TextTagTable,
@ -19,7 +17,6 @@ pub struct Tag {
pub h3: TextTag,
pub list: TextTag,
pub quote: TextTag,
pub title: TextTag,
pub plain: TextTag,
}
@ -38,7 +35,6 @@ impl Tag {
let h3 = TextTag::h3();
let list = TextTag::list();
let quote = TextTag::quote();
let title = TextTag::title();
let plain = TextTag::plain();
// Init tag table
@ -47,7 +43,6 @@ impl Tag {
text_tag_table.add(&h1);
text_tag_table.add(&h2);
text_tag_table.add(&h3);
text_tag_table.add(&title);
text_tag_table.add(&list);
text_tag_table.add(&quote);
text_tag_table.add(&plain);
@ -60,7 +55,6 @@ impl Tag {
h3,
list,
quote,
title,
plain,
}
}

View file

@ -1,16 +0,0 @@
use gtk::{TextTag, WrapMode};
pub trait Title {
fn title() -> Self;
}
impl Title for TextTag {
fn title() -> Self {
TextTag::builder()
.pixels_above_lines(4)
.pixels_below_lines(8)
.weight(500)
.wrap_mode(WrapMode::None)
.build()
}
}

View file

@ -1,18 +1,10 @@
mod ansi;
mod syntax;
use gtk::{
Align, Box, Button, Label, Orientation, PolicyType, ScrolledWindow, Separator, TextBuffer,
TextSearchFlags, TextTagTable, TextView, WrapMode,
gdk::Display,
glib::{ControlFlow, GString, idle_add_local, uuid_string_random},
prelude::{
BoxExt, ButtonExt, DisplayExt, TextBufferExt, TextBufferExtManual, TextViewExt, WidgetExt,
},
TextBuffer, TextSearchFlags, TextView,
glib::{GString, uuid_string_random},
prelude::{TextBufferExt, TextBufferExtManual, TextViewExt},
};
use regex::Regex;
use std::{cell::Cell, collections::HashMap, rc::Rc};
use syntax::Syntax;
use std::collections::HashMap;
const REGEX_CODE: &str = r"(?s)```[ \t]*(?P<alt>.*?)\n(?P<data>.*?)```";
@ -69,8 +61,6 @@ impl Code {
/// Apply code `Tag` to given `TextView` using `Self.index`
pub fn render(&mut self, text_view: &TextView) {
let buffer = text_view.buffer();
let syntax = Syntax::new();
let copied = Rc::new(Cell::new(None));
for (k, v) in self.0.iter() {
while let Some((mut m_start, mut m_end)) =
buffer
@ -79,134 +69,14 @@ impl Code {
{
buffer.delete(&mut m_start, &mut m_end);
text_view.add_child_at_anchor(
&{
const MARGIN: i32 = 16;
let widget = Box::builder()
.css_classes(["card"])
.halign(Align::Fill)
.hexpand(true)
.margin_bottom(MARGIN / 2)
.orientation(Orientation::Vertical)
.build();
widget.append(&{
let header = Box::builder()
.halign(Align::Fill)
.hexpand(true)
.orientation(Orientation::Horizontal)
.build();
if let Some(ref alt) = v.alt {
header.append(
&Label::builder()
.halign(Align::Start)
.hexpand(true)
.label(alt)
.margin_bottom(MARGIN)
.margin_end(MARGIN)
.margin_start(MARGIN)
.margin_top(MARGIN)
.selectable(true)
.build(),
);
}
header.append(&{
const TOGGLE_BUTTON_CLASS: &str = "dimmed";
const TOGGLE_BUTTON_TOOLTIP: (&str, &str) = ("Copy", "Copied");
let copy = Button::builder()
.css_classes(["circular", "flat", TOGGLE_BUTTON_CLASS])
.halign(Align::End)
.icon_name("edit-copy-symbolic")
.margin_bottom(MARGIN / 2)
.margin_end(MARGIN / 2)
.margin_start(MARGIN / 2)
.margin_top(MARGIN / 2)
.tooltip_text(TOGGLE_BUTTON_TOOLTIP.0)
.valign(Align::Center)
.build();
copy.set_cursor_from_name(Some("pointer"));
copy.connect_clicked({
let source = v.data.clone();
let copied = copied.clone();
move |this| {
if let Some(prev) = copied.replace(Some(this.clone())) {
prev.set_tooltip_text(Some(TOGGLE_BUTTON_TOOLTIP.0));
prev.add_css_class(TOGGLE_BUTTON_CLASS)
}
this.set_tooltip_text(Some(TOGGLE_BUTTON_TOOLTIP.1));
this.remove_css_class(TOGGLE_BUTTON_CLASS);
Display::default().unwrap().clipboard().set_text(&source)
}
});
copy
});
header
});
widget.append(
&Separator::builder()
.orientation(Orientation::Horizontal)
.build(),
);
widget.append(&{
ScrolledWindow::builder()
.child(
&TextView::builder()
.buffer(&{
let b = TextBuffer::new(Some(&TextTagTable::new()));
let mut start = b.start_iter();
match syntax.highlight(&v.data, v.alt.as_ref()) {
Ok(highlight) => {
for (syntax_tag, entity) in highlight {
assert!(b.tag_table().add(&syntax_tag));
b.insert_with_tags(
&mut start,
&entity,
&[&syntax_tag],
&super::super::super::common::Code::init(
text_view,
v.data.as_ref(),
v.alt.as_ref(),
)
}
}
Err(_) => {
// Try ANSI/SGR format (terminal emulation) @TODO optional
for (syntax_tag, entity) in
ansi::format(&v.data)
{
assert!(b.tag_table().add(&syntax_tag));
b.insert_with_tags(
&mut start,
&entity,
&[&syntax_tag],
)
}
}
}
b
})
.css_classes(["code-block"])
.cursor_visible(false)
.editable(false)
.wrap_mode(WrapMode::None)
.build(),
)
.margin_bottom(MARGIN)
.margin_end(MARGIN)
.margin_start(MARGIN)
.margin_top(MARGIN)
.vscrollbar_policy(PolicyType::Never)
.hscrollbar_policy(PolicyType::Automatic)
.propagate_natural_height(true)
.build()
});
idle_add_local({
let widget = widget.clone();
let text_view = text_view.clone();
move || {
widget.set_width_request(text_view.width() - 22);
ControlFlow::Break
}
});
widget
},
.widget,
&buffer.create_child_anchor(&mut m_end),
);
)
}
}
}

View file

@ -1,33 +0,0 @@
mod rgba;
mod tag;
use tag::Tag;
use ansi_parser::{AnsiParser, AnsiSequence, Output};
use gtk::{TextTag, prelude::TextTagExt};
/// Apply ANSI/SGR format to new buffer
pub fn format(source_code: &str) -> Vec<(TextTag, String)> {
let mut buffer = Vec::new();
let mut tag = Tag::new();
for ref entity in source_code.ansi_parse() {
if let Output::Escape(AnsiSequence::SetGraphicsMode(color)) = entity
&& color.len() > 1
{
if color[0] == 38 {
tag.text_tag
.set_foreground_rgba(rgba::default(*color.last().unwrap()).as_ref());
} else {
tag.text_tag
.set_background_rgba(rgba::default(*color.last().unwrap()).as_ref());
}
}
if let Output::TextBlock(text) = entity {
buffer.push((tag.text_tag, text.to_string()));
tag = Tag::new();
}
}
buffer
}

View file

@ -1,256 +0,0 @@
use gtk::gdk::RGBA;
/// Default RGBa palette for ANSI terminal emulation
pub fn default(color: u8) -> Option<RGBA> {
match color {
7 => Some(RGBA::new(0.854, 0.854, 0.854, 1.0)),
8 => Some(RGBA::new(0.424, 0.424, 0.424, 1.0)),
10 => Some(RGBA::new(0.0, 1.0, 0.0, 1.0)),
11 => Some(RGBA::new(1.0, 1.0, 0.0, 1.0)),
12 => Some(RGBA::new(0.0, 0.0, 1.0, 1.0)),
13 => Some(RGBA::new(1.0, 0.0, 1.0, 1.0)),
14 => Some(RGBA::new(0.0, 1.0, 1.0, 1.0)),
15 => Some(RGBA::new(1.0, 1.0, 1.0, 1.0)),
16 => Some(RGBA::new(0.0, 0.0, 0.0, 1.0)),
17 => Some(RGBA::new(0.0, 0.020, 0.373, 1.0)),
18 => Some(RGBA::new(0.0, 0.031, 0.529, 1.0)),
19 => Some(RGBA::new(0.0, 0.0, 0.686, 1.0)),
20 => Some(RGBA::new(0.0, 0.0, 0.823, 1.0)),
21 => Some(RGBA::new(0.0, 0.0, 1.0, 1.0)),
22 => Some(RGBA::new(0.0, 0.373, 0.0, 1.0)),
23 => Some(RGBA::new(0.0, 0.373, 0.373, 1.0)),
24 => Some(RGBA::new(0.0, 0.373, 0.529, 1.0)),
25 => Some(RGBA::new(0.0, 0.373, 0.686, 1.0)),
26 => Some(RGBA::new(0.0, 0.373, 0.823, 1.0)),
27 => Some(RGBA::new(0.0, 0.373, 1.0, 1.0)),
28 => Some(RGBA::new(0.0, 0.533, 0.0, 1.0)),
29 => Some(RGBA::new(0.0, 0.533, 0.373, 1.0)),
30 => Some(RGBA::new(0.0, 0.533, 0.533, 1.0)),
31 => Some(RGBA::new(0.0, 0.533, 0.686, 1.0)),
32 => Some(RGBA::new(0.0, 0.533, 0.823, 1.0)),
33 => Some(RGBA::new(0.0, 0.533, 1.0, 1.0)),
34 => Some(RGBA::new(0.039, 0.686, 0.0, 1.0)),
35 => Some(RGBA::new(0.039, 0.686, 0.373, 1.0)),
36 => Some(RGBA::new(0.039, 0.686, 0.529, 1.0)),
37 => Some(RGBA::new(0.039, 0.686, 0.686, 1.0)),
38 => Some(RGBA::new(0.039, 0.686, 0.823, 1.0)),
39 => Some(RGBA::new(0.039, 0.686, 1.0, 1.0)),
40 => Some(RGBA::new(0.0, 0.843, 0.0, 1.0)),
41 => Some(RGBA::new(0.0, 0.843, 0.373, 1.0)),
42 => Some(RGBA::new(0.0, 0.843, 0.529, 1.0)),
43 => Some(RGBA::new(0.0, 0.843, 0.686, 1.0)),
44 => Some(RGBA::new(0.0, 0.843, 0.843, 1.0)),
45 => Some(RGBA::new(0.0, 0.843, 1.0, 1.0)),
46 => Some(RGBA::new(0.0, 1.0, 0.0, 1.0)),
47 => Some(RGBA::new(0.0, 1.0, 0.373, 1.0)),
48 => Some(RGBA::new(0.0, 1.0, 0.529, 1.0)),
49 => Some(RGBA::new(0.0, 1.0, 0.686, 1.0)),
50 => Some(RGBA::new(0.0, 1.0, 0.843, 1.0)),
51 => Some(RGBA::new(0.0, 1.0, 1.0, 1.0)),
52 => Some(RGBA::new(0.373, 0.0, 0.0, 1.0)),
53 => Some(RGBA::new(0.373, 0.0, 0.373, 1.0)),
54 => Some(RGBA::new(0.373, 0.0, 0.529, 1.0)),
55 => Some(RGBA::new(0.373, 0.0, 0.686, 1.0)),
56 => Some(RGBA::new(0.373, 0.0, 0.843, 1.0)),
57 => Some(RGBA::new(0.373, 0.0, 1.0, 1.0)),
58 => Some(RGBA::new(0.373, 0.373, 0.0, 1.0)),
59 => Some(RGBA::new(0.373, 0.373, 0.373, 1.0)),
60 => Some(RGBA::new(0.373, 0.373, 0.529, 1.0)),
61 => Some(RGBA::new(0.373, 0.373, 0.686, 1.0)),
62 => Some(RGBA::new(0.373, 0.373, 0.843, 1.0)),
63 => Some(RGBA::new(0.373, 0.373, 1.0, 1.0)),
64 => Some(RGBA::new(0.373, 0.529, 0.0, 1.0)),
65 => Some(RGBA::new(0.373, 0.529, 0.373, 1.0)),
66 => Some(RGBA::new(0.373, 0.529, 0.529, 1.0)),
67 => Some(RGBA::new(0.373, 0.529, 0.686, 1.0)),
68 => Some(RGBA::new(0.373, 0.529, 0.843, 1.0)),
69 => Some(RGBA::new(0.373, 0.529, 1.0, 1.0)),
70 => Some(RGBA::new(0.373, 0.686, 0.0, 1.0)),
71 => Some(RGBA::new(0.373, 0.686, 0.373, 1.0)),
72 => Some(RGBA::new(0.373, 0.686, 0.529, 1.0)),
73 => Some(RGBA::new(0.373, 0.686, 0.686, 1.0)),
74 => Some(RGBA::new(0.373, 0.686, 0.843, 1.0)),
75 => Some(RGBA::new(0.373, 0.686, 1.0, 1.0)),
76 => Some(RGBA::new(0.373, 0.843, 0.0, 1.0)),
77 => Some(RGBA::new(0.373, 0.843, 0.373, 1.0)),
78 => Some(RGBA::new(0.373, 0.843, 0.529, 1.0)),
79 => Some(RGBA::new(0.373, 0.843, 0.686, 1.0)),
80 => Some(RGBA::new(0.373, 0.843, 0.843, 1.0)),
81 => Some(RGBA::new(0.373, 0.843, 1.0, 1.0)),
82 => Some(RGBA::new(0.373, 1.0, 0.0, 1.0)),
83 => Some(RGBA::new(0.373, 1.0, 0.373, 1.0)),
84 => Some(RGBA::new(0.373, 1.0, 0.529, 1.0)),
85 => Some(RGBA::new(0.373, 1.0, 0.686, 1.0)),
86 => Some(RGBA::new(0.373, 1.0, 0.843, 1.0)),
87 => Some(RGBA::new(0.373, 1.0, 1.0, 1.0)),
88 => Some(RGBA::new(0.529, 0.0, 0.0, 1.0)),
89 => Some(RGBA::new(0.529, 0.0, 0.373, 1.0)),
90 => Some(RGBA::new(0.529, 0.0, 0.529, 1.0)),
91 => Some(RGBA::new(0.529, 0.0, 0.686, 1.0)),
92 => Some(RGBA::new(0.529, 0.0, 0.843, 1.0)),
93 => Some(RGBA::new(0.529, 0.0, 1.0, 1.0)),
94 => Some(RGBA::new(0.529, 0.373, 0.0, 1.0)),
95 => Some(RGBA::new(0.529, 0.373, 0.373, 1.0)),
96 => Some(RGBA::new(0.529, 0.373, 0.529, 1.0)),
97 => Some(RGBA::new(0.529, 0.373, 0.686, 1.0)),
98 => Some(RGBA::new(0.529, 0.373, 0.843, 1.0)),
99 => Some(RGBA::new(0.529, 0.373, 1.0, 1.0)),
100 => Some(RGBA::new(0.529, 0.529, 0.0, 1.0)),
101 => Some(RGBA::new(0.529, 0.529, 0.373, 1.0)),
102 => Some(RGBA::new(0.529, 0.529, 0.529, 1.0)),
103 => Some(RGBA::new(0.529, 0.529, 0.686, 1.0)),
104 => Some(RGBA::new(0.529, 0.529, 0.843, 1.0)),
105 => Some(RGBA::new(0.529, 0.529, 1.0, 1.0)),
106 => Some(RGBA::new(0.533, 0.686, 0.0, 1.0)),
107 => Some(RGBA::new(0.533, 0.686, 0.373, 1.0)),
108 => Some(RGBA::new(0.533, 0.686, 0.529, 1.0)),
109 => Some(RGBA::new(0.533, 0.686, 0.686, 1.0)),
110 => Some(RGBA::new(0.533, 0.686, 0.843, 1.0)),
111 => Some(RGBA::new(0.533, 0.686, 1.0, 1.0)),
112 => Some(RGBA::new(0.533, 0.843, 0.0, 1.0)),
113 => Some(RGBA::new(0.533, 0.843, 0.373, 1.0)),
114 => Some(RGBA::new(0.533, 0.843, 0.529, 1.0)),
115 => Some(RGBA::new(0.533, 0.843, 0.686, 1.0)),
116 => Some(RGBA::new(0.533, 0.843, 0.843, 1.0)),
117 => Some(RGBA::new(0.533, 0.843, 1.0, 1.0)),
118 => Some(RGBA::new(0.533, 1.0, 0.0, 1.0)),
119 => Some(RGBA::new(0.533, 1.0, 0.373, 1.0)),
120 => Some(RGBA::new(0.533, 1.0, 0.529, 1.0)),
121 => Some(RGBA::new(0.533, 1.0, 0.686, 1.0)),
122 => Some(RGBA::new(0.533, 1.0, 0.843, 1.0)),
123 => Some(RGBA::new(0.533, 1.0, 1.0, 1.0)),
124 => Some(RGBA::new(0.686, 0.0, 0.0, 1.0)),
125 => Some(RGBA::new(0.686, 0.0, 0.373, 1.0)),
126 => Some(RGBA::new(0.686, 0.0, 0.529, 1.0)),
127 => Some(RGBA::new(0.686, 0.0, 0.686, 1.0)),
128 => Some(RGBA::new(0.686, 0.0, 0.843, 1.0)),
129 => Some(RGBA::new(0.686, 0.0, 1.0, 1.0)),
130 => Some(RGBA::new(0.686, 0.373, 0.0, 1.0)),
131 => Some(RGBA::new(0.686, 0.373, 0.373, 1.0)),
132 => Some(RGBA::new(0.686, 0.373, 0.529, 1.0)),
133 => Some(RGBA::new(0.686, 0.373, 0.686, 1.0)),
134 => Some(RGBA::new(0.686, 0.373, 0.843, 1.0)),
135 => Some(RGBA::new(0.686, 0.373, 1.0, 1.0)),
136 => Some(RGBA::new(0.686, 0.529, 0.0, 1.0)),
137 => Some(RGBA::new(0.686, 0.529, 0.373, 1.0)),
138 => Some(RGBA::new(0.686, 0.529, 0.529, 1.0)),
139 => Some(RGBA::new(0.686, 0.529, 0.686, 1.0)),
140 => Some(RGBA::new(0.686, 0.529, 0.843, 1.0)),
141 => Some(RGBA::new(0.686, 0.529, 1.0, 1.0)),
142 => Some(RGBA::new(0.686, 0.686, 0.0, 1.0)),
143 => Some(RGBA::new(0.686, 0.686, 0.373, 1.0)),
144 => Some(RGBA::new(0.686, 0.686, 0.529, 1.0)),
145 => Some(RGBA::new(0.686, 0.686, 0.686, 1.0)),
146 => Some(RGBA::new(0.686, 0.686, 0.843, 1.0)),
147 => Some(RGBA::new(0.686, 0.686, 1.0, 1.0)),
148 => Some(RGBA::new(0.686, 0.843, 0.0, 1.0)),
149 => Some(RGBA::new(0.686, 0.843, 0.373, 1.0)),
150 => Some(RGBA::new(0.686, 0.843, 0.529, 1.0)),
151 => Some(RGBA::new(0.686, 0.843, 0.686, 1.0)),
152 => Some(RGBA::new(0.686, 0.843, 0.843, 1.0)),
153 => Some(RGBA::new(0.686, 0.843, 1.0, 1.0)),
154 => Some(RGBA::new(0.686, 1.0, 0.0, 1.0)),
155 => Some(RGBA::new(0.686, 1.0, 0.373, 1.0)),
156 => Some(RGBA::new(0.686, 1.0, 0.529, 1.0)),
157 => Some(RGBA::new(0.686, 1.0, 0.686, 1.0)),
158 => Some(RGBA::new(0.686, 1.0, 0.843, 1.0)),
159 => Some(RGBA::new(0.686, 1.0, 1.0, 1.0)),
160 => Some(RGBA::new(0.847, 0.0, 0.0, 1.0)),
161 => Some(RGBA::new(0.847, 0.0, 0.373, 1.0)),
162 => Some(RGBA::new(0.847, 0.0, 0.529, 1.0)),
163 => Some(RGBA::new(0.847, 0.0, 0.686, 1.0)),
164 => Some(RGBA::new(0.847, 0.0, 0.843, 1.0)),
165 => Some(RGBA::new(0.847, 0.0, 1.0, 1.0)),
166 => Some(RGBA::new(0.847, 0.373, 0.0, 1.0)),
167 => Some(RGBA::new(0.847, 0.373, 0.373, 1.0)),
168 => Some(RGBA::new(0.847, 0.373, 0.529, 1.0)),
169 => Some(RGBA::new(0.847, 0.373, 0.686, 1.0)),
170 => Some(RGBA::new(0.847, 0.373, 0.843, 1.0)),
171 => Some(RGBA::new(0.847, 0.373, 1.0, 1.0)),
172 => Some(RGBA::new(0.847, 0.529, 0.0, 1.0)),
173 => Some(RGBA::new(0.847, 0.529, 0.373, 1.0)),
174 => Some(RGBA::new(0.847, 0.529, 0.529, 1.0)),
175 => Some(RGBA::new(0.847, 0.529, 0.686, 1.0)),
176 => Some(RGBA::new(0.847, 0.529, 0.843, 1.0)),
177 => Some(RGBA::new(0.847, 0.529, 1.0, 1.0)),
178 => Some(RGBA::new(0.847, 0.686, 0.0, 1.0)),
179 => Some(RGBA::new(0.847, 0.686, 0.373, 1.0)),
180 => Some(RGBA::new(0.847, 0.686, 0.529, 1.0)),
181 => Some(RGBA::new(0.847, 0.686, 0.686, 1.0)),
182 => Some(RGBA::new(0.847, 0.686, 0.843, 1.0)),
183 => Some(RGBA::new(0.847, 0.686, 1.0, 1.0)),
184 => Some(RGBA::new(0.847, 0.843, 0.0, 1.0)),
185 => Some(RGBA::new(0.847, 0.843, 0.373, 1.0)),
186 => Some(RGBA::new(0.847, 0.843, 0.529, 1.0)),
187 => Some(RGBA::new(0.847, 0.843, 0.686, 1.0)),
188 => Some(RGBA::new(0.847, 0.843, 0.843, 1.0)),
189 => Some(RGBA::new(0.847, 0.843, 1.0, 1.0)),
190 => Some(RGBA::new(0.847, 1.0, 0.0, 1.0)),
191 => Some(RGBA::new(0.847, 1.0, 0.373, 1.0)),
192 => Some(RGBA::new(0.847, 1.0, 0.529, 1.0)),
193 => Some(RGBA::new(0.847, 1.0, 0.686, 1.0)),
194 => Some(RGBA::new(0.847, 1.0, 0.843, 1.0)),
195 => Some(RGBA::new(0.847, 1.0, 1.0, 1.0)),
196 => Some(RGBA::new(1.0, 0.0, 0.0, 1.0)),
197 => Some(RGBA::new(1.0, 0.0, 0.373, 1.0)),
198 => Some(RGBA::new(1.0, 0.0, 0.529, 1.0)),
199 => Some(RGBA::new(1.0, 0.0, 0.686, 1.0)),
200 => Some(RGBA::new(1.0, 0.0, 0.843, 1.0)),
201 => Some(RGBA::new(1.0, 0.0, 1.0, 1.0)),
202 => Some(RGBA::new(1.0, 0.373, 0.0, 1.0)),
203 => Some(RGBA::new(1.0, 0.373, 0.373, 1.0)),
204 => Some(RGBA::new(1.0, 0.373, 0.529, 1.0)),
205 => Some(RGBA::new(1.0, 0.373, 0.686, 1.0)),
206 => Some(RGBA::new(1.0, 0.373, 0.843, 1.0)),
207 => Some(RGBA::new(1.0, 0.373, 1.0, 1.0)),
208 => Some(RGBA::new(1.0, 0.529, 0.0, 1.0)),
209 => Some(RGBA::new(1.0, 0.529, 0.373, 1.0)),
210 => Some(RGBA::new(1.0, 0.529, 0.529, 1.0)),
211 => Some(RGBA::new(1.0, 0.529, 0.686, 1.0)),
212 => Some(RGBA::new(1.0, 0.529, 0.843, 1.0)),
213 => Some(RGBA::new(1.0, 0.529, 1.0, 1.0)),
214 => Some(RGBA::new(1.0, 0.686, 0.0, 1.0)),
215 => Some(RGBA::new(1.0, 0.686, 0.373, 1.0)),
216 => Some(RGBA::new(1.0, 0.686, 0.529, 1.0)),
217 => Some(RGBA::new(1.0, 0.686, 0.686, 1.0)),
218 => Some(RGBA::new(1.0, 0.686, 0.843, 1.0)),
219 => Some(RGBA::new(1.0, 0.686, 1.0, 1.0)),
220 => Some(RGBA::new(1.0, 0.843, 0.0, 1.0)),
221 => Some(RGBA::new(1.0, 0.843, 0.373, 1.0)),
222 => Some(RGBA::new(1.0, 0.843, 0.529, 1.0)),
223 => Some(RGBA::new(1.0, 0.843, 0.686, 1.0)),
224 => Some(RGBA::new(1.0, 0.843, 0.843, 1.0)),
225 => Some(RGBA::new(1.0, 0.843, 1.0, 1.0)),
226 => Some(RGBA::new(1.0, 1.0, 0.0, 1.0)),
227 => Some(RGBA::new(1.0, 1.0, 0.373, 1.0)),
228 => Some(RGBA::new(1.0, 1.0, 0.529, 1.0)),
229 => Some(RGBA::new(1.0, 1.0, 0.686, 1.0)),
230 => Some(RGBA::new(1.0, 1.0, 0.843, 1.0)),
231 => Some(RGBA::new(1.0, 1.0, 1.0, 1.0)),
232 => Some(RGBA::new(0.031, 0.031, 0.031, 1.0)),
233 => Some(RGBA::new(0.071, 0.071, 0.071, 1.0)),
234 => Some(RGBA::new(0.110, 0.110, 0.110, 1.0)),
235 => Some(RGBA::new(0.149, 0.149, 0.149, 1.0)),
236 => Some(RGBA::new(0.188, 0.188, 0.188, 1.0)),
237 => Some(RGBA::new(0.227, 0.227, 0.227, 1.0)),
238 => Some(RGBA::new(0.267, 0.267, 0.267, 1.0)),
239 => Some(RGBA::new(0.306, 0.306, 0.306, 1.0)),
240 => Some(RGBA::new(0.345, 0.345, 0.345, 1.0)),
241 => Some(RGBA::new(0.384, 0.384, 0.384, 1.0)),
242 => Some(RGBA::new(0.424, 0.424, 0.424, 1.0)),
243 => Some(RGBA::new(0.462, 0.462, 0.462, 1.0)),
244 => Some(RGBA::new(0.502, 0.502, 0.502, 1.0)),
245 => Some(RGBA::new(0.541, 0.541, 0.541, 1.0)),
246 => Some(RGBA::new(0.580, 0.580, 0.580, 1.0)),
247 => Some(RGBA::new(0.620, 0.620, 0.620, 1.0)),
248 => Some(RGBA::new(0.659, 0.659, 0.659, 1.0)),
249 => Some(RGBA::new(0.694, 0.694, 0.694, 1.0)),
250 => Some(RGBA::new(0.733, 0.733, 0.733, 1.0)),
251 => Some(RGBA::new(0.777, 0.777, 0.777, 1.0)),
252 => Some(RGBA::new(0.816, 0.816, 0.816, 1.0)),
253 => Some(RGBA::new(0.855, 0.855, 0.855, 1.0)),
254 => Some(RGBA::new(0.890, 0.890, 0.890, 1.0)),
255 => Some(RGBA::new(0.933, 0.933, 0.933, 1.0)),
_ => None,
}
}

View file

@ -1,152 +0,0 @@
pub mod error;
mod tag;
pub use error::Error;
use tag::Tag;
use adw::StyleManager;
use gtk::{
TextTag,
gdk::RGBA,
pango::{Style, Underline},
prelude::TextTagExt,
};
use syntect::{
easy::HighlightLines,
highlighting::{Color, FontStyle, ThemeSet},
parsing::{SyntaxReference, SyntaxSet},
};
/* Default theme
@TODO make optional
base16-ocean.dark
base16-eighties.dark
base16-mocha.dark
base16-ocean.light
InspiredGitHub
Solarized (dark)
Solarized (light)
*/
pub const DEFAULT_THEME_DARK: &str = "base16-eighties.dark";
pub const DEFAULT_THEME_LIGHT: &str = "InspiredGitHub";
pub struct Syntax {
syntax_set: SyntaxSet,
theme_set: ThemeSet,
}
impl Default for Syntax {
fn default() -> Self {
Self::new()
}
}
impl Syntax {
// Constructors
/// Create new `Self`
pub fn new() -> Self {
Self {
syntax_set: SyntaxSet::load_defaults_newlines(),
theme_set: ThemeSet::load_defaults(),
}
}
// Actions
/// Apply `Syntect` highlight to new buffer returned,
/// according to given `alt` and `source_code` content
pub fn highlight(
&self,
source_code: &str,
alt: Option<&String>,
) -> Result<Vec<(TextTag, String)>, Error> {
if let Some(value) = alt {
if let Some(reference) = self.syntax_set.find_syntax_by_name(value) {
return self.buffer(source_code, reference);
}
if let Some(reference) = self.syntax_set.find_syntax_by_token(value) {
return self.buffer(source_code, reference);
}
if let Some(reference) = self.syntax_set.find_syntax_by_path(value) {
return self.buffer(source_code, reference);
}
}
if let Some(reference) = self.syntax_set.find_syntax_by_first_line(source_code) {
return self.buffer(source_code, reference);
}
Err(Error::Parse)
}
fn buffer(
&self,
source: &str,
syntax_reference: &SyntaxReference,
) -> Result<Vec<(TextTag, String)>, Error> {
// Init new line buffer
let mut buffer = Vec::new();
// Apply syntect decorator
let mut ranges = HighlightLines::new(
syntax_reference,
&self.theme_set.themes[if StyleManager::default().is_dark() {
DEFAULT_THEME_DARK
} else {
DEFAULT_THEME_LIGHT
}], // @TODO apply on env change
);
match ranges.highlight_line(source, &self.syntax_set) {
Ok(result) => {
// Build tags
for (style, entity) in result {
// Create new tag from default preset
let tag = Tag::new();
// Tuneup using syntect conversion
// tag.set_background_rgba(Some(&color_to_rgba(style.background)));
tag.text_tag
.set_foreground_rgba(Some(&color_to_rgba(style.foreground)));
tag.text_tag
.set_style(font_style_to_style(style.font_style));
tag.text_tag
.set_underline(font_style_to_underline(style.font_style));
// Append
buffer.push((tag.text_tag, entity.to_string()));
}
Ok(buffer)
}
Err(e) => Err(Error::Syntect(e)),
}
}
}
// Tools
fn color_to_rgba(color: Color) -> RGBA {
RGBA::new(
color.r as f32 / 255.0,
color.g as f32 / 255.0,
color.b as f32 / 255.0,
color.a as f32 / 255.0,
)
}
fn font_style_to_style(font_style: FontStyle) -> Style {
match font_style {
FontStyle::ITALIC => Style::Italic,
_ => Style::Normal,
}
}
fn font_style_to_underline(font_style: FontStyle) -> Underline {
match font_style {
FontStyle::UNDERLINE => Underline::Single,
_ => Underline::None,
}
}

View file

@ -1,18 +0,0 @@
use std::fmt::{Display, Formatter, Result};
#[derive(Debug)]
pub enum Error {
Parse,
Syntect(syntect::Error),
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter) -> Result {
match self {
Self::Parse => write!(f, "Parse error"),
Self::Syntect(e) => {
write!(f, "Syntect error: {e}")
}
}
}
}