From 5cf4c530cae3633ae9248b500ee0a4b0bbef98cf Mon Sep 17 00:00:00 2001 From: David Drysdale Date: Fri, 1 Mar 2024 11:33:59 +0000 Subject: [PATCH] Add {{#shiftinclude}} to L/R shift on include Syntax is the same as {{#include}} except with a shift value and colon before the remaining arguments, e.g. {{#include -2:somefile.rs:myanchor}} A positive value for the shift prepends spaces to each line. A negative value for the shift removes chars from the beginning of each line (including non-whitespace chars, although this will emit an error log). Possibly helpful/relevant to: - #1564: #include with indented - #1601: option to remove indentation of included file snippets --- src/preprocess/links.rs | 160 ++++++++++++++++++++------ src/utils/mod.rs | 4 +- src/utils/string.rs | 249 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 371 insertions(+), 42 deletions(-) diff --git a/src/preprocess/links.rs b/src/preprocess/links.rs index 0af211960a..1fb4987add 100644 --- a/src/preprocess/links.rs +++ b/src/preprocess/links.rs @@ -1,9 +1,10 @@ use crate::errors::*; use crate::utils::{ - take_anchored_lines, take_lines, take_rustdoc_include_anchored_lines, - take_rustdoc_include_lines, + take_anchored_lines_with_shift, take_lines_with_shift, take_rustdoc_include_anchored_lines, + take_rustdoc_include_lines, Shift, }; use regex::{CaptureMatches, Captures, Regex}; +use std::cmp::Ordering; use std::fs; use std::ops::{Bound, Range, RangeBounds, RangeFrom, RangeFull, RangeTo}; use std::path::{Path, PathBuf}; @@ -20,6 +21,8 @@ const MAX_LINK_NESTED_DEPTH: usize = 10; /// /// - `{{# include}}` - Insert an external file of any type. Include the whole file, only particular ///. lines, or only between the specified anchors. +/// - `{{# shiftinclude}}` - Insert content from an external file like include but shift the content +///. left or right by a specified amount. /// - `{{# rustdoc_include}}` - Insert an external Rust file, showing the particular lines ///. specified or the lines between specified anchors, and include the rest of the file behind `#`. /// This hides the lines from initial display but shows them when the reader expands the code @@ -135,7 +138,7 @@ where #[derive(PartialEq, Debug, Clone)] enum LinkType<'a> { Escaped, - Include(PathBuf, RangeOrAnchor), + Include(PathBuf, RangeOrAnchor, Shift), Playground(PathBuf, Vec<&'a str>), RustdocInclude(PathBuf, RangeOrAnchor), Title(&'a str), @@ -206,7 +209,7 @@ impl<'a> LinkType<'a> { let base = base.as_ref(); match self { LinkType::Escaped => None, - LinkType::Include(p, _) => Some(return_relative_path(base, &p)), + LinkType::Include(p, _, _) => Some(return_relative_path(base, &p)), LinkType::Playground(p, _) => Some(return_relative_path(base, &p)), LinkType::RustdocInclude(p, _) => Some(return_relative_path(base, &p)), LinkType::Title(_) => None, @@ -257,7 +260,27 @@ fn parse_include_path(path: &str) -> LinkType<'static> { let path = parts.next().unwrap().into(); let range_or_anchor = parse_range_or_anchor(parts.next()); - LinkType::Include(path, range_or_anchor) + LinkType::Include(path, range_or_anchor, Shift::None) +} + +fn parse_shift_include_path(params: &str) -> LinkType<'static> { + let mut params = params.splitn(2, ':'); + let shift = params.next().unwrap(); + let shift: isize = shift.parse().unwrap_or_else(|e| { + log::error!("failed to parse shift amount: {e:?}"); + 0 + }); + let shift = match shift.cmp(&0) { + Ordering::Greater => Shift::Right(shift as usize), + Ordering::Equal => Shift::None, + Ordering::Less => Shift::Left(-shift as usize), + }; + let mut parts = params.next().unwrap().splitn(2, ':'); + + let path = parts.next().unwrap().into(); + let range_or_anchor = parse_range_or_anchor(parts.next()); + + LinkType::Include(path, range_or_anchor, shift) } fn parse_rustdoc_include_path(path: &str) -> LinkType<'static> { @@ -289,6 +312,7 @@ impl<'a> Link<'a> { let props: Vec<&str> = path_props.collect(); match (typ.as_str(), file_arg) { + ("shiftinclude", Some(pth)) => Some(parse_shift_include_path(pth)), ("include", Some(pth)) => Some(parse_include_path(pth)), ("playground", Some(pth)) => Some(LinkType::Playground(pth.into(), props)), ("playpen", Some(pth)) => { @@ -328,13 +352,17 @@ impl<'a> Link<'a> { match self.link_type { // omit the escape char LinkType::Escaped => Ok(self.link_text[1..].to_owned()), - LinkType::Include(ref pat, ref range_or_anchor) => { + LinkType::Include(ref pat, ref range_or_anchor, shift) => { let target = base.join(pat); fs::read_to_string(&target) .map(|s| match range_or_anchor { - RangeOrAnchor::Range(range) => take_lines(&s, range.clone()), - RangeOrAnchor::Anchor(anchor) => take_anchored_lines(&s, anchor), + RangeOrAnchor::Range(range) => { + take_lines_with_shift(&s, range.clone(), shift) + } + RangeOrAnchor::Anchor(anchor) => { + take_anchored_lines_with_shift(&s, anchor, shift) + } }) .with_context(|| { format!( @@ -544,7 +572,8 @@ mod tests { end_index: 48, link_type: LinkType::Include( PathBuf::from("file.rs"), - RangeOrAnchor::Range(LineRange::from(9..20)) + RangeOrAnchor::Range(LineRange::from(9..20)), + Shift::None ), link_text: "{{#include file.rs:10:20}}", }] @@ -563,7 +592,8 @@ mod tests { end_index: 45, link_type: LinkType::Include( PathBuf::from("file.rs"), - RangeOrAnchor::Range(LineRange::from(9..10)) + RangeOrAnchor::Range(LineRange::from(9..10)), + Shift::None ), link_text: "{{#include file.rs:10}}", }] @@ -582,7 +612,8 @@ mod tests { end_index: 46, link_type: LinkType::Include( PathBuf::from("file.rs"), - RangeOrAnchor::Range(LineRange::from(9..)) + RangeOrAnchor::Range(LineRange::from(9..)), + Shift::None ), link_text: "{{#include file.rs:10:}}", }] @@ -601,7 +632,8 @@ mod tests { end_index: 46, link_type: LinkType::Include( PathBuf::from("file.rs"), - RangeOrAnchor::Range(LineRange::from(..20)) + RangeOrAnchor::Range(LineRange::from(..20)), + Shift::None ), link_text: "{{#include file.rs::20}}", }] @@ -620,7 +652,8 @@ mod tests { end_index: 44, link_type: LinkType::Include( PathBuf::from("file.rs"), - RangeOrAnchor::Range(LineRange::from(..)) + RangeOrAnchor::Range(LineRange::from(..)), + Shift::None ), link_text: "{{#include file.rs::}}", }] @@ -639,7 +672,8 @@ mod tests { end_index: 42, link_type: LinkType::Include( PathBuf::from("file.rs"), - RangeOrAnchor::Range(LineRange::from(..)) + RangeOrAnchor::Range(LineRange::from(..)), + Shift::None ), link_text: "{{#include file.rs}}", }] @@ -658,7 +692,8 @@ mod tests { end_index: 49, link_type: LinkType::Include( PathBuf::from("file.rs"), - RangeOrAnchor::Anchor(String::from("anchor")) + RangeOrAnchor::Anchor(String::from("anchor")), + Shift::None ), link_text: "{{#include file.rs:anchor}}", }] @@ -717,12 +752,12 @@ mod tests { fn test_find_all_link_types() { let s = "Some random text with escaped playground {{#include file.rs}} and \\{{#contents are \ - insignifficant in escaped link}} some more\n text {{#playground my.rs editable \ + insignifficant in escaped link}} some more\n shifted {{#shiftinclude -2:file.rs}} text {{#playground my.rs editable \ no_run should_panic}} ..."; let res = find_links(s).collect::>(); println!("\nOUTPUT: {:?}\n", res); - assert_eq!(res.len(), 3); + assert_eq!(res.len(), 4); assert_eq!( res[0], Link { @@ -730,7 +765,8 @@ mod tests { end_index: 61, link_type: LinkType::Include( PathBuf::from("file.rs"), - RangeOrAnchor::Range(LineRange::from(..)) + RangeOrAnchor::Range(LineRange::from(..)), + Shift::None ), link_text: "{{#include file.rs}}", } @@ -747,8 +783,21 @@ mod tests { assert_eq!( res[2], Link { - start_index: 133, - end_index: 183, + start_index: 135, + end_index: 163, + link_type: LinkType::Include( + PathBuf::from("file.rs"), + RangeOrAnchor::Range(LineRange::from(..)), + Shift::Left(2) + ), + link_text: "{{#shiftinclude -2:file.rs}}", + } + ); + assert_eq!( + res[3], + Link { + start_index: 170, + end_index: 220, link_type: LinkType::Playground( PathBuf::from("my.rs"), vec!["editable", "no_run", "should_panic"] @@ -765,7 +814,8 @@ mod tests { link_type, LinkType::Include( PathBuf::from("arbitrary"), - RangeOrAnchor::Range(LineRange::from(RangeFull)) + RangeOrAnchor::Range(LineRange::from(RangeFull)), + Shift::None ) ); } @@ -777,7 +827,8 @@ mod tests { link_type, LinkType::Include( PathBuf::from("arbitrary"), - RangeOrAnchor::Range(LineRange::from(RangeFull)) + RangeOrAnchor::Range(LineRange::from(RangeFull)), + Shift::None ) ); } @@ -789,7 +840,8 @@ mod tests { link_type, LinkType::Include( PathBuf::from("arbitrary"), - RangeOrAnchor::Range(LineRange::from(RangeFull)) + RangeOrAnchor::Range(LineRange::from(RangeFull)), + Shift::None ) ); } @@ -801,7 +853,8 @@ mod tests { link_type, LinkType::Include( PathBuf::from("arbitrary"), - RangeOrAnchor::Range(LineRange::from(RangeFull)) + RangeOrAnchor::Range(LineRange::from(RangeFull)), + Shift::None ) ); } @@ -813,7 +866,8 @@ mod tests { link_type, LinkType::Include( PathBuf::from("arbitrary"), - RangeOrAnchor::Range(LineRange::from(4..5)) + RangeOrAnchor::Range(LineRange::from(4..5)), + Shift::None ) ); } @@ -825,7 +879,8 @@ mod tests { link_type, LinkType::Include( PathBuf::from("arbitrary"), - RangeOrAnchor::Range(LineRange::from(0..1)) + RangeOrAnchor::Range(LineRange::from(0..1)), + Shift::None ) ); } @@ -837,7 +892,8 @@ mod tests { link_type, LinkType::Include( PathBuf::from("arbitrary"), - RangeOrAnchor::Range(LineRange::from(0..1)) + RangeOrAnchor::Range(LineRange::from(0..1)), + Shift::None ) ); } @@ -849,7 +905,8 @@ mod tests { link_type, LinkType::Include( PathBuf::from("arbitrary"), - RangeOrAnchor::Range(LineRange::from(4..)) + RangeOrAnchor::Range(LineRange::from(4..)), + Shift::None ) ); } @@ -861,7 +918,8 @@ mod tests { link_type, LinkType::Include( PathBuf::from("arbitrary"), - RangeOrAnchor::Range(LineRange::from(4..)) + RangeOrAnchor::Range(LineRange::from(4..)), + Shift::None ) ); } @@ -873,7 +931,8 @@ mod tests { link_type, LinkType::Include( PathBuf::from("arbitrary"), - RangeOrAnchor::Range(LineRange::from(..5)) + RangeOrAnchor::Range(LineRange::from(..5)), + Shift::None ) ); } @@ -885,7 +944,8 @@ mod tests { link_type, LinkType::Include( PathBuf::from("arbitrary"), - RangeOrAnchor::Range(LineRange::from(4..10)) + RangeOrAnchor::Range(LineRange::from(4..10)), + Shift::None ) ); } @@ -897,7 +957,8 @@ mod tests { link_type, LinkType::Include( PathBuf::from("arbitrary"), - RangeOrAnchor::Anchor("-5".to_string()) + RangeOrAnchor::Anchor("-5".to_string()), + Shift::None ) ); } @@ -909,7 +970,8 @@ mod tests { link_type, LinkType::Include( PathBuf::from("arbitrary"), - RangeOrAnchor::Anchor("-5.7".to_string()) + RangeOrAnchor::Anchor("-5.7".to_string()), + Shift::None ) ); } @@ -921,7 +983,8 @@ mod tests { link_type, LinkType::Include( PathBuf::from("arbitrary"), - RangeOrAnchor::Anchor("some-anchor".to_string()) + RangeOrAnchor::Anchor("some-anchor".to_string()), + Shift::None ) ); } @@ -933,7 +996,34 @@ mod tests { link_type, LinkType::Include( PathBuf::from("arbitrary"), - RangeOrAnchor::Range(LineRange::from(4..10)) + RangeOrAnchor::Range(LineRange::from(4..10)), + Shift::None + ) + ); + } + + #[test] + fn parse_start_and_end_shifted_left_range() { + let link_type = parse_shift_include_path("-2:arbitrary:5:10"); + assert_eq!( + link_type, + LinkType::Include( + PathBuf::from("arbitrary"), + RangeOrAnchor::Range(LineRange::from(4..10)), + Shift::Left(2) + ) + ); + } + + #[test] + fn parse_start_and_end_shifted_right_range() { + let link_type = parse_shift_include_path("2:arbitrary:5:10"); + assert_eq!( + link_type, + LinkType::Include( + PathBuf::from("arbitrary"), + RangeOrAnchor::Range(LineRange::from(4..10)), + Shift::Right(2) ) ); } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 2b17cc7d84..28b558c5ac 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -15,8 +15,8 @@ use std::fmt::Write; use std::path::Path; pub use self::string::{ - take_anchored_lines, take_lines, take_rustdoc_include_anchored_lines, - take_rustdoc_include_lines, + take_anchored_lines, take_anchored_lines_with_shift, take_lines, take_lines_with_shift, + take_rustdoc_include_anchored_lines, take_rustdoc_include_lines, Shift, }; /// Replaces multiple consecutive whitespace characters with a single space character. diff --git a/src/utils/string.rs b/src/utils/string.rs index 6dafe2603a..6af686ae7b 100644 --- a/src/utils/string.rs +++ b/src/utils/string.rs @@ -1,16 +1,47 @@ use once_cell::sync::Lazy; use regex::Regex; +use std::borrow::Cow; use std::ops::Bound::{Excluded, Included, Unbounded}; use std::ops::RangeBounds; +/// Indication of whether to shift included text. +#[derive(PartialEq, Eq, Debug, Clone, Copy)] +pub enum Shift { + None, + Left(usize), + Right(usize), +} + +fn shift_line(l: &str, shift: Shift) -> Cow<'_, str> { + match shift { + Shift::None => Cow::Borrowed(l), + Shift::Right(shift) => { + let indent = " ".repeat(shift); + Cow::Owned(format!("{indent}{l}")) + } + Shift::Left(skip) => { + if l.chars().take(skip).any(|c| !c.is_whitespace()) { + log::error!("left-shifting away non-whitespace"); + } + let rest = l.chars().skip(skip).collect::(); + Cow::Owned(rest) + } + } +} + /// Take a range of lines from a string. pub fn take_lines>(s: &str, range: R) -> String { + take_lines_with_shift(s, range, Shift::None) +} + +/// Take a range of lines from a string, shifting all lines left or right. +pub fn take_lines_with_shift>(s: &str, range: R, shift: Shift) -> String { let start = match range.start_bound() { Excluded(&n) => n + 1, Included(&n) => n, Unbounded => 0, }; - let lines = s.lines().skip(start); + let lines = s.lines().skip(start).map(|l| shift_line(l, shift)); match range.end_bound() { Excluded(end) => lines .take(end.saturating_sub(start)) @@ -32,7 +63,13 @@ static ANCHOR_END: Lazy = /// Take anchored lines from a string. /// Lines containing anchor are ignored. pub fn take_anchored_lines(s: &str, anchor: &str) -> String { - let mut retained = Vec::<&str>::new(); + take_anchored_lines_with_shift(s, anchor, Shift::None) +} + +/// Take anchored lines from a string, shifting all lines left or right. +/// Lines containing anchor are ignored. +pub fn take_anchored_lines_with_shift(s: &str, anchor: &str, shift: Shift) -> String { + let mut retained = Vec::::new(); let mut anchor_found = false; for l in s.lines() { @@ -45,7 +82,7 @@ pub fn take_anchored_lines(s: &str, anchor: &str) -> String { } None => { if !ANCHOR_START.is_match(l) { - retained.push(l); + retained.push(shift_line(l, shift).to_string()); } } } @@ -118,10 +155,24 @@ pub fn take_rustdoc_include_anchored_lines(s: &str, anchor: &str) -> String { #[cfg(test)] mod tests { use super::{ - take_anchored_lines, take_lines, take_rustdoc_include_anchored_lines, - take_rustdoc_include_lines, + shift_line, take_anchored_lines, take_anchored_lines_with_shift, take_lines, + take_lines_with_shift, take_rustdoc_include_anchored_lines, take_rustdoc_include_lines, + Shift, }; + #[test] + fn shift_line_test() { + let s = " Line with 4 space intro"; + assert_eq!(shift_line(s, Shift::None), s); + assert_eq!(shift_line(s, Shift::Left(4)), "Line with 4 space intro"); + assert_eq!(shift_line(s, Shift::Left(2)), " Line with 4 space intro"); + assert_eq!(shift_line(s, Shift::Left(6)), "ne with 4 space intro"); + assert_eq!( + shift_line(s, Shift::Right(2)), + " Line with 4 space intro" + ); + } + #[test] #[allow(clippy::reversed_empty_ranges)] // Intentionally checking that those are correctly handled fn take_lines_test() { @@ -135,6 +186,56 @@ mod tests { assert_eq!(take_lines(s, ..100), s); } + #[test] + #[allow(clippy::reversed_empty_ranges)] // Intentionally checking that those are correctly handled + fn take_lines_with_shift_test() { + let s = " Lorem\n ipsum\n dolor\n sit\n amet"; + assert_eq!( + take_lines_with_shift(s, 1..3, Shift::None), + " ipsum\n dolor" + ); + assert_eq!( + take_lines_with_shift(s, 1..3, Shift::Left(2)), + "ipsum\n dolor" + ); + assert_eq!( + take_lines_with_shift(s, 1..3, Shift::Right(2)), + " ipsum\n dolor" + ); + assert_eq!(take_lines_with_shift(s, 3.., Shift::None), " sit\n amet"); + assert_eq!( + take_lines_with_shift(s, 3.., Shift::Right(1)), + " sit\n amet" + ); + assert_eq!(take_lines_with_shift(s, 3.., Shift::Left(1)), " sit\n amet"); + assert_eq!( + take_lines_with_shift(s, ..3, Shift::None), + " Lorem\n ipsum\n dolor" + ); + assert_eq!( + take_lines_with_shift(s, ..3, Shift::Right(4)), + " Lorem\n ipsum\n dolor" + ); + assert_eq!( + take_lines_with_shift(s, ..3, Shift::Left(4)), + "rem\nsum\ndolor" + ); + assert_eq!(take_lines_with_shift(s, .., Shift::None), s); + // corner cases + assert_eq!(take_lines_with_shift(s, 4..3, Shift::None), ""); + assert_eq!(take_lines_with_shift(s, 4..3, Shift::Left(2)), ""); + assert_eq!(take_lines_with_shift(s, 4..3, Shift::Right(2)), ""); + assert_eq!(take_lines_with_shift(s, ..100, Shift::None), s); + assert_eq!( + take_lines_with_shift(s, ..100, Shift::Right(2)), + " Lorem\n ipsum\n dolor\n sit\n amet" + ); + assert_eq!( + take_lines_with_shift(s, ..100, Shift::Left(2)), + "Lorem\nipsum\n dolor\nsit\namet" + ); + } + #[test] fn take_anchored_lines_test() { let s = "Lorem\nipsum\ndolor\nsit\namet"; @@ -164,6 +265,144 @@ mod tests { assert_eq!(take_anchored_lines(s, "something"), ""); } + #[test] + fn take_anchored_lines_with_shift_test() { + let s = "Lorem\nipsum\ndolor\nsit\namet"; + assert_eq!(take_anchored_lines_with_shift(s, "test", Shift::None), ""); + assert_eq!( + take_anchored_lines_with_shift(s, "test", Shift::Right(2)), + "" + ); + assert_eq!( + take_anchored_lines_with_shift(s, "test", Shift::Left(2)), + "" + ); + + let s = "Lorem\nipsum\ndolor\nANCHOR_END: test\nsit\namet"; + assert_eq!(take_anchored_lines_with_shift(s, "test", Shift::None), ""); + assert_eq!( + take_anchored_lines_with_shift(s, "test", Shift::Right(2)), + "" + ); + assert_eq!( + take_anchored_lines_with_shift(s, "test", Shift::Left(2)), + "" + ); + + let s = " Lorem\n ipsum\n ANCHOR: test\n dolor\n sit\n amet"; + assert_eq!( + take_anchored_lines_with_shift(s, "test", Shift::None), + " dolor\n sit\n amet" + ); + assert_eq!( + take_anchored_lines_with_shift(s, "test", Shift::Right(2)), + " dolor\n sit\n amet" + ); + assert_eq!( + take_anchored_lines_with_shift(s, "test", Shift::Left(2)), + "dolor\nsit\namet" + ); + assert_eq!( + take_anchored_lines_with_shift(s, "something", Shift::None), + "" + ); + assert_eq!( + take_anchored_lines_with_shift(s, "something", Shift::Right(2)), + "" + ); + assert_eq!( + take_anchored_lines_with_shift(s, "something", Shift::Left(2)), + "" + ); + + let s = " Lorem\n ipsum\n ANCHOR: test\n dolor\n sit\n amet\n ANCHOR_END: test\n lorem\n ipsum"; + assert_eq!( + take_anchored_lines_with_shift(s, "test", Shift::None), + " dolor\n sit\n amet" + ); + assert_eq!( + take_anchored_lines_with_shift(s, "test", Shift::Right(2)), + " dolor\n sit\n amet" + ); + assert_eq!( + take_anchored_lines_with_shift(s, "test", Shift::Left(2)), + "dolor\nsit\namet" + ); + assert_eq!( + take_anchored_lines_with_shift(s, "test", Shift::Left(4)), + "lor\nt\net" + ); + assert_eq!( + take_anchored_lines_with_shift(s, "test", Shift::Left(44)), + "\n\n" + ); + assert_eq!( + take_anchored_lines_with_shift(s, "something", Shift::None), + "" + ); + + let s = " Lorem\n ANCHOR: test\n ipsum\n ANCHOR: test\n dolor\n sit\n amet\n ANCHOR_END: test\n lorem\n ipsum"; + assert_eq!( + take_anchored_lines_with_shift(s, "test", Shift::None), + " ipsum\n dolor\n sit\n amet" + ); + assert_eq!( + take_anchored_lines_with_shift(s, "test", Shift::Right(2)), + " ipsum\n dolor\n sit\n amet" + ); + assert_eq!( + take_anchored_lines_with_shift(s, "test", Shift::Left(2)), + "ipsum\ndolor\nsit\namet" + ); + assert_eq!( + take_anchored_lines_with_shift(s, "something", Shift::None), + "" + ); + assert_eq!( + take_anchored_lines_with_shift(s, "something", Shift::Right(2)), + "" + ); + assert_eq!( + take_anchored_lines_with_shift(s, "something", Shift::Left(2)), + "" + ); + + // Include non-ASCII. + let s = " Lorem\n ANCHOR: test2\n ípsum\n ANCHOR: test\n dôlor\n sit\n amet\n ANCHOR_END: test\n lorem\n ANCHOR_END:test2\n ipsum"; + assert_eq!( + take_anchored_lines_with_shift(s, "test2", Shift::None), + " ípsum\n dôlor\n sit\n amet\n lorem" + ); + assert_eq!( + take_anchored_lines_with_shift(s, "test2", Shift::Right(2)), + " ípsum\n dôlor\n sit\n amet\n lorem" + ); + assert_eq!( + take_anchored_lines_with_shift(s, "test2", Shift::Left(2)), + "ípsum\ndôlor\nsit\namet\nlorem" + ); + assert_eq!( + take_anchored_lines_with_shift(s, "test2", Shift::Left(4)), + "sum\nlor\nt\net\nrem" + ); + assert_eq!( + take_anchored_lines_with_shift(s, "test", Shift::None), + " dôlor\n sit\n amet" + ); + assert_eq!( + take_anchored_lines_with_shift(s, "test", Shift::Right(2)), + " dôlor\n sit\n amet" + ); + assert_eq!( + take_anchored_lines_with_shift(s, "test", Shift::Left(2)), + "dôlor\nsit\namet" + ); + assert_eq!( + take_anchored_lines_with_shift(s, "something", Shift::None), + "" + ); + } + #[test] #[allow(clippy::reversed_empty_ranges)] // Intentionally checking that those are correctly handled fn take_rustdoc_include_lines_test() {