1use std::fmt;
13use std::ops::Range;
14
15use regex_cursor::engines::meta::Regex;
16use regex_cursor::regex_automata::util::captures::Captures;
17use regex_cursor::regex_automata::util::interpolate;
18use regex_cursor::regex_automata::util::syntax;
19use regex_cursor::regex_automata::{Anchored, PatternID};
20use regex_cursor::{Cursor, Input};
21use serde::{Deserialize, Serialize};
22
23use crate::position::{ByteOffset, next_grapheme_boundary};
24use crate::selection::SelectionSet;
25use crate::text::Document;
26
27#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
28pub struct SearchOptions {
29 pub regex: bool,
30 pub match_case: bool,
31 pub whole_word: bool,
32 pub wrap: bool,
33 pub in_selection: bool,
35}
36
37#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
39pub struct SearchQuery {
40 pub pattern: String,
41 pub options: SearchOptions,
42}
43
44impl SearchQuery {
45 pub fn new(pattern: impl Into<String>, options: SearchOptions) -> Self {
46 Self {
47 pattern: pattern.into(),
48 options,
49 }
50 }
51}
52
53#[derive(Clone, Debug, PartialEq, Eq)]
54pub struct SearchError(pub String);
55
56impl fmt::Display for SearchError {
57 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58 f.write_str(&self.0)
59 }
60}
61
62pub trait ByteSource: Sync {
65 fn len(&self) -> u64;
67
68 fn is_empty(&self) -> bool {
69 self.len() == 0
70 }
71
72 fn read_at(&self, at: u64, buf: &mut [u8]) -> usize;
76}
77
78pub trait Matcher: Send + Sync {
80 fn find(&self, doc: &Document, span: Range<usize>) -> Option<Range<usize>>;
83
84 fn find_iter<'a>(
86 &'a self,
87 doc: &'a Document,
88 span: Range<usize>,
89 ) -> Box<dyn Iterator<Item = Range<usize>> + 'a>;
90
91 fn find_iter_source<'a>(
93 &'a self,
94 src: &'a dyn ByteSource,
95 span: Range<usize>,
96 ) -> Box<dyn Iterator<Item = Range<usize>> + 'a>;
97
98 fn find_iter_bytes<'a>(
100 &'a self,
101 hay: &'a [u8],
102 span: Range<usize>,
103 ) -> Box<dyn Iterator<Item = Range<usize>> + 'a>;
104
105 fn expand(&self, doc: &Document, m: Range<usize>, replacement: &str) -> String;
107}
108
109const SOURCE_CHUNK: usize = 1 << 20;
111
112fn is_continuation(b: u8) -> bool {
113 b & 0xC0 == 0x80
114}
115
116struct SourceCursor<'a> {
122 src: &'a dyn ByteSource,
123 len: usize,
124 buf: Vec<u8>,
125 spare: Vec<u8>,
127 used: usize,
129 start: usize,
130}
131
132impl<'a> SourceCursor<'a> {
133 fn at(src: &'a dyn ByteSource, offset: usize) -> Self {
135 let len = usize::try_from(src.len()).unwrap_or(usize::MAX);
136 let mut cursor = Self {
137 src,
138 len,
139 buf: Vec::new(),
140 spare: Vec::new(),
141 used: 0,
142 start: 0,
143 };
144 let mut start = (offset.min(len) / SOURCE_CHUNK) * SOURCE_CHUNK;
146 let mut probe = [0u8; 1];
147 for _ in 0..3 {
148 if start == 0 || src.read_at(start as u64, &mut probe) == 0 {
149 break;
150 }
151 if !is_continuation(probe[0]) {
152 break;
153 }
154 start -= 1;
155 }
156 if !cursor.load_from(start) {
157 cursor.len = 0;
160 }
161 cursor
162 }
163
164 fn load_from(&mut self, from: usize) -> bool {
167 let want = (SOURCE_CHUNK + 3).min(self.len.saturating_sub(from));
168 if want == 0 {
169 return false;
170 }
171 self.spare.resize(want, 0);
172 let n = self.src.read_at(from as u64, &mut self.spare);
173 if n == 0 {
174 return false;
175 }
176 let mut end = n.min(SOURCE_CHUNK);
177 if n > SOURCE_CHUNK {
178 for _ in 0..3 {
180 if !is_continuation(self.spare[end]) {
181 break;
182 }
183 end -= 1;
184 }
185 }
186 std::mem::swap(&mut self.buf, &mut self.spare);
187 self.used = end;
188 self.start = from;
189 true
190 }
191
192 fn load_to(&mut self, to: usize) -> bool {
195 if to == 0 {
196 return false;
197 }
198 let from = to.saturating_sub(SOURCE_CHUNK);
199 self.spare.resize(to - from, 0);
200 let n = self.src.read_at(from as u64, &mut self.spare);
201 if n < to - from {
202 self.spare[n..].fill(0);
206 }
207 let mut skip = 0;
208 if from > 0 {
209 while skip < 3 && is_continuation(self.spare[skip]) {
210 skip += 1;
211 }
212 }
213 self.spare.copy_within(skip.., 0);
214 self.spare.truncate(to - from - skip);
215 std::mem::swap(&mut self.buf, &mut self.spare);
216 self.used = self.buf.len();
217 self.start = from + skip;
218 true
219 }
220}
221
222impl Cursor for SourceCursor<'_> {
223 fn chunk(&self) -> &[u8] {
224 &self.buf[..self.used]
225 }
226
227 fn advance(&mut self) -> bool {
228 let next = self.start + self.used;
229 next < self.len && self.load_from(next)
230 }
231
232 fn backtrack(&mut self) -> bool {
233 self.start > 0 && self.load_to(self.start)
234 }
235
236 fn total_bytes(&self) -> Option<usize> {
237 Some(self.len)
238 }
239
240 fn offset(&self) -> usize {
241 self.start
242 }
243}
244
245struct DocCursor<'a> {
247 doc: &'a Document,
248 chunk: &'a [u8],
249 start: usize,
250}
251
252impl<'a> DocCursor<'a> {
253 fn at(doc: &'a Document, offset: usize) -> Self {
254 let (chunk, start) = doc.chunk_at(ByteOffset(offset.min(doc.len().0)));
255 Self {
256 doc,
257 chunk: chunk.as_bytes(),
258 start: start.0,
259 }
260 }
261}
262
263impl Cursor for DocCursor<'_> {
264 fn chunk(&self) -> &[u8] {
265 self.chunk
266 }
267
268 fn advance(&mut self) -> bool {
269 let next = self.start + self.chunk.len();
270 if next >= self.doc.len().0 {
271 return false;
272 }
273 let (chunk, start) = self.doc.chunk_at(ByteOffset(next));
274 self.chunk = chunk.as_bytes();
275 self.start = start.0;
276 true
277 }
278
279 fn backtrack(&mut self) -> bool {
280 if self.start == 0 {
281 return false;
282 }
283 let (chunk, start) = self.doc.chunk_at(ByteOffset(self.start - 1));
284 self.chunk = chunk.as_bytes();
285 self.start = start.0;
286 true
287 }
288
289 fn total_bytes(&self) -> Option<usize> {
290 Some(self.doc.len().0)
291 }
292
293 fn offset(&self) -> usize {
294 self.start
295 }
296}
297
298struct CursorRegex {
299 re: Regex,
300 expand_captures: bool,
302}
303
304fn input(doc: &Document, span: Range<usize>) -> Input<DocCursor<'_>> {
305 Input::new(DocCursor::at(doc, span.start)).range(span)
306}
307
308impl Matcher for CursorRegex {
309 fn find(&self, doc: &Document, span: Range<usize>) -> Option<Range<usize>> {
310 self.re.find(input(doc, span)).map(|m| m.range())
311 }
312
313 fn find_iter<'a>(
314 &'a self,
315 doc: &'a Document,
316 span: Range<usize>,
317 ) -> Box<dyn Iterator<Item = Range<usize>> + 'a> {
318 Box::new(self.re.find_iter(input(doc, span)).map(|m| m.range()))
319 }
320
321 fn find_iter_source<'a>(
322 &'a self,
323 src: &'a dyn ByteSource,
324 span: Range<usize>,
325 ) -> Box<dyn Iterator<Item = Range<usize>> + 'a> {
326 let cursor = SourceCursor::at(src, span.start);
327 let len = cursor.len;
328 let span = span.start.min(len)..span.end.min(len);
329 Box::new(
330 self.re
331 .find_iter(Input::new(cursor).range(span))
332 .map(|m| m.range()),
333 )
334 }
335
336 fn find_iter_bytes<'a>(
337 &'a self,
338 hay: &'a [u8],
339 span: Range<usize>,
340 ) -> Box<dyn Iterator<Item = Range<usize>> + 'a> {
341 let span = span.start.min(hay.len())..span.end.min(hay.len());
342 Box::new(
343 self.re
344 .find_iter(Input::new(hay).range(span))
345 .map(|m| m.range()),
346 )
347 }
348
349 fn expand(&self, doc: &Document, m: Range<usize>, replacement: &str) -> String {
350 if !self.expand_captures {
351 return replacement.to_owned();
352 }
353 let mut caps: Captures = self.re.create_captures();
354 let mut inp = input(doc, m.start..doc.len().0);
355 inp.anchored(Anchored::Yes);
356 self.re.captures(inp, &mut caps);
357 let replacement = unescape(replacement);
358 let mut out = String::new();
359 interpolate::string(
360 &replacement,
361 |i, dst| {
362 if let Some(span) = caps.get_group(i) {
363 dst.push_str(&doc.slice(ByteOffset(span.start)..ByteOffset(span.end)));
364 }
365 },
366 |name| caps.group_info().to_index(PatternID::ZERO, name),
367 &mut out,
368 );
369 out
370 }
371}
372
373fn unescape(s: &str) -> String {
375 let mut out = String::with_capacity(s.len());
376 let mut chars = s.chars();
377 while let Some(c) = chars.next() {
378 if c != '\\' {
379 out.push(c);
380 continue;
381 }
382 match chars.next() {
383 Some('n') => out.push('\n'),
384 Some('t') => out.push('\t'),
385 Some('\\') => out.push('\\'),
386 Some(other) => {
387 out.push('\\');
388 out.push(other);
389 }
390 None => out.push('\\'),
391 }
392 }
393 out
394}
395
396pub fn escape(s: &str) -> String {
398 let mut out = String::with_capacity(s.len() * 2);
399 for c in s.chars() {
400 if r"\.+*?()|[]{}^$#&-~".contains(c) {
401 out.push('\\');
402 }
403 out.push(c);
404 }
405 out
406}
407
408fn is_word_char(c: char) -> bool {
409 c.is_alphanumeric() || c == '_'
410}
411
412pub struct Searcher {
414 matcher: Box<dyn Matcher>,
415 options: SearchOptions,
416}
417
418impl Searcher {
419 pub fn new(query: &SearchQuery) -> Result<Self, SearchError> {
420 Self::build(query, false)
421 }
422
423 pub fn new_raw(query: &SearchQuery) -> Result<Self, SearchError> {
427 Self::build(query, true)
428 }
429
430 fn build(query: &SearchQuery, crlf: bool) -> Result<Self, SearchError> {
431 let o = &query.options;
432 if query.pattern.is_empty() {
433 return Err(SearchError("nothing to find".to_owned()));
434 }
435 let pattern = if o.regex {
436 if o.whole_word {
437 format!(r"\b(?:{})\b", query.pattern)
438 } else {
439 query.pattern.clone()
440 }
441 } else {
442 let mut p = escape(&query.pattern);
443 if o.whole_word {
446 if query.pattern.chars().next().is_some_and(is_word_char) {
447 p.insert_str(0, r"\b");
448 }
449 if query.pattern.chars().last().is_some_and(is_word_char) {
450 p.push_str(r"\b");
451 }
452 }
453 p
454 };
455 let re = Regex::builder()
456 .syntax(
457 syntax::Config::new()
458 .case_insensitive(!o.match_case)
459 .multi_line(true)
460 .crlf(crlf),
461 )
462 .build(&pattern)
463 .map_err(|e| SearchError(e.to_string()))?;
464 Ok(Self {
465 matcher: Box::new(CursorRegex {
466 re,
467 expand_captures: o.regex,
468 }),
469 options: o.clone(),
470 })
471 }
472
473 pub fn options(&self) -> &SearchOptions {
474 &self.options
475 }
476
477 pub fn find_next(&self, doc: &Document, from: ByteOffset) -> Option<Range<ByteOffset>> {
479 let len = doc.len().0;
480 (from.0 <= len)
481 .then(|| self.matcher.find(doc, from.0..len))
482 .flatten()
483 .map(to_offsets)
484 }
485
486 pub fn find_prev(&self, doc: &Document, before: ByteOffset) -> Option<Range<ByteOffset>> {
489 let len = doc.len().0;
490 let mut window = 64 * 1024;
491 loop {
492 let start = before.0.saturating_sub(window);
493 let last = self
494 .matcher
495 .find_iter(doc, start..len)
496 .take_while(|m| m.start < before.0)
497 .last();
498 if last.is_some() || start == 0 {
499 return last.map(to_offsets);
500 }
501 window *= 4;
502 }
503 }
504
505 pub fn matches<'a>(
508 &'a self,
509 doc: &'a Document,
510 range: Range<ByteOffset>,
511 ) -> impl Iterator<Item = Range<ByteOffset>> + 'a {
512 self.matcher
513 .find_iter(doc, range.start.0..range.end.0)
514 .map(to_offsets)
515 }
516
517 pub fn find_all(&self, doc: &Document, range: Range<ByteOffset>) -> Vec<Range<ByteOffset>> {
519 self.matcher
520 .find_iter(doc, range.start.0..range.end.0)
521 .map(to_offsets)
522 .collect()
523 }
524
525 pub fn find_all_bytes(&self, hay: &[u8]) -> Vec<Range<usize>> {
528 self.matcher.find_iter_bytes(hay, 0..hay.len()).collect()
529 }
530
531 pub fn matches_in<'a>(
534 &'a self,
535 src: &'a dyn ByteSource,
536 range: Range<u64>,
537 ) -> impl Iterator<Item = Range<u64>> + 'a {
538 self.matcher
539 .find_iter_source(src, range.start as usize..range.end as usize)
540 .map(|m| m.start as u64..m.end as u64)
541 }
542
543 pub fn find_next_in(&self, src: &dyn ByteSource, from: u64) -> Option<Range<u64>> {
545 let len = src.len();
546 (from <= len)
547 .then(|| self.matches_in(src, from..len).next())
548 .flatten()
549 }
550
551 pub fn find_prev_in(&self, src: &dyn ByteSource, before: u64) -> Option<Range<u64>> {
556 let len = src.len();
557 let before = before.min(len);
558 let end = before.saturating_add(64 * 1024).min(len);
559 let mut window: u64 = 64 * 1024;
560 loop {
561 let start = before.saturating_sub(window);
562 let last = self
563 .matches_in(src, start..end)
564 .take_while(|m| m.start < before)
565 .last();
566 if last.is_some() || start == 0 {
567 return last;
568 }
569 window = window.saturating_mul(4);
570 }
571 }
572
573 pub fn expand(&self, doc: &Document, m: Range<ByteOffset>, replacement: &str) -> String {
575 self.matcher.expand(doc, m.start.0..m.end.0, replacement)
576 }
577
578 pub fn ranges(&self, doc: &Document, sels: &SelectionSet) -> Vec<Range<ByteOffset>> {
581 let selected: Vec<_> = sels
582 .ranges()
583 .iter()
584 .filter(|s| !s.is_empty())
585 .map(|s| s.range())
586 .collect();
587 if self.options.in_selection && !selected.is_empty() {
588 selected
589 } else {
590 vec![ByteOffset(0)..doc.len()]
591 }
592 }
593
594 pub fn find_in(&self, doc: &Document, sels: &SelectionSet) -> Vec<Range<ByteOffset>> {
596 self.ranges(doc, sels)
597 .into_iter()
598 .flat_map(|r| self.find_all(doc, r))
599 .collect()
600 }
601
602 pub fn next_from(
605 &self,
606 doc: &Document,
607 sels: &SelectionSet,
608 backward: bool,
609 ) -> Option<Range<ByteOffset>> {
610 let cur = sels.primary().range();
611 if backward {
612 self.find_prev(doc, cur.start)
613 .filter(|m| *m != cur)
614 .or_else(|| {
615 self.options
616 .wrap
617 .then(|| self.find_prev(doc, ByteOffset(doc.len().0 + 1)))
618 .flatten()
619 })
620 } else {
621 let mut found = self.find_next(doc, cur.end);
622 if found.as_ref() == Some(&cur) && cur.end < doc.len() {
624 found = self.find_next(doc, next_grapheme_boundary(doc, cur.end));
625 } else if found.as_ref() == Some(&cur) {
626 found = None;
627 }
628 found.or_else(|| {
629 self.options
630 .wrap
631 .then(|| self.find_next(doc, ByteOffset(0)))
632 .flatten()
633 })
634 }
635 }
636
637 pub fn replacement_for_selection(
640 &self,
641 doc: &Document,
642 sels: &SelectionSet,
643 replacement: &str,
644 ) -> Option<(Range<ByteOffset>, String)> {
645 let cur = sels.primary().range();
646 let m = self.find_next(doc, cur.start)?;
647 (m == cur).then(|| {
648 let text = self.expand(doc, m.clone(), replacement);
649 (m, text)
650 })
651 }
652}
653
654fn to_offsets(r: Range<usize>) -> Range<ByteOffset> {
655 ByteOffset(r.start)..ByteOffset(r.end)
656}
657
658#[cfg(test)]
659mod tests {
660 use super::*;
661
662 fn q(pattern: &str, f: impl FnOnce(&mut SearchOptions)) -> Searcher {
663 let mut o = SearchOptions::default();
664 f(&mut o);
665 Searcher::new(&SearchQuery::new(pattern, o)).expect("valid pattern")
666 }
667
668 fn all(s: &Searcher, text: &str) -> Vec<(usize, usize)> {
669 let doc = Document::new(text);
670 s.find_all(&doc, ByteOffset(0)..doc.len())
671 .into_iter()
672 .map(|r| (r.start.0, r.end.0))
673 .collect()
674 }
675
676 #[test]
677 fn literal_case_and_whole_word() {
678 let text = "Foo foo food (foo) foo(";
679 assert_eq!(all(&q("foo", |_| {}), text).len(), 5);
680 assert_eq!(all(&q("foo", |o| o.match_case = true), text).len(), 4);
681 assert_eq!(
682 all(&q("foo", |o| o.whole_word = true), text),
683 vec![(0, 3), (4, 7), (14, 17), (19, 22)]
684 );
685 assert_eq!(
687 all(&q("foo(", |o| o.whole_word = true), text),
688 vec![(19, 23)]
689 );
690 assert_eq!(all(&q("(foo)", |_| {}), text), vec![(13, 18)]);
692 }
693
694 #[test]
695 fn regex_lines_and_invalid_patterns() {
696 let s = q(r"^\w+", |o| o.regex = true);
697 assert_eq!(all(&s, "ab cd\nef"), vec![(0, 2), (6, 8)]);
698 let err = Searcher::new(&SearchQuery::new(
699 "(unclosed",
700 SearchOptions {
701 regex: true,
702 ..Default::default()
703 },
704 ));
705 assert!(err.is_err());
706 assert!(
708 Searcher::new(&SearchQuery::new(
709 "a(?=b)",
710 SearchOptions {
711 regex: true,
712 ..Default::default()
713 }
714 ))
715 .is_err()
716 );
717 }
718
719 #[test]
720 fn matches_across_rope_chunks() {
721 let text = format!("{}needle{}", "x".repeat(5000), "y".repeat(5000)).repeat(20);
722 let s = q("needle", |_| {});
723 let found = all(&s, &text);
724 assert_eq!(found.len(), 20);
725 let doc = Document::new(&text);
726 for (a, b) in found {
727 assert_eq!(doc.slice(ByteOffset(a)..ByteOffset(b)), "needle");
728 }
729 }
730
731 #[test]
732 fn previous_and_next() {
733 let doc = Document::new(&format!("ab{}ab", "-".repeat(200_000)));
734 let s = q("ab", |_| {});
735 let end = doc.len();
736 assert_eq!(s.find_prev(&doc, end).map(|r| r.start.0), Some(200_002));
737 assert_eq!(
738 s.find_prev(&doc, ByteOffset(200_002)).map(|r| r.start.0),
739 Some(0)
740 );
741 assert_eq!(s.find_prev(&doc, ByteOffset(0)), None);
742 assert_eq!(
743 s.find_next(&doc, ByteOffset(1)).map(|r| r.start.0),
744 Some(200_002)
745 );
746 }
747
748 struct Bytes {
751 data: Vec<u8>,
752 shrink_to: Option<usize>,
753 }
754
755 impl ByteSource for Bytes {
756 fn len(&self) -> u64 {
757 self.data.len() as u64
758 }
759
760 fn read_at(&self, at: u64, buf: &mut [u8]) -> usize {
761 let end = self.shrink_to.unwrap_or(self.data.len());
762 let at = (at as usize).min(end);
763 let n = buf.len().min(end - at);
764 buf[..n].copy_from_slice(&self.data[at..at + n]);
765 n
766 }
767 }
768
769 fn source(text: &str) -> Bytes {
770 Bytes {
771 data: text.as_bytes().to_vec(),
772 shrink_to: None,
773 }
774 }
775
776 fn found(s: &Searcher, src: &Bytes) -> Vec<(usize, usize)> {
777 s.matches_in(src, 0..src.len())
778 .map(|m| (m.start as usize, m.end as usize))
779 .collect()
780 }
781
782 #[test]
783 fn sources_are_searched_across_chunk_boundaries() {
784 for shift in 0..8 {
787 let pad = SOURCE_CHUNK - 3 - shift;
788 let text = format!("{}é needle éé needle", "x".repeat(pad));
789 let src = source(&text);
790 let s = q("needle", |_| {});
791 let expected = all(&s, &text);
792 assert_eq!(expected.len(), 2);
793 assert_eq!(found(&s, &src), expected, "shift {shift}");
794 let w = q("é", |o| o.whole_word = true);
796 assert_eq!(found(&w, &src), all(&w, &text), "shift {shift}");
797 }
798 }
799
800 #[test]
801 fn next_and_previous_in_a_source() {
802 let text = format!("ab{}ab", "-".repeat(3 * SOURCE_CHUNK));
803 let src = source(&text);
804 let s = q("ab", |_| {});
805 let last = (text.len() - 2) as u64;
806 assert_eq!(s.find_next_in(&src, 1).map(|m| m.start), Some(last));
807 assert_eq!(s.find_prev_in(&src, src.len()).map(|m| m.start), Some(last));
808 assert_eq!(s.find_prev_in(&src, last).map(|m| m.start), Some(0));
809 assert_eq!(s.find_prev_in(&src, 0), None);
810 assert_eq!(s.find_next_in(&src, last + 1), None);
811 }
812
813 #[test]
814 fn raw_searchers_treat_cr_as_part_of_the_line_break() {
815 let text = "one\r\ntwo\rthree\n";
816 let raw = Searcher::new_raw(&SearchQuery::new(
817 r"\w+$",
818 SearchOptions {
819 regex: true,
820 ..Default::default()
821 },
822 ))
823 .unwrap();
824 let src = source(text);
825 assert_eq!(found(&raw, &src), vec![(0, 3), (5, 8), (9, 14)]);
826 assert_eq!(raw.find_all_bytes(text.as_bytes()).len(), 3);
827 }
828
829 #[test]
830 fn a_source_that_shrinks_mid_search_ends_it() {
831 let text = format!(
832 "{}needle{}needle",
833 "x".repeat(SOURCE_CHUNK),
834 "y".repeat(2 * SOURCE_CHUNK)
835 );
836 let mut src = source(&text);
837 src.shrink_to = Some(SOURCE_CHUNK + 100);
838 let s = q("needle", |_| {});
839 assert_eq!(found(&s, &src), vec![(SOURCE_CHUNK, SOURCE_CHUNK + 6)]);
840 assert!(s.find_prev_in(&src, src.len()).is_some());
842 let empty = source("");
843 assert_eq!(found(&s, &empty), vec![]);
844 }
845
846 #[test]
847 fn expansion() {
848 let doc = Document::new("John Smith");
849 let s = q(r"(?<first>\w+) (\w+)", |o| o.regex = true);
850 let m = s.find_next(&doc, ByteOffset(0)).unwrap();
851 assert_eq!(s.expand(&doc, m.clone(), "$2, ${first}"), "Smith, John");
852 assert_eq!(s.expand(&doc, m.clone(), r"$0\n\t$$"), "John Smith\n\t$");
853 let lit = q("John", |_| {});
855 let m = lit.find_next(&doc, ByteOffset(0)).unwrap();
856 assert_eq!(lit.expand(&doc, m, "$1 \\n"), "$1 \\n");
857 }
858}