Compare commits

...

2 Commits

Author SHA1 Message Date
Michael Sloan
8f9cb3ebb9 WIP markdown parser text preservation property test 2024-11-07 16:12:24 -07:00
Michael Sloan
5a54bfb357 Fix markdown preview handling of empty list items
`parse_block` was consuming events that it doesn't handle. This was
fine in its use in `parse_document`, but in its use in `parse_list`
this breaks when there is an empty list item, causing it to consume
list end tags / list item starts / etc.
2024-10-19 00:00:49 -06:00
4 changed files with 182 additions and 12 deletions

131
Cargo.lock generated
View File

@@ -2510,6 +2510,35 @@ dependencies = [
"objc",
]
[[package]]
name = "code-product"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9feea482d196b435bb8857c2ec1274926993bc3342953515620eb9641337ee01"
dependencies = [
"code-product-lib",
"code-product-macro",
]
[[package]]
name = "code-product-lib"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5938681371198e7a690aa008843d39bd3b3b65f17b0101518c388f453c0aaf4e"
dependencies = [
"proc-macro2",
]
[[package]]
name = "code-product-macro"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0371403e32bb895fc60f96b3b76a46a17b99a9ff85b3dbff0e1c5ff0f1ec8e13"
dependencies = [
"code-product-lib",
"proc-macro2",
]
[[package]]
name = "codespan-reporting"
version = "0.11.1"
@@ -2787,6 +2816,12 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
[[package]]
name = "constptr"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f6b816ec3d57d4febea032f3ce2bf9dca119f999a865f9f24d7020d9eb5167e"
[[package]]
name = "context_servers"
version = "0.1.0"
@@ -3001,6 +3036,17 @@ dependencies = [
"unicode-segmentation",
]
[[package]]
name = "cowstr"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613de46416b29f30c0c581b5f4cd7dfc01991f59c416b30aa85a0a82af7c791e"
dependencies = [
"code-product",
"constptr",
"mutants",
]
[[package]]
name = "cpal"
version = "0.15.3"
@@ -5759,7 +5805,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f79afb8cbee2ef20f59ccd477a218c12a93943d075b492015ecb1bb81f8ee904"
dependencies = [
"byteorder-lite",
"quick-error",
"quick-error 2.0.1",
]
[[package]]
@@ -6758,13 +6804,17 @@ dependencies = [
"anyhow",
"async-recursion 1.1.1",
"collections",
"cowstr",
"editor",
"gpui",
"language",
"linkify",
"log",
"pretty_assertions",
"proptest",
"pulldown-cmark 0.12.1",
"pulldown-cmark-to-cmark",
"rand 0.8.5",
"settings",
"theme",
"ui",
@@ -7062,6 +7112,12 @@ version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a"
[[package]]
name = "mutants"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc0287524726960e07b119cebd01678f852f147742ae0d925e6a520dca956126"
[[package]]
name = "naga"
version = "22.1.0"
@@ -8547,6 +8603,26 @@ dependencies = [
"thiserror",
]
[[package]]
name = "proptest"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d"
dependencies = [
"bit-set 0.5.3",
"bit-vec 0.6.3",
"bitflags 2.6.0",
"lazy_static",
"num-traits",
"rand 0.8.5",
"rand_chacha 0.3.1",
"rand_xorshift",
"regex-syntax 0.8.4",
"rusty-fork",
"tempfile",
"unarray",
]
[[package]]
name = "prost"
version = "0.9.0"
@@ -8675,6 +8751,15 @@ version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd348ff538bc9caeda7ee8cad2d1d48236a1f443c1fa3913c6a02fe0043b1dd3"
[[package]]
name = "pulldown-cmark-to-cmark"
version = "18.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e02b63adcb49f2eb675b1694b413b3e9fedbf549dfe2cc98727ad97a0c30650"
dependencies = [
"pulldown-cmark 0.12.1",
]
[[package]]
name = "qoi"
version = "0.4.1"
@@ -8684,6 +8769,12 @@ dependencies = [
"bytemuck",
]
[[package]]
name = "quick-error"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quick-error"
version = "2.0.1"
@@ -8859,6 +8950,15 @@ dependencies = [
"rand_core 0.5.1",
]
[[package]]
name = "rand_xorshift"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f"
dependencies = [
"rand_core 0.6.4",
]
[[package]]
name = "rangemap"
version = "1.5.1"
@@ -8909,7 +9009,7 @@ dependencies = [
"avif-serialize",
"imgref",
"loop9",
"quick-error",
"quick-error 2.0.1",
"rav1e",
"rgb",
]
@@ -9802,6 +9902,18 @@ version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6"
[[package]]
name = "rusty-fork"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f"
dependencies = [
"fnv",
"quick-error 1.2.3",
"tempfile",
"wait-timeout",
]
[[package]]
name = "rustybuzz"
version = "0.14.1"
@@ -12610,6 +12722,12 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "unarray"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
[[package]]
name = "unicase"
version = "2.7.0"
@@ -12979,6 +13097,15 @@ dependencies = [
"quote",
]
[[package]]
name = "wait-timeout"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
dependencies = [
"libc",
]
[[package]]
name = "waker-fn"
version = "1.2.0"

View File

@@ -343,6 +343,7 @@ cocoa = "0.26"
convert_case = "0.6.0"
core-foundation = "0.9.3"
core-foundation-sys = "0.8.6"
cowstr = "1.3.0"
ctor = "0.2.6"
dashmap = "6.0"
derive_more = "0.99.17"
@@ -382,10 +383,12 @@ pathdiff = "0.2"
postage = { version = "0.5", features = ["futures-traits"] }
pretty_assertions = "1.3.0"
profiling = "1"
proptest = "1.5.0"
prost = "0.9"
prost-build = "0.9"
prost-types = "0.9"
pulldown-cmark = { version = "0.12.0", default-features = false }
pulldown-cmark-to-cmark = "18.0.0"
rand = "0.8.5"
regex = "1.5"
repair_json = "0.1.0"

View File

@@ -15,6 +15,7 @@ path = "src/markdown_preview.rs"
test-support = []
[dependencies]
rand.workspace = true
anyhow.workspace = true
async-recursion.workspace = true
collections.workspace = true
@@ -31,4 +32,7 @@ ui.workspace = true
workspace.workspace = true
[dev-dependencies]
cowstr.workspace = true
editor = { workspace = true, features = ["test-support"] }
proptest.workspace = true
pulldown-cmark-to-cmark.workspace = true

View File

@@ -6,6 +6,13 @@ use language::LanguageRegistry;
use pulldown_cmark::{Alignment, Event, Options, Parser, Tag, TagEnd};
use std::{ops::Range, path::PathBuf, sync::Arc};
#[cfg(test)]
use cowstr::CowStr;
#[cfg(test)]
use proptest;
#[cfg(test)]
use proptest::prelude::*;
pub async fn parse_markdown(
markdown_input: &str,
file_location_directory: Option<PathBuf>,
@@ -102,6 +109,8 @@ impl<'a> MarkdownParser<'a> {
while !self.eof() {
if let Some(block) = self.parse_block().await {
self.parsed.extend(block);
} else {
self.cursor += 1;
}
}
self
@@ -163,20 +172,14 @@ impl<'a> MarkdownParser<'a> {
let code_block = self.parse_code_block(language).await;
Some(vec![ParsedMarkdownElement::CodeBlock(code_block)])
}
_ => {
self.cursor += 1;
None
}
_ => None,
},
Event::Rule => {
let source_range = source_range.clone();
self.cursor += 1;
Some(vec![ParsedMarkdownElement::HorizontalRule(source_range)])
}
_ => {
self.cursor += 1;
None
}
_ => None,
}
}
@@ -1000,6 +1003,8 @@ Some other content
- Inner
- Inner
2. Goodbyte
- Next item empty
-
* Last
",
)
@@ -1021,8 +1026,10 @@ Some other content
list_item(97..116, 3, Ordered(1), vec![p("Goodbyte", 100..108)]),
list_item(117..124, 4, Unordered, vec![p("Inner", 119..124)]),
list_item(133..140, 4, Unordered, vec![p("Inner", 135..140)]),
list_item(143..154, 2, Ordered(2), vec![p("Goodbyte", 146..154)]),
list_item(155..161, 1, Unordered, vec![p("Last", 157..161)]),
list_item(143..159, 2, Ordered(2), vec![p("Goodbyte", 146..154)]),
list_item(160..180, 3, Unordered, vec![p("Next item empty", 165..180)]),
list_item(186..190, 3, Unordered, vec![]),
list_item(191..197, 1, Unordered, vec![p("Last", 193..197)]),
]
);
}
@@ -1223,6 +1230,35 @@ fn main() {
);
}
fn arbitrary_events<'a>() -> impl Strategy<Value = Vec<Event<'static>>> {
proptest::collection::vec(arbitrary_event(), 1..100)
}
fn arbitrary_event() -> impl Strategy<Value = Event<'static>> {
prop_oneof![
arbitrary_tag().prop_map(Event::Start),
arbitrary_tag_end().prop_map(Event::End),
any::<CowStr>().prop_map(Event::Text),
any::<CowStr>().prop_map(Event::Code),
any::<CowStr>().prop_map(Event::InlineMath),
any::<CowStr>().prop_map(Event::DisplayMath),
any::<CowStr>().prop_map(Event::Html),
any::<CowStr>().prop_map(Event::InlineHtml),
any::<CowStr>().prop_map(Event::FootnoteReference),
Just(Event::SoftBreak),
Just(Event::HardBreak),
Just(Event::Rule),
any::<bool>().prop_map(Event::TaskListMarker),
]
}
proptest! {
#[test]
fn test_text_preservation_property(events in arbitrary_events()) {
assert_eq!(events, vec![])
}
}
fn rust_lang() -> Arc<Language> {
Arc::new(Language::new(
LanguageConfig {