mirror of
https://github.com/YGGverse/Yoda.git
synced 2026-03-31 16:45:27 +00:00
Compare commits
12 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac83ace83b | ||
|
|
38f9cca422 | ||
|
|
e92eb318b3 | ||
|
|
2891d73b37 | ||
|
|
ca9c2058ed | ||
|
|
2ef5e52079 | ||
|
|
416c0ac434 | ||
|
|
3bdabbe1b8 | ||
|
|
3358a89735 | ||
|
|
563b228e9e | ||
|
|
b6b8f96bba | ||
|
|
86ce8ceff5 |
10 changed files with 404 additions and 160 deletions
188
Cargo.lock
generated
188
Cargo.lock
generated
|
|
@ -4,7 +4,7 @@ version = 4
|
|||
|
||||
[[package]]
|
||||
name = "Yoda"
|
||||
version = "0.12.8"
|
||||
version = "0.12.10"
|
||||
dependencies = [
|
||||
"ansi-parser",
|
||||
"anyhow",
|
||||
|
|
@ -23,6 +23,7 @@ dependencies = [
|
|||
"regex",
|
||||
"rusqlite",
|
||||
"sourceview5",
|
||||
"strip-tags",
|
||||
"syntect",
|
||||
]
|
||||
|
||||
|
|
@ -121,9 +122,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.56"
|
||||
version = "1.2.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2"
|
||||
checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
|
|
@ -131,9 +132,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cfg-expr"
|
||||
version = "0.20.6"
|
||||
version = "0.20.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78cef5b5a1a6827c7322ae2a636368a573006b27cfa76c7ebd53e834daeaab6a"
|
||||
checksum = "3c6b04e07d8080154ed4ac03546d9a2b303cc2fe1901ba0b35b301516e289368"
|
||||
dependencies = [
|
||||
"smallvec",
|
||||
"target-lexicon",
|
||||
|
|
@ -145,6 +146,26 @@ version = "1.0.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "chacha20"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.5.0"
|
||||
|
|
@ -365,18 +386,6 @@ dependencies = [
|
|||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi 5.3.0",
|
||||
"wasip2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.4.2"
|
||||
|
|
@ -385,7 +394,8 @@ checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
|
|||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi 6.0.0",
|
||||
"r-efi",
|
||||
"rand_core",
|
||||
"wasip2",
|
||||
"wasip3",
|
||||
]
|
||||
|
|
@ -687,9 +697,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.17"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
|
|
@ -740,9 +750,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.182"
|
||||
version = "0.2.183"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
|
||||
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
|
||||
|
||||
[[package]]
|
||||
name = "libspelling"
|
||||
|
|
@ -866,9 +876,9 @@ checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050"
|
|||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "onig"
|
||||
|
|
@ -894,9 +904,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.75"
|
||||
version = "0.10.76"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328"
|
||||
checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
|
|
@ -920,9 +930,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.111"
|
||||
version = "0.9.112"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321"
|
||||
checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
|
|
@ -1014,15 +1024,6 @@ version = "0.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||
dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.2.37"
|
||||
|
|
@ -1035,9 +1036,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "3.4.0"
|
||||
version = "3.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983"
|
||||
checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"
|
||||
dependencies = [
|
||||
"toml_edit",
|
||||
]
|
||||
|
|
@ -1062,19 +1063,13 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.44"
|
||||
version = "1.0.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
|
||||
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "6.0.0"
|
||||
|
|
@ -1105,32 +1100,20 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.9.2"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
||||
checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8"
|
||||
dependencies = [
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"chacha20",
|
||||
"getrandom",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.9.5"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
|
||||
dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
]
|
||||
checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
|
|
@ -1369,6 +1352,12 @@ version = "1.2.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
||||
|
||||
[[package]]
|
||||
name = "strip-tags"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ecd2b127e68202f5f285a116f616d5d11735cca5e4befaea0347becd445b05b2"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.117"
|
||||
|
|
@ -1480,10 +1469,10 @@ dependencies = [
|
|||
"indexmap",
|
||||
"serde_core",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_datetime 0.7.5+spec-1.1.0",
|
||||
"toml_parser",
|
||||
"toml_writer",
|
||||
"winnow",
|
||||
"winnow 0.7.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1496,31 +1485,40 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.23.10+spec-1.0.0"
|
||||
name = "toml_datetime"
|
||||
version = "1.0.1+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269"
|
||||
checksum = "9b320e741db58cac564e26c607d3cc1fdc4a88fd36c879568c07856ed83ff3e9"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.25.5+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ca1a40644a28bce036923f6a431df0b34236949d111cc07cb6dca830c9ef2e1"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime",
|
||||
"toml_datetime 1.0.1+spec-1.1.0",
|
||||
"toml_parser",
|
||||
"winnow",
|
||||
"winnow 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_parser"
|
||||
version = "1.0.9+spec-1.1.0"
|
||||
version = "1.0.10+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4"
|
||||
checksum = "7df25b4befd31c4816df190124375d5a20c6b6921e2cad937316de3fccd63420"
|
||||
dependencies = [
|
||||
"winnow",
|
||||
"winnow 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_writer"
|
||||
version = "1.0.6+spec-1.1.0"
|
||||
version = "1.0.7+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607"
|
||||
checksum = "f17aaa1c6e3dc22b1da4b6bba97d066e354c7945cac2f7852d4e4e7ca7a6b56d"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
|
|
@ -1536,11 +1534,11 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
|||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.21.0"
|
||||
version = "1.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb"
|
||||
checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37"
|
||||
dependencies = [
|
||||
"getrandom 0.4.2",
|
||||
"getrandom",
|
||||
"js-sys",
|
||||
"rand",
|
||||
"wasm-bindgen",
|
||||
|
|
@ -1691,9 +1689,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.14"
|
||||
version = "0.7.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"
|
||||
checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
|
@ -1795,26 +1799,6 @@ dependencies = [
|
|||
"linked-hash-map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.21"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "Yoda"
|
||||
version = "0.12.8"
|
||||
version = "0.12.10"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
|
|
@ -42,6 +42,7 @@ plurify = "0.2.0"
|
|||
r2d2 = "0.8.10"
|
||||
r2d2_sqlite = "0.32.0"
|
||||
regex = "1.12.3"
|
||||
strip-tags = "0.1.0"
|
||||
syntect = "5.2.0"
|
||||
|
||||
# development
|
||||
|
|
|
|||
|
|
@ -151,11 +151,7 @@ impl Gemini {
|
|||
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
|
||||
assert!(tag.text_tag_table.add(&syntax_tag));
|
||||
buffer.insert_with_tags(
|
||||
&mut buffer.end_iter(),
|
||||
&entity,
|
||||
|
|
@ -166,11 +162,7 @@ impl Gemini {
|
|||
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
|
||||
assert!(tag.text_tag_table.add(&syntax_tag));
|
||||
buffer.insert_with_tags(
|
||||
&mut buffer.end_iter(),
|
||||
&entity,
|
||||
|
|
@ -187,7 +179,7 @@ impl Gemini {
|
|||
// Skip other actions for this line
|
||||
continue;
|
||||
}
|
||||
Err(_) => todo!(),
|
||||
Err(_) => panic!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,8 +12,10 @@ use gtk::{
|
|||
prelude::{EditableExt, PopoverExt, TextBufferExt, TextTagExt, TextViewExt, WidgetExt},
|
||||
};
|
||||
use gutter::Gutter;
|
||||
use regex::Regex;
|
||||
use sourceview::prelude::{ActionExt, ActionMapExt, DisplayExt, ToVariant};
|
||||
use std::{cell::Cell, collections::HashMap, rc::Rc};
|
||||
use strip_tags::*;
|
||||
use tags::Tags;
|
||||
|
||||
pub struct Markdown {
|
||||
|
|
@ -39,9 +41,6 @@ impl Markdown {
|
|||
// * maybe less expensive than update entire HashMap by iter
|
||||
let hover: Rc<Cell<Option<TextTag>>> = Rc::new(Cell::new(None));
|
||||
|
||||
// Init code features
|
||||
//let mut code = None;
|
||||
|
||||
// Init colors
|
||||
// @TODO use accent colors in adw 1.6 / ubuntu 24.10+
|
||||
let link_color = (
|
||||
|
|
@ -54,7 +53,12 @@ impl Markdown {
|
|||
|
||||
// Init new text buffer
|
||||
let buffer = TextBuffer::new(Some(&TextTagTable::new()));
|
||||
buffer.set_text(markdown);
|
||||
buffer.set_text(
|
||||
Regex::new(r"\n{3,}")
|
||||
.unwrap()
|
||||
.replace_all(&strip_tags(markdown), "\n\n")
|
||||
.trim(),
|
||||
); // @TODO extract `<img>` tags?
|
||||
|
||||
// Init main widget
|
||||
let text_view = {
|
||||
|
|
@ -76,7 +80,7 @@ impl Markdown {
|
|||
let gutter = Gutter::build(&text_view);
|
||||
|
||||
// Render markdown tags
|
||||
let title = tags.render(&buffer, base, &link_color.0, &mut links, &mut headers);
|
||||
let title = tags.render(&text_view, base, &link_color.0, &mut links, &mut headers);
|
||||
|
||||
// Headers context menu (fragment capture)
|
||||
let action_header_copy_url =
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
mod bold;
|
||||
mod code;
|
||||
mod header;
|
||||
mod hr;
|
||||
mod italic;
|
||||
mod list;
|
||||
mod pre;
|
||||
mod quote;
|
||||
|
|
@ -11,12 +13,13 @@ mod underline;
|
|||
use bold::Bold;
|
||||
use code::Code;
|
||||
use gtk::{
|
||||
TextBuffer, TextSearchFlags, TextTag,
|
||||
TextSearchFlags, TextTag, TextView,
|
||||
gdk::RGBA,
|
||||
glib::{GString, Uri},
|
||||
prelude::TextBufferExt,
|
||||
prelude::{TextBufferExt, TextViewExt},
|
||||
};
|
||||
use header::Header;
|
||||
use italic::Italic;
|
||||
use pre::Pre;
|
||||
use quote::Quote;
|
||||
use std::collections::HashMap;
|
||||
|
|
@ -27,6 +30,7 @@ pub struct Tags {
|
|||
pub bold: Bold,
|
||||
pub code: Code,
|
||||
pub header: Header,
|
||||
pub italic: Italic,
|
||||
pub pre: Pre,
|
||||
pub quote: Quote,
|
||||
pub strike: Strike,
|
||||
|
|
@ -46,6 +50,7 @@ impl Tags {
|
|||
bold: Bold::new(),
|
||||
code: Code::new(),
|
||||
header: Header::new(),
|
||||
italic: Italic::new(),
|
||||
pre: Pre::new(),
|
||||
quote: Quote::new(),
|
||||
strike: Strike::new(),
|
||||
|
|
@ -54,31 +59,33 @@ impl Tags {
|
|||
}
|
||||
pub fn render(
|
||||
&mut self,
|
||||
buffer: &TextBuffer,
|
||||
text_view: &TextView,
|
||||
base: &Uri,
|
||||
link_color: &RGBA,
|
||||
links: &mut HashMap<TextTag, Uri>,
|
||||
headers: &mut HashMap<TextTag, (String, Uri)>,
|
||||
) -> Option<String> {
|
||||
let buffer = text_view.buffer();
|
||||
|
||||
// Collect all code blocks first,
|
||||
// and temporarily replace them with placeholder ID
|
||||
self.code.collect(buffer);
|
||||
self.code.collect(&buffer);
|
||||
|
||||
// Keep in order!
|
||||
let title = self.header.render(buffer, base, headers);
|
||||
let title = self.header.render(&buffer, base, headers);
|
||||
|
||||
list::render(buffer);
|
||||
list::render(&buffer);
|
||||
|
||||
self.quote.render(buffer);
|
||||
self.quote.render(&buffer);
|
||||
|
||||
self.bold.render(buffer);
|
||||
self.pre.render(buffer);
|
||||
self.strike.render(buffer);
|
||||
self.underline.render(buffer);
|
||||
self.bold.render(&buffer);
|
||||
self.italic.render(&buffer);
|
||||
self.pre.render(&buffer);
|
||||
self.strike.render(&buffer);
|
||||
self.underline.render(&buffer);
|
||||
|
||||
reference::render_images_links(buffer, base, link_color, links);
|
||||
reference::render_images(buffer, base, link_color, links);
|
||||
reference::render_links(buffer, base, link_color, links);
|
||||
reference::render(&buffer, base, link_color, links);
|
||||
hr::render(text_view);
|
||||
|
||||
// Cleanup unformatted escape chars
|
||||
for e in ESCAPE_ENTRIES {
|
||||
|
|
@ -94,11 +101,13 @@ impl Tags {
|
|||
}
|
||||
|
||||
// Render placeholders
|
||||
self.code.render(buffer);
|
||||
self.code.render(&buffer);
|
||||
|
||||
// Format document title string
|
||||
title.map(|mut s| {
|
||||
s = bold::strip_tags(&s);
|
||||
s = hr::strip_tags(&s);
|
||||
s = italic::strip_tags(&s);
|
||||
s = pre::strip_tags(&s);
|
||||
s = reference::strip_tags(&s);
|
||||
s = strike::strip_tags(&s);
|
||||
|
|
@ -118,10 +127,13 @@ pub fn format_header_fragment(value: &str) -> GString {
|
|||
|
||||
const ESCAPE_ENTRIES: &[&str] = &[
|
||||
"\\\n", "\\\\", "\\>", "\\`", "\\!", "\\[", "\\]", "\\(", "\\)", "\\*", "\\#", "\\~", "\\_",
|
||||
"\\-",
|
||||
];
|
||||
#[test]
|
||||
fn test_escape_entries() {
|
||||
let mut set = std::collections::HashSet::new();
|
||||
for e in ESCAPE_ENTRIES {
|
||||
assert_eq!(e.len(), 2)
|
||||
assert_eq!(e.len(), 2);
|
||||
assert!(set.insert(*e))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use gtk::{
|
|||
};
|
||||
use regex::Regex;
|
||||
|
||||
const REGEX_BOLD: &str = r"\*\*(?P<text>[^\*]*)\*\*";
|
||||
const REGEX_BOLD: &str = r"(\*\*|__)(?P<text>[^\*_]*)(\*\*|__)";
|
||||
|
||||
pub struct Bold(TextTag);
|
||||
|
||||
|
|
@ -14,7 +14,7 @@ impl Bold {
|
|||
Self(TextTag::builder().weight(600).wrap_mode(Word).build())
|
||||
}
|
||||
|
||||
/// Apply **bold** `Tag` to given `TextBuffer`
|
||||
/// Apply **bold**/__bold__ `Tag` to given `TextBuffer`
|
||||
pub fn render(&self, buffer: &TextBuffer) {
|
||||
assert!(buffer.tag_table().add(&self.0));
|
||||
|
||||
|
|
@ -72,7 +72,8 @@ pub fn strip_tags(value: &str) -> String {
|
|||
|
||||
#[test]
|
||||
fn test_strip_tags() {
|
||||
const VALUE: &str = r"Some **bold 1** and **bold 2** with ";
|
||||
const VALUE: &str =
|
||||
"Some **bold 1** and **bold 2** and __bold 3__ and *italic 1* and _italic 2_";
|
||||
let mut result = String::from(VALUE);
|
||||
for cap in Regex::new(REGEX_BOLD).unwrap().captures_iter(VALUE) {
|
||||
if let Some(m) = cap.get(0) {
|
||||
|
|
@ -81,7 +82,7 @@ fn test_strip_tags() {
|
|||
}
|
||||
assert_eq!(
|
||||
result,
|
||||
"Some bold 1 and bold 2 with "
|
||||
"Some bold 1 and bold 2 and bold 3 and *italic 1* and _italic 2_"
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -89,9 +90,15 @@ fn test_strip_tags() {
|
|||
fn test_regex() {
|
||||
let cap: Vec<_> = Regex::new(REGEX_BOLD)
|
||||
.unwrap()
|
||||
.captures_iter(r"Some **bold 1** and **bold 2** with ")
|
||||
.captures_iter(
|
||||
"Some **bold 1** and **bold 2** and __bold 3__ and *italic 1* and _italic 2_",
|
||||
)
|
||||
.collect();
|
||||
|
||||
assert_eq!(&cap.first().unwrap()["text"], "bold 1");
|
||||
assert_eq!(&cap.get(1).unwrap()["text"], "bold 2");
|
||||
assert_eq!(cap.len(), 3);
|
||||
|
||||
let mut c = cap.into_iter();
|
||||
assert_eq!(&c.next().unwrap()["text"], "bold 1");
|
||||
assert_eq!(&c.next().unwrap()["text"], "bold 2");
|
||||
assert_eq!(&c.next().unwrap()["text"], "bold 3");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,93 @@
|
|||
use gtk::{
|
||||
Orientation, Separator, TextView,
|
||||
glib::{ControlFlow, idle_add_local},
|
||||
prelude::*,
|
||||
};
|
||||
use regex::Regex;
|
||||
|
||||
const REGEX_HR: &str = r"(?m)^(?P<hr>\\?[-]{3,})$";
|
||||
|
||||
/// Apply --- `Tag` to given `TextBuffer`
|
||||
pub fn render(text_view: &TextView) {
|
||||
let separator = Separator::builder()
|
||||
.orientation(Orientation::Horizontal)
|
||||
.build();
|
||||
idle_add_local({
|
||||
let text_view = text_view.clone();
|
||||
let separator = separator.clone();
|
||||
move || {
|
||||
separator.set_width_request(text_view.width() - 18);
|
||||
ControlFlow::Break
|
||||
}
|
||||
});
|
||||
|
||||
let buffer = text_view.buffer();
|
||||
|
||||
let (start, end) = buffer.bounds();
|
||||
let full_content = buffer.text(&start, &end, true).to_string();
|
||||
|
||||
let matches: Vec<_> = Regex::new(REGEX_HR)
|
||||
.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);
|
||||
|
||||
if start_char_offset > 0
|
||||
&& buffer
|
||||
.text(
|
||||
&buffer.iter_at_offset(start_char_offset - 1),
|
||||
&end_iter,
|
||||
false,
|
||||
)
|
||||
.contains("\\")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
buffer.delete(&mut start_iter, &mut end_iter);
|
||||
text_view.add_child_at_anchor(&separator, &buffer.create_child_anchor(&mut end_iter));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn strip_tags(value: &str) -> String {
|
||||
let mut result = String::from(value);
|
||||
for cap in Regex::new(REGEX_HR).unwrap().captures_iter(value) {
|
||||
if let Some(m) = cap.get(0) {
|
||||
result = result.replace(m.as_str(), &cap["hr"]);
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_strip_tags() {
|
||||
const VALUE: &str = "Some line\n---\nSome another-line with ";
|
||||
let mut result = String::from(VALUE);
|
||||
for cap in Regex::new(REGEX_HR).unwrap().captures_iter(VALUE) {
|
||||
if let Some(m) = cap.get(0) {
|
||||
result = result.replace(m.as_str(), "");
|
||||
}
|
||||
}
|
||||
assert_eq!(
|
||||
result,
|
||||
"Some line\n\nSome another-line with "
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_regex() {
|
||||
let cap: Vec<_> = Regex::new(REGEX_HR)
|
||||
.unwrap()
|
||||
.captures_iter("Some line\n---\nSome another-line with ")
|
||||
.collect();
|
||||
|
||||
assert_eq!(&cap.first().unwrap()["hr"], "---");
|
||||
}
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
use gtk::{
|
||||
TextBuffer, TextTag,
|
||||
WrapMode::Word,
|
||||
pango::Style,
|
||||
prelude::{TextBufferExt, TextBufferExtManual},
|
||||
};
|
||||
use regex::Regex;
|
||||
|
||||
const REGEX_ITALIC_1: &str = r"\*(?P<text>[^\*]*)\*";
|
||||
const REGEX_ITALIC_2: &str = r"\b_(?P<text>[^_]*)_\b";
|
||||
|
||||
pub struct Italic(TextTag);
|
||||
|
||||
impl Italic {
|
||||
pub fn new() -> Self {
|
||||
Self(
|
||||
TextTag::builder()
|
||||
.style(Style::Italic)
|
||||
.wrap_mode(Word)
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Apply *italic*/_italic_ `Tag` to given `TextBuffer`
|
||||
/// * run after `Bold` tag!
|
||||
pub fn render(&self, buffer: &TextBuffer) {
|
||||
assert!(buffer.tag_table().add(&self.0));
|
||||
|
||||
render(self, buffer, REGEX_ITALIC_1);
|
||||
render(self, buffer, REGEX_ITALIC_2);
|
||||
}
|
||||
}
|
||||
|
||||
fn render(this: &Italic, buffer: &TextBuffer, regex: &str) {
|
||||
let (start, end) = buffer.bounds();
|
||||
let full_content = buffer.text(&start, &end, true).to_string();
|
||||
|
||||
let matches: Vec<_> = Regex::new(regex)
|
||||
.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);
|
||||
|
||||
if start_char_offset > 0
|
||||
&& buffer
|
||||
.text(
|
||||
&buffer.iter_at_offset(start_char_offset - 1),
|
||||
&end_iter,
|
||||
false,
|
||||
)
|
||||
.contains("\\")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut tags = start_iter.tags();
|
||||
tags.push(this.0.clone());
|
||||
|
||||
buffer.delete(&mut start_iter, &mut end_iter);
|
||||
buffer.insert_with_tags(
|
||||
&mut start_iter,
|
||||
&cap["text"],
|
||||
&tags.iter().collect::<Vec<&TextTag>>(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// * run after `Bold` tag!
|
||||
pub fn strip_tags(value: &str) -> String {
|
||||
let mut s = String::from(value);
|
||||
for cap in Regex::new(REGEX_ITALIC_1).unwrap().captures_iter(value) {
|
||||
if let Some(m) = cap.get(0) {
|
||||
s = s.replace(m.as_str(), &cap["text"]);
|
||||
}
|
||||
}
|
||||
for cap in Regex::new(REGEX_ITALIC_2).unwrap().captures_iter(value) {
|
||||
if let Some(m) = cap.get(0) {
|
||||
s = s.replace(m.as_str(), &cap["text"]);
|
||||
}
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_strip_tags() {
|
||||
const S: &str = "Some *italic 1*\nand *italic 2* and _italic 3_";
|
||||
{
|
||||
let mut result = String::from(S);
|
||||
for cap in Regex::new(REGEX_ITALIC_1).unwrap().captures_iter(S) {
|
||||
if let Some(m) = cap.get(0) {
|
||||
result = result.replace(m.as_str(), &cap["text"]);
|
||||
}
|
||||
}
|
||||
assert_eq!(result, "Some italic 1\nand italic 2 and _italic 3_")
|
||||
}
|
||||
{
|
||||
let mut result = String::from(S);
|
||||
for cap in Regex::new(REGEX_ITALIC_2).unwrap().captures_iter(S) {
|
||||
if let Some(m) = cap.get(0) {
|
||||
result = result.replace(m.as_str(), &cap["text"]);
|
||||
}
|
||||
}
|
||||
assert_eq!(result, "Some *italic 1*\nand *italic 2* and italic 3")
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_regex() {
|
||||
const S: &str = "Some *italic 1*\nand *italic 2* and _italic 3_";
|
||||
{
|
||||
let cap: Vec<_> = Regex::new(REGEX_ITALIC_1)
|
||||
.unwrap()
|
||||
.captures_iter(S)
|
||||
.collect();
|
||||
|
||||
assert_eq!(cap.len(), 2);
|
||||
|
||||
let mut c = cap.into_iter();
|
||||
assert_eq!(&c.next().unwrap()["text"], "italic 1");
|
||||
assert_eq!(&c.next().unwrap()["text"], "italic 2");
|
||||
}
|
||||
{
|
||||
let cap: Vec<_> = Regex::new(REGEX_ITALIC_2)
|
||||
.unwrap()
|
||||
.captures_iter(S)
|
||||
.collect();
|
||||
|
||||
assert_eq!(cap.len(), 1);
|
||||
|
||||
let mut c = cap.into_iter();
|
||||
assert_eq!(&c.next().unwrap()["text"], "italic 3");
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ use gtk::{
|
|||
};
|
||||
use regex::Regex;
|
||||
|
||||
const REGEX_QUOTE: &str = r"(?m)>\s*(?P<text>.*)$";
|
||||
const REGEX_QUOTE: &str = r"(?m)^>(?:[ \t]*(?P<text>.*))?$";
|
||||
|
||||
pub struct Quote(TextTag);
|
||||
|
||||
|
|
@ -16,7 +16,7 @@ impl Quote {
|
|||
TextTag::builder()
|
||||
.left_margin(28)
|
||||
.wrap_mode(Word)
|
||||
.style(Italic) // what about the italic tags decoration? @TODO
|
||||
.style(Italic) // conflicts the italic tags decoration @TODO
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
|
|
@ -51,18 +51,16 @@ impl Quote {
|
|||
#[test]
|
||||
fn test_regex() {
|
||||
let cap: Vec<_> = Regex::new(REGEX_QUOTE).unwrap().captures_iter(
|
||||
"> Some quote 1 with \n> 2\\)Some quote 2 with text\nplain text\n> Some quote 3"
|
||||
"> Some quote 1 with \n>\n> 2\\)Some quote 2 with text\nplain text\n> Some quote 3"
|
||||
).collect();
|
||||
{
|
||||
let m = cap.first().unwrap();
|
||||
assert_eq!(&m["text"], "Some quote 1 with ");
|
||||
}
|
||||
{
|
||||
let m = cap.get(1).unwrap();
|
||||
assert_eq!(&m["text"], "2\\)Some quote 2 with text");
|
||||
}
|
||||
{
|
||||
let m = cap.get(2).unwrap();
|
||||
assert_eq!(&m["text"], "Some quote 3");
|
||||
}
|
||||
|
||||
let mut i = cap.into_iter();
|
||||
|
||||
assert_eq!(
|
||||
&i.next().unwrap()["text"],
|
||||
"Some quote 1 with "
|
||||
);
|
||||
assert!(&i.next().unwrap()["text"].is_empty());
|
||||
assert_eq!(&i.next().unwrap()["text"], "2\\)Some quote 2 with text");
|
||||
assert_eq!(&i.next().unwrap()["text"], "Some quote 3");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ impl Reference {
|
|||
}
|
||||
|
||||
/// Image links `[![]()]()`
|
||||
pub fn render_images_links(
|
||||
fn render_images_links(
|
||||
buffer: &TextBuffer,
|
||||
base: &Uri,
|
||||
link_color: &RGBA,
|
||||
|
|
@ -159,8 +159,20 @@ pub fn render_images_links(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(
|
||||
buffer: &TextBuffer,
|
||||
base: &Uri,
|
||||
link_color: &RGBA,
|
||||
links: &mut HashMap<TextTag, Uri>,
|
||||
) {
|
||||
render_images_links(buffer, base, link_color, links);
|
||||
render_images(buffer, base, link_color, links);
|
||||
render_links(buffer, base, link_color, links)
|
||||
}
|
||||
|
||||
/// Image tags `![]()`
|
||||
pub fn render_images(
|
||||
fn render_images(
|
||||
buffer: &TextBuffer,
|
||||
base: &Uri,
|
||||
link_color: &RGBA,
|
||||
|
|
@ -211,7 +223,7 @@ pub fn render_images(
|
|||
}
|
||||
}
|
||||
/// Links `[]()`
|
||||
pub fn render_links(
|
||||
fn render_links(
|
||||
buffer: &TextBuffer,
|
||||
base: &Uri,
|
||||
link_color: &RGBA,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue