mirror of
https://github.com/YGGverse/Yoda.git
synced 2026-03-31 16:45:27 +00:00
implement link, linked images, and images parser; temporarily disable header impl
This commit is contained in:
parent
5675809320
commit
9843d49326
2 changed files with 183 additions and 108 deletions
|
|
@ -19,8 +19,6 @@ use gtk::{
|
|||
};
|
||||
use gutter::Gutter;
|
||||
use icon::Icon;
|
||||
use reference::Reference;
|
||||
use regex::Regex;
|
||||
use sourceview::prelude::{ActionExt, ActionMapExt, DisplayExt, ToVariant};
|
||||
use std::{cell::Cell, collections::HashMap, rc::Rc};
|
||||
use syntax::Syntax;
|
||||
|
|
@ -53,7 +51,7 @@ impl Markdown {
|
|||
let hover: Rc<Cell<Option<TextTag>>> = Rc::new(Cell::new(None));
|
||||
|
||||
// Init code features
|
||||
let mut code = None;
|
||||
//let mut code = None;
|
||||
|
||||
// Init quote icon feature
|
||||
let mut is_line_after_quote = false;
|
||||
|
|
@ -76,6 +74,7 @@ impl Markdown {
|
|||
|
||||
// Init new text buffer
|
||||
let buffer = TextBuffer::new(Some(&tag.text_tag_table));
|
||||
buffer.set_text(markdown);
|
||||
|
||||
// Init main widget
|
||||
let text_view = {
|
||||
|
|
@ -109,8 +108,15 @@ impl Markdown {
|
|||
t == 0 || t.is_multiple_of(2)
|
||||
};
|
||||
|
||||
// Parse in-line markdown tags
|
||||
// * keep order!
|
||||
|
||||
reference::image_link(&buffer, &tag, base, &link_color.0, &mut links);
|
||||
reference::image(&buffer, &tag, base, &link_color.0, &mut links);
|
||||
reference::link(&buffer, &tag, base, &link_color.0, &mut links);
|
||||
|
||||
// Parse single-line markdown tags
|
||||
'l: for line in markdown.lines() {
|
||||
/*'l: for line in markdown.lines() {
|
||||
if is_code_enabled {
|
||||
use ggemtext::line::Code;
|
||||
match code {
|
||||
|
|
@ -254,12 +260,7 @@ impl Markdown {
|
|||
// just append plain text covered in empty tag (to handle controller events properly)
|
||||
buffer.insert_with_tags(&mut buffer.end_iter(), line, &[&tag.plain]);
|
||||
buffer.insert(&mut buffer.end_iter(), NEW_LINE);
|
||||
}
|
||||
|
||||
// Parse in-line markdown tags
|
||||
|
||||
image_link(&buffer, &tag, base, &link_color.0, &mut links);
|
||||
link(&buffer, &tag, base, &link_color.0, &mut links);
|
||||
}*/
|
||||
|
||||
// Context menu
|
||||
let action_link_tab =
|
||||
|
|
@ -557,91 +558,6 @@ fn link_prefix(request: String, prefix: &str) -> String {
|
|||
format!("{prefix}{}", request.trim_start_matches(prefix))
|
||||
}
|
||||
|
||||
/// Link
|
||||
fn image_link(
|
||||
buffer: &TextBuffer,
|
||||
tag: &Tag,
|
||||
base: &Uri,
|
||||
link_color: &RGBA,
|
||||
links: &mut HashMap<TextTag, Uri>,
|
||||
) {
|
||||
let start_iter = buffer.start_iter();
|
||||
let end_iter = buffer.end_iter();
|
||||
let full_content = buffer.text(&start_iter, &end_iter, true).to_string();
|
||||
|
||||
buffer.set_text("");
|
||||
|
||||
let mut last_pos = 0;
|
||||
for cap in Regex::new(r"(?P<full_match>\[(?P<is_img>!|)?\[(?P<alt>[^\]]+)\]\((?P<img_url>[^\)]+)\)\]\((?P<link_url>[^\)]+)\))")
|
||||
.unwrap()
|
||||
.captures_iter(&full_content)
|
||||
{
|
||||
let full_match = cap.get(0).unwrap();
|
||||
let before = &full_content[last_pos..full_match.start()];
|
||||
if !before.is_empty() {
|
||||
buffer.insert(&mut buffer.end_iter(), before);
|
||||
}
|
||||
if let Some(link) = Reference::parse(
|
||||
&cap["link_url"],
|
||||
None,
|
||||
base,
|
||||
) {
|
||||
link.into_buffer(buffer,
|
||||
link_color,
|
||||
tag,
|
||||
links)
|
||||
}
|
||||
last_pos = full_match.end();
|
||||
}
|
||||
let after = &full_content[last_pos..];
|
||||
if !after.is_empty() {
|
||||
buffer.insert(&mut buffer.end_iter(), after);
|
||||
}
|
||||
}
|
||||
|
||||
/// Link
|
||||
fn link(
|
||||
buffer: &TextBuffer,
|
||||
tag: &Tag,
|
||||
base: &Uri,
|
||||
link_color: &RGBA,
|
||||
links: &mut HashMap<TextTag, Uri>,
|
||||
) {
|
||||
let start_iter = buffer.start_iter();
|
||||
let end_iter = buffer.end_iter();
|
||||
let full_content = buffer.text(&start_iter, &end_iter, true).to_string();
|
||||
|
||||
buffer.set_text("");
|
||||
|
||||
let mut last_pos = 0;
|
||||
for cap in Regex::new(r"(?P<is_img>!)\[(?P<text>[^\]]+)\]\((?P<url>[^\)]+)\)")
|
||||
.unwrap()
|
||||
.captures_iter(&full_content)
|
||||
{
|
||||
let full_match = cap.get(0).unwrap();
|
||||
let before = &full_content[last_pos..full_match.start()];
|
||||
if !before.is_empty() {
|
||||
buffer.insert(&mut buffer.end_iter(), before);
|
||||
}
|
||||
if let Some(link) = Reference::parse(
|
||||
&cap["url"],
|
||||
if cap["text"].is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(&cap["text"])
|
||||
},
|
||||
base,
|
||||
) {
|
||||
link.into_buffer(buffer, link_color, tag, links)
|
||||
}
|
||||
last_pos = full_match.end();
|
||||
}
|
||||
let after = &full_content[last_pos..];
|
||||
if !after.is_empty() {
|
||||
buffer.insert(&mut buffer.end_iter(), after);
|
||||
}
|
||||
}
|
||||
|
||||
/// Header tag
|
||||
fn header(buffer: &TextBuffer, tag: &TextTag, line: &str, pattern: &str) -> Option<String> {
|
||||
if let Some(h) = line.trim_start().strip_prefix(pattern)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,16 @@
|
|||
use gtk::glib::{Uri, UriFlags};
|
||||
use super::Tag;
|
||||
use gtk::{
|
||||
TextBuffer, TextIter, TextTag, WrapMode,
|
||||
gdk::RGBA,
|
||||
glib::{Uri, UriFlags},
|
||||
prelude::{TextBufferExt, TextBufferExtManual},
|
||||
};
|
||||
use regex::Regex;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub const REGEX_LINK: &str = r"\[(?P<text>[^\]]+)\]\((?P<url>[^\)]+)\)";
|
||||
|
||||
pub const REGEX_IMAGE_LINK: &str =
|
||||
const REGEX_LINK: &str = r"\[(?P<text>[^\]]+)\]\((?P<url>[^\)]+)\)";
|
||||
const REGEX_IMAGE: &str = r"!\[(?P<alt>[^\]]+)\]\((?P<url>[^\)]+)\)";
|
||||
const REGEX_IMAGE_LINK: &str =
|
||||
r"\[(?P<is_img>!)\[(?P<alt>[^\]]+)\]\((?P<img_url>[^\)]+)\)\]\((?P<link_url>[^\)]+)\)";
|
||||
|
||||
pub struct Reference {
|
||||
|
|
@ -11,7 +19,7 @@ pub struct Reference {
|
|||
}
|
||||
|
||||
impl Reference {
|
||||
pub fn parse(address: &str, alt: Option<&str>, base: &Uri) -> Option<Self> {
|
||||
fn parse(address: &str, alt: Option<&str>, base: &Uri) -> Option<Self> {
|
||||
// Convert address to the valid URI,
|
||||
// resolve to absolute URL format if the target is relative
|
||||
match Uri::resolve_relative(
|
||||
|
|
@ -55,16 +63,15 @@ impl Reference {
|
|||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
pub fn into_buffer(
|
||||
fn into_buffer(
|
||||
self,
|
||||
buffer: >k::TextBuffer,
|
||||
position: &mut gtk::TextIter,
|
||||
link_color: >k::gdk::RGBA,
|
||||
buffer: &TextBuffer,
|
||||
position: &mut TextIter,
|
||||
link_color: &RGBA,
|
||||
tag: &super::Tag,
|
||||
is_annotation: bool,
|
||||
links: &mut std::collections::HashMap<gtk::TextTag, Uri>,
|
||||
links: &mut HashMap<TextTag, Uri>,
|
||||
) {
|
||||
use gtk::{TextTag, WrapMode, prelude::TextBufferExtManual};
|
||||
let a = if is_annotation {
|
||||
buffer.insert_with_tags(position, " ", &[]);
|
||||
TextTag::builder()
|
||||
|
|
@ -94,25 +101,151 @@ impl Reference {
|
|||
}
|
||||
}
|
||||
|
||||
/// Image links `[![]()]()`
|
||||
pub fn image_link(
|
||||
buffer: &TextBuffer,
|
||||
tag: &Tag,
|
||||
base: &Uri,
|
||||
link_color: &RGBA,
|
||||
links: &mut HashMap<TextTag, Uri>,
|
||||
) {
|
||||
let (start, end) = buffer.bounds();
|
||||
let full_content = buffer.text(&start, &end, true).to_string();
|
||||
|
||||
let matches: Vec<_> = Regex::new(REGEX_IMAGE_LINK)
|
||||
.unwrap()
|
||||
.captures_iter(&full_content)
|
||||
.collect();
|
||||
|
||||
for cap in matches.into_iter().rev() {
|
||||
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);
|
||||
|
||||
if let Some(reference) = Reference::parse(
|
||||
&cap["img_url"],
|
||||
if cap["alt"].is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(&cap["alt"])
|
||||
},
|
||||
base,
|
||||
) {
|
||||
reference.into_buffer(buffer, &mut start_iter, link_color, tag, false, links)
|
||||
}
|
||||
if let Some(reference) = Reference::parse(&cap["link_url"], Some("1"), base) {
|
||||
reference.into_buffer(buffer, &mut start_iter, link_color, tag, true, links)
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Image tags `![]()`
|
||||
pub fn image(
|
||||
buffer: &TextBuffer,
|
||||
tag: &Tag,
|
||||
base: &Uri,
|
||||
link_color: &RGBA,
|
||||
links: &mut HashMap<TextTag, Uri>,
|
||||
) {
|
||||
let (start, end) = buffer.bounds();
|
||||
let full_content = buffer.text(&start, &end, true).to_string();
|
||||
|
||||
let matches: Vec<_> = Regex::new(REGEX_IMAGE)
|
||||
.unwrap()
|
||||
.captures_iter(&full_content)
|
||||
.collect();
|
||||
|
||||
for cap in matches.into_iter().rev() {
|
||||
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);
|
||||
|
||||
if let Some(reference) = Reference::parse(
|
||||
&cap["url"],
|
||||
if cap["alt"].is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(&cap["alt"])
|
||||
},
|
||||
base,
|
||||
) {
|
||||
reference.into_buffer(buffer, &mut start_iter, link_color, tag, false, links)
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Links `[]()`
|
||||
pub fn link(
|
||||
buffer: &TextBuffer,
|
||||
tag: &Tag,
|
||||
base: &Uri,
|
||||
link_color: &RGBA,
|
||||
links: &mut HashMap<TextTag, Uri>,
|
||||
) {
|
||||
let (start, end) = buffer.bounds();
|
||||
let full_content = buffer.text(&start, &end, true).to_string();
|
||||
|
||||
let matches: Vec<_> = Regex::new(REGEX_LINK)
|
||||
.unwrap()
|
||||
.captures_iter(&full_content)
|
||||
.collect();
|
||||
|
||||
for cap in matches.into_iter().rev() {
|
||||
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);
|
||||
|
||||
if let Some(reference) = Reference::parse(
|
||||
&cap["url"],
|
||||
if cap["text"].is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(&cap["text"])
|
||||
},
|
||||
base,
|
||||
) {
|
||||
reference.into_buffer(buffer, &mut start_iter, link_color, tag, false, links)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_regex_link() {
|
||||
let cap: Vec<_> = regex::Regex::new(REGEX_LINK)
|
||||
let cap: Vec<_> = Regex::new(REGEX_LINK)
|
||||
.unwrap()
|
||||
.captures_iter(r#"[link1](https://link1.com) [link2](https://link2.com)"#)
|
||||
.collect();
|
||||
|
||||
let first = cap.get(0).unwrap();
|
||||
assert_eq!(&first[0], "[link1](https://link1.com)");
|
||||
assert_eq!(&first["text"], "link1");
|
||||
assert_eq!(&first["url"], "https://link1.com");
|
||||
|
||||
let second = cap.get(1).unwrap();
|
||||
assert_eq!(&second[0], "[link2](https://link2.com)");
|
||||
assert_eq!(&second["text"], "link2");
|
||||
assert_eq!(&second["url"], "https://link2.com");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_regex_image_link() {
|
||||
let cap: Vec<_> = regex::Regex::new(
|
||||
let cap: Vec<_> = Regex::new(
|
||||
REGEX_IMAGE_LINK,
|
||||
)
|
||||
.unwrap().captures_iter(
|
||||
|
|
@ -120,12 +253,38 @@ fn test_regex_image_link() {
|
|||
).collect();
|
||||
|
||||
let first = cap.get(0).unwrap();
|
||||
assert_eq!(
|
||||
&first[0],
|
||||
"[](https://image2.com)"
|
||||
);
|
||||
assert_eq!(&first["alt"], "image1");
|
||||
assert_eq!(&first["img_url"], "https://image1.com");
|
||||
assert_eq!(&first["link_url"], "https://image2.com");
|
||||
|
||||
let second = cap.get(1).unwrap();
|
||||
assert_eq!(
|
||||
&second[0],
|
||||
"[](https://image4.com)"
|
||||
);
|
||||
assert_eq!(&second["alt"], "image3");
|
||||
assert_eq!(&second["img_url"], "https://image3.com");
|
||||
assert_eq!(&second["link_url"], "https://image4.com");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_regex_image() {
|
||||
let cap: Vec<_> = Regex::new(REGEX_IMAGE)
|
||||
.unwrap()
|
||||
.captures_iter(r#" "#)
|
||||
.collect();
|
||||
|
||||
let first = cap.get(0).unwrap();
|
||||
assert_eq!(&first[0], "");
|
||||
assert_eq!(&first["alt"], "image1");
|
||||
assert_eq!(&first["url"], "https://image1.com");
|
||||
|
||||
let second = cap.get(1).unwrap();
|
||||
assert_eq!(&second[0], "");
|
||||
assert_eq!(&second["alt"], "image2");
|
||||
assert_eq!(&second["url"], "https://image2.com");
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue