mirror of
https://github.com/YGGverse/Yoda.git
synced 2026-03-31 16:45:27 +00:00
draft basic multi-line code tags impl
This commit is contained in:
parent
d674edc7d0
commit
a8d25e695f
3 changed files with 119 additions and 4 deletions
|
|
@ -6,7 +6,6 @@ mod tags;
|
||||||
|
|
||||||
use super::{ItemAction, WindowAction};
|
use super::{ItemAction, WindowAction};
|
||||||
use crate::app::browser::window::action::Position;
|
use crate::app::browser::window::action::Position;
|
||||||
pub use error::Error;
|
|
||||||
use gtk::{
|
use gtk::{
|
||||||
EventControllerMotion, GestureClick, TextBuffer, TextTag, TextTagTable, TextView,
|
EventControllerMotion, GestureClick, TextBuffer, TextTag, TextTagTable, TextView,
|
||||||
TextWindowType, UriLauncher, Window, WrapMode,
|
TextWindowType, UriLauncher, Window, WrapMode,
|
||||||
|
|
@ -56,7 +55,7 @@ impl Markdown {
|
||||||
let syntax = Syntax::new();
|
let syntax = Syntax::new();
|
||||||
|
|
||||||
// Init tags
|
// Init tags
|
||||||
let tags = Tags::new();
|
let mut tags = Tags::new();
|
||||||
|
|
||||||
// Init new text buffer
|
// Init new text buffer
|
||||||
let buffer = TextBuffer::new(Some(&TextTagTable::new()));
|
let buffer = TextBuffer::new(Some(&TextTagTable::new()));
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
mod bold;
|
mod bold;
|
||||||
mod header;
|
mod header;
|
||||||
|
mod pre;
|
||||||
mod quote;
|
mod quote;
|
||||||
mod reference;
|
mod reference;
|
||||||
mod strike;
|
mod strike;
|
||||||
|
|
@ -10,6 +11,7 @@ use std::collections::HashMap;
|
||||||
use bold::Bold;
|
use bold::Bold;
|
||||||
use gtk::{TextBuffer, TextTag, gdk::RGBA, glib::Uri};
|
use gtk::{TextBuffer, TextTag, gdk::RGBA, glib::Uri};
|
||||||
use header::Header;
|
use header::Header;
|
||||||
|
use pre::Pre;
|
||||||
use quote::Quote;
|
use quote::Quote;
|
||||||
use strike::Strike;
|
use strike::Strike;
|
||||||
use underline::Underline;
|
use underline::Underline;
|
||||||
|
|
@ -17,6 +19,7 @@ use underline::Underline;
|
||||||
pub struct Tags {
|
pub struct Tags {
|
||||||
pub bold: Bold,
|
pub bold: Bold,
|
||||||
pub header: Header,
|
pub header: Header,
|
||||||
|
pub pre: Pre,
|
||||||
pub quote: Quote,
|
pub quote: Quote,
|
||||||
pub strike: Strike,
|
pub strike: Strike,
|
||||||
pub underline: Underline,
|
pub underline: Underline,
|
||||||
|
|
@ -34,19 +37,23 @@ impl Tags {
|
||||||
Self {
|
Self {
|
||||||
bold: Bold::new(),
|
bold: Bold::new(),
|
||||||
header: Header::new(),
|
header: Header::new(),
|
||||||
|
pre: Pre::new(),
|
||||||
quote: Quote::new(),
|
quote: Quote::new(),
|
||||||
strike: Strike::new(),
|
strike: Strike::new(),
|
||||||
underline: Underline::new(),
|
underline: Underline::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn render(
|
pub fn render(
|
||||||
&self,
|
&mut self,
|
||||||
buffer: &TextBuffer,
|
buffer: &TextBuffer,
|
||||||
base: &Uri,
|
base: &Uri,
|
||||||
link_color: &RGBA,
|
link_color: &RGBA,
|
||||||
links: &mut HashMap<TextTag, Uri>,
|
links: &mut HashMap<TextTag, Uri>,
|
||||||
) -> Option<String> {
|
) -> Option<String> {
|
||||||
// * keep in order!
|
// Collect all preformatted blocks first, and replace them with tmp macro ID
|
||||||
|
self.pre.collect(buffer);
|
||||||
|
|
||||||
|
// Keep in order!
|
||||||
let title = self.header.render(buffer);
|
let title = self.header.render(buffer);
|
||||||
|
|
||||||
self.quote.render(buffer);
|
self.quote.render(buffer);
|
||||||
|
|
@ -59,6 +66,9 @@ impl Tags {
|
||||||
reference::render_images(&buffer, base, &link_color, links);
|
reference::render_images(&buffer, base, &link_color, links);
|
||||||
reference::render_links(&buffer, base, &link_color, links);
|
reference::render_links(&buffer, base, &link_color, links);
|
||||||
|
|
||||||
|
self.pre.render(buffer);
|
||||||
|
|
||||||
|
// Format document title string
|
||||||
title.map(|mut s| {
|
title.map(|mut s| {
|
||||||
s = bold::strip_tags(&s);
|
s = bold::strip_tags(&s);
|
||||||
s = reference::strip_tags(&s);
|
s = reference::strip_tags(&s);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
use gtk::{
|
||||||
|
TextBuffer, TextSearchFlags, TextTag,
|
||||||
|
WrapMode::Word,
|
||||||
|
glib::{GString, uuid_string_random},
|
||||||
|
prelude::{TextBufferExt, TextBufferExtManual},
|
||||||
|
};
|
||||||
|
use regex::Regex;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
const REGEX_PRE: &str = r"(?s)```[ \t]*(?P<alt>.*?)\n(?P<data>.*?)```";
|
||||||
|
|
||||||
|
struct Entry {
|
||||||
|
alt: Option<String>,
|
||||||
|
data: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Pre {
|
||||||
|
index: HashMap<GString, Entry>,
|
||||||
|
tag: TextTag,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pre {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
index: HashMap::new(),
|
||||||
|
tag: TextTag::builder().wrap_mode(Word).build(), // @TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Collect all preformatted blocks into `Self.index` (to prevent formatting)
|
||||||
|
pub fn collect(&mut self, buffer: &TextBuffer) {
|
||||||
|
let (start, end) = buffer.bounds();
|
||||||
|
let full_content = buffer.text(&start, &end, true).to_string();
|
||||||
|
|
||||||
|
let matches: Vec<_> = Regex::new(REGEX_PRE)
|
||||||
|
.unwrap()
|
||||||
|
.captures_iter(&full_content)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
for cap in matches.into_iter().rev() {
|
||||||
|
let id = uuid_string_random();
|
||||||
|
|
||||||
|
let full_match = cap.get(0).unwrap();
|
||||||
|
|
||||||
|
let start_char_offset = full_content[..full_match.start()].chars().count() as i32;
|
||||||
|
let end_char_offset = full_content[..full_match.end()].chars().count() as i32;
|
||||||
|
|
||||||
|
let mut start_iter = buffer.iter_at_offset(start_char_offset);
|
||||||
|
let mut end_iter = buffer.iter_at_offset(end_char_offset);
|
||||||
|
|
||||||
|
buffer.delete(&mut start_iter, &mut end_iter);
|
||||||
|
|
||||||
|
buffer.insert_with_tags(&mut start_iter, &id, &[]);
|
||||||
|
assert!(
|
||||||
|
self.index
|
||||||
|
.insert(
|
||||||
|
id,
|
||||||
|
Entry {
|
||||||
|
alt: alt(cap["alt"].into()).map(|s| s.into()),
|
||||||
|
data: cap["data"].into(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.is_none()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply preformatted `Tag` to given `TextBuffer` using `Self.index`
|
||||||
|
pub fn render(&mut self, buffer: &TextBuffer) {
|
||||||
|
assert!(buffer.tag_table().add(&self.tag));
|
||||||
|
for (k, v) in self.index.iter() {
|
||||||
|
while let Some((mut m_start, mut m_end)) =
|
||||||
|
buffer
|
||||||
|
.start_iter()
|
||||||
|
.forward_search(k, TextSearchFlags::VISIBLE_ONLY, None)
|
||||||
|
{
|
||||||
|
buffer.delete(&mut m_start, &mut m_end);
|
||||||
|
|
||||||
|
let alt_text = v.alt.as_deref().unwrap_or("");
|
||||||
|
let display_text = format!("{} |\n {}", alt_text, v.data);
|
||||||
|
|
||||||
|
buffer.insert_with_tags(&mut m_start, &display_text, &[&self.tag]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alt(value: Option<&str>) -> Option<&str> {
|
||||||
|
value.map(|m| m.trim()).filter(|s| !s.is_empty())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_regex() {
|
||||||
|
let cap: Vec<_> = Regex::new(REGEX_PRE)
|
||||||
|
.unwrap()
|
||||||
|
.captures_iter("Some ``` alt text\ncode line 1\ncode line 2``` and ```\ncode line 3\ncode line 4``` with ")
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let first = cap.get(0).unwrap();
|
||||||
|
assert_eq!(alt(first.name("alt").map(|m| m.as_str())), Some("alt text"));
|
||||||
|
assert_eq!(&first["data"], "code line 1\ncode line 2");
|
||||||
|
|
||||||
|
let second = cap.get(1).unwrap();
|
||||||
|
assert_eq!(alt(second.name("alt").map(|m| m.as_str())), None);
|
||||||
|
assert_eq!(&second["data"], "code line 3\ncode line 4");
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue