From c9641fccedc51737a74ed47009279fa688462ea9 Mon Sep 17 00:00:00 2001 From: Jason Rodney Hansen Date: Sun, 21 Nov 2021 10:38:41 -0700 Subject: Add `Increment` trait --- helix-core/src/date.rs | 465 ---------------------------------- helix-core/src/increment/date.rs | 474 ++++++++++++++++++++++++++++++++++ helix-core/src/increment/mod.rs | 8 + helix-core/src/increment/number.rs | 507 +++++++++++++++++++++++++++++++++++++ helix-core/src/lib.rs | 3 +- helix-core/src/numbers.rs | 499 ------------------------------------ 6 files changed, 990 insertions(+), 966 deletions(-) delete mode 100644 helix-core/src/date.rs create mode 100644 helix-core/src/increment/date.rs create mode 100644 helix-core/src/increment/mod.rs create mode 100644 helix-core/src/increment/number.rs delete mode 100644 helix-core/src/numbers.rs (limited to 'helix-core/src') diff --git a/helix-core/src/date.rs b/helix-core/src/date.rs deleted file mode 100644 index c447ef70..00000000 --- a/helix-core/src/date.rs +++ /dev/null @@ -1,465 +0,0 @@ -use regex::Regex; - -use std::borrow::Cow; -use std::cmp; - -use ropey::RopeSlice; - -use crate::{Range, Tendril}; - -use chrono::{Datelike, Duration, NaiveDate}; - -fn ndays_in_month(year: i32, month: u32) -> u32 { - // The first day of the next month... - let (y, m) = if month == 12 { - (year + 1, 1) - } else { - (year, month + 1) - }; - let d = NaiveDate::from_ymd(y, m, 1); - - // ...is preceded by the last day of the original month. - d.pred().day() -} - -fn add_days(date: NaiveDate, amount: i64) -> Option { - date.checked_add_signed(Duration::days(amount)) -} - -fn add_months(date: NaiveDate, amount: i64) -> Option { - let month = date.month0() as i64 + amount; - let year = date.year() + i32::try_from(month / 12).ok()?; - - // Normalize month - let month = month % 12; - let month = if month.is_negative() { - month + 13 - } else { - month + 1 - } as u32; - - let day = cmp::min(date.day(), ndays_in_month(year, month)); - - Some(NaiveDate::from_ymd(year, month, day)) -} - -fn add_years(date: NaiveDate, amount: i64) -> Option { - let year = i32::try_from(date.year() as i64 + amount).ok()?; - - let ndays = ndays_in_month(year, date.month()); - - if date.day() > ndays { - let d = NaiveDate::from_ymd(year, date.month(), ndays); - Some(d.succ()) - } else { - date.with_year(year) - } -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -struct Format { - regex: &'static str, - separator: char, -} - -// Only support formats that aren't region specific. -static FORMATS: &[Format] = &[ - Format { - regex: r"(\d{4})-(\d{2})-(\d{2})", - separator: '-', - }, - Format { - regex: r"(\d{4})/(\d{2})/(\d{2})", - separator: '/', - }, -]; - -const DATE_LENGTH: usize = 10; - -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -enum DateField { - Year, - Month, - Day, -} - -#[derive(Debug, PartialEq, Eq)] -pub struct DateIncrementor { - pub date: NaiveDate, - pub range: Range, - - field: DateField, - format: Format, -} - -impl DateIncrementor { - pub fn from_range(text: RopeSlice, range: Range) -> Option { - let range = if range.is_empty() { - if range.anchor < text.len_bytes() { - // Treat empty range as a cursor range. - range.put_cursor(text, range.anchor + 1, true) - } else { - // The range is empty and at the end of the text. - return None; - } - } else { - range - }; - - let from = range.from().saturating_sub(DATE_LENGTH); - let to = (range.from() + DATE_LENGTH).min(text.len_chars()); - - let (from_in_text, to_in_text) = (range.from() - from, range.to() - from); - let text: Cow = text.slice(from..to).into(); - - FORMATS.iter().find_map(|&format| { - let re = Regex::new(format.regex).ok()?; - let captures = re.captures(&text)?; - - let date = captures.get(0)?; - let offset = range.from() - from_in_text; - let range = Range::new(date.start() + offset, date.end() + offset); - - let (year, month, day) = (captures.get(1)?, captures.get(2)?, captures.get(3)?); - let (year_range, month_range, day_range) = (year.range(), month.range(), day.range()); - - let field = if year_range.contains(&from_in_text) - && year_range.contains(&(to_in_text - 1)) - { - DateField::Year - } else if month_range.contains(&from_in_text) && month_range.contains(&(to_in_text - 1)) - { - DateField::Month - } else if day_range.contains(&from_in_text) && day_range.contains(&(to_in_text - 1)) { - DateField::Day - } else { - return None; - }; - - let date = NaiveDate::from_ymd_opt( - year.as_str().parse::().ok()?, - month.as_str().parse::().ok()?, - day.as_str().parse::().ok()?, - )?; - - Some(DateIncrementor { - date, - field, - range, - format, - }) - }) - } - - pub fn incremented_text(&self, amount: i64) -> Tendril { - let date = match self.field { - DateField::Year => add_years(self.date, amount), - DateField::Month => add_months(self.date, amount), - DateField::Day => add_days(self.date, amount), - } - .unwrap_or(self.date); - - format!( - "{:04}{}{:02}{}{:02}", - date.year(), - self.format.separator, - date.month(), - self.format.separator, - date.day() - ) - .into() - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::Rope; - - #[test] - fn test_create_incrementor_for_year_with_dashes() { - let rope = Rope::from_str("2021-11-15"); - - for cursor in 0..=3 { - let range = Range::new(cursor, cursor + 1); - assert_eq!( - DateIncrementor::from_range(rope.slice(..), range), - Some(DateIncrementor { - date: NaiveDate::from_ymd(2021, 11, 15), - range: Range::new(0, 10), - field: DateField::Year, - format: FORMATS[0], - }) - ); - } - } - - #[test] - fn test_create_incrementor_for_month_with_dashes() { - let rope = Rope::from_str("2021-11-15"); - - for cursor in 5..=6 { - let range = Range::new(cursor, cursor + 1); - assert_eq!( - DateIncrementor::from_range(rope.slice(..), range), - Some(DateIncrementor { - date: NaiveDate::from_ymd(2021, 11, 15), - range: Range::new(0, 10), - field: DateField::Month, - format: FORMATS[0], - }) - ); - } - } - - #[test] - fn test_create_incrementor_for_day_with_dashes() { - let rope = Rope::from_str("2021-11-15"); - - for cursor in 8..=9 { - let range = Range::new(cursor, cursor + 1); - assert_eq!( - DateIncrementor::from_range(rope.slice(..), range), - Some(DateIncrementor { - date: NaiveDate::from_ymd(2021, 11, 15), - range: Range::new(0, 10), - field: DateField::Day, - format: FORMATS[0], - }) - ); - } - } - - #[test] - fn test_try_create_incrementor_on_dashes() { - let rope = Rope::from_str("2021-11-15"); - - for &cursor in &[4, 7] { - let range = Range::new(cursor, cursor + 1); - assert_eq!(DateIncrementor::from_range(rope.slice(..), range), None,); - } - } - - #[test] - fn test_create_incrementor_for_year_with_slashes() { - let rope = Rope::from_str("2021/11/15"); - - for cursor in 0..=3 { - let range = Range::new(cursor, cursor + 1); - assert_eq!( - DateIncrementor::from_range(rope.slice(..), range), - Some(DateIncrementor { - date: NaiveDate::from_ymd(2021, 11, 15), - range: Range::new(0, 10), - field: DateField::Year, - format: FORMATS[1], - }) - ); - } - } - - #[test] - fn test_create_incrementor_for_month_with_slashes() { - let rope = Rope::from_str("2021/11/15"); - - for cursor in 5..=6 { - let range = Range::new(cursor, cursor + 1); - assert_eq!( - DateIncrementor::from_range(rope.slice(..), range), - Some(DateIncrementor { - date: NaiveDate::from_ymd(2021, 11, 15), - range: Range::new(0, 10), - field: DateField::Month, - format: FORMATS[1], - }) - ); - } - } - - #[test] - fn test_create_incrementor_for_day_with_slashes() { - let rope = Rope::from_str("2021/11/15"); - - for cursor in 8..=9 { - let range = Range::new(cursor, cursor + 1); - assert_eq!( - DateIncrementor::from_range(rope.slice(..), range), - Some(DateIncrementor { - date: NaiveDate::from_ymd(2021, 11, 15), - range: Range::new(0, 10), - field: DateField::Day, - format: FORMATS[1], - }) - ); - } - } - - #[test] - fn test_try_create_incrementor_on_slashes() { - let rope = Rope::from_str("2021/11/15"); - - for &cursor in &[4, 7] { - let range = Range::new(cursor, cursor + 1); - assert_eq!(DateIncrementor::from_range(rope.slice(..), range), None,); - } - } - - #[test] - fn test_date_surrounded_by_spaces() { - let rope = Rope::from_str(" 2021-11-15 "); - let range = Range::new(3, 4); - assert_eq!( - DateIncrementor::from_range(rope.slice(..), range), - Some(DateIncrementor { - date: NaiveDate::from_ymd(2021, 11, 15), - range: Range::new(3, 13), - field: DateField::Year, - format: FORMATS[0], - }) - ); - } - - #[test] - fn test_date_in_single_quotes() { - let rope = Rope::from_str("date = '2021-11-15'"); - let range = Range::new(10, 11); - assert_eq!( - DateIncrementor::from_range(rope.slice(..), range), - Some(DateIncrementor { - date: NaiveDate::from_ymd(2021, 11, 15), - range: Range::new(8, 18), - field: DateField::Year, - format: FORMATS[0], - }) - ); - } - - #[test] - fn test_date_in_double_quotes() { - let rope = Rope::from_str("let date = \"2021-11-15\";"); - let range = Range::new(12, 13); - assert_eq!( - DateIncrementor::from_range(rope.slice(..), range), - Some(DateIncrementor { - date: NaiveDate::from_ymd(2021, 11, 15), - range: Range::new(12, 22), - field: DateField::Year, - format: FORMATS[0], - }) - ); - } - - #[test] - fn test_date_cursor_one_right_of_date() { - let rope = Rope::from_str("2021-11-15 "); - let range = Range::new(10, 11); - assert_eq!(DateIncrementor::from_range(rope.slice(..), range), None); - } - - #[test] - fn test_date_cursor_one_left_of_number() { - let rope = Rope::from_str(" 2021-11-15"); - let range = Range::new(0, 1); - assert_eq!(DateIncrementor::from_range(rope.slice(..), range), None); - } - - #[test] - fn test_date_empty_range_at_beginning() { - let rope = Rope::from_str("2021-11-15"); - let range = Range::point(0); - assert_eq!( - DateIncrementor::from_range(rope.slice(..), range), - Some(DateIncrementor { - date: NaiveDate::from_ymd(2021, 11, 15), - range: Range::new(0, 10), - field: DateField::Year, - format: FORMATS[0], - }) - ); - } - - #[test] - fn test_date_empty_range_at_in_middle() { - let rope = Rope::from_str("2021-11-15"); - let range = Range::point(5); - assert_eq!( - DateIncrementor::from_range(rope.slice(..), range), - Some(DateIncrementor { - date: NaiveDate::from_ymd(2021, 11, 15), - range: Range::new(0, 10), - field: DateField::Month, - format: FORMATS[0], - }) - ); - } - - #[test] - fn test_date_empty_range_at_end() { - let rope = Rope::from_str("2021-11-15"); - let range = Range::point(10); - assert_eq!(DateIncrementor::from_range(rope.slice(..), range), None); - } - - #[test] - fn test_invalid_dates() { - let tests = [ - "0000-00-00", - "1980-2-21", - "1980-12-1", - "12345", - "2020-02-30", - "1999-12-32", - "19-12-32", - "1-2-3", - "0000/00/00", - "1980/2/21", - "1980/12/1", - "12345", - "2020/02/30", - "1999/12/32", - "19/12/32", - "1/2/3", - ]; - - for invalid in tests { - let rope = Rope::from_str(invalid); - let range = Range::new(0, 1); - - assert_eq!(DateIncrementor::from_range(rope.slice(..), range), None); - } - } - - #[test] - fn test_increment_dates() { - let tests = [ - // (original, cursor, amount, expected) - ("2020-02-28", 0, 1, "2021-02-28"), - ("2020-02-29", 0, 1, "2021-03-01"), - ("2020-01-31", 5, 1, "2020-02-29"), - ("2020-01-20", 5, 1, "2020-02-20"), - ("2020-02-28", 8, 1, "2020-02-29"), - ("2021-02-28", 8, 1, "2021-03-01"), - ("2021-02-28", 0, -1, "2020-02-28"), - ("2021-03-01", 0, -1, "2020-03-01"), - ("2020-02-29", 5, -1, "2020-01-29"), - ("2020-02-20", 5, -1, "2020-01-20"), - ("2020-02-29", 8, -1, "2020-02-28"), - ("2021-03-01", 8, -1, "2021-02-28"), - ("1980/12/21", 8, 100, "1981/03/31"), - ("1980/12/21", 8, -100, "1980/09/12"), - ("1980/12/21", 8, 1000, "1983/09/17"), - ("1980/12/21", 8, -1000, "1978/03/27"), - ]; - - for (original, cursor, amount, expected) in tests { - let rope = Rope::from_str(original); - let range = Range::new(cursor, cursor + 1); - assert_eq!( - DateIncrementor::from_range(rope.slice(..), range) - .unwrap() - .incremented_text(amount), - expected.into() - ); - } - } -} diff --git a/helix-core/src/increment/date.rs b/helix-core/src/increment/date.rs new file mode 100644 index 00000000..05442990 --- /dev/null +++ b/helix-core/src/increment/date.rs @@ -0,0 +1,474 @@ +use regex::Regex; + +use std::borrow::Cow; +use std::cmp; + +use ropey::RopeSlice; + +use crate::{Range, Tendril}; + +use chrono::{Datelike, Duration, NaiveDate}; + +use super::Increment; + +fn ndays_in_month(year: i32, month: u32) -> u32 { + // The first day of the next month... + let (y, m) = if month == 12 { + (year + 1, 1) + } else { + (year, month + 1) + }; + let d = NaiveDate::from_ymd(y, m, 1); + + // ...is preceded by the last day of the original month. + d.pred().day() +} + +fn add_days(date: NaiveDate, amount: i64) -> Option { + date.checked_add_signed(Duration::days(amount)) +} + +fn add_months(date: NaiveDate, amount: i64) -> Option { + let month = date.month0() as i64 + amount; + let year = date.year() + i32::try_from(month / 12).ok()?; + let year = if month.is_negative() { year - 1 } else { year }; + + // Normalize month + let month = month % 12; + let month = if month.is_negative() { + month + 13 + } else { + month + 1 + } as u32; + + let day = cmp::min(date.day(), ndays_in_month(year, month)); + + Some(NaiveDate::from_ymd(year, month, day)) +} + +fn add_years(date: NaiveDate, amount: i64) -> Option { + let year = i32::try_from(date.year() as i64 + amount).ok()?; + let ndays = ndays_in_month(year, date.month()); + + if date.day() > ndays { + let d = NaiveDate::from_ymd(year, date.month(), ndays); + Some(d.succ()) + } else { + date.with_year(year) + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +struct Format { + regex: &'static str, + separator: char, +} + +// Only support formats that aren't region specific. +static FORMATS: &[Format] = &[ + Format { + regex: r"(\d{4})-(\d{2})-(\d{2})", + separator: '-', + }, + Format { + regex: r"(\d{4})/(\d{2})/(\d{2})", + separator: '/', + }, +]; + +const DATE_LENGTH: usize = 10; + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +enum DateField { + Year, + Month, + Day, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct DateIncrementor { + date: NaiveDate, + range: Range, + field: DateField, + format: Format, +} + +impl DateIncrementor { + pub fn from_range(text: RopeSlice, range: Range) -> Option { + let range = if range.is_empty() { + if range.anchor < text.len_bytes() { + // Treat empty range as a cursor range. + range.put_cursor(text, range.anchor + 1, true) + } else { + // The range is empty and at the end of the text. + return None; + } + } else { + range + }; + + let from = range.from().saturating_sub(DATE_LENGTH); + let to = (range.from() + DATE_LENGTH).min(text.len_chars()); + + let (from_in_text, to_in_text) = (range.from() - from, range.to() - from); + let text: Cow = text.slice(from..to).into(); + + FORMATS.iter().find_map(|&format| { + let re = Regex::new(format.regex).ok()?; + let captures = re.captures(&text)?; + + let date = captures.get(0)?; + let offset = range.from() - from_in_text; + let range = Range::new(date.start() + offset, date.end() + offset); + + let (year, month, day) = (captures.get(1)?, captures.get(2)?, captures.get(3)?); + let (year_range, month_range, day_range) = (year.range(), month.range(), day.range()); + + let field = if year_range.contains(&from_in_text) + && year_range.contains(&(to_in_text - 1)) + { + DateField::Year + } else if month_range.contains(&from_in_text) && month_range.contains(&(to_in_text - 1)) + { + DateField::Month + } else if day_range.contains(&from_in_text) && day_range.contains(&(to_in_text - 1)) { + DateField::Day + } else { + return None; + }; + + let date = NaiveDate::from_ymd_opt( + year.as_str().parse::().ok()?, + month.as_str().parse::().ok()?, + day.as_str().parse::().ok()?, + )?; + + Some(DateIncrementor { + date, + field, + range, + format, + }) + }) + } +} + +impl Increment for DateIncrementor { + fn increment(&self, amount: i64) -> (Range, Tendril) { + let date = match self.field { + DateField::Year => add_years(self.date, amount), + DateField::Month => add_months(self.date, amount), + DateField::Day => add_days(self.date, amount), + } + .unwrap_or(self.date); + + ( + self.range, + format!( + "{:04}{}{:02}{}{:02}", + date.year(), + self.format.separator, + date.month(), + self.format.separator, + date.day() + ) + .into(), + ) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::Rope; + + #[test] + fn test_create_incrementor_for_year_with_dashes() { + let rope = Rope::from_str("2021-11-15"); + + for cursor in 0..=3 { + let range = Range::new(cursor, cursor + 1); + assert_eq!( + DateIncrementor::from_range(rope.slice(..), range), + Some(DateIncrementor { + date: NaiveDate::from_ymd(2021, 11, 15), + range: Range::new(0, 10), + field: DateField::Year, + format: FORMATS[0], + }) + ); + } + } + + #[test] + fn test_create_incrementor_for_month_with_dashes() { + let rope = Rope::from_str("2021-11-15"); + + for cursor in 5..=6 { + let range = Range::new(cursor, cursor + 1); + assert_eq!( + DateIncrementor::from_range(rope.slice(..), range), + Some(DateIncrementor { + date: NaiveDate::from_ymd(2021, 11, 15), + range: Range::new(0, 10), + field: DateField::Month, + format: FORMATS[0], + }) + ); + } + } + + #[test] + fn test_create_incrementor_for_day_with_dashes() { + let rope = Rope::from_str("2021-11-15"); + + for cursor in 8..=9 { + let range = Range::new(cursor, cursor + 1); + assert_eq!( + DateIncrementor::from_range(rope.slice(..), range), + Some(DateIncrementor { + date: NaiveDate::from_ymd(2021, 11, 15), + range: Range::new(0, 10), + field: DateField::Day, + format: FORMATS[0], + }) + ); + } + } + + #[test] + fn test_try_create_incrementor_on_dashes() { + let rope = Rope::from_str("2021-11-15"); + + for &cursor in &[4, 7] { + let range = Range::new(cursor, cursor + 1); + assert_eq!(DateIncrementor::from_range(rope.slice(..), range), None,); + } + } + + #[test] + fn test_create_incrementor_for_year_with_slashes() { + let rope = Rope::from_str("2021/11/15"); + + for cursor in 0..=3 { + let range = Range::new(cursor, cursor + 1); + assert_eq!( + DateIncrementor::from_range(rope.slice(..), range), + Some(DateIncrementor { + date: NaiveDate::from_ymd(2021, 11, 15), + range: Range::new(0, 10), + field: DateField::Year, + format: FORMATS[1], + }) + ); + } + } + + #[test] + fn test_create_incrementor_for_month_with_slashes() { + let rope = Rope::from_str("2021/11/15"); + + for cursor in 5..=6 { + let range = Range::new(cursor, cursor + 1); + assert_eq!( + DateIncrementor::from_range(rope.slice(..), range), + Some(DateIncrementor { + date: NaiveDate::from_ymd(2021, 11, 15), + range: Range::new(0, 10), + field: DateField::Month, + format: FORMATS[1], + }) + ); + } + } + + #[test] + fn test_create_incrementor_for_day_with_slashes() { + let rope = Rope::from_str("2021/11/15"); + + for cursor in 8..=9 { + let range = Range::new(cursor, cursor + 1); + assert_eq!( + DateIncrementor::from_range(rope.slice(..), range), + Some(DateIncrementor { + date: NaiveDate::from_ymd(2021, 11, 15), + range: Range::new(0, 10), + field: DateField::Day, + format: FORMATS[1], + }) + ); + } + } + + #[test] + fn test_try_create_incrementor_on_slashes() { + let rope = Rope::from_str("2021/11/15"); + + for &cursor in &[4, 7] { + let range = Range::new(cursor, cursor + 1); + assert_eq!(DateIncrementor::from_range(rope.slice(..), range), None,); + } + } + + #[test] + fn test_date_surrounded_by_spaces() { + let rope = Rope::from_str(" 2021-11-15 "); + let range = Range::new(3, 4); + assert_eq!( + DateIncrementor::from_range(rope.slice(..), range), + Some(DateIncrementor { + date: NaiveDate::from_ymd(2021, 11, 15), + range: Range::new(3, 13), + field: DateField::Year, + format: FORMATS[0], + }) + ); + } + + #[test] + fn test_date_in_single_quotes() { + let rope = Rope::from_str("date = '2021-11-15'"); + let range = Range::new(10, 11); + assert_eq!( + DateIncrementor::from_range(rope.slice(..), range), + Some(DateIncrementor { + date: NaiveDate::from_ymd(2021, 11, 15), + range: Range::new(8, 18), + field: DateField::Year, + format: FORMATS[0], + }) + ); + } + + #[test] + fn test_date_in_double_quotes() { + let rope = Rope::from_str("let date = \"2021-11-15\";"); + let range = Range::new(12, 13); + assert_eq!( + DateIncrementor::from_range(rope.slice(..), range), + Some(DateIncrementor { + date: NaiveDate::from_ymd(2021, 11, 15), + range: Range::new(12, 22), + field: DateField::Year, + format: FORMATS[0], + }) + ); + } + + #[test] + fn test_date_cursor_one_right_of_date() { + let rope = Rope::from_str("2021-11-15 "); + let range = Range::new(10, 11); + assert_eq!(DateIncrementor::from_range(rope.slice(..), range), None); + } + + #[test] + fn test_date_cursor_one_left_of_number() { + let rope = Rope::from_str(" 2021-11-15"); + let range = Range::new(0, 1); + assert_eq!(DateIncrementor::from_range(rope.slice(..), range), None); + } + + #[test] + fn test_date_empty_range_at_beginning() { + let rope = Rope::from_str("2021-11-15"); + let range = Range::point(0); + assert_eq!( + DateIncrementor::from_range(rope.slice(..), range), + Some(DateIncrementor { + date: NaiveDate::from_ymd(2021, 11, 15), + range: Range::new(0, 10), + field: DateField::Year, + format: FORMATS[0], + }) + ); + } + + #[test] + fn test_date_empty_range_at_in_middle() { + let rope = Rope::from_str("2021-11-15"); + let range = Range::point(5); + assert_eq!( + DateIncrementor::from_range(rope.slice(..), range), + Some(DateIncrementor { + date: NaiveDate::from_ymd(2021, 11, 15), + range: Range::new(0, 10), + field: DateField::Month, + format: FORMATS[0], + }) + ); + } + + #[test] + fn test_date_empty_range_at_end() { + let rope = Rope::from_str("2021-11-15"); + let range = Range::point(10); + assert_eq!(DateIncrementor::from_range(rope.slice(..), range), None); + } + + #[test] + fn test_invalid_dates() { + let tests = [ + "0000-00-00", + "1980-2-21", + "1980-12-1", + "12345", + "2020-02-30", + "1999-12-32", + "19-12-32", + "1-2-3", + "0000/00/00", + "1980/2/21", + "1980/12/1", + "12345", + "2020/02/30", + "1999/12/32", + "19/12/32", + "1/2/3", + ]; + + for invalid in tests { + let rope = Rope::from_str(invalid); + let range = Range::new(0, 1); + + assert_eq!(DateIncrementor::from_range(rope.slice(..), range), None); + } + } + + #[test] + fn test_increment_dates() { + let tests = [ + // (original, cursor, amount, expected) + ("2020-02-28", 0, 1, "2021-02-28"), + ("2020-02-29", 0, 1, "2021-03-01"), + ("2020-01-31", 5, 1, "2020-02-29"), + ("2020-01-20", 5, 1, "2020-02-20"), + ("2021-01-01", 5, -1, "2020-12-01"), + ("2021-01-31", 5, -2, "2020-11-30"), + ("2020-02-28", 8, 1, "2020-02-29"), + ("2021-02-28", 8, 1, "2021-03-01"), + ("2021-02-28", 0, -1, "2020-02-28"), + ("2021-03-01", 0, -1, "2020-03-01"), + ("2020-02-29", 5, -1, "2020-01-29"), + ("2020-02-20", 5, -1, "2020-01-20"), + ("2020-02-29", 8, -1, "2020-02-28"), + ("2021-03-01", 8, -1, "2021-02-28"), + ("1980/12/21", 8, 100, "1981/03/31"), + ("1980/12/21", 8, -100, "1980/09/12"), + ("1980/12/21", 8, 1000, "1983/09/17"), + ("1980/12/21", 8, -1000, "1978/03/27"), + ]; + + for (original, cursor, amount, expected) in tests { + let rope = Rope::from_str(original); + let range = Range::new(cursor, cursor + 1); + assert_eq!( + DateIncrementor::from_range(rope.slice(..), range) + .unwrap() + .increment(amount) + .1, + expected.into() + ); + } + } +} diff --git a/helix-core/src/increment/mod.rs b/helix-core/src/increment/mod.rs new file mode 100644 index 00000000..71a1f183 --- /dev/null +++ b/helix-core/src/increment/mod.rs @@ -0,0 +1,8 @@ +pub mod date; +pub mod number; + +use crate::{Range, Tendril}; + +pub trait Increment { + fn increment(&self, amount: i64) -> (Range, Tendril); +} diff --git a/helix-core/src/increment/number.rs b/helix-core/src/increment/number.rs new file mode 100644 index 00000000..a19b7e75 --- /dev/null +++ b/helix-core/src/increment/number.rs @@ -0,0 +1,507 @@ +use std::borrow::Cow; + +use ropey::RopeSlice; + +use super::Increment; + +use crate::{ + textobject::{textobject_word, TextObject}, + Range, Tendril, +}; + +#[derive(Debug, PartialEq, Eq)] +pub struct NumberIncrementor<'a> { + value: i64, + radix: u32, + range: Range, + + text: RopeSlice<'a>, +} + +impl<'a> NumberIncrementor<'a> { + /// Return information about number under rang if there is one. + pub fn from_range(text: RopeSlice, range: Range) -> Option { + // If the cursor is on the minus sign of a number we want to get the word textobject to the + // right of it. + let range = if range.to() < text.len_chars() + && range.to() - range.from() <= 1 + && text.char(range.from()) == '-' + { + Range::new(range.from() + 1, range.to() + 1) + } else { + range + }; + + let range = textobject_word(text, range, TextObject::Inside, 1, false); + + // If there is a minus sign to the left of the word object, we want to include it in the range. + let range = if range.from() > 0 && text.char(range.from() - 1) == '-' { + range.extend(range.from() - 1, range.from()) + } else { + range + }; + + let word: String = text + .slice(range.from()..range.to()) + .chars() + .filter(|&c| c != '_') + .collect(); + let (radix, prefixed) = if word.starts_with("0x") { + (16, true) + } else if word.starts_with("0o") { + (8, true) + } else if word.starts_with("0b") { + (2, true) + } else { + (10, false) + }; + + let number = if prefixed { &word[2..] } else { &word }; + + let value = i128::from_str_radix(number, radix).ok()?; + if (value.is_positive() && value.leading_zeros() < 64) + || (value.is_negative() && value.leading_ones() < 64) + { + return None; + } + + let value = value as i64; + Some(NumberIncrementor { + range, + value, + radix, + text, + }) + } +} + +impl<'a> Increment for NumberIncrementor<'a> { + fn increment(&self, amount: i64) -> (Range, Tendril) { + let old_text: Cow = self.text.slice(self.range.from()..self.range.to()).into(); + let old_length = old_text.len(); + let new_value = self.value.wrapping_add(amount); + + // Get separator indexes from right to left. + let separator_rtl_indexes: Vec = old_text + .chars() + .rev() + .enumerate() + .filter_map(|(i, c)| if c == '_' { Some(i) } else { None }) + .collect(); + + let format_length = if self.radix == 10 { + match (self.value.is_negative(), new_value.is_negative()) { + (true, false) => old_length - 1, + (false, true) => old_length + 1, + _ => old_text.len(), + } + } else { + old_text.len() - 2 + } - separator_rtl_indexes.len(); + + let mut new_text = match self.radix { + 2 => format!("0b{:01$b}", new_value, format_length), + 8 => format!("0o{:01$o}", new_value, format_length), + 10 if old_text.starts_with('0') || old_text.starts_with("-0") => { + format!("{:01$}", new_value, format_length) + } + 10 => format!("{}", new_value), + 16 => { + let (lower_count, upper_count): (usize, usize) = + old_text.chars().skip(2).fold((0, 0), |(lower, upper), c| { + ( + lower + c.is_ascii_lowercase().then(|| 1).unwrap_or(0), + upper + c.is_ascii_uppercase().then(|| 1).unwrap_or(0), + ) + }); + if upper_count > lower_count { + format!("0x{:01$X}", new_value, format_length) + } else { + format!("0x{:01$x}", new_value, format_length) + } + } + _ => unimplemented!("radix not supported: {}", self.radix), + }; + + // Add separators from original number. + for &rtl_index in &separator_rtl_indexes { + if rtl_index < new_text.len() { + let new_index = new_text.len() - rtl_index; + new_text.insert(new_index, '_'); + } + } + + // Add in additional separators if necessary. + if new_text.len() > old_length && !separator_rtl_indexes.is_empty() { + let spacing = match separator_rtl_indexes.as_slice() { + [.., b, a] => a - b - 1, + _ => separator_rtl_indexes[0], + }; + + let prefix_length = if self.radix == 10 { 0 } else { 2 }; + if let Some(mut index) = new_text.find('_') { + while index - prefix_length > spacing { + index -= spacing; + new_text.insert(index, '_'); + } + } + } + + (self.range, new_text.into()) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::Rope; + + #[test] + fn test_decimal_at_point() { + let rope = Rope::from_str("Test text 12345 more text."); + let range = Range::point(12); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range), + Some(NumberIncrementor { + range: Range::new(10, 15), + value: 12345, + radix: 10, + text: rope.slice(..), + }) + ); + } + + #[test] + fn test_uppercase_hexadecimal_at_point() { + let rope = Rope::from_str("Test text 0x123ABCDEF more text."); + let range = Range::point(12); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range), + Some(NumberIncrementor { + range: Range::new(10, 21), + value: 0x123ABCDEF, + radix: 16, + text: rope.slice(..), + }) + ); + } + + #[test] + fn test_lowercase_hexadecimal_at_point() { + let rope = Rope::from_str("Test text 0xfa3b4e more text."); + let range = Range::point(12); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range), + Some(NumberIncrementor { + range: Range::new(10, 18), + value: 0xfa3b4e, + radix: 16, + text: rope.slice(..), + }) + ); + } + + #[test] + fn test_octal_at_point() { + let rope = Rope::from_str("Test text 0o1074312 more text."); + let range = Range::point(12); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range), + Some(NumberIncrementor { + range: Range::new(10, 19), + value: 0o1074312, + radix: 8, + text: rope.slice(..), + }) + ); + } + + #[test] + fn test_binary_at_point() { + let rope = Rope::from_str("Test text 0b10111010010101 more text."); + let range = Range::point(12); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range), + Some(NumberIncrementor { + range: Range::new(10, 26), + value: 0b10111010010101, + radix: 2, + text: rope.slice(..), + }) + ); + } + + #[test] + fn test_negative_decimal_at_point() { + let rope = Rope::from_str("Test text -54321 more text."); + let range = Range::point(12); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range), + Some(NumberIncrementor { + range: Range::new(10, 16), + value: -54321, + radix: 10, + text: rope.slice(..), + }) + ); + } + + #[test] + fn test_decimal_with_leading_zeroes_at_point() { + let rope = Rope::from_str("Test text 000045326 more text."); + let range = Range::point(12); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range), + Some(NumberIncrementor { + range: Range::new(10, 19), + value: 45326, + radix: 10, + text: rope.slice(..), + }) + ); + } + + #[test] + fn test_negative_decimal_cursor_on_minus_sign() { + let rope = Rope::from_str("Test text -54321 more text."); + let range = Range::point(10); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range), + Some(NumberIncrementor { + range: Range::new(10, 16), + value: -54321, + radix: 10, + text: rope.slice(..), + }) + ); + } + + #[test] + fn test_number_under_range_start_of_rope() { + let rope = Rope::from_str("100"); + let range = Range::point(0); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range), + Some(NumberIncrementor { + range: Range::new(0, 3), + value: 100, + radix: 10, + text: rope.slice(..), + }) + ); + } + + #[test] + fn test_number_under_range_end_of_rope() { + let rope = Rope::from_str("100"); + let range = Range::point(2); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range), + Some(NumberIncrementor { + range: Range::new(0, 3), + value: 100, + radix: 10, + text: rope.slice(..), + }) + ); + } + + #[test] + fn test_number_surrounded_by_punctuation() { + let rope = Rope::from_str(",100;"); + let range = Range::point(1); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range), + Some(NumberIncrementor { + range: Range::new(1, 4), + value: 100, + radix: 10, + text: rope.slice(..), + }) + ); + } + + #[test] + fn test_not_a_number_point() { + let rope = Rope::from_str("Test text 45326 more text."); + let range = Range::point(6); + assert_eq!(NumberIncrementor::from_range(rope.slice(..), range), None); + } + + #[test] + fn test_number_too_large_at_point() { + let rope = Rope::from_str("Test text 0xFFFFFFFFFFFFFFFFF more text."); + let range = Range::point(12); + assert_eq!(NumberIncrementor::from_range(rope.slice(..), range), None); + } + + #[test] + fn test_number_cursor_one_right_of_number() { + let rope = Rope::from_str("100 "); + let range = Range::point(3); + assert_eq!(NumberIncrementor::from_range(rope.slice(..), range), None); + } + + #[test] + fn test_number_cursor_one_left_of_number() { + let rope = Rope::from_str(" 100"); + let range = Range::point(0); + assert_eq!(NumberIncrementor::from_range(rope.slice(..), range), None); + } + + #[test] + fn test_increment_basic_decimal_numbers() { + let tests = [ + ("100", 1, "101"), + ("100", -1, "99"), + ("99", 1, "100"), + ("100", 1000, "1100"), + ("100", -1000, "-900"), + ("-1", 1, "0"), + ("-1", 2, "1"), + ("1", -1, "0"), + ("1", -2, "-1"), + ]; + + for (original, amount, expected) in tests { + let rope = Rope::from_str(original); + let range = Range::point(0); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range) + .unwrap() + .increment(amount) + .1, + expected.into() + ); + } + } + + #[test] + fn test_increment_basic_hexadedimal_numbers() { + let tests = [ + ("0x0100", 1, "0x0101"), + ("0x0100", -1, "0x00ff"), + ("0x0001", -1, "0x0000"), + ("0x0000", -1, "0xffffffffffffffff"), + ("0xffffffffffffffff", 1, "0x0000000000000000"), + ("0xffffffffffffffff", 2, "0x0000000000000001"), + ("0xffffffffffffffff", -1, "0xfffffffffffffffe"), + ("0xABCDEF1234567890", 1, "0xABCDEF1234567891"), + ("0xabcdef1234567890", 1, "0xabcdef1234567891"), + ]; + + for (original, amount, expected) in tests { + let rope = Rope::from_str(original); + let range = Range::point(0); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range) + .unwrap() + .increment(amount) + .1, + expected.into() + ); + } + } + + #[test] + fn test_increment_basic_octal_numbers() { + let tests = [ + ("0o0107", 1, "0o0110"), + ("0o0110", -1, "0o0107"), + ("0o0001", -1, "0o0000"), + ("0o7777", 1, "0o10000"), + ("0o1000", -1, "0o0777"), + ("0o0107", 10, "0o0121"), + ("0o0000", -1, "0o1777777777777777777777"), + ("0o1777777777777777777777", 1, "0o0000000000000000000000"), + ("0o1777777777777777777777", 2, "0o0000000000000000000001"), + ("0o1777777777777777777777", -1, "0o1777777777777777777776"), + ]; + + for (original, amount, expected) in tests { + let rope = Rope::from_str(original); + let range = Range::point(0); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range) + .unwrap() + .increment(amount) + .1, + expected.into() + ); + } + } + + #[test] + fn test_increment_basic_binary_numbers() { + let tests = [ + ("0b00000100", 1, "0b00000101"), + ("0b00000100", -1, "0b00000011"), + ("0b00000100", 2, "0b00000110"), + ("0b00000100", -2, "0b00000010"), + ("0b00000001", -1, "0b00000000"), + ("0b00111111", 10, "0b01001001"), + ("0b11111111", 1, "0b100000000"), + ("0b10000000", -1, "0b01111111"), + ( + "0b0000", + -1, + "0b1111111111111111111111111111111111111111111111111111111111111111", + ), + ( + "0b1111111111111111111111111111111111111111111111111111111111111111", + 1, + "0b0000000000000000000000000000000000000000000000000000000000000000", + ), + ( + "0b1111111111111111111111111111111111111111111111111111111111111111", + 2, + "0b0000000000000000000000000000000000000000000000000000000000000001", + ), + ( + "0b1111111111111111111111111111111111111111111111111111111111111111", + -1, + "0b1111111111111111111111111111111111111111111111111111111111111110", + ), + ]; + + for (original, amount, expected) in tests { + let rope = Rope::from_str(original); + let range = Range::point(0); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range) + .unwrap() + .increment(amount) + .1, + expected.into() + ); + } + } + + #[test] + fn test_increment_with_separators() { + let tests = [ + ("999_999", 1, "1_000_000"), + ("1_000_000", -1, "999_999"), + ("-999_999", -1, "-1_000_000"), + ("0x0000_0000_0001", 0x1_ffff_0000, "0x0001_ffff_0001"), + ("0x0000_0000_0001", 0x1_ffff_0000, "0x0001_ffff_0001"), + ("0x0000_0000_0001", 0x1_ffff_0000, "0x0001_ffff_0001"), + ("0x0000_0000", -1, "0xffff_ffff_ffff_ffff"), + ("0x0000_0000_0000", -1, "0xffff_ffff_ffff_ffff"), + ("0b01111111_11111111", 1, "0b10000000_00000000"), + ("0b11111111_11111111", 1, "0b1_00000000_00000000"), + ]; + + for (original, amount, expected) in tests { + let rope = Rope::from_str(original); + let range = Range::point(0); + assert_eq!( + NumberIncrementor::from_range(rope.slice(..), range) + .unwrap() + .increment(amount) + .1, + expected.into() + ); + } + } +} diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs index b16a716f..4ae044cc 100644 --- a/helix-core/src/lib.rs +++ b/helix-core/src/lib.rs @@ -1,17 +1,16 @@ pub mod auto_pairs; pub mod chars; pub mod comment; -pub mod date; pub mod diagnostic; pub mod diff; pub mod graphemes; pub mod history; +pub mod increment; pub mod indent; pub mod line_ending; pub mod macros; pub mod match_brackets; pub mod movement; -pub mod numbers; pub mod object; pub mod path; mod position; diff --git a/helix-core/src/numbers.rs b/helix-core/src/numbers.rs deleted file mode 100644 index e9f3c898..00000000 --- a/helix-core/src/numbers.rs +++ /dev/null @@ -1,499 +0,0 @@ -use std::borrow::Cow; - -use ropey::RopeSlice; - -use crate::{ - textobject::{textobject_word, TextObject}, - Range, Tendril, -}; - -#[derive(Debug, PartialEq, Eq)] -pub struct NumberIncrementor<'a> { - pub range: Range, - pub value: i64, - pub radix: u32, - - text: RopeSlice<'a>, -} - -impl<'a> NumberIncrementor<'a> { - /// Return information about number under rang if there is one. - pub fn from_range(text: RopeSlice, range: Range) -> Option { - // If the cursor is on the minus sign of a number we want to get the word textobject to the - // right of it. - let range = if range.to() < text.len_chars() - && range.to() - range.from() <= 1 - && text.char(range.from()) == '-' - { - Range::new(range.from() + 1, range.to() + 1) - } else { - range - }; - - let range = textobject_word(text, range, TextObject::Inside, 1, false); - - // If there is a minus sign to the left of the word object, we want to include it in the range. - let range = if range.from() > 0 && text.char(range.from() - 1) == '-' { - range.extend(range.from() - 1, range.from()) - } else { - range - }; - - let word: String = text - .slice(range.from()..range.to()) - .chars() - .filter(|&c| c != '_') - .collect(); - let (radix, prefixed) = if word.starts_with("0x") { - (16, true) - } else if word.starts_with("0o") { - (8, true) - } else if word.starts_with("0b") { - (2, true) - } else { - (10, false) - }; - - let number = if prefixed { &word[2..] } else { &word }; - - let value = i128::from_str_radix(number, radix).ok()?; - if (value.is_positive() && value.leading_zeros() < 64) - || (value.is_negative() && value.leading_ones() < 64) - { - return None; - } - - let value = value as i64; - Some(NumberIncrementor { - range, - value, - radix, - text, - }) - } - - /// Add `amount` to the number and return the formatted text. - pub fn incremented_text(&self, amount: i64) -> Tendril { - let old_text: Cow = self.text.slice(self.range.from()..self.range.to()).into(); - let old_length = old_text.len(); - let new_value = self.value.wrapping_add(amount); - - // Get separator indexes from right to left. - let separator_rtl_indexes: Vec = old_text - .chars() - .rev() - .enumerate() - .filter_map(|(i, c)| if c == '_' { Some(i) } else { None }) - .collect(); - - let format_length = if self.radix == 10 { - match (self.value.is_negative(), new_value.is_negative()) { - (true, false) => old_length - 1, - (false, true) => old_length + 1, - _ => old_text.len(), - } - } else { - old_text.len() - 2 - } - separator_rtl_indexes.len(); - - let mut new_text = match self.radix { - 2 => format!("0b{:01$b}", new_value, format_length), - 8 => format!("0o{:01$o}", new_value, format_length), - 10 if old_text.starts_with('0') || old_text.starts_with("-0") => { - format!("{:01$}", new_value, format_length) - } - 10 => format!("{}", new_value), - 16 => { - let (lower_count, upper_count): (usize, usize) = - old_text.chars().skip(2).fold((0, 0), |(lower, upper), c| { - ( - lower + c.is_ascii_lowercase().then(|| 1).unwrap_or(0), - upper + c.is_ascii_uppercase().then(|| 1).unwrap_or(0), - ) - }); - if upper_count > lower_count { - format!("0x{:01$X}", new_value, format_length) - } else { - format!("0x{:01$x}", new_value, format_length) - } - } - _ => unimplemented!("radix not supported: {}", self.radix), - }; - - // Add separators from original number. - for &rtl_index in &separator_rtl_indexes { - if rtl_index < new_text.len() { - let new_index = new_text.len() - rtl_index; - new_text.insert(new_index, '_'); - } - } - - // Add in additional separators if necessary. - if new_text.len() > old_length && !separator_rtl_indexes.is_empty() { - let spacing = match separator_rtl_indexes.as_slice() { - [.., b, a] => a - b - 1, - _ => separator_rtl_indexes[0], - }; - - let prefix_length = if self.radix == 10 { 0 } else { 2 }; - if let Some(mut index) = new_text.find('_') { - while index - prefix_length > spacing { - index -= spacing; - new_text.insert(index, '_'); - } - } - } - - new_text.into() - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::Rope; - - #[test] - fn test_decimal_at_point() { - let rope = Rope::from_str("Test text 12345 more text."); - let range = Range::point(12); - assert_eq!( - NumberIncrementor::from_range(rope.slice(..), range), - Some(NumberIncrementor { - range: Range::new(10, 15), - value: 12345, - radix: 10, - text: rope.slice(..), - }) - ); - } - - #[test] - fn test_uppercase_hexadecimal_at_point() { - let rope = Rope::from_str("Test text 0x123ABCDEF more text."); - let range = Range::point(12); - assert_eq!( - NumberIncrementor::from_range(rope.slice(..), range), - Some(NumberIncrementor { - range: Range::new(10, 21), - value: 0x123ABCDEF, - radix: 16, - text: rope.slice(..), - }) - ); - } - - #[test] - fn test_lowercase_hexadecimal_at_point() { - let rope = Rope::from_str("Test text 0xfa3b4e more text."); - let range = Range::point(12); - assert_eq!( - NumberIncrementor::from_range(rope.slice(..), range), - Some(NumberIncrementor { - range: Range::new(10, 18), - value: 0xfa3b4e, - radix: 16, - text: rope.slice(..), - }) - ); - } - - #[test] - fn test_octal_at_point() { - let rope = Rope::from_str("Test text 0o1074312 more text."); - let range = Range::point(12); - assert_eq!( - NumberIncrementor::from_range(rope.slice(..), range), - Some(NumberIncrementor { - range: Range::new(10, 19), - value: 0o1074312, - radix: 8, - text: rope.slice(..), - }) - ); - } - - #[test] - fn test_binary_at_point() { - let rope = Rope::from_str("Test text 0b10111010010101 more text."); - let range = Range::point(12); - assert_eq!( - NumberIncrementor::from_range(rope.slice(..), range), - Some(NumberIncrementor { - range: Range::new(10, 26), - value: 0b10111010010101, - radix: 2, - text: rope.slice(..), - }) - ); - } - - #[test] - fn test_negative_decimal_at_point() { - let rope = Rope::from_str("Test text -54321 more text."); - let range = Range::point(12); - assert_eq!( - NumberIncrementor::from_range(rope.slice(..), range), - Some(NumberIncrementor { - range: Range::new(10, 16), - value: -54321, - radix: 10, - text: rope.slice(..), - }) - ); - } - - #[test] - fn test_decimal_with_leading_zeroes_at_point() { - let rope = Rope::from_str("Test text 000045326 more text."); - let range = Range::point(12); - assert_eq!( - NumberIncrementor::from_range(rope.slice(..), range), - Some(NumberIncrementor { - range: Range::new(10, 19), - value: 45326, - radix: 10, - text: rope.slice(..), - }) - ); - } - - #[test] - fn test_negative_decimal_cursor_on_minus_sign() { - let rope = Rope::from_str("Test text -54321 more text."); - let range = Range::point(10); - assert_eq!( - NumberIncrementor::from_range(rope.slice(..), range), - Some(NumberIncrementor { - range: Range::new(10, 16), - value: -54321, - radix: 10, - text: rope.slice(..), - }) - ); - } - - #[test] - fn test_number_under_range_start_of_rope() { - let rope = Rope::from_str("100"); - let range = Range::point(0); - assert_eq!( - NumberIncrementor::from_range(rope.slice(..), range), - Some(NumberIncrementor { - range: Range::new(0, 3), - value: 100, - radix: 10, - text: rope.slice(..), - }) - ); - } - - #[test] - fn test_number_under_range_end_of_rope() { - let rope = Rope::from_str("100"); - let range = Range::point(2); - assert_eq!( - NumberIncrementor::from_range(rope.slice(..), range), - Some(NumberIncrementor { - range: Range::new(0, 3), - value: 100, - radix: 10, - text: rope.slice(..), - }) - ); - } - - #[test] - fn test_number_surrounded_by_punctuation() { - let rope = Rope::from_str(",100;"); - let range = Range::point(1); - assert_eq!( - NumberIncrementor::from_range(rope.slice(..), range), - Some(NumberIncrementor { - range: Range::new(1, 4), - value: 100, - radix: 10, - text: rope.slice(..), - }) - ); - } - - #[test] - fn test_not_a_number_point() { - let rope = Rope::from_str("Test text 45326 more text."); - let range = Range::point(6); - assert_eq!(NumberIncrementor::from_range(rope.slice(..), range), None); - } - - #[test] - fn test_number_too_large_at_point() { - let rope = Rope::from_str("Test text 0xFFFFFFFFFFFFFFFFF more text."); - let range = Range::point(12); - assert_eq!(NumberIncrementor::from_range(rope.slice(..), range), None); - } - - #[test] - fn test_number_cursor_one_right_of_number() { - let rope = Rope::from_str("100 "); - let range = Range::point(3); - assert_eq!(NumberIncrementor::from_range(rope.slice(..), range), None); - } - - #[test] - fn test_number_cursor_one_left_of_number() { - let rope = Rope::from_str(" 100"); - let range = Range::point(0); - assert_eq!(NumberIncrementor::from_range(rope.slice(..), range), None); - } - - #[test] - fn test_increment_basic_decimal_numbers() { - let tests = [ - ("100", 1, "101"), - ("100", -1, "99"), - ("99", 1, "100"), - ("100", 1000, "1100"), - ("100", -1000, "-900"), - ("-1", 1, "0"), - ("-1", 2, "1"), - ("1", -1, "0"), - ("1", -2, "-1"), - ]; - - for (original, amount, expected) in tests { - let rope = Rope::from_str(original); - let range = Range::point(0); - assert_eq!( - NumberIncrementor::from_range(rope.slice(..), range) - .unwrap() - .incremented_text(amount), - expected.into() - ); - } - } - - #[test] - fn test_increment_basic_hexadedimal_numbers() { - let tests = [ - ("0x0100", 1, "0x0101"), - ("0x0100", -1, "0x00ff"), - ("0x0001", -1, "0x0000"), - ("0x0000", -1, "0xffffffffffffffff"), - ("0xffffffffffffffff", 1, "0x0000000000000000"), - ("0xffffffffffffffff", 2, "0x0000000000000001"), - ("0xffffffffffffffff", -1, "0xfffffffffffffffe"), - ("0xABCDEF1234567890", 1, "0xABCDEF1234567891"), - ("0xabcdef1234567890", 1, "0xabcdef1234567891"), - ]; - - for (original, amount, expected) in tests { - let rope = Rope::from_str(original); - let range = Range::point(0); - assert_eq!( - NumberIncrementor::from_range(rope.slice(..), range) - .unwrap() - .incremented_text(amount), - expected.into() - ); - } - } - - #[test] - fn test_increment_basic_octal_numbers() { - let tests = [ - ("0o0107", 1, "0o0110"), - ("0o0110", -1, "0o0107"), - ("0o0001", -1, "0o0000"), - ("0o7777", 1, "0o10000"), - ("0o1000", -1, "0o0777"), - ("0o0107", 10, "0o0121"), - ("0o0000", -1, "0o1777777777777777777777"), - ("0o1777777777777777777777", 1, "0o0000000000000000000000"), - ("0o1777777777777777777777", 2, "0o0000000000000000000001"), - ("0o1777777777777777777777", -1, "0o1777777777777777777776"), - ]; - - for (original, amount, expected) in tests { - let rope = Rope::from_str(original); - let range = Range::point(0); - assert_eq!( - NumberIncrementor::from_range(rope.slice(..), range) - .unwrap() - .incremented_text(amount), - expected.into() - ); - } - } - - #[test] - fn test_increment_basic_binary_numbers() { - let tests = [ - ("0b00000100", 1, "0b00000101"), - ("0b00000100", -1, "0b00000011"), - ("0b00000100", 2, "0b00000110"), - ("0b00000100", -2, "0b00000010"), - ("0b00000001", -1, "0b00000000"), - ("0b00111111", 10, "0b01001001"), - ("0b11111111", 1, "0b100000000"), - ("0b10000000", -1, "0b01111111"), - ( - "0b0000", - -1, - "0b1111111111111111111111111111111111111111111111111111111111111111", - ), - ( - "0b1111111111111111111111111111111111111111111111111111111111111111", - 1, - "0b0000000000000000000000000000000000000000000000000000000000000000", - ), - ( - "0b1111111111111111111111111111111111111111111111111111111111111111", - 2, - "0b0000000000000000000000000000000000000000000000000000000000000001", - ), - ( - "0b1111111111111111111111111111111111111111111111111111111111111111", - -1, - "0b1111111111111111111111111111111111111111111111111111111111111110", - ), - ]; - - for (original, amount, expected) in tests { - let rope = Rope::from_str(original); - let range = Range::point(0); - assert_eq!( - NumberIncrementor::from_range(rope.slice(..), range) - .unwrap() - .incremented_text(amount), - expected.into() - ); - } - } - - #[test] - fn test_increment_with_separators() { - let tests = [ - ("999_999", 1, "1_000_000"), - ("1_000_000", -1, "999_999"), - ("-999_999", -1, "-1_000_000"), - ("0x0000_0000_0001", 0x1_ffff_0000, "0x0001_ffff_0001"), - ("0x0000_0000_0001", 0x1_ffff_0000, "0x0001_ffff_0001"), - ("0x0000_0000_0001", 0x1_ffff_0000, "0x0001_ffff_0001"), - ("0x0000_0000", -1, "0xffff_ffff_ffff_ffff"), - ("0x0000_0000_0000", -1, "0xffff_ffff_ffff_ffff"), - ("0b01111111_11111111", 1, "0b10000000_00000000"), - ("0b11111111_11111111", 1, "0b1_00000000_00000000"), - ]; - - for (original, amount, expected) in tests { - let rope = Rope::from_str(original); - let range = Range::point(0); - assert_eq!( - NumberIncrementor::from_range(rope.slice(..), range) - .unwrap() - .incremented_text(amount), - expected.into() - ); - } - } -} -- cgit v1.2.3-70-g09d2