code_block.rs 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. use pulldown_cmark::{Event, Parser, Tag};
  2. use std::ops::Range;
  3. #[derive(Debug)]
  4. pub struct ColorCodeBlocks {
  5. code: Vec<Range<usize>>,
  6. }
  7. impl ColorCodeBlocks {
  8. pub fn new(s: &str) -> ColorCodeBlocks {
  9. let mut code = Vec::new();
  10. let mut parser = Parser::new(s).into_offset_iter();
  11. while let Some((event, range)) = parser.next() {
  12. if let Event::Start(Tag::CodeBlock(_)) = event {
  13. let start = range.start;
  14. while let Some((event, range)) = parser.next() {
  15. if let Event::End(Tag::CodeBlock(_)) = event {
  16. code.push(start..range.end);
  17. break;
  18. }
  19. }
  20. } else if let Event::Code(_) = event {
  21. code.push(range);
  22. }
  23. }
  24. ColorCodeBlocks { code }
  25. }
  26. pub fn overlaps_code(&self, region: Range<usize>) -> Option<Range<usize>> {
  27. for code in &self.code {
  28. // See https://stackoverflow.com/questions/3269434.
  29. if code.start <= region.end && region.start <= code.end {
  30. return Some(code.clone());
  31. }
  32. }
  33. None
  34. }
  35. }
  36. #[cfg(test)]
  37. #[derive(Debug, PartialEq, Eq)]
  38. enum Code<'a> {
  39. Yes(&'a str),
  40. No(&'a str),
  41. }
  42. #[cfg(test)]
  43. fn bodies(s: &str) -> Vec<Code<'_>> {
  44. let mut bodies = Vec::new();
  45. let cbs = ColorCodeBlocks::new(s);
  46. let mut previous = 0..0;
  47. for range in &cbs.code {
  48. let range = range.clone();
  49. if previous.end != range.start {
  50. bodies.push(Code::No(&s[previous.end..range.start]));
  51. }
  52. bodies.push(Code::Yes(&s[range.clone()]));
  53. previous = range.clone();
  54. }
  55. if let Some(range) = cbs.code.last() {
  56. if range.end != s.len() {
  57. bodies.push(Code::No(&s[range.end..]));
  58. }
  59. }
  60. bodies
  61. }
  62. #[test]
  63. fn cbs_1() {
  64. assert_eq!(
  65. bodies("`hey you`bar me too"),
  66. [Code::Yes("`hey you`"), Code::No("bar me too")]
  67. );
  68. }
  69. #[test]
  70. fn cbs_2() {
  71. assert_eq!(
  72. bodies("`hey you` <b>me too</b>"),
  73. [Code::Yes("`hey you`"), Code::No(" <b>me too</b>")]
  74. );
  75. }
  76. #[test]
  77. fn cbs_3() {
  78. assert_eq!(
  79. bodies(r"`hey you\` <b>`me too</b>"),
  80. [Code::Yes(r"`hey you\`"), Code::No(" <b>`me too</b>")]
  81. );
  82. }
  83. #[test]
  84. fn cbs_4() {
  85. assert_eq!(
  86. bodies(
  87. "
  88. ```language_spec
  89. testing
  90. ```
  91. nope
  92. "
  93. ),
  94. [
  95. Code::No("\n"),
  96. Code::Yes("```language_spec\ntesting\n```"),
  97. Code::No("\n\nnope\n")
  98. ],
  99. );
  100. }
  101. #[test]
  102. fn cbs_5() {
  103. assert_eq!(
  104. bodies(concat!(
  105. "
  106. ``` tag_after_space
  107. testing
  108. ```",
  109. " "
  110. )),
  111. [
  112. Code::No("\n"),
  113. Code::Yes("``` tag_after_space\ntesting\n``` "),
  114. ],
  115. );
  116. }
  117. #[test]
  118. fn cbs_6() {
  119. assert_eq!(
  120. bodies(
  121. "
  122. this is indented
  123. this is indented too
  124. "
  125. ),
  126. [
  127. Code::No("\n "),
  128. Code::Yes("this is indented\n this is indented too\n"),
  129. ],
  130. );
  131. }
  132. #[test]
  133. fn cbs_7() {
  134. assert_eq!(
  135. bodies(
  136. "
  137. ```
  138. testing unclosed
  139. "
  140. ),
  141. [Code::No("\n"), Code::Yes("```\ntesting unclosed\n"),],
  142. );
  143. }
  144. #[test]
  145. fn cbs_8() {
  146. assert_eq!(
  147. bodies("`one` not `two`"),
  148. [Code::Yes("`one`"), Code::No(" not "), Code::Yes("`two`")]
  149. );
  150. }