mirror of
https://github.com/sharkdp/bat.git
synced 2025-09-01 19:02:22 +01:00
fix(src/line_range.rs): 🐛 support three-part (start🔚context) syntax, clamp lower at zero and extend upper, and add tests for invalid formats
769 lines
25 KiB
Rust
769 lines
25 KiB
Rust
use crate::error::*;
|
|
use itertools::{Itertools, MinMaxResult};
|
|
|
|
#[derive(Debug, Copy, Clone)]
|
|
pub struct LineRange {
|
|
lower: RangeBound,
|
|
upper: RangeBound,
|
|
}
|
|
|
|
/// Defines a boundary for a range
|
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
|
pub(crate) enum RangeBound {
|
|
// An absolute line number marking the boundary of a range
|
|
Absolute(usize),
|
|
// A relative (implicitly negative) offset from the end of the file as a boundary
|
|
OffsetFromEnd(usize),
|
|
}
|
|
|
|
impl Default for LineRange {
|
|
fn default() -> LineRange {
|
|
LineRange {
|
|
lower: RangeBound::Absolute(usize::MIN),
|
|
upper: RangeBound::Absolute(usize::MAX),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl LineRange {
|
|
pub fn new(from: usize, to: usize) -> Self {
|
|
LineRange {
|
|
lower: RangeBound::Absolute(from),
|
|
upper: RangeBound::Absolute(to),
|
|
}
|
|
}
|
|
|
|
pub fn from(range_raw: &str) -> Result<LineRange> {
|
|
LineRange::parse_range(range_raw)
|
|
}
|
|
|
|
fn parse_range(range_raw: &str) -> Result<LineRange> {
|
|
let mut new_range = LineRange::default();
|
|
let mut raw_range_iter = range_raw.bytes();
|
|
let first_byte = raw_range_iter.next().ok_or("Empty line range")?;
|
|
|
|
if first_byte == b':' {
|
|
if raw_range_iter.next() == Some(b'-') {
|
|
// E.g. ':-3'
|
|
let value = range_raw[2..].parse()?;
|
|
new_range.upper = RangeBound::OffsetFromEnd(value);
|
|
} else {
|
|
let value = range_raw[1..].parse()?;
|
|
new_range.upper = RangeBound::Absolute(value);
|
|
}
|
|
return Ok(new_range);
|
|
} else if range_raw.bytes().last().ok_or("Empty line range")? == b':' {
|
|
if first_byte == b'-' {
|
|
// E.g. '-3:'
|
|
let value = range_raw[1..range_raw.len() - 1].parse()?;
|
|
new_range.lower = RangeBound::OffsetFromEnd(value);
|
|
} else {
|
|
let value = range_raw[..range_raw.len() - 1].parse()?;
|
|
new_range.lower = RangeBound::Absolute(value);
|
|
}
|
|
return Ok(new_range);
|
|
}
|
|
|
|
let line_numbers: Vec<&str> = range_raw.split(':').collect();
|
|
match line_numbers.len() {
|
|
1 => {
|
|
new_range.lower = RangeBound::Absolute(line_numbers[0].parse()?);
|
|
new_range.upper = new_range.lower;
|
|
Ok(new_range)
|
|
}
|
|
2 => {
|
|
let mut lower_absolute_bound: usize = line_numbers[0].parse()?;
|
|
let first_byte = line_numbers[1].bytes().next();
|
|
|
|
let upper_absolute_bound = if first_byte == Some(b'+') {
|
|
let more_lines = &line_numbers[1][1..]
|
|
.parse()
|
|
.map_err(|_| "Invalid character after +")?;
|
|
lower_absolute_bound.saturating_add(*more_lines)
|
|
} else if first_byte == Some(b'-') {
|
|
// this will prevent values like "-+5" even though "+5" is valid integer
|
|
if line_numbers[1][1..].bytes().next() == Some(b'+') {
|
|
return Err("Invalid character after -".into());
|
|
}
|
|
let prior_lines = &line_numbers[1][1..]
|
|
.parse()
|
|
.map_err(|_| "Invalid character after -")?;
|
|
let prev_lower = lower_absolute_bound;
|
|
lower_absolute_bound = lower_absolute_bound.saturating_sub(*prior_lines);
|
|
prev_lower
|
|
} else {
|
|
line_numbers[1].parse()?
|
|
};
|
|
new_range.lower = RangeBound::Absolute(lower_absolute_bound);
|
|
new_range.upper = RangeBound::Absolute(upper_absolute_bound);
|
|
Ok(new_range)
|
|
}
|
|
3 => {
|
|
// Handle context syntax: N::C or N:M:C
|
|
if line_numbers[1].is_empty() {
|
|
// Format: N::C - single line with context
|
|
let line_number: usize = line_numbers[0].parse()
|
|
.map_err(|_| "Invalid line number in N::C format")?;
|
|
let context: usize = line_numbers[2].parse()
|
|
.map_err(|_| "Invalid context number in N::C format")?;
|
|
|
|
new_range.lower = RangeBound::Absolute(line_number.saturating_sub(context));
|
|
new_range.upper = RangeBound::Absolute(line_number.saturating_add(context));
|
|
} else {
|
|
// Format: N:M:C - range with context
|
|
let start_line: usize = line_numbers[0].parse()
|
|
.map_err(|_| "Invalid start line number in N:M:C format")?;
|
|
let end_line: usize = line_numbers[1].parse()
|
|
.map_err(|_| "Invalid end line number in N:M:C format")?;
|
|
let context: usize = line_numbers[2].parse()
|
|
.map_err(|_| "Invalid context number in N:M:C format")?;
|
|
|
|
new_range.lower = RangeBound::Absolute(start_line.saturating_sub(context));
|
|
new_range.upper = RangeBound::Absolute(end_line.saturating_add(context));
|
|
}
|
|
Ok(new_range)
|
|
}
|
|
_ => Err(
|
|
"Line range contained too many ':' characters. Expected format: 'N', 'N:M', 'N::C', or 'N:M:C'"
|
|
.into(),
|
|
),
|
|
}
|
|
}
|
|
|
|
/// Checks if a line number is inside the range.
|
|
/// For ranges with relative offsets range bounds `max_buffered_line_number` is necessary
|
|
/// to convert the offset to an absolute value.
|
|
pub(crate) fn is_inside(
|
|
&self,
|
|
line: usize,
|
|
max_buffered_line_number: MaxBufferedLineNumber,
|
|
) -> bool {
|
|
match (self.lower, self.upper, max_buffered_line_number) {
|
|
(RangeBound::Absolute(lower), RangeBound::Absolute(upper), _) => {
|
|
lower <= line && line <= upper
|
|
}
|
|
(
|
|
RangeBound::Absolute(lower),
|
|
RangeBound::OffsetFromEnd(offset),
|
|
MaxBufferedLineNumber::Final(last_line_number),
|
|
) => lower <= line && line <= last_line_number.saturating_sub(offset),
|
|
(
|
|
RangeBound::Absolute(lower),
|
|
RangeBound::OffsetFromEnd(_),
|
|
MaxBufferedLineNumber::Tentative(_),
|
|
) => {
|
|
// We don't know the final line number yet, so the assumption is that the line is
|
|
// still far enough away from the upper end of the range
|
|
lower <= line
|
|
}
|
|
(
|
|
RangeBound::OffsetFromEnd(offset),
|
|
RangeBound::Absolute(upper),
|
|
MaxBufferedLineNumber::Final(last_line_number),
|
|
) => last_line_number.saturating_sub(offset) <= line && line <= upper,
|
|
(
|
|
RangeBound::OffsetFromEnd(_),
|
|
RangeBound::Absolute(_),
|
|
MaxBufferedLineNumber::Tentative(_),
|
|
) => {
|
|
// We don't know the final line number yet, so the assumption is that the line is
|
|
// still too far away from the having reached the lower end of the range
|
|
false
|
|
}
|
|
(
|
|
RangeBound::OffsetFromEnd(lower),
|
|
RangeBound::OffsetFromEnd(upper),
|
|
MaxBufferedLineNumber::Final(last_line_number),
|
|
) => {
|
|
last_line_number.saturating_sub(lower) <= line
|
|
&& line <= last_line_number.saturating_sub(upper)
|
|
}
|
|
(
|
|
RangeBound::OffsetFromEnd(_),
|
|
RangeBound::OffsetFromEnd(_),
|
|
MaxBufferedLineNumber::Tentative(_),
|
|
) => {
|
|
// We don't know the final line number yet, so the assumption is that we're still
|
|
// too far away from the having reached the lower end of the range
|
|
false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_full() {
|
|
let range = LineRange::from("40:50").expect("Shouldn't fail on test!");
|
|
assert_eq!(RangeBound::Absolute(40), range.lower);
|
|
assert_eq!(RangeBound::Absolute(50), range.upper);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_partial_min() {
|
|
let range = LineRange::from(":50").expect("Shouldn't fail on test!");
|
|
assert_eq!(RangeBound::Absolute(usize::MIN), range.lower);
|
|
assert_eq!(RangeBound::Absolute(50), range.upper);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_partial_relative_negative_from_back() {
|
|
let range = LineRange::from(":-5").expect("Shouldn't fail on test!");
|
|
assert_eq!(RangeBound::Absolute(usize::MIN), range.lower);
|
|
assert_eq!(RangeBound::OffsetFromEnd(5), range.upper);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_relative_negative_from_back_partial() {
|
|
let range = LineRange::from("-5:").expect("Shouldn't fail on test!");
|
|
assert_eq!(RangeBound::OffsetFromEnd(5), range.lower);
|
|
assert_eq!(RangeBound::Absolute(usize::MAX), range.upper);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_partial_max() {
|
|
let range = LineRange::from("40:").expect("Shouldn't fail on test!");
|
|
assert_eq!(RangeBound::Absolute(40), range.lower);
|
|
assert_eq!(RangeBound::Absolute(usize::MAX), range.upper);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_single() {
|
|
let range = LineRange::from("40").expect("Shouldn't fail on test!");
|
|
assert_eq!(RangeBound::Absolute(40), range.lower);
|
|
assert_eq!(RangeBound::Absolute(40), range.upper);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_fail() {
|
|
// Test 4+ colon parts should still fail
|
|
let range = LineRange::from("40:50:80:90");
|
|
assert!(range.is_err());
|
|
// Test invalid formats that should still fail
|
|
let range = LineRange::from("-2:5");
|
|
assert!(range.is_err());
|
|
let range = LineRange::from(":40:");
|
|
assert!(range.is_err());
|
|
// Test completely malformed input
|
|
let range = LineRange::from("abc:def");
|
|
assert!(range.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_plus() {
|
|
let range = LineRange::from("40:+10").expect("Shouldn't fail on test!");
|
|
assert_eq!(RangeBound::Absolute(40), range.lower);
|
|
assert_eq!(RangeBound::Absolute(50), range.upper);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_plus_overflow() {
|
|
let range = LineRange::from(&format!("{}:+1", usize::MAX)).expect("Shouldn't fail on test!");
|
|
assert_eq!(RangeBound::Absolute(usize::MAX), range.lower);
|
|
assert_eq!(RangeBound::Absolute(usize::MAX), range.upper);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_plus_fail() {
|
|
let range = LineRange::from("40:+z");
|
|
assert!(range.is_err());
|
|
let range = LineRange::from("40:+-10");
|
|
assert!(range.is_err());
|
|
let range = LineRange::from("40:+");
|
|
assert!(range.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_minus_success() {
|
|
let range = LineRange::from("40:-10").expect("Shouldn't fail on test!");
|
|
assert_eq!(RangeBound::Absolute(30), range.lower);
|
|
assert_eq!(RangeBound::Absolute(40), range.upper);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_minus_edge_cases_success() {
|
|
let range = LineRange::from("5:-4").expect("Shouldn't fail on test!");
|
|
assert_eq!(RangeBound::Absolute(1), range.lower);
|
|
assert_eq!(RangeBound::Absolute(5), range.upper);
|
|
let range = LineRange::from("5:-5").expect("Shouldn't fail on test!");
|
|
assert_eq!(RangeBound::Absolute(0), range.lower);
|
|
assert_eq!(RangeBound::Absolute(5), range.upper);
|
|
let range = LineRange::from("5:-100").expect("Shouldn't fail on test!");
|
|
assert_eq!(RangeBound::Absolute(0), range.lower);
|
|
assert_eq!(RangeBound::Absolute(5), range.upper);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_minus_fail() {
|
|
let range = LineRange::from("40:-z");
|
|
assert!(range.is_err());
|
|
let range = LineRange::from("40:-+10");
|
|
assert!(range.is_err());
|
|
let range = LineRange::from("40:-");
|
|
assert!(range.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_context_single_line() {
|
|
let range = LineRange::from("35::5").expect("Shouldn't fail on test!");
|
|
assert_eq!(RangeBound::Absolute(30), range.lower);
|
|
assert_eq!(RangeBound::Absolute(40), range.upper);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_context_range() {
|
|
let range = LineRange::from("30:40:2").expect("Shouldn't fail on test!");
|
|
assert_eq!(RangeBound::Absolute(28), range.lower);
|
|
assert_eq!(RangeBound::Absolute(42), range.upper);
|
|
|
|
// Test the case that used to fail but should now work
|
|
let range = LineRange::from("40:50:80").expect("Shouldn't fail on test!");
|
|
assert_eq!(RangeBound::Absolute(0), range.lower); // 40 - 80 = 0 (saturated)
|
|
assert_eq!(RangeBound::Absolute(130), range.upper); // 50 + 80 = 130
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_context_edge_cases() {
|
|
// Test with small line numbers that would underflow
|
|
let range = LineRange::from("5::10").expect("Shouldn't fail on test!");
|
|
assert_eq!(RangeBound::Absolute(0), range.lower);
|
|
assert_eq!(RangeBound::Absolute(15), range.upper);
|
|
|
|
// Test with zero context
|
|
let range = LineRange::from("50::0").expect("Shouldn't fail on test!");
|
|
assert_eq!(RangeBound::Absolute(50), range.lower);
|
|
assert_eq!(RangeBound::Absolute(50), range.upper);
|
|
|
|
// Test range with zero context
|
|
let range = LineRange::from("30:40:0").expect("Shouldn't fail on test!");
|
|
assert_eq!(RangeBound::Absolute(30), range.lower);
|
|
assert_eq!(RangeBound::Absolute(40), range.upper);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_context_fail() {
|
|
let range = LineRange::from("40::z");
|
|
assert!(range.is_err());
|
|
let range = LineRange::from("::5");
|
|
assert!(range.is_err());
|
|
let range = LineRange::from("40::");
|
|
assert!(range.is_err());
|
|
let range = LineRange::from("30:40:z");
|
|
assert!(range.is_err());
|
|
let range = LineRange::from("30::40:5");
|
|
assert!(range.is_err());
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
pub enum RangeCheckResult {
|
|
// Within one of the given ranges
|
|
InRange,
|
|
|
|
// Before the first range or within two ranges
|
|
BeforeOrBetweenRanges,
|
|
|
|
// Line number is outside of all ranges and larger than the last range.
|
|
AfterLastRange,
|
|
}
|
|
|
|
/// Represents the maximum line number in the buffer when reading a file.
|
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
|
pub(crate) enum MaxBufferedLineNumber {
|
|
// The currently known maximum line number, may not be the final line number
|
|
Tentative(usize),
|
|
// The final line number, when EOF has been reached
|
|
Final(usize),
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct LineRanges {
|
|
ranges: Vec<LineRange>,
|
|
// The largest absolute upper line number of all ranges
|
|
largest_absolute_upper_bound: usize,
|
|
// The smallest relative offset from the end of all ranges
|
|
smallest_offset_from_end: usize,
|
|
// The largest relative offset from the end of all ranges
|
|
largest_offset_from_end: usize,
|
|
}
|
|
|
|
impl LineRanges {
|
|
pub fn none() -> LineRanges {
|
|
LineRanges::from(vec![])
|
|
}
|
|
|
|
pub fn all() -> LineRanges {
|
|
LineRanges::from(vec![LineRange::default()])
|
|
}
|
|
|
|
pub fn from(ranges: Vec<LineRange>) -> LineRanges {
|
|
let largest_absolute_upper_bound = ranges
|
|
.iter()
|
|
.filter_map(|r| match r.upper {
|
|
RangeBound::Absolute(upper) => Some(upper),
|
|
_ => None,
|
|
})
|
|
.max()
|
|
.unwrap_or(usize::MAX);
|
|
|
|
let offsets_min_max = ranges
|
|
.iter()
|
|
.flat_map(|r| [r.lower, r.upper])
|
|
.filter_map(|r| match r {
|
|
RangeBound::OffsetFromEnd(offset) => Some(offset),
|
|
_ => None,
|
|
})
|
|
.minmax();
|
|
|
|
let (smallest_offset_from_end, largest_offset_from_end) = match offsets_min_max {
|
|
MinMaxResult::NoElements => (usize::MIN, usize::MIN),
|
|
MinMaxResult::OneElement(offset) => (offset, offset),
|
|
MinMaxResult::MinMax(min, max) => (min, max),
|
|
};
|
|
|
|
LineRanges {
|
|
ranges,
|
|
largest_absolute_upper_bound,
|
|
smallest_offset_from_end,
|
|
largest_offset_from_end,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn check(
|
|
&self,
|
|
line: usize,
|
|
max_buffered_line_number: MaxBufferedLineNumber,
|
|
) -> RangeCheckResult {
|
|
if self
|
|
.ranges
|
|
.iter()
|
|
.any(|r| r.is_inside(line, max_buffered_line_number))
|
|
{
|
|
RangeCheckResult::InRange
|
|
} else if matches!(max_buffered_line_number, MaxBufferedLineNumber::Final(final_line_number) if line > final_line_number.saturating_sub(self.smallest_offset_from_end))
|
|
{
|
|
RangeCheckResult::AfterLastRange
|
|
} else if line < self.largest_absolute_upper_bound {
|
|
RangeCheckResult::BeforeOrBetweenRanges
|
|
} else {
|
|
RangeCheckResult::AfterLastRange
|
|
}
|
|
}
|
|
|
|
pub(crate) fn largest_offset_from_end(&self) -> usize {
|
|
self.largest_offset_from_end
|
|
}
|
|
}
|
|
|
|
impl Default for LineRanges {
|
|
fn default() -> Self {
|
|
Self::all()
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct HighlightedLineRanges(pub LineRanges);
|
|
|
|
impl Default for HighlightedLineRanges {
|
|
fn default() -> Self {
|
|
HighlightedLineRanges(LineRanges::none())
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
fn ranges(rs: &[&str]) -> LineRanges {
|
|
LineRanges::from(rs.iter().map(|r| LineRange::from(r).unwrap()).collect())
|
|
}
|
|
|
|
#[test]
|
|
fn test_ranges_simple() {
|
|
let ranges = ranges(&["3:8"]);
|
|
|
|
assert_eq!(
|
|
RangeCheckResult::BeforeOrBetweenRanges,
|
|
ranges.check(2, MaxBufferedLineNumber::Tentative(2))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::InRange,
|
|
ranges.check(5, MaxBufferedLineNumber::Tentative(5))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::AfterLastRange,
|
|
ranges.check(9, MaxBufferedLineNumber::Tentative(9))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_ranges_advanced() {
|
|
let ranges = ranges(&["3:8", "11:20", "25:30"]);
|
|
|
|
assert_eq!(
|
|
RangeCheckResult::BeforeOrBetweenRanges,
|
|
ranges.check(2, MaxBufferedLineNumber::Tentative(2))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::InRange,
|
|
ranges.check(5, MaxBufferedLineNumber::Tentative(5))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::BeforeOrBetweenRanges,
|
|
ranges.check(9, MaxBufferedLineNumber::Tentative(9))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::InRange,
|
|
ranges.check(11, MaxBufferedLineNumber::Tentative(11))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::BeforeOrBetweenRanges,
|
|
ranges.check(22, MaxBufferedLineNumber::Tentative(22))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::InRange,
|
|
ranges.check(28, MaxBufferedLineNumber::Tentative(28))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::AfterLastRange,
|
|
ranges.check(31, MaxBufferedLineNumber::Tentative(31))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_ranges_open_low() {
|
|
let ranges = ranges(&["3:8", ":5"]);
|
|
|
|
assert_eq!(
|
|
RangeCheckResult::InRange,
|
|
ranges.check(1, MaxBufferedLineNumber::Tentative(1))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::InRange,
|
|
ranges.check(3, MaxBufferedLineNumber::Tentative(3))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::InRange,
|
|
ranges.check(7, MaxBufferedLineNumber::Tentative(7))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::AfterLastRange,
|
|
ranges.check(9, MaxBufferedLineNumber::Tentative(9))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_ranges_open_high() {
|
|
let ranges = ranges(&["3:", "2:5"]);
|
|
assert_eq!(
|
|
RangeCheckResult::BeforeOrBetweenRanges,
|
|
ranges.check(1, MaxBufferedLineNumber::Tentative(1))
|
|
);
|
|
|
|
assert_eq!(
|
|
RangeCheckResult::BeforeOrBetweenRanges,
|
|
ranges.check(1, MaxBufferedLineNumber::Final(10))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::InRange,
|
|
ranges.check(2, MaxBufferedLineNumber::Final(10))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::InRange,
|
|
ranges.check(9, MaxBufferedLineNumber::Final(10))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::InRange,
|
|
ranges.check(10, MaxBufferedLineNumber::Final(10))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::InRange,
|
|
ranges.check(3, MaxBufferedLineNumber::Tentative(3))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::InRange,
|
|
ranges.check(5, MaxBufferedLineNumber::Tentative(5))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::InRange,
|
|
ranges.check(9, MaxBufferedLineNumber::Tentative(9))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_ranges_open_up_to_3_from_end() {
|
|
let ranges = ranges(&[":-3"]);
|
|
assert_eq!(
|
|
RangeCheckResult::InRange,
|
|
ranges.check(1, MaxBufferedLineNumber::Tentative(1))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::InRange,
|
|
ranges.check(3, MaxBufferedLineNumber::Tentative(3))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::InRange,
|
|
ranges.check(5, MaxBufferedLineNumber::Tentative(8))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::InRange,
|
|
ranges.check(1, MaxBufferedLineNumber::Final(6))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::InRange,
|
|
ranges.check(2, MaxBufferedLineNumber::Final(6))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::InRange,
|
|
ranges.check(3, MaxBufferedLineNumber::Final(6))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::AfterLastRange,
|
|
ranges.check(4, MaxBufferedLineNumber::Final(6))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::AfterLastRange,
|
|
ranges.check(5, MaxBufferedLineNumber::Final(6))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::AfterLastRange,
|
|
ranges.check(6, MaxBufferedLineNumber::Final(6))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_ranges_multiple_negative_from_back() {
|
|
let ranges = ranges(&[":-3", ":-9"]);
|
|
assert_eq!(
|
|
RangeCheckResult::InRange,
|
|
ranges.check(1, MaxBufferedLineNumber::Tentative(1))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::InRange,
|
|
ranges.check(3, MaxBufferedLineNumber::Tentative(3))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::InRange,
|
|
ranges.check(5, MaxBufferedLineNumber::Tentative(14))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::InRange,
|
|
ranges.check(1, MaxBufferedLineNumber::Final(16))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::InRange,
|
|
ranges.check(7, MaxBufferedLineNumber::Final(16))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::InRange,
|
|
ranges.check(13, MaxBufferedLineNumber::Final(16))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::AfterLastRange,
|
|
ranges.check(14, MaxBufferedLineNumber::Final(16))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::AfterLastRange,
|
|
ranges.check(16, MaxBufferedLineNumber::Final(16))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_ranges_3_from_back_up_to_end() {
|
|
let ranges = ranges(&["-3:"]);
|
|
|
|
assert_eq!(
|
|
RangeCheckResult::BeforeOrBetweenRanges,
|
|
ranges.check(1, MaxBufferedLineNumber::Tentative(1))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::BeforeOrBetweenRanges,
|
|
ranges.check(3, MaxBufferedLineNumber::Tentative(3))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::BeforeOrBetweenRanges,
|
|
ranges.check(5, MaxBufferedLineNumber::Tentative(8))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::BeforeOrBetweenRanges,
|
|
ranges.check(1, MaxBufferedLineNumber::Final(5))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::InRange,
|
|
ranges.check(2, MaxBufferedLineNumber::Final(5))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::InRange,
|
|
ranges.check(3, MaxBufferedLineNumber::Final(5))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::InRange,
|
|
ranges.check(4, MaxBufferedLineNumber::Final(5))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::InRange,
|
|
ranges.check(5, MaxBufferedLineNumber::Final(5))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_ranges_multiple_negative_offsets_to_end() {
|
|
let ranges = ranges(&["-3:", "-12:"]);
|
|
assert_eq!(
|
|
RangeCheckResult::BeforeOrBetweenRanges,
|
|
ranges.check(5, MaxBufferedLineNumber::Tentative(8))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::BeforeOrBetweenRanges,
|
|
ranges.check(5, MaxBufferedLineNumber::Tentative(17))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::InRange,
|
|
ranges.check(8, MaxBufferedLineNumber::Final(20))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::InRange,
|
|
ranges.check(9, MaxBufferedLineNumber::Final(20))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_ranges_absolute_bound_and_offset() {
|
|
let ranges = ranges(&["5:", ":-2"]);
|
|
assert_eq!(
|
|
RangeCheckResult::InRange,
|
|
ranges.check(4, MaxBufferedLineNumber::Tentative(6))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::InRange,
|
|
ranges.check(5, MaxBufferedLineNumber::Tentative(7))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::InRange,
|
|
ranges.check(8, MaxBufferedLineNumber::Final(10))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::InRange,
|
|
ranges.check(9, MaxBufferedLineNumber::Final(10))
|
|
);
|
|
assert_eq!(
|
|
RangeCheckResult::InRange,
|
|
ranges.check(10, MaxBufferedLineNumber::Final(10))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_ranges_all() {
|
|
let ranges = LineRanges::all();
|
|
|
|
assert_eq!(
|
|
RangeCheckResult::InRange,
|
|
ranges.check(1, MaxBufferedLineNumber::Tentative(1))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_ranges_none() {
|
|
let ranges = LineRanges::none();
|
|
|
|
assert_ne!(
|
|
RangeCheckResult::InRange,
|
|
ranges.check(1, MaxBufferedLineNumber::Tentative(1))
|
|
);
|
|
}
|