Compare commits

..

No commits in common. "master" and "0.12.8" have entirely different histories.

14 changed files with 190 additions and 416 deletions

198
Cargo.lock generated
View file

@ -4,7 +4,7 @@ version = 4
[[package]] [[package]]
name = "Yoda" name = "Yoda"
version = "0.12.11" version = "0.12.8"
dependencies = [ dependencies = [
"ansi-parser", "ansi-parser",
"anyhow", "anyhow",
@ -121,9 +121,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.58" version = "1.2.56"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2"
dependencies = [ dependencies = [
"find-msvc-tools", "find-msvc-tools",
"shlex", "shlex",
@ -131,9 +131,9 @@ dependencies = [
[[package]] [[package]]
name = "cfg-expr" name = "cfg-expr"
version = "0.20.7" version = "0.20.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c6b04e07d8080154ed4ac03546d9a2b303cc2fe1901ba0b35b301516e289368" checksum = "78cef5b5a1a6827c7322ae2a636368a573006b27cfa76c7ebd53e834daeaab6a"
dependencies = [ dependencies = [
"smallvec", "smallvec",
"target-lexicon", "target-lexicon",
@ -145,26 +145,6 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" 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]] [[package]]
name = "crc32fast" name = "crc32fast"
version = "1.5.0" version = "1.5.0"
@ -385,6 +365,18 @@ dependencies = [
"system-deps", "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]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.4.2" version = "0.4.2"
@ -393,8 +385,7 @@ checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"r-efi", "r-efi 6.0.0",
"rand_core",
"wasip2", "wasip2",
"wasip3", "wasip3",
] ]
@ -696,15 +687,15 @@ dependencies = [
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.18" version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.94" version = "0.3.91"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c"
dependencies = [ dependencies = [
"once_cell", "once_cell",
"wasm-bindgen", "wasm-bindgen",
@ -749,9 +740,9 @@ dependencies = [
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.184" version = "0.2.182"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
[[package]] [[package]]
name = "libspelling" name = "libspelling"
@ -869,15 +860,15 @@ dependencies = [
[[package]] [[package]]
name = "num-conv" name = "num-conv"
version = "0.2.1" version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050"
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.21.4" version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]] [[package]]
name = "onig" name = "onig"
@ -903,9 +894,9 @@ dependencies = [
[[package]] [[package]]
name = "openssl" name = "openssl"
version = "0.10.76" version = "0.10.75"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"cfg-if", "cfg-if",
@ -929,9 +920,9 @@ dependencies = [
[[package]] [[package]]
name = "openssl-sys" name = "openssl-sys"
version = "0.9.112" version = "0.9.111"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321"
dependencies = [ dependencies = [
"cc", "cc",
"libc", "libc",
@ -1023,6 +1014,15 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 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]] [[package]]
name = "prettyplease" name = "prettyplease"
version = "0.2.37" version = "0.2.37"
@ -1035,9 +1035,9 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro-crate" name = "proc-macro-crate"
version = "3.5.0" version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983"
dependencies = [ dependencies = [
"toml_edit", "toml_edit",
] ]
@ -1062,13 +1062,19 @@ dependencies = [
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.45" version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "r-efi"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]] [[package]]
name = "r-efi" name = "r-efi"
version = "6.0.0" version = "6.0.0"
@ -1099,20 +1105,32 @@ dependencies = [
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.10.0" version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [ dependencies = [
"chacha20", "rand_chacha",
"getrandom", "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",
"rand_core", "rand_core",
] ]
[[package]] [[package]]
name = "rand_core" name = "rand_core"
version = "0.10.0" version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
dependencies = [
"getrandom 0.3.4",
]
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
@ -1267,9 +1285,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_spanned" name = "serde_spanned"
version = "1.1.1" version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776"
dependencies = [ dependencies = [
"serde_core", "serde_core",
] ]
@ -1282,9 +1300,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]] [[package]]
name = "simd-adler32" name = "simd-adler32"
version = "0.3.9" version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
[[package]] [[package]]
name = "slab" name = "slab"
@ -1385,9 +1403,9 @@ dependencies = [
[[package]] [[package]]
name = "system-deps" name = "system-deps"
version = "7.0.8" version = "7.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "396a35feb67335377e0251fcbc1092fc85c484bd4e3a7a54319399da127796e7" checksum = "48c8f33736f986f16d69b6cb8b03f55ddcad5c41acc4ccc39dd88e84aa805e7f"
dependencies = [ dependencies = [
"cfg-expr", "cfg-expr",
"heck", "heck",
@ -1455,9 +1473,9 @@ dependencies = [
[[package]] [[package]]
name = "toml" name = "toml"
version = "1.1.2+spec-1.1.0" version = "0.9.12+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"serde_core", "serde_core",
@ -1470,18 +1488,18 @@ dependencies = [
[[package]] [[package]]
name = "toml_datetime" name = "toml_datetime"
version = "1.1.1+spec-1.1.0" version = "0.7.5+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347"
dependencies = [ dependencies = [
"serde_core", "serde_core",
] ]
[[package]] [[package]]
name = "toml_edit" name = "toml_edit"
version = "0.25.10+spec-1.1.0" version = "0.23.10+spec-1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a82418ca169e235e6c399a84e395ab6debeb3bc90edc959bf0f48647c6a32d1b" checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"toml_datetime", "toml_datetime",
@ -1491,18 +1509,18 @@ dependencies = [
[[package]] [[package]]
name = "toml_parser" name = "toml_parser"
version = "1.1.2+spec-1.1.0" version = "1.0.9+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4"
dependencies = [ dependencies = [
"winnow", "winnow",
] ]
[[package]] [[package]]
name = "toml_writer" name = "toml_writer"
version = "1.1.1+spec-1.1.0" version = "1.0.6+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
@ -1518,11 +1536,11 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.23.0" version = "1.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb"
dependencies = [ dependencies = [
"getrandom", "getrandom 0.4.2",
"js-sys", "js-sys",
"rand", "rand",
"wasm-bindgen", "wasm-bindgen",
@ -1570,9 +1588,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.117" version = "0.2.114"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"once_cell", "once_cell",
@ -1583,9 +1601,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.117" version = "0.2.114"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6"
dependencies = [ dependencies = [
"quote", "quote",
"wasm-bindgen-macro-support", "wasm-bindgen-macro-support",
@ -1593,9 +1611,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro-support" name = "wasm-bindgen-macro-support"
version = "0.2.117" version = "0.2.114"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"proc-macro2", "proc-macro2",
@ -1606,9 +1624,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-shared" name = "wasm-bindgen-shared"
version = "0.2.117" version = "0.2.114"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@ -1673,9 +1691,9 @@ dependencies = [
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "1.0.1" version = "0.7.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@ -1777,6 +1795,26 @@ dependencies = [
"linked-hash-map", "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]] [[package]]
name = "zmij" name = "zmij"
version = "1.0.21" version = "1.0.21"

View file

@ -1,6 +1,6 @@
[package] [package]
name = "Yoda" name = "Yoda"
version = "0.12.11" version = "0.12.8"
edition = "2024" edition = "2024"
license = "MIT" license = "MIT"
readme = "README.md" readme = "README.md"

View file

@ -327,7 +327,7 @@ fn handle(
}, },
_ => match success.mime() { _ => match success.mime() {
Ok(mime) => match mime.as_str() { Ok(mime) => match mime.as_str() {
"text/gemini" | "text/plain" | "text/markdown" => memory_input_stream::from_stream_async( "text/gemini" | "text/plain" => memory_input_stream::from_stream_async(
connection.stream(), connection.stream(),
Priority::DEFAULT, Priority::DEFAULT,
cancellable.clone(), cancellable.clone(),

View file

@ -151,7 +151,11 @@ impl Gemini {
match syntax.highlight(&c.value, alt) { match syntax.highlight(&c.value, alt) {
Ok(highlight) => { Ok(highlight) => {
for (syntax_tag, entity) in highlight { for (syntax_tag, entity) in highlight {
assert!(tag.text_tag_table.add(&syntax_tag)); // Register new tag
if !tag.text_tag_table.add(&syntax_tag) {
todo!()
}
// Append tag to buffer
buffer.insert_with_tags( buffer.insert_with_tags(
&mut buffer.end_iter(), &mut buffer.end_iter(),
&entity, &entity,
@ -162,7 +166,11 @@ impl Gemini {
Err(_) => { Err(_) => {
// Try ANSI/SGR format (terminal emulation) @TODO optional // Try ANSI/SGR format (terminal emulation) @TODO optional
for (syntax_tag, entity) in ansi::format(&c.value) { for (syntax_tag, entity) in ansi::format(&c.value) {
assert!(tag.text_tag_table.add(&syntax_tag)); // Register new tag
if !tag.text_tag_table.add(&syntax_tag) {
todo!()
}
// Append tag to buffer
buffer.insert_with_tags( buffer.insert_with_tags(
&mut buffer.end_iter(), &mut buffer.end_iter(),
&entity, &entity,
@ -179,7 +187,7 @@ impl Gemini {
// Skip other actions for this line // Skip other actions for this line
continue; continue;
} }
Err(_) => panic!(), Err(_) => todo!(),
} }
} }
} }

View file

@ -12,7 +12,6 @@ use gtk::{
prelude::{EditableExt, PopoverExt, TextBufferExt, TextTagExt, TextViewExt, WidgetExt}, prelude::{EditableExt, PopoverExt, TextBufferExt, TextTagExt, TextViewExt, WidgetExt},
}; };
use gutter::Gutter; use gutter::Gutter;
use regex::Regex;
use sourceview::prelude::{ActionExt, ActionMapExt, DisplayExt, ToVariant}; use sourceview::prelude::{ActionExt, ActionMapExt, DisplayExt, ToVariant};
use std::{cell::Cell, collections::HashMap, rc::Rc}; use std::{cell::Cell, collections::HashMap, rc::Rc};
use tags::Tags; use tags::Tags;
@ -40,6 +39,9 @@ impl Markdown {
// * maybe less expensive than update entire HashMap by iter // * maybe less expensive than update entire HashMap by iter
let hover: Rc<Cell<Option<TextTag>>> = Rc::new(Cell::new(None)); let hover: Rc<Cell<Option<TextTag>>> = Rc::new(Cell::new(None));
// Init code features
//let mut code = None;
// Init colors // Init colors
// @TODO use accent colors in adw 1.6 / ubuntu 24.10+ // @TODO use accent colors in adw 1.6 / ubuntu 24.10+
let link_color = ( let link_color = (
@ -52,12 +54,7 @@ impl Markdown {
// Init new text buffer // Init new text buffer
let buffer = TextBuffer::new(Some(&TextTagTable::new())); let buffer = TextBuffer::new(Some(&TextTagTable::new()));
buffer.set_text( buffer.set_text(markdown);
Regex::new(r"\n{3,}")
.unwrap()
.replace_all(markdown, "\n\n")
.trim(),
); // @TODO extract `<img>` tags?
// Init main widget // Init main widget
let text_view = { let text_view = {
@ -79,7 +76,7 @@ impl Markdown {
let gutter = Gutter::build(&text_view); let gutter = Gutter::build(&text_view);
// Render markdown tags // Render markdown tags
let title = tags.render(&text_view, base, &link_color.0, &mut links, &mut headers); let title = tags.render(&buffer, base, &link_color.0, &mut links, &mut headers);
// Headers context menu (fragment capture) // Headers context menu (fragment capture)
let action_header_copy_url = let action_header_copy_url =

View file

@ -1,8 +1,6 @@
mod bold; mod bold;
mod code; mod code;
mod header; mod header;
mod hr;
mod italic;
mod list; mod list;
mod pre; mod pre;
mod quote; mod quote;
@ -13,13 +11,12 @@ mod underline;
use bold::Bold; use bold::Bold;
use code::Code; use code::Code;
use gtk::{ use gtk::{
TextSearchFlags, TextTag, TextView, TextBuffer, TextSearchFlags, TextTag,
gdk::RGBA, gdk::RGBA,
glib::{GString, Uri}, glib::{GString, Uri},
prelude::{TextBufferExt, TextViewExt}, prelude::TextBufferExt,
}; };
use header::Header; use header::Header;
use italic::Italic;
use pre::Pre; use pre::Pre;
use quote::Quote; use quote::Quote;
use std::collections::HashMap; use std::collections::HashMap;
@ -30,7 +27,6 @@ pub struct Tags {
pub bold: Bold, pub bold: Bold,
pub code: Code, pub code: Code,
pub header: Header, pub header: Header,
pub italic: Italic,
pub pre: Pre, pub pre: Pre,
pub quote: Quote, pub quote: Quote,
pub strike: Strike, pub strike: Strike,
@ -50,7 +46,6 @@ impl Tags {
bold: Bold::new(), bold: Bold::new(),
code: Code::new(), code: Code::new(),
header: Header::new(), header: Header::new(),
italic: Italic::new(),
pre: Pre::new(), pre: Pre::new(),
quote: Quote::new(), quote: Quote::new(),
strike: Strike::new(), strike: Strike::new(),
@ -59,33 +54,31 @@ impl Tags {
} }
pub fn render( pub fn render(
&mut self, &mut self,
text_view: &TextView, buffer: &TextBuffer,
base: &Uri, base: &Uri,
link_color: &RGBA, link_color: &RGBA,
links: &mut HashMap<TextTag, Uri>, links: &mut HashMap<TextTag, Uri>,
headers: &mut HashMap<TextTag, (String, Uri)>, headers: &mut HashMap<TextTag, (String, Uri)>,
) -> Option<String> { ) -> Option<String> {
let buffer = text_view.buffer();
// Collect all code blocks first, // Collect all code blocks first,
// and temporarily replace them with placeholder ID // and temporarily replace them with placeholder ID
self.code.collect(&buffer); self.code.collect(buffer);
// Keep in order! // 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.bold.render(buffer);
self.italic.render(&buffer); self.pre.render(buffer);
self.pre.render(&buffer); self.strike.render(buffer);
self.strike.render(&buffer); self.underline.render(buffer);
self.underline.render(&buffer);
reference::render(&buffer, base, link_color, links); reference::render_images_links(buffer, base, link_color, links);
hr::render(text_view); reference::render_images(buffer, base, link_color, links);
reference::render_links(buffer, base, link_color, links);
// Cleanup unformatted escape chars // Cleanup unformatted escape chars
for e in ESCAPE_ENTRIES { for e in ESCAPE_ENTRIES {
@ -101,13 +94,11 @@ impl Tags {
} }
// Render placeholders // Render placeholders
self.code.render(&buffer); self.code.render(buffer);
// Format document title string // Format document title string
title.map(|mut s| { title.map(|mut s| {
s = bold::strip_tags(&s); s = bold::strip_tags(&s);
s = hr::strip_tags(&s);
s = italic::strip_tags(&s);
s = pre::strip_tags(&s); s = pre::strip_tags(&s);
s = reference::strip_tags(&s); s = reference::strip_tags(&s);
s = strike::strip_tags(&s); s = strike::strip_tags(&s);
@ -127,13 +118,10 @@ pub fn format_header_fragment(value: &str) -> GString {
const ESCAPE_ENTRIES: &[&str] = &[ const ESCAPE_ENTRIES: &[&str] = &[
"\\\n", "\\\\", "\\>", "\\`", "\\!", "\\[", "\\]", "\\(", "\\)", "\\*", "\\#", "\\~", "\\_", "\\\n", "\\\\", "\\>", "\\`", "\\!", "\\[", "\\]", "\\(", "\\)", "\\*", "\\#", "\\~", "\\_",
"\\-",
]; ];
#[test] #[test]
fn test_escape_entries() { fn test_escape_entries() {
let mut set = std::collections::HashSet::new();
for e in ESCAPE_ENTRIES { for e in ESCAPE_ENTRIES {
assert_eq!(e.len(), 2); assert_eq!(e.len(), 2)
assert!(set.insert(*e))
} }
} }

View file

@ -5,7 +5,7 @@ use gtk::{
}; };
use regex::Regex; use regex::Regex;
const REGEX_BOLD: &str = r"(\*\*|__)(?P<text>[^\*_]*)(\*\*|__)"; const REGEX_BOLD: &str = r"\*\*(?P<text>[^\*]*)\*\*";
pub struct Bold(TextTag); pub struct Bold(TextTag);
@ -14,7 +14,7 @@ impl Bold {
Self(TextTag::builder().weight(600).wrap_mode(Word).build()) Self(TextTag::builder().weight(600).wrap_mode(Word).build())
} }
/// Apply **bold**/__bold__ `Tag` to given `TextBuffer` /// Apply **bold** `Tag` to given `TextBuffer`
pub fn render(&self, buffer: &TextBuffer) { pub fn render(&self, buffer: &TextBuffer) {
assert!(buffer.tag_table().add(&self.0)); assert!(buffer.tag_table().add(&self.0));
@ -42,7 +42,7 @@ impl Bold {
&end_iter, &end_iter,
false, false,
) )
.starts_with("\\") .contains("\\")
{ {
continue; continue;
} }
@ -72,8 +72,7 @@ pub fn strip_tags(value: &str) -> String {
#[test] #[test]
fn test_strip_tags() { fn test_strip_tags() {
const VALUE: &str = const VALUE: &str = r"Some **bold 1** and **bold 2** with ![img](https://link.com)";
"Some **bold 1** and **bold 2** and __bold 3__ and *italic 1* and _italic 2_";
let mut result = String::from(VALUE); let mut result = String::from(VALUE);
for cap in Regex::new(REGEX_BOLD).unwrap().captures_iter(VALUE) { for cap in Regex::new(REGEX_BOLD).unwrap().captures_iter(VALUE) {
if let Some(m) = cap.get(0) { if let Some(m) = cap.get(0) {
@ -82,7 +81,7 @@ fn test_strip_tags() {
} }
assert_eq!( assert_eq!(
result, result,
"Some bold 1 and bold 2 and bold 3 and *italic 1* and _italic 2_" "Some bold 1 and bold 2 with ![img](https://link.com)"
) )
} }
@ -90,15 +89,9 @@ fn test_strip_tags() {
fn test_regex() { fn test_regex() {
let cap: Vec<_> = Regex::new(REGEX_BOLD) let cap: Vec<_> = Regex::new(REGEX_BOLD)
.unwrap() .unwrap()
.captures_iter( .captures_iter(r"Some **bold 1** and **bold 2** with ![img](https://link.com)")
"Some **bold 1** and **bold 2** and __bold 3__ and *italic 1* and _italic 2_",
)
.collect(); .collect();
assert_eq!(cap.len(), 3); assert_eq!(&cap.first().unwrap()["text"], "bold 1");
assert_eq!(&cap.get(1).unwrap()["text"], "bold 2");
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");
} }

View file

@ -1,99 +0,0 @@
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) {
const OFFSET: i32 = 18;
let separator = Separator::builder()
.orientation(Orientation::Horizontal)
.build();
idle_add_local({
let text_view = text_view.clone();
let separator = separator.clone();
move || {
let w = text_view.width();
if w < OFFSET {
ControlFlow::Continue
} else {
separator.set_width_request(w - OFFSET);
ControlFlow::Break
}
}
}); // @TODO something with the widgets init/activation priority..
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,
)
.starts_with("\\")
{
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 ![img](https://link.com)";
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 ![img](https://link.com)"
)
}
#[test]
fn test_regex() {
let cap: Vec<_> = Regex::new(REGEX_HR)
.unwrap()
.captures_iter("Some line\n---\nSome another-line with ![img](https://link.com)")
.collect();
assert_eq!(&cap.first().unwrap()["hr"], "---");
}

View file

@ -1,141 +0,0 @@
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,
)
.starts_with("\\")
{
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");
}
}

View file

@ -60,7 +60,7 @@ impl Pre {
&end_iter, &end_iter,
false, false,
) )
.starts_with("\\") .contains("\\")
{ {
continue; continue;
} }

View file

@ -6,7 +6,7 @@ use gtk::{
}; };
use regex::Regex; use regex::Regex;
const REGEX_QUOTE: &str = r"(?m)^>(?:[ \t]*(?P<text>.*))?$"; const REGEX_QUOTE: &str = r"(?m)>\s*(?P<text>.*)$";
pub struct Quote(TextTag); pub struct Quote(TextTag);
@ -16,7 +16,7 @@ impl Quote {
TextTag::builder() TextTag::builder()
.left_margin(28) .left_margin(28)
.wrap_mode(Word) .wrap_mode(Word)
.style(Italic) // conflicts the italic tags decoration @TODO .style(Italic) // what about the italic tags decoration? @TODO
.build(), .build(),
) )
} }
@ -51,16 +51,18 @@ impl Quote {
#[test] #[test]
fn test_regex() { fn test_regex() {
let cap: Vec<_> = Regex::new(REGEX_QUOTE).unwrap().captures_iter( let cap: Vec<_> = Regex::new(REGEX_QUOTE).unwrap().captures_iter(
"> Some quote 1 with ![img](https://link.com)\n>\n> 2\\)Some quote 2 with text\nplain text\n> Some quote 3" "> Some quote 1 with ![img](https://link.com)\n> 2\\)Some quote 2 with text\nplain text\n> Some quote 3"
).collect(); ).collect();
{
let mut i = cap.into_iter(); let m = cap.first().unwrap();
assert_eq!(&m["text"], "Some quote 1 with ![img](https://link.com)");
assert_eq!( }
&i.next().unwrap()["text"], {
"Some quote 1 with ![img](https://link.com)" let m = cap.get(1).unwrap();
); assert_eq!(&m["text"], "2\\)Some quote 2 with text");
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"); let m = cap.get(2).unwrap();
assert_eq!(&m["text"], "Some quote 3");
}
} }

View file

@ -7,10 +7,10 @@ use gtk::{
use regex::Regex; use regex::Regex;
use std::collections::HashMap; use std::collections::HashMap;
const REGEX_LINK: &str = r"\[(?P<text>.*?)\]\((?P<url>[^\)]+)\)"; const REGEX_LINK: &str = r"\[(?P<text>[^\]]*)\]\((?P<url>[^\)]+)\)";
const REGEX_IMAGE: &str = r"!\[(?P<alt>.*?)\]\((?P<url>[^\)]+)\)"; const REGEX_IMAGE: &str = r"!\[(?P<alt>[^\]]*)\]\((?P<url>[^\)]+)\)";
const REGEX_IMAGE_LINK: &str = const REGEX_IMAGE_LINK: &str =
r"\[(?P<is_img>!)\[(?P<alt>.*?)\]\((?P<img_url>[^\)]+)\)\]\((?P<link_url>[^\)]+)\)"; r"\[(?P<is_img>!)\[(?P<alt>[^\]]*)\]\((?P<img_url>[^\)]+)\)\]\((?P<link_url>[^\)]+)\)";
struct Reference { struct Reference {
uri: Uri, uri: Uri,
@ -106,7 +106,7 @@ impl Reference {
} }
/// Image links `[![]()]()` /// Image links `[![]()]()`
fn render_images_links( pub fn render_images_links(
buffer: &TextBuffer, buffer: &TextBuffer,
base: &Uri, base: &Uri,
link_color: &RGBA, link_color: &RGBA,
@ -136,7 +136,7 @@ fn render_images_links(
&end_iter, &end_iter,
false, false,
) )
.starts_with("\\") .contains("\\")
{ {
continue; continue;
} }
@ -159,20 +159,8 @@ 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 `![]()` /// Image tags `![]()`
fn render_images( pub fn render_images(
buffer: &TextBuffer, buffer: &TextBuffer,
base: &Uri, base: &Uri,
link_color: &RGBA, link_color: &RGBA,
@ -202,7 +190,7 @@ fn render_images(
&end_iter, &end_iter,
false, false,
) )
.starts_with("\\") .contains("\\")
{ {
continue; continue;
} }
@ -223,7 +211,7 @@ fn render_images(
} }
} }
/// Links `[]()` /// Links `[]()`
fn render_links( pub fn render_links(
buffer: &TextBuffer, buffer: &TextBuffer,
base: &Uri, base: &Uri,
link_color: &RGBA, link_color: &RGBA,
@ -253,7 +241,7 @@ fn render_links(
&end_iter, &end_iter,
false, false,
) )
.starts_with("\\") .contains("\\")
{ {
continue; continue;
} }
@ -300,7 +288,7 @@ fn test_strip_tags() {
fn test_regex_link() { fn test_regex_link() {
let cap: Vec<_> = Regex::new(REGEX_LINK) let cap: Vec<_> = Regex::new(REGEX_LINK)
.unwrap() .unwrap()
.captures_iter(r"[link1](https://link1.com) [link2 [square] (round)](https://link2.com)") .captures_iter(r"[link1](https://link1.com) [link2](https://link2.com)")
.collect(); .collect();
let first = cap.first().unwrap(); let first = cap.first().unwrap();
@ -309,8 +297,8 @@ fn test_regex_link() {
assert_eq!(&first["url"], "https://link1.com"); assert_eq!(&first["url"], "https://link1.com");
let second = cap.get(1).unwrap(); let second = cap.get(1).unwrap();
assert_eq!(&second[0], "[link2 [square] (round)](https://link2.com)"); assert_eq!(&second[0], "[link2](https://link2.com)");
assert_eq!(&second["text"], "link2 [square] (round)"); assert_eq!(&second["text"], "link2");
assert_eq!(&second["url"], "https://link2.com"); assert_eq!(&second["url"], "https://link2.com");
} }

View file

@ -47,7 +47,7 @@ impl Strike {
&end_iter, &end_iter,
false, false,
) )
.starts_with("\\") .contains("\\")
{ {
continue; continue;
} }

View file

@ -43,7 +43,7 @@ impl Underline {
&end_iter, &end_iter,
false, false,
) )
.starts_with("\\") .contains("\\")
{ {
continue; continue;
} }